ComponentDispatcher

IKIS depends on the message loop calling the root IKIS's TranslateAccelerator/TranslateChar/OnMnemonic.  But every message loop is a little different, and not all of them are aware of IKIS, so WPF provides an API called ComponentDispatcher that defines a simple protocol for message loop to follow to allow nice interop.

ComponentDispatcher provides a set of static APIs (which are implicitly tied to the calling thread).  The message loop promises to call some of those APIs at critical times.  Other ComponentDispatcher APIs provide events that other parties (like an IKIS) can listen to.  WPF's Dispatcher class, which is essentially a message loop, calls all the right ComponentDispatcher methods, but you are also free to use your own message loop.

ComponentDispatcher is:

public static class ComponentDispatcher {
    //Properties
    public static bool IsThreadModal { get; }

    //Methods
    public static void PushModal();
    public static void PopModal();
    public static void RaiseIdle();
    //Raises ThreadFilterMessage and if this does not set handled to true raises ThreadPreProcessMessage
    public static bool RaiseThreadMessage(ref MSG msg);

    //Events
    public static event EventHandler ThreadIdle;
    public static event ThreadFilterMessageEventHandler ThreadFilterMessage;
    public static event ThreadPreProcessMessageEventHandler ThreadPreProcessMessage;
    public static event EventHandler EnterThreadModal;
    public static event EventHandler LeaveThreadModal;
}

All members of the ComponentDispatcher are specific to a thread. Calling IsThreadModal on two different threads may return to different answers. Calling ComponentDispatcher methods on thread A will only invoke event handlers that registered on thread A.

Methods message loops should call:

PushModal is called by the message loop to indicate the thread is modal, and PopModal does the reverse. These calls can nest.
RaiseIdle is called by the message loop to indicate that this is a good time for ComponentDispatcher to raise the ThreadIdle event. ComponentDispatcher will not raise ThreadIdle if IsThreadModel is true, but message loops are free to call RaiseIdle even if ComponentDispatcher will ignore it.

RaiseThreadMessage is called by the message loop to indicate that a new message is available (PeekMessage). The return value indicates whether someone listening to a ComponentDispatcher event handled the message. If RaiseThreadMessage returns true (handled), the dispatcher should do nothing further with the message. If the return value is false, the dispatcher is expected to call Win32's TranslateMessage then DispatchMessage.

Functionality if you're not a message loop:

IsThreadModal returns whether the application has gone modal (e.g., a modal message loop has been pushed). ComponentDispatcher knows this because it counts PushModal/PopModal calls from the message loop.

ThreadFilterMessage and ThreadPreProcessMessage events have standard CLR rules for invoking delegates. Delegates are invoked in an unspecified order, and all delegates are invoked even if the first one marks the message as handled.

ThreadIdle indicates a good time to do idle processing (i.e., there’s no other pending messages for the thread). ThreadIdle will not be raised if the thread is modal.

ThreadFilterMessage event is raised for all messages that the message pump processes.  ThreadPreProcessMessage is raised for all messages that weren't handled during ThreadFilterMessage.

A message is considered handled if after the ThreadFilterMessage event or ThreadPreProcessMessage event, the "handled" parameter passed by ref into those events is true. Event handlers should ignore the message if bool handled is true, because that means the different handler handled the message first. Event handlers to both events may modify the message, the dispatcher should be sure to dispatch the modified message and not the original unchanged message. ThreadPreProcessMessage is delivered to anyone who listens, but the intention is that only the top-level window containing the hwnd at which the messages targeted should pay attention to the message.

How HwndSource talks to ComponentDispatcher
If the HwndSource is a top-level window (no parent hwnd), it will register with ComponentDispatcher.ThreadPreProcessMessage -- when that event is raised, if the message is intended for the HwndSource or descendent hwnd, HwndSource calls its IKIS.TranslateAccelerator/TranslateChar/OnMnemonic.

If the HwndSource is not a top-level window (it has a parent hwnd), it expects the parent will figure out how to talk to the message loop and invoke the appropriate IKIS methods.

If HwndSource's WndProc is called without an appropriate IKIS method being called first (eg, because the message loop didn't properly notify the ComponentDispatcher, or because the HwndSource's parent hwnd didn't call IKIS methods), HwndSource will do the best it can, and the WPF content will receive the usual keyboard events like UIElement.KeyDown.  However, no IKIS methods will be called.

Comments

  • Anonymous
    June 20, 2006
    The comment has been removed