10

Consider this small program:

class Program
{
    [STAThread]
    static void Main(string[] args)
    {
        Console.WriteLine("Please copy something into the clipboard.");
        WaitForClipboardChange();
        Console.WriteLine("You copied " + Clipboard.GetText());
        Console.ReadKey();
    }

    static void WaitForClipboardChange()
    {
        Clipboard.SetText("xxPlaceholderxx");
        while (Clipboard.GetText() == "xxPlaceholderxx" && 
               Clipboard.GetText().Trim() != "")
            Thread.Sleep(90);
    }
}

I run it, and I copy a string from Notepad. But the program just gets an empty string from the clipboard and writes "You copied ".

What's the problem here? Is there something that makes clipboard access behave weirdly in a console application?

This is Windows 7 SP1 x86, .NET 4 Client Profile.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
LTR
  • 1,226
  • 2
  • 17
  • 39
  • I don't think it will make a difference, but try `!string.IsNullOrWhitespace(Clipboard.GetText())` in the while loop. – gunr2171 Dec 05 '13 at 17:51
  • Thanks, tried it! Didn't make a difference though. – LTR Dec 05 '13 at 17:52
  • 3
    @gunr2171 you mean the no-op second half of the while? If something == "foo" then what it doesn't equal doesn't matter... PS - I'd check what `Clipboard.GetText()` returns the first time it's an interesting value (and every time after). –  Dec 05 '13 at 17:52
  • Did you try running this small program? Is it possible this is not an accurate reproduction of your code causing the problem you describe? I mean: works for me. Also, try [`ClipBoard.ContainsText()`](http://msdn.microsoft.com/en-us/library/a3cyzt72(v=vs.110).aspx). – CodeCaster Dec 05 '13 at 18:00
  • Note: I'm seeing a return of nothing every call to `Clipboard.GetText()` regardless if `.SetText()` is called or not. Peculiarity may be that it's not actually attaching to the clipboard and hence nothing is ever returned. –  Dec 05 '13 at 18:08
  • Yes, I created and ran the program before posting. It's the exact same program that I ran. – LTR Dec 05 '13 at 18:14
  • 1
    I just ran your code (with no-op on right side of while...&& removed) and it seems to run as expected. I did have to debug step a couple times at first though. Apparently `.GetText()` doesn't like being called quickly in succession. –  Dec 05 '13 at 18:17

4 Answers4

12

Use this function

static string GetMeText()
  {
     string res = "starting value";
     Thread staThread = new Thread(x => 
       {
         try
         {
             res = Clipboard.GetText();
         }
         catch (Exception ex) 
         {
            res = ex.Message;            
         }
       });
    staThread.SetApartmentState(ApartmentState.STA);
    staThread.Start();
    staThread.Join();
    return res;
  }

In this line:

  Console.WriteLine("You copied " + Clipboard.GetMeText());

The problem is that the clipboard only works with certain threading models (ApartmentState.STA) so you have to make a new thread and give it that model this code does that.

Hogan
  • 69,564
  • 10
  • 76
  • 117
  • @ebyrob - it looks like it, but it I had the same problem and fixed it with this function... I'm not sure STAThread on main works the same as setting it on a thread -- not sure why but it worked for me. Maybe main can't be STAThread in the debugger? – Hogan Dec 05 '13 at 18:25
  • Very good! This solves both the problem with the empty text, and the delay that I encountered with the other solutions. – LTR Dec 05 '13 at 18:27
  • @Hogan Yes, it may be `[STAThread]` in main doesn't work at all for console, I know `[STAThread]` will fail in many many cases silently without warning. I was about to flag the question as dupe with a link to STA thread rules for clipboard, then I saw the `[STAThread]` directive and it threw me off the scent. Good work! –  Dec 05 '13 at 18:29
5

I can reproduce the problem with your code on .NET 4 Client Profile, but when I switch to .NET 4 or 4.5 it works as expected.

However, the ClipBoard.GetText() manual states:

Use the ContainsText method to determine whether the Clipboard contains text data before retrieving it with this method.

I take that as an instruction, not a suggestion, so, try this:

class Program
{
    [STAThread]
    static void Main(string[] args)
    {
        Console.WriteLine("Please copy something into the clipboard.");
        WaitForClipboardChange();
        Console.WriteLine("You copied " + Clipboard.GetText());
        Console.ReadKey();
    }
    static void WaitForClipboardChange()
    {
        Clipboard.Clear();

        while (!Clipboard.ContainsText())
            Thread.Sleep(90);
    }
}

It does show the copied text, although I must say this lets my system hang horribly when I copy some text.

CodeCaster
  • 147,647
  • 23
  • 218
  • 272
  • Thank you, this solves the problem aswell, like RobSkilos' solution. Only problem is, it makes the program hang for about 3 seconds after copying. See my comment on RobSkilos' answer for an explanation. – LTR Dec 05 '13 at 18:24
  • @LTR it makes the program where I copied from hang for me. Maybe you're more interested in [`AddClipboardFormatListener()` as explained here](http://stackoverflow.com/a/11901709/266143). – CodeCaster Dec 05 '13 at 18:27
  • Update: I used Hogan's solution, and this fixes both the problem from my question, and the problem with the 3-second delay after copying! – LTR Dec 05 '13 at 18:28
  • 1
    @LTR this is just a proof of concept right? Hogan's solution still hangs every few times I copy, under any framework version. If you really want to make an application to watch the clipboard, create a listener as described in the link in my previous comment. – CodeCaster Dec 05 '13 at 19:04
3

This works for me:

static void Main(string[] args)
{
    Console.WriteLine("Please copy something into the clipboard.");
    string text = WaitForClipboardChange();
    Console.WriteLine("You copied " + text);
}
static string WaitForClipboardChange()
{
    string placeholderText = "xxPlaceholderxx";
    Clipboard.SetText(placeholderText);

    string text = null;
    do 
    {
        Thread.Sleep(90);
        text = Clipboard.GetText();
    }
    while (string.IsNullOrWhiteSpace(text) || text.Equals(placeholderText));

    return text;
}
RobSiklos
  • 8,348
  • 5
  • 47
  • 77
  • Yes, this solves the problem! Only strange thing, after copying something my program hangs for about 3 seconds. That's acceptable (it's only software for personal use), but it would be interesting to fix that aswell. – LTR Dec 05 '13 at 18:22
1

Your current code explicitly waits for first change from "xxPlaceholderxx" to anything (your condition is "not particular string AND not empty" which turns false as soon as string changes from "xxPlaceholderxx" to anything, including ""):

while (Clipboard.GetText() == "xxPlaceholderxx" 
    && Clipboard.GetText().Trim() != "")

You probably want || (or) instead of and:

// assuming System.Windows.Forms.Clipboard
static void WaitForClipboardChange()
{
    Clipboard.SetText("xxPlaceholderxx");
    while (Clipboard.GetText() == "xxPlaceholderxx" 
    || string.IsNullOrWhiteSpace(Clipboard.GetText()))
        Thread.Sleep(90);
}
Alexei Levenkov
  • 98,904
  • 14
  • 127
  • 179
  • Alexei, you're right. However, this modification does not solve the problem. The behaviour is still the same. – LTR Dec 05 '13 at 18:12