C#控制台应用程序中剪贴板的奇怪行为

10

考虑这个小程序:

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);
    }
}

我运行了程序,并从记事本中复制了一个字符串。但是程序只从剪贴板获取了一个空字符串,并写入了“你复制了”。

问题出在哪里?有什么让控制台应用程序的剪贴板访问表现异常的因素吗?

我的系统是 Windows 7 SP1 x86,使用的 .NET 4 客户端框架。


我不认为这会有什么区别,但是在 while 循环中尝试使用 !string.IsNullOrWhitespace(Clipboard.GetText()) - gunr2171
3
@gunr2171 你是指 while 循环中的无操作后半部分吗?如果 something == "foo",那么它不等于什么并不重要...另外,我建议在第一次出现有趣值时(以及每次之后)检查 Clipboard.GetText() 返回的内容。 - user645280
注意:每次调用 Clipboard.GetText() 都返回空值,无论是否调用了 .SetText()。这种奇怪的情况可能是由于它实际上没有连接到剪贴板,因此永远不会返回任何内容。 - user645280
是的,我在发布之前创建并运行了该程序。这就是我运行的完全相同的程序。 - LTR
1
我刚刚运行了你的代码(在while右侧加上no-op并删除&&),它似乎按预期运行。不过一开始我确实需要进行几次调试。显然,.GetText() 不喜欢被快速连续地调用。 - user645280
显示剩余2条评论
4个回答

12

使用此函数

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;
  }
在这一行中:
  Console.WriteLine("You copied " + Clipboard.GetMeText());

问题在于剪贴板只能在某些线程模型(ApartmentState.STA)中正常工作,因此您需要创建一个新线程并将其设置为该模型,该代码就是这样做的。


@ebyrob - 看起来是这样,但我遇到了同样的问题,并通过这个函数解决了它...我不确定在主线程上设置STAThread是否与在线程上设置它相同--不确定为什么,但对我有用。也许在调试器中主线程不能是STAThread? - Hogan
非常好!这解决了空文本的问题,也解决了其他解决方案中遇到的延迟问题。 - LTR
@Hogan 是的,对于控制台应用程序,主函数中的[STAThread]可能根本不起作用,我知道[STAThread]在许多情况下会默默地失败而没有警告。我本来想将该问题标记为重复,并附上一个关于剪贴板STA线程规则的链接,但是我看到了[STAThread]指令,这让我有些摸不着头脑。干得好! - user645280

5
我可以在.NET 4客户端框架上使用您的代码重现问题,但是当我切换到.NET 4或4.5时,它按预期工作。
然而, ClipBoard.GetText() 手册指出:
请使用 ContainsText 方法确定剪贴板是否包含文本数据,然后再使用此方法检索它。
我认为这是一条指令,而不是建议,所以请尝试这个方法。
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);
    }
}

它确实显示了复制的文本,但我必须说当我复制一些文本时,它会让我的系统变得非常卡顿。

谢谢,这也解决了问题,就像RobSkilos的解决方案一样。唯一的问题是,在复制后它会让程序挂起大约3秒钟。请参见我在RobSkilos答案中的评论以获取解释。 - LTR
@LTR 当我从中复制的程序对我来说挂起时,它使程序出现了问题。也许你更感兴趣的是AddClipboardFormatListener(),如此处所述 - CodeCaster
更新:我使用了Hogan的解决方案,这解决了我的问题,并且解决了复制后3秒延迟的问题! - LTR
1
@LTR 这只是一个概念验证,对吧?Hogan的解决方案仍然会在任何框架版本下每隔几次复制时挂起。如果你真的想创建一个监视剪贴板的应用程序,请按照我之前评论中提供的链接创建一个监听器。 - CodeCaster

3
这对我有用:
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;
}

是的,这解决了问题!唯一奇怪的是,在复制东西后,我的程序会挂起约3秒钟。这是可以接受的(只是用于个人使用的软件),但也有修复它的兴趣。 - LTR

1

您当前的代码明确等待从"xxPlaceholderxx"到任何内容的第一个更改(您的条件是“非特定字符串且非空”,一旦字符串从"xxPlaceholderxx"更改为任何内容,包括"",条件将变为false):

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

您可能需要使用||(或)而不是and:

// assuming System.Windows.Forms.Clipboard
static void WaitForClipboardChange()
{
    Clipboard.SetText("xxPlaceholderxx");
    while (Clipboard.GetText() == "xxPlaceholderxx" 
    || string.IsNullOrWhiteSpace(Clipboard.GetText()))
        Thread.Sleep(90);
}

Alexei,你是对的。然而,这个修改并没有解决问题。行为仍然是一样的。 - LTR

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接