Question

I have an application that captures Windows messages sent to the Form, and runs a method if an update is made to the system Clipboard:

protected override void WndProc(ref Message m)
{
    switch (m.Msg)
    {
        case WM_CLIPBOARDUPDATE:

            if (IsClipboardListenerOn)
                OnClipboardChanged();

            break;
    }

    base.WndProc(ref m);
}

In some instances I don't want my method, OnClipboardChanged(), to run. My solution was to set a static global variable IsClipboardListenerOn and toggle it as necessary. This didn't get the job done because the Windows message wasn't processed till after my function that did the toggling returned, so it was always true:

private void some_event(object sender, EventArgs e)
{
    MainForm.IsClipboardListenerOn = false;

    // some code that makes the clipboard changed message fire

    MainForm.IsClipboardListenerOn = true;

   // when this returns the WM_CLIPBOARDUPDATE message gets caught
}

The next idea was to run the code that triggered a message event on a thread and wait for the thread to return to ensure it executed while the global flag was toggled off:

    Thread thread1 = new Thread(() => clipboard_stuff());
    thread1.SetApartmentState(ApartmentState.STA);

    MainForm.IsClipboardListenerOn = false;

    thread1.Start();        

    thread1.Join();
    MainForm.IsClipboardListenerOn = true;

But this didn't work, either. In fact, sometimes it would execute my OnClipboardChanged() method twice. I know the system Clipboard requires Single Threaded Apartment; Is this part of my problem? Am I going about this wrong? Is there some other technique I can utilize?

Was it helpful?

Solution

Right, that's not going to work. Windows is waiting for you to start pumping messages again. A workaround is to delay setting the flag back to true until all pending messages were dispatched. That can be elegantly done by using the Control.BeginInvoke() method. Like this:

private void some_event(object sender, EventArgs e)
{
    MainForm.IsClipboardListenerOn = false;
    // some code that makes the clipboard changed message fire
    //...
    this.BeginInvoke(new Action(() => MainForm.IsClipboardListenerOn = true));
}

With the assumption that some_event() is a member of a form. I'd guess that MainForm.BeginInvoke() would work as well.

OTHER TIPS

It sounds like there should be a cleaner solution than what I'm about to suggest, but you know what they say... if it's stupid and it works, it's not stupid.

If you're sure you're going to trigger a clipboard message that you'd like to ignore, set a flag and let the WndProc shut it off:

private void some_event(object sender, EventArgs e)
{
    MainForm.SkipNextClipboardMessage = true;
    // some code that makes the clipboard changed message fire
}

protected override void WndProc(ref Message m)
{
    switch (m.Msg)
    {
        case WM_CLIPBOARDUPDATE: 
            if (SkipNextClipboardMessage)
                SkipNextClipboardMessage = false;
            else                    
                OnClipboardChanged();
            break;
    }
    base.WndProc(ref m);
}

Another terrible idea that's bound to get some stinkeyes from readers is to do as you did in your first version of the code, and add a call to Application.DoEvents() after inducing your clipboard changing method to force messages to be processed.

Yes, yes, it's evil and I'm going to hell, I know.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top