首页 > 解决方案 > C# 监视器剪贴板更改控制台

问题描述

我正在开发一个控制台应用程序,该应用程序应该在剪贴板内容更改时捕获事件。WinRT 中有一个用于此的 API,Windows.ApplicationModel.DataTransfer.Clipboard.ContentChanged。我已经在 WinForms 和 WPF 应用程序上对此进行了测试,并且效果很好。但是,在控制台应用程序中执行此操作时遇到问题。代码非常基本。在 WinForms 应用程序上执行此操作时,我只需编写这行代码:

public MyApp()
{
    InitializeComponent();
    Windows.ApplicationModel.DataTransfer.Clipboard.ContentChanged += OnClipboardChanged;

}


public async void OnClipboardChanged(Object sender, Object e)
{
   MyCodeHere
}

但是,当尝试在我的控制台应用程序中执行相同操作时:

class Program
{

    [STAThread]
    static void Main(string[] args)
    {
        Windows.ApplicationModel.DataTransfer.Clipboard.ContentChanged += OnClipboardChanged;
    }

    public static void OnClipboardChanged(Object sender, Object e)
    {
        Console.WriteLine("Hello");
    }
}

然而控制台只是在启动后退出。如果我输入“Console.ReadKey”,那么它仍然会出错但不会退出。这两种方式都不会调用我在 Main 中编写的事件。我希望控制台正在运行而不是结束,即使剪贴板发生了变化。所以它应该不断地在后台运行,并且每次剪贴板发生变化时,它应该只是向控制台写入一个“Hello”。我已经完成了所有其他答案,但没有一个对我有用,因为他们想要操作剪贴板,而我正在调用剪贴板内容更改的事件。感谢所有的帮助!

另一个问题,如果我改用 C++/winRT 会有任何性能差异吗?

标签: c#windows-runtimeconsole-applicationclipboard

解决方案


在控制台上下文中,您必须确保处理 Windows 消息,因为剪贴板依赖于它,因此,例如,您可以使用 Winforms 的DoEvents方法(如果您没有真正的窗口并且没有任何消息泵送):

class Program
{
    static void Main()
    {
        Windows.ApplicationModel.DataTransfer.Clipboard.ContentChanged += (s, e) => Console.WriteLine("ContentChanged");
        Console.WriteLine("Press any key to quit");
        do
        {
            System.Windows.Forms.Application.DoEvents();
            if (Console.KeyAvailable)
                break;

            Thread.Sleep(100); // for example
        }
        while (true);
    }
}

要在 .NET 5 控制台项目中启用 Winforms 支持,这是最简单的方法(您甚至不需要添加 Windows.SDK nuget 包),只需将 .csproj 修改为如下所示:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net5.0-windows10.0.19041.0</TargetFramework>
    <UseWindowsForms>true</UseWindowsForms>
    <DisableWinExeOutputInference>true</DisableWinExeOutputInference>
  </PropertyGroup>

</Project>

如果您没有 .NET 5,或者不想引用 Winforms,则可以使用 P/Invoke 声明自己的消息泵,如下所示:

class Program
{
    [STAThread]
    static void Main()
    {
        Windows.ApplicationModel.DataTransfer.Clipboard.ContentChanged += (s, e) => Console.WriteLine("ContentChanged");
        Console.WriteLine("Press any key to quit");
        do
        {
            while (GetMessage(out var msg, IntPtr.Zero, 0, 0) != 0)
            {
                TranslateMessage(ref msg);
                DispatchMessage(ref msg);
            }
            if (Console.KeyAvailable)
                break;

            Thread.Sleep(100);
            Console.Write(".");
        }
        while (true);
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct MSG
    {
        public IntPtr hwnd;
        public int message;
        public IntPtr wParam;
        public IntPtr lParam;
        public int time;
        public POINT pt;
        public int lPrivate;
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct POINT
    {
        public int x;
        public int y;
    }

    [DllImport("user32")]
    private static extern int GetMessage(out MSG lpMsg, IntPtr hWnd, int wMsgFilterMin, int wMsgFilterMax);

    [DllImport("user32")]
    private static extern bool TranslateMessage(ref MSG lpMsg);

    [DllImport("user32")]
    private static extern IntPtr DispatchMessage(ref MSG lpmsg);
}

推荐阅读