0

I have created a console application to monitor when the clipboard changes. It works fine and the code is shown below in the console code section.

My problem is now trying to do this for a WPF application using MVVM. I think I need to pass the MainWindow to my view model, is this the correct way for MVVM?

Update

This is my attempt at creating a window. I have read that I need to implement HwndHost, which I have done in my class MyFirstWindow. I am not sure how to hide the window though?

I assume in my other class below ClipboardManager I need to create an instance of MyFirstWindow. However I am unsure of what HandleRef to pass?

 class MyFirstWindow : HwndHost
{

    IntPtr hwndHost;

    protected override HandleRef BuildWindowCore(HandleRef hwndParent)
    {
        hwndHost = IntPtr.Zero;
        hwndHost = CreateWindowEx(0, "static", "", WS_CHILD,
            0, 0, 50, 50, hwndParent.Handle,
            (IntPtr)HOST_ID,
            IntPtr.Zero, 0);

        return new HandleRef(this, hwndHost);
    }

    protected override void DestroyWindowCore(HandleRef hwnd)
    {            
        DestroyWindow(hwnd.Handle);
    }

    internal const int
      WS_CHILD = 0x40000000,
      WS_VISIBLE = 0x10000000,
      LBS_NOTIFY = 0x00000001,
      HOST_ID = 0x00000002,
      LISTBOX_ID = 0x00000001,
      WS_VSCROLL = 0x00200000,
      WS_BORDER = 0x00800000;

    [DllImport("user32.dll", EntryPoint = "CreateWindowEx", CharSet = CharSet.Unicode)]
    internal static extern IntPtr CreateWindowEx(int dwExStyle,
                                          string lpszClassName,
                                          string lpszWindowName,
                                          int style,
                                          int x, int y,
                                          int width, int height,
                                          IntPtr hwndParent,
                                          IntPtr hMenu,
                                          IntPtr hInst,
                                          [MarshalAs(UnmanagedType.AsAny)] object pvParam);

    [DllImport("user32.dll", EntryPoint = "DestroyWindow", CharSet = CharSet.Unicode)]
    internal static extern bool DestroyWindow(IntPtr hwnd);
}

I have created the ClipboardManager class below in my WPF application.

class ClipboardManager : IDisposable
{
    #region variable declaration
    /// <summary>
    /// Next clipboard viewer window 
    /// </summary>
    private IntPtr _hWndNextViewer;
    /// <summary>
    /// The <see cref="HwndSource"/> for this window.
    /// </summary>
    private HwndSource _hWndSource;
    private string _clipboardContent;
    #endregion

    #region property declaration
    public string ClipboardContent
    {
        get
        {
            return _clipboardContent;
        }
        set
        {
            _clipboardContent = value;
        }
    }
    #endregion

    public ClipboardManager()
    {
        hwnd = new MyFirstWindow();
        InitCBViewer();
    }

    private void InitCBViewer(System.Windows.Window wnd)
    {
        WindowInteropHelper wih = new WindowInteropHelper(wnd);
        _hWndSource = HwndSource.FromHwnd(wih.Handle);

        _hWndSource.AddHook(WinProc);   // start processing window messages
        _hWndNextViewer = Win32.SetClipboardViewer(_hWndSource.Handle);   // set this window as a viewer

    }

    private void CloseCBViewer()
    {
        // remove this window from the clipboard viewer chain
        Win32.ChangeClipboardChain(_hWndSource.Handle, _hWndNextViewer);

        _hWndNextViewer = IntPtr.Zero;
        _hWndSource.RemoveHook(this.WinProc);
    }

    private IntPtr WinProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
    {
        switch (msg)
        {
            case Win32.WM_CHANGECBCHAIN:
                if (wParam == _hWndNextViewer)
                {
                    // clipboard viewer chain changed, need to fix it.
                    _hWndNextViewer = lParam;
                }
                else if (_hWndNextViewer != IntPtr.Zero)
                {
                    // pass the message to the next viewer.
                    Win32.SendMessage(_hWndNextViewer, msg, wParam, lParam);
                }
                break;

            case Win32.WM_DRAWCLIPBOARD:
                // clipboard content changed
                if (Clipboard.ContainsText())
                {
                    ClipboardContent = Clipboard.GetText();
                }

                // pass the message to the next viewer.
                Win32.SendMessage(_hWndNextViewer, msg, wParam, lParam);
                break;
        }

        return IntPtr.Zero;
    }

    public void Dispose()
    {
        CloseCBViewer();
    }
}

win 32 class

 internal static class Win32
{
    /// <summary>
    /// The WM_DRAWCLIPBOARD message notifies a clipboard viewer window that 
    /// the content of the clipboard has changed. 
    /// </summary>
    internal const int WM_DRAWCLIPBOARD = 0x0308;

    /// <summary>
    /// A clipboard viewer window receives the WM_CHANGECBCHAIN message when 
    /// another window is removing itself from the clipboard viewer chain.
    /// </summary>
    internal const int WM_CHANGECBCHAIN = 0x030D;

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    internal static extern IntPtr SetClipboardViewer(IntPtr hWndNewViewer);

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    internal static extern bool ChangeClipboardChain(IntPtr hWndRemove, IntPtr hWndNewNext);

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    internal static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
}
mHelpMe
  • 6,336
  • 24
  • 75
  • 150
  • 1
    You might want to look here for different approaches with regard to WinProc in WPF: https://stackoverflow.com/questions/624367/how-to-handle-wndproc-messages-in-wpf. Another option (albeit not in the question i linked, and one i would personally prefer) would be to manually create a hidden native Win32 window and use the WndProc code from your console app (since you already do Win32-specific stuff, this would not add more dependencies or further requirments). This way, your VM could maintain its independence from the WPF UI. –  Sep 19 '18 at 17:48
  • Are you suggesting that I add a reference to my console application that is monitoring the clipboard. Then I guess I need to pass the current text on the clipboard to my viewmodel, how would I do that? – mHelpMe Sep 19 '18 at 18:03
  • 1
    No, i meant you take the code from your console project over in your new (WPF) project. Adapt this code put it in a class with methods and notification event(s) that are appropriate for you usage scenario. This class should also handle (create/destroy) any native window you create (use IDisposable). Let the VM in question use the class (subscribe to its event(s) and call its methods). Make sure you don't leak native handles by ensuring that the class i mentioned is disposed of when your program doesn't need it anymore... –  Sep 19 '18 at 18:07
  • Ok I see, I have added a new class (ClipboardManager) to my WPF project. However I'm getting an error linking to the window variable, I have updated my post, cheers – mHelpMe Sep 19 '18 at 19:44
  • 1
    Read the error: `Hwnd of zero is not valid` is simple enough to understand. As i suggested, if you want to do that in the view model (which ideally should not mess with the UI elements directly, but only through bindings and such), create a native Win32 window by yourself. The alternative is to introduce a dependency to ye'ole Windows Forms in your project (as per one answer in the linked question) or to somehow get a window handle from your UI back to your view model (which could be tricky to get right in a clean manner, and thus carries considerable risk of leaking native windows handles...) –  Sep 19 '18 at 19:51

0 Answers0