Ashok
Ashok

Reputation: 1906

How to get clipboard data in non main thread?

I'm trying to get data from Clipboard with the below code.

private void TestBtn_Click(object sender, EventArgs e)
{
 Thread sampleThread = new Thread(SampleMethod);
 sampleThread.IsBackground = true;
 sampleThread.Start();
 Thread.Sleep(2000);
 var textFromMain = Clipboard.GetText(TextDataFormat.Text);
}

private void SampleMethod()
{
 var textFromThread = Clipboard.GetText(TextDataFormat.Text);
 Thread.Sleep(1000);
}

I'm getting whatever text copied to clipboard with this line -

var textFromMain = Clipboard.GetText(TextDataFormat.Text);

But below line returns empty string or blank string.

var textFromThread = Clipboard.GetText(TextDataFormat.Text);

I'm not getting what's the issue. Can somebody help me to understand. If it's multi-threading, please point me to right direction.

Upvotes: 2

Views: 6360

Answers (3)

Ashok
Ashok

Reputation: 1906

I finally used below method to access Clipboard text.

private string GetClipboardData()
    {
        try
        {
            string clipboardData= null;
            Exception threadEx = null;
            Thread staThread = new Thread(
                delegate ()
                {
                    try
                    {
                        clipboardData= Clipboard.GetText(TextDataFormat.Text);
                    }

                    catch (Exception ex)
                    {
                        threadEx = ex;
                    }
                });
            staThread.SetApartmentState(ApartmentState.STA);
            staThread.Start();
            staThread.Join();
            return clipboardData;
        }
        catch (Exception exception)
        {
            return string.Empty;
        }
    }

Upvotes: 6

dNP
dNP

Reputation: 491

Clipboard.GetText(TextDataFormat.Text) use COM and throws exception if called in thread which not marked with STAThreadAttribute.

One way to solve is use delegate to return call of Clipboard.GetText to main thread with Invoke. But in this case thread will freeze it execution on Invoke till the SampleMethod() ends it execution on main form thread and main thread will be free.

Other way is to use own call to COM to get clipboard text instead of System.Windows.Forms.Clipboard.GetText(), see ClipboardCom.GetText(), this method not need waiting main form thread.

    private string _textFromMain, _textFromThreadByInvoke, _textFromThreadByCom;

    private delegate string GetClipboardInvoke(TextDataFormat textformat);
    private void SampleInvokeMethod()
    {
        GetClipboardInvoke invokerClipboard = new GetClipboardInvoke(Clipboard.GetText);
        _textFromThreadByInvoke = (string)this.Invoke(invokerClipboard, TextDataFormat.Text); // where this is a Form
        Thread.Sleep(1000);
    }



    private void button1_Click(object sender, EventArgs e)
    {
        Thread sampleInvokeThread = new Thread(SampleInvokeMethod) { IsBackground = true };
        sampleInvokeThread.Start();

        Thread sampleComThread = new Thread(SampleComMethod) { IsBackground = true };
        sampleComThread.Start();

        Thread.Sleep(10000);
        _textFromMain = Clipboard.GetText(TextDataFormat.Text);
    }


    private void SampleComMethod()
    {
        _textFromThreadByCom = ClipboardCom.GetText();
        Thread.Sleep(1000);
    }

    public static class ClipboardCom
    {
        [DllImport("user32.dll")]
        static extern IntPtr GetClipboardData(uint uFormat);
        [DllImport("user32.dll")]
        static extern bool IsClipboardFormatAvailable(uint format);
        [DllImport("user32.dll", SetLastError = true)]
        static extern bool OpenClipboard(IntPtr hWndNewOwner);
        [DllImport("user32.dll", SetLastError = true)]
        static extern bool CloseClipboard();
        [DllImport("kernel32.dll")]
        static extern IntPtr GlobalLock(IntPtr hMem);
        [DllImport("kernel32.dll")]
        static extern bool GlobalUnlock(IntPtr hMem);

        const uint CF_UNICODETEXT = 13;

        public static string GetText()
        {
            if (!IsClipboardFormatAvailable(CF_UNICODETEXT))
                return null;
            if (!OpenClipboard(IntPtr.Zero))
                return null;

            string data = null;
            var hGlobal = GetClipboardData(CF_UNICODETEXT);
            if (hGlobal != IntPtr.Zero)
            {
                var lpwcstr = GlobalLock(hGlobal);
                if (lpwcstr != IntPtr.Zero)
                {
                    data = Marshal.PtrToStringUni(lpwcstr);
                    GlobalUnlock(lpwcstr);
                }
            }
            CloseClipboard();

            return data;
        }
    }

Upvotes: 5

Sajeeb Chandan Saha
Sajeeb Chandan Saha

Reputation: 865

The following code worked for me.

        Thread theThread = new Thread((ThreadStart)delegate
        {
            var selectedPodcastPlaylist = (PodcastPlaylist)metroGridPodcastPlaylist.SelectedRows[0].DataBoundItem;
            Clipboard.SetText(selectedPodcastPlaylist.URI);
        });

        try
        {
            //set STA as the Open file dialog needs it to work
            theThread.TrySetApartmentState(ApartmentState.STA);

            //start the thread
            theThread.Start();

            // Wait for thread to get started
            while (!theThread.IsAlive) { Thread.Sleep(1); }

            // Wait a tick more (@see: http://scn.sap.com/thread/45710)
            Thread.Sleep(1);

            //wait for the dialog thread to finish
            theThread.Join();
        }
        catch (Exception)
        {
        }

Inspired by the article and source from here Thread Apartment Safe Open/Save File Dialogs for C#

Upvotes: 1

Related Questions