namespace System.Windows.Threading
{
public sealed class DispatcherHooks
{
// boring stuff elided
public event EventHandler DispatcherInactive { add; remove; }
public event DispatcherHookEventHandler OperationPosted { add; remove; }
public event DispatcherHookEventHandler OperationCompleted { add; remove; }
public event DispatcherHookEventHandler OperationPriorityChanged { add; remove; }
public event DispatcherHookEventHandler OperationAborted { add; remove; }
}
}
The names of these events are self-explanatory. DispatcherInactive doesn't need to provide any additional information, but the other four events need to identify the affected operation.
namespace System.Windows.Threading
{public delegate void DispatcherHookEventHandler(object sender, DispatcherHookEventArgs e);
public sealed class DispatcherHookEventArgs : EventArgs
{
public DispatcherHookEventArgs(DispatcherOperation operation) { ... }
public Dispatcher Dispatcher { get; }
public DispatcherOperation Operation { get; }
}
}
Now that we are familiar with the DispatcherOperation class, we know what information is easily available to us once we receive a set of DispatcherHookEventArgs. Again, I find it odd that DispatcherOperation doesn't expose its Name property, but we can use reflection to easily get around that.
Starting with a standard WPF application, I edited my App.xaml.cs file to hook into the UI Dispatcher and log events to standard output.
public partial class App : Application
{
private PropertyInfo namePropertyInfo;
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
namePropertyInfo = typeof (DispatcherOperation).GetProperty("Name",
BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.NonPublic);
var hooks = Dispatcher.Hooks;
hooks.DispatcherInactive += HandleDispatcherInactive;
hooks.OperationAborted += HandleOperationAborted;
hooks.OperationCompleted += HandleOperationCompleted;
hooks.OperationPosted += HandleOperationPosted;
hooks.OperationPriorityChanged += HandleOperationPriorityChanged;
}
protected override void OnExit(ExitEventArgs e)
{
var hooks = Dispatcher.Hooks;
hooks.DispatcherInactive -= HandleDispatcherInactive;
hooks.OperationAborted -= HandleOperationAborted;
hooks.OperationCompleted -= HandleOperationCompleted;
hooks.OperationPosted -= HandleOperationPosted;
hooks.OperationPriorityChanged -= HandleOperationPriorityChanged;
base.OnExit(e);
}
private void HandleDispatcherInactive(object sender, EventArgs e)
{
LogOperationEvent("Inactive", null);
}
private void HandleOperationAborted(object sender, DispatcherHookEventArgs e)
{
LogOperationEvent("Aborted", e.Operation);
}
private void HandleOperationCompleted(object sender, DispatcherHookEventArgs e)
{
LogOperationEvent("Completed", e.Operation);
}
private void HandleOperationPosted(object sender, DispatcherHookEventArgs e)
{
LogOperationEvent("Posted", e.Operation);
}
private void HandleOperationPriorityChanged(object sender, DispatcherHookEventArgs e)
{
LogOperationEvent("PriorityChanged" , e.Operation);
}
private void LogOperationEvent(string eventName, DispatcherOperation operation)
{
var builder = new StringBuilder();
builder.AppendFormat("{0,-16}", eventName);
if (operation != null)
{
var operationName = namePropertyInfo.GetValue(operation, new object[0]);
builder.AppendFormat("{0,-16}", operation.Priority);
builder.Append(operationName);
}
Console.WriteLine(builder.ToString());
}
}
When you run this example, interact with the main window by clicking on it, typing characters, moving it, resizing it, etc. The output window will show you a stream of operations posted to the Dispatcher's work queue to respond to your requests:
Posted Input System.Windows.Input.InputManager.ContinueProcessingStagingArea
Posted Background System.Windows.Input.CommandManager.RaiseRequerySuggested
Inactive
Posted Input System.Windows.Interop.HwndMouseInputProvider.<FilterMessage>b__0
Completed Input System.Windows.Interop.HwndMouseInputProvider.<FilterMessage>b__0
Inactive
Posted Input System.Windows.Interop.HwndMouseInputProvider.<FilterMessage>b__0
Posted Render System.Windows.Media.MediaContext.RenderMessageHandler
Posted Inactive System.Windows.Input.InputManager.HitTestInvalidatedAsyncCallback
Posted Inactive System.Windows.Threading.DispatcherTimer.FireTick
Completed Render System.Windows.Media.MediaContext.RenderMessageHandler
Completed Input System.Windows.Interop.HwndMouseInputProvider.<FilterMessage>b__0
Inactive
PriorityChanged Inactive System.Windows.Threading.DispatcherTimer.FireTick
PriorityChanged Inactive System.Windows.Input.InputManager.HitTestInvalidatedAsyncCallback
Completed Background System.Windows.Threading.DispatcherTimer.FireTick
Completed Input System.Windows.Input.InputManager.HitTestInvalidatedAsyncCallback
Inactive
Posted Normal System.Windows.Interop.HwndSource.RestoreCharMessages
Posted Input System.Windows.Input.InputManager.ContinueProcessingStagingArea
Aborted Normal System.Windows.Interop.HwndSource.RestoreCharMessages
Completed Input System.Windows.Input.InputManager.ContinueProcessingStagingArea
Inactive
Posted Input System.Windows.Interop.HwndMouseInputProvider.<FilterMessage>b__0
Posted Input System.Windows.Input.InputManager.ContinueProcessingStagingArea
Posted Render System.Windows.Media.MediaContext.RenderMessageHandler
Posted Input System.Windows.Input.KeyboardDevice.ReevaluateFocusCallback
Posted Input System.Windows.Input.InputManager.HitTestInvalidatedAsyncCallback
Posted Loaded System.Windows.BroadcastEventHelper.BroadcastUnloadedEvent
Posted Background System.Windows.Input.CommandManager.RaiseRequerySuggested
Posted Normal System.Windows.Application.ShutdownCallback
Posted Normal MS.Win32.HwndWrapper.UnregisterClass
I considered putting this information in a separate WPF Window, but I was worried about how that would affect the Dispatcher queue! I hope to play with multiple UI Dispatchers shortly and should then be able to work up a separately-dispatched "spy" window. For now, this output gives us good feedback on what our main Dispatcher is doing for us; naturally, you could apply the same technique to non-UI Dispatchers.
[Wolfgang Gartner: Illmerica -- YouTube]
No comments:
Post a Comment