1

I have a requirement where I need to create a class library to notify the caller when the user does cntrl+c. I have created a windows form (disabling form UI, even the process is hidden from the taskbar) inside this class library!, so that will have a handler for a SetClipboardViewer WinApi32 method. Below is the code snippet.

Form Initializer,

  private void InitializeComponent()
  {
       this.SuspendLayout();
       this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None;
       this.ClientSize = new System.Drawing.Size(326, 90);
       this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
       this.Name = "ClipboardHandle";
       this.Text = "ClipboardHandler";
       this.Opacity = 0D;
       this.ShowIcon = false;
       this.ShowInTaskbar = false;
       this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
       this.WindowState = System.Windows.Forms.FormWindowState.Minimized;
       this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.OnFormClosing);
       this.Load += new System.EventHandler(this.OnLoad);
       this.ResumeLayout(false);    
   }

And in my form class (i.e. ClipboardHandle) OnLoad event, passing the form handler to SetClipboardViewer method as shown below,

  IntPtr nextClipboardViewer;
  private void OnLoad(object sender, EventArgs e)
  {
      nextClipboardViewer = SetClipboardViewer(this.Handle);
  }

WinApi32 method

  [DllImport("User32.dll")]
  internal static extern IntPtr SetClipboardViewer(IntPtr hWndNewViewer);

Since this is a class library I can't use Application.Run to load the form instead I am initiating the form class like below,

   monitorTask = Task.Run(async delegate
            {
                log.Info("STA thread start");

                var stathread = new Thread(() =>
                {
                    try
                    {
                        handler = new ClipboardHandle(); // form class
                        handler.ClipboardEvent += (e) => { onEventOccured("clipboardCopy", e); };
                        handler.ShowDialog(); 
                    }
                    catch (Exception ex)
                    {
                        log.Error("Exception: ", ex);
                    }
                });
                stathread.SetApartmentState(ApartmentState.STA);
                stathread.Start();

                while (!ct.IsCancellationRequested)
                {
                    await Task.Delay(100);
                }

                log.Info("unregister and close");
                handler.UnRegister();
                handler.Close();
                stathread.Join(); 
                ct.ThrowIfCancellationRequested();

            }, ct);             

I have tried using both handler.ShowDialog() as well as handler.Show(), the below error I get when I use handler.ShowDialog() and the later one doesn't show any error but there won't be any event trigger at the WndProc method on clipboard changes.

Exception: :System.InvalidOperationException: Showing a modal dialog box or form when the application is not running in UserInteractive mode is not a valid operation. Specify the ServiceNotification or DefaultDesktopOnly style to display a notification from a service application. at System.Windows.Forms.Form.ShowDialog(IWin32Window owner)

First thing, I understand that using ShowDialog() inside a class library is not a way to go, but Show() method doesn't either fire up the clipboard change events and the thing I don't understand is, This application runs perfectly fine when the handler.ShowDialog() method is used inside the class library and referred to in a console app (test application) in visual studio (in debug mode) but fails when hosted as a windows service.

Why the WndProc doesn't get fired on clipboard changes when handler.Show() is used but fires when handler.ShowDialog() ?

I have also tried running windows service in user context mode. Also referred below posts,

stackoverflow.com/questions/8928713/how-to-resolve-error-showing-a-modal-dialog-box-or-form-when-the-application-i

Any insights or help greatly appreciated. Thanks in advance.

mabiyan
  • 667
  • 7
  • 25
  • You're missing a couple of pieces. You should do [something like this](https://stackoverflow.com/a/11901709/7444103). You can also use this class, if you prefer WPF [HwndSource Class](https://learn.microsoft.com/en-us/dotnet/api/system.windows.interop.hwndsource). Of course, use [AddClipboardFormatListener](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-addclipboardformatlistener) (and its `RemoveClipboardFormatListener` companion) instead of `SetClipboardViewer`, which complicates things without reason (today). – Jimi Apr 15 '20 at 11:11
  • I tried that, but it doesn't seem to be like working. – mabiyan Apr 15 '20 at 11:31
  • 1
    It may need a couple of tweaks, but I can assure you it works quite well (both classes do, you can use `HwndSource` with a Form to create a [message-only Window](https://learn.microsoft.com/en-us/windows/win32/winmsg/window-features#message-only-windows). You never call `Show()` on this kind of Window). – Jimi Apr 15 '20 at 12:09

0 Answers0