During the last weeks I've been doing a lot of WPF when migrating a MVC (Model-View-Controller) based WinForms application to the Windows Presentation Foundation using the MVVM (Model-View-ViewModel) pattern and the fabulous Caliburn Framework.
When I touched WPF the first time about 1,5 years ago, my first thought was: "Oh no... not just XML again!". But over the time, I started to love it. I'm feeling very comfortable with editing the xaml files by hand, without the support of any design tool - the basic and commonly used layouts and components are as easy to use as HTML. When I did some GTK development with Glade yesterday, I painfully missed my beloved Stackpanel, Grid and Margin Property :-)
While 90% of the daily work with WPF is pretty easy, the other 10% sometimes can be tricky. Thankfully in most cases I'm not the first one that encounters a specific problem and often find a solution in a blog or forum post.
"Time to post some hopefully helpful tips myself", I thought, and here we go:
Click me!
For a WPF touch screen interface, I required all buttons in the application to make a "click" sound when beeing pressed.
"There must be a way to do this in XAML only", I thought and came up with this solution:
<Window x:Class="RoutedClickEvent.Window1" ...>
<Window.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<EventTrigger.Actions>
<SoundPlayerAction Source="/RoutedClickEvent;component/click.wav" />
</EventTrigger.Actions>
</EventTrigger>
</Window.Triggers>
...
This works fine - Easy Peasy!
But not so fast my young padawan!
If I have anything in a buttons event handler, that causes a noticable delay (e.g. showing a modal dialog), the button sound will be delayed as well.
The reason is, that the Click event is a bubbling event. It bubbles from the button itself up to the Window. That means, the buttons click event handler will be executed, before the event is triggerd on the window, causing the click sound to appear delayed.
My next thought was, to go with the PreviewMouseDown event (which is a tunneling event, that will be handeled before the buttons click event). Unfortunately, this required some code:
private void Window_PreviewMouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
var source = e.OriginalSource as DependencyObject;
var button = source as ButtonBase;
while (button == null && source != null)
{
source = VisualTreeHelper.GetParent(source);
button = source as ButtonBase;
}
if (button != null)
{
var stream = Application.GetResourceStream(new Uri("pack://application:,,,/PreviewMouseDownHandler;Component/click.wav"));
new SoundPlayer(stream.Stream).Play();
}
}
Works fine, but walking up the visual tree to figure out, if the mouse down event happend on a button, feels terribly wrong!
"Use the Styles Luke!" - Now I tried to apply a style to the buttons, that makes them play the click sound when pressed. You indeed can wire events with the <EventSetter> tag. But I want my style to be defined in a separate ResourceDictionary, so I don't have any instance I could wire the event to.
And now it becomes a little bit weired - I created a code behind file for the ResourceDictionary! I didn't really expected this to work, but it did! To do so, just add a x:Class attribute to the ResourceDictionary, create a new class file with "MyResourceDictionary.xaml.cs" and the partial class "MyResourceDictionary" and vour la! If you want the code behind file to appear as a child of the xaml file in VisuaStudio, you can also add a DependentUpon tag to the code behind node in the *.csproj:
<Compile Include="NoisyButton.xaml.cs">
<DependentUpon>NoisyButton.xaml</DependentUpon>
</Compile>
Now that the ResourceDictonary has a code behind, I can wire the click event in the style settings to the code behind class:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="StyleWithCodeBehind.NoisyButton">
<Style TargetType="Button">
<EventSetter Event="PreviewMouseDown"
Handler="OnClick" />
</Style>
</ResourceDictionary>
public partial class NoisyButton
{
private void OnClick(object sender, RoutedEventArgs e)
{
var stream = Application.GetResourceStream(new Uri("pack://application:,,,/StyleWithCodeBehind;Component/click.wav"));
new SoundPlayer(stream.Stream).Play();
}
}
Now just merge the ResourceDictionary in the main window or App and you will hear the buttons click:
<Window x:Class="StyleWithCodeBehind.Window1" ...>
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="NoisyButton.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<StackPanel>
<Button>Click me!</Button>
<Button Click="Button_Click">Click me harder!</Button>
</StackPanel>
</Window>
A ResourceDictionary with a code behind file is kinda cool, but there's an easier way to make the buttons click. Indeed it's so easy, I should have come up with this in the first place:
<Style TargetType="Button">
<Style.Triggers>
<EventTrigger RoutedEvent="PreviewMouseDown">
<SoundPlayerAction Source="/XamlOnlyClickingButton;component/click.wav" />
</EventTrigger>
</Style.Triggers>
</Style>
Lesson learned - don't think too complicated!




