Udostępnij za pośrednictwem


Microcode: Scripting Tricks : Exploring WPF Routed Events with PowerShell

Both WPF and PowerShell are both full of great little touches.  One of WPF's nice touches is Routed Events.  Routed Events allow you to capture events raised by child controls or parent controls.  This can be incredibly useful, because it allows you write less code (which I believe is generally good) by re-using handlers.

A good example of why you might want to do this is wanting to have more than one button in a user interface that updates a database.  What each button might do may be different, but they all to connect to a database.  This means you could write either write three longer (and less portable) methods that connect to the database and perform an operation, or three shorter ( and more portable ) methods that just perform the operation on a database and one operation that connects to the database.   By having each of the buttons go through a routed event on a container, you can write one short routing handler that connects to the database, looks at where the message came from, and performs the operation.

WPF comes with an amazing number of controls, and a pretty nice selection of Routed Events.  The WPF team was event nice enough to have a manager for these routed events, which means that we can explore them dynamically using PowerShell.

The event manager is available in the class System.Windows.EventManager.  Let's start exploring it with PowerShell.  To do this, make sure you've loaded the WPF assembly into PowerShell  by using the line [Reflection.Assembly]::LoadWithPartialName("PresentationCore"). We can omit the System namespace when referring to a type in PowerShell, so we can call it [Windows.EventManager] from now on.  We can use Get-Member -static to see what this class can do.

[Windows.EventManager] | Get-Member -static

It has the following methods:

  • Equals (every .NET object has this static member)
  • GetRoutedEvents()
  • GetRoutedEventsForOwner(Type ownerType)
  • ReferenceEquals (every .NET object has this static member)
  • RegisterClassHandler (Type classType, RoutedEvent routedEvent, Delegate handler)
  • RegisterClassHandler (Type classType, RoutedEvent routedEvent, Delegate handler, Boolean handledEventsToo)
  • RegisterRoutedEvent(String name, RoutingStrategy routingStrategy, Type handlerType, Type ownerType)

Since I'm just exploring, GetRoutedEvents() is the method I'm interested in.

I can see every routed event with the simple PowerShell one-liner:

[Windows.EventManager]::GetRoutedEvents()

Of course we can pipe that to Get-Member in order to see what we've got for every Routed Event:

[Windows.EventManager]::GetRoutedEvents() | Get-Member

It's more interesting to me to figure out what events I can bubble up to the parent controls.  Since each RoutedEvent has a RoutingStrategy property, I can write this as a simple filter using Where-Object:

[Windows.EventManager]::GetRoutedEvents() | Where-Object { $_.RoutingStrategy -eq "Bubble" } | Sort-Object Name

Since each RoutedEvent also has a HandlerType property, I can also see what types of different handlers exist by using

[Windows.EventManager]::GetRoutedEvents() | Group-Object HandlerType | Sort-Object Count -descending

Finally, I can use the EventManager to help guess about what types of controls could fit the bill for whatever I need to do. The next one liner gives me all of the routed events that work with Drag and Drop

[Windows.EventManager]::GetRoutedEvents() | Where-Object {$_.Name -like "*Drag*" -or $_.Name -like "*Drop*" }

Associating these events with a class needs to use some tricks of reflection.  In the sample below, I'll look for all drag and drop handlers within PresentationBase (where the EventManager and some base components live) and PresentationFramework (where most of the built-in WPF controls reside):

$handlers = [Windows.EventManager]::GetRoutedEvents() |
Where-Object {
$_.Name -like "*Drag*" -or $_.Name -like "*Drop*"
}
$assemblies = [Windows.EventManager].Assembly,
[Windows.Window].Assembly

$assemblies | Foreach-Object {
$_.GetTypes() | Where-Object {
foreach ($handler in $handlers) {
if ($_.GetEvent($handler.Name)) {
return $true
}
}
}
}

The script above takes a bit of explaining.

I store the Drag and Drop handlers into a variable, and store the assemblies I wanted to search in another variable (to search all of my currently loaded types for these handlers, I'd use just assign $assemblies to [AppDomain]::CurrentDomain.GetAssemblies() ).  Then I go through each assembly, retrieve the types, and pipe them into Where-Object, which will determine if they meet my criteria.  I go through each class and each handler that met my criteria, and use the GetEvent method (which is defined on any .NET type definition) to determine if the event is present.  If the event is present, I return $true, which makes the Where-Object output the type.  If the event's not present, then code will go onto the next handler and the next type.

These are just a few examples of the ways you can use PowerShell to explore WPF routed events.  The techniques used in this post can be used on to explore just about any .NET type.  Exploring .NET through reflection is a great way to save time looking things up, and the pathway to using existing .NET code to solve a problem rather than reinventing the wheel.

Hope this helps,

James Brundage [MSFT]