1

I am working on a console application that is supposed to catch the event when the clipboard content changes. There is an API in WinRT for this, Windows.ApplicationModel.DataTransfer.Clipboard.ContentChanged. I have already tested this on a WinForms and WPF application, and it works perfectly. However I am experiencing problems when doing this in a console application. The code is pretty basic. When doing it on a WinForms application, I simply write this line of code:

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

}


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

However when trying to do the same in my console application:

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

Yet the console just exits after starting it. If I put "Console.ReadKey" then it still errors but does not exit. Neither way invokes the event I have written in Main. I want the console to be running and not end, even if there is a clipboard change. So it should constantly run in the background, and everytime the clipboard changes then it should just WriteLine a "Hello" to the console. I have gone through all the other answers but none of them works for me, because they want to manipulate the clipboard whereas I am invoking an event on the content change of the clipboard. Thanks for all the help!

Another question, will there be any perfomance difference if I use C++/winRT instead?

Crystalii
  • 167
  • 1
  • 12

1 Answers1

0

In a console context, you must ensure windows messages are processed as clipboard depends on it, so for example, you can use Winforms' DoEvents method (if you don't have a real window and nothing pumps messages):

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

To enable Winforms support in a .NET 5 Console project, here is the simplest way of doing it (you don't even need to add the Windows.SDK nuget package), just modify the .csproj to something like this:

<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>

If you don't have .NET 5, or don't want to reference Winforms, then you can declare your own message pump using P/Invoke, like this:

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);
}
riQQ
  • 9,878
  • 7
  • 49
  • 66
Simon Mourier
  • 132,049
  • 21
  • 248
  • 298
  • I am using .NET Core 3.1, can I add Winforms support the same way there as well? Do note that I am not using the Clipboard of Windows Forms. I am using the Clipboard in Windows RT API, the one in WPF and UPF applications. Also the application should not close after clipboard content has changed. Rather it should just WriteLine "Hello" and continue listening for clipboard changes. – Crystalii Apr 23 '21 at 12:52
  • I know you don't use Winforms' clipboard, but clipboard is a Windows thing, it's the same underlying technology. Winforms is not strictly necessary, but it was easier to use and is supported by .NET 5. I have added a .NET Core 3.x compatible version. – Simon Mourier Apr 23 '21 at 13:05