ITHCWY: Robert Ellison's Blog

Merging Resource Dictionaries for fun and profit

Here are two scenarios where merged ResourceDictionary objects are the way forward.

I’m working on a WPF project that needs to be single instance. Heaven forbid that the WPF team should pollute the purity of their framework with support for this kind of thing (or NotifyIcon support but that’s another story) so I’m using the code recommended by Arik Poznanski: WPF Single Instance Application. I like this because it both enforces a single instance and provides an interface that reports the command line passed to any attempt to launch another instance.

An issue with using this code is that you need to write a Main function and so App.xaml is set to Page instead of Application Definition. Once you’ve done this the program works fine but the Visual Studio designer fails to load resources in UserControls (and in Windows containing those UserControls).

The fix is to factor all of the application level resources out into a separate ResourceDictionary (i.e. MergedResources.xaml). Once you’ve done this merge the new ResourceDictionary into App.xaml as follows:

<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="MasterResources.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>

Next, in each Window or UserControl reference the same ResourceDictionary:

<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="MasterResources.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>

The designer will now be able to find the correct resources for each UserControl and Window.

The second scenario is factoring resources and other Xaml into a DLL. To pull resources in from a referenced assembly you just need to use a Pack Uri when merging in the remote ResourceDictionary:

<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/MyDll;component/ExternalResources.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>

If you’re putting Windows and UserControls in the DLL use exactly the same approach to reference the resources using ResourceDictionary.MergedDictionaries and you’ll get designer support for these as well.

UAC shield icon in WPF

WPF: When it's good it’s very, very good and when it’s bad it’s like sautéing your own eyeballs.

When you’re about to launch a process that will trigger an elevation prompt it’s polite to decorate it with the little UAC shield so the user knows what to expect. Of course there’s no such capability in WPF, and WPF controls have no handles so you can’t use SendMessage / BCM_SETSHIELD as with Windows Forms.

System.Drawing.SystemIcons.Shield seems promising, but it returns the wrong icon on Windows 7 (at least in .NET 4).

SHGetStockIconInfo will allow you to get the correct icon, but isn’t supported on Windows XP. I’ve just added the necessary interop signatures for SHGetStockIconInfo to pinvoke.net so I won’t duplicate that code here. Once you have the interop you can get the correct icon as a BitmapSource using the following code:

BitmapSource shieldSource = null;

if (Environment.OSVersion.Version.Major >= 6)
{
    SHSTOCKICONINFO sii = new SHSTOCKICONINFO();
    sii.cbSize = (UInt32) Marshal.SizeOf(typeof(SHSTOCKICONINFO));

    Marshal.ThrowExceptionForHR(SHGetStockIconInfo(SHSTOCKICONID.SIID_SHIELD,
        SHGSI.SHGSI_ICON | SHGSI.SHGSI_SMALLICON,
        ref sii));

    shieldSource = System.Windows.Interop.Imaging.CreateBitmapSourceFromHIcon(
        sii.hIcon,
        Int32Rect.Empty, 
        BitmapSizeOptions.FromEmptyOptions());

    DestroyIcon(sii.hIcon);
}
else
{
    shieldSource = System.Windows.Interop.Imaging.CreateBitmapSourceFromHIcon(
        System.Drawing.SystemIcons.Shield.Handle,
        Int32Rect.Empty, 
        BitmapSizeOptions.FromEmptyOptions());
}

WPF commands with nested focus scope

Here's a frustrating WPF scenario — you use the ApplicationCommands class to add Cut, Copy and Paste commands to toolbar and then put a TextBox on another toolbar. Click in the TextBox and the commands remain disabled. WTF, WPF?

The problem is with focus scopes. Your window is a focus scope and so are any menus or toolbars. This has the desirable property of allowing commands to target the control you were in immediately before invoking the command. You want paste to target the text box you're editing, not the menu item or button you clicked to request the paste.

So far so good. The problem is that the commanding system isn't smart enough to target the control with keyboard focus if it's in a nested focus scope. Remember that the window itself is a focus scope so our TextBox in a ToolBar (also a focus scope) is nested and immune to commands from our menu or toolbar.

Here's a simple window that demonstrates the problem:

<Window x:Class="WpfFocusScopeTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525" Loaded="Window_Loaded">

    <Window.CommandBindings>
        <CommandBinding Command="Paste" x:Name="bindingPaste" PreviewCanExecute="bindingPaste_PreviewCanExecute" />
    </Window.CommandBindings>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" /><RowDefinition Height="Auto"  /><RowDefinition />
        </Grid.RowDefinitions>
        
        <Menu>
            <MenuItem Header="Edit"><MenuItem Command="Paste" Name="menuItemPaste"/></MenuItem>
        </Menu>
        
        <ToolBarTray Grid.Row="1">
            <ToolBar Band="0" BandIndex="0">
                <Button Command="Paste" Name="buttonPaste">Paste</Button>
            </ToolBar>
            <ToolBar Band="0" BandIndex="1">
                <TextBox Width="100" />
            </ToolBar>
        </ToolBarTray>

        <DockPanel Grid.Row="2">
            <TextBox />
        </DockPanel>

    </Grid>
</Window>

Ignore the PreviewCanExecute handler for now. If you run this window and click in the main TextBox the paste button and menu item are enabled. Click in the toolbar TextBox and pasting isn't an option. Well, Ctrl-V still works and there's a context menu but you know what I mean.

The problem can be fixed by adding a command binding for ApplicationCommands.Paste and handling the PreviewCanExecute event:

public partial class MainWindow : Window
{
    private DependencyObject _menuFocusScope;
    private DependencyObject _toolbarFocusScope;

    public MainWindow()
    {
        InitializeComponent();
    }

    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        _menuFocusScope = FocusManager.GetFocusScope(menuItemPaste);
        _toolbarFocusScope = FocusManager.GetFocusScope(buttonPaste);
    }  

    private void bindingPaste_PreviewCanExecute(object sender, CanExecuteRoutedEventArgs e)
    {
        IInputElement keyboardFocus = Keyboard.FocusedElement;

        if ((keyboardFocus != null) && (keyboardFocus != this))
        {
            DependencyObject pasteTargetAsDependencyObjeect = 
                keyboardFocus as DependencyObject;
            if (pasteTargetAsDependencyObjeect != null)
            {
                DependencyObject targetFocusScope = 
                    FocusManager.GetFocusScope(pasteTargetAsDependencyObjeect);

                if ((targetFocusScope != _menuFocusScope) && 
                    (targetFocusScope != _toolbarFocusScope) )
                {
                    menuItemPaste.CommandTarget = keyboardFocus;
                    buttonPaste.CommandTarget = keyboardFocus;
                }
            }
        }
    }
}

When the window loads we're making note of the focus scopes for the toolbar and menu. Then when PreviewCanExecute fires we check to see if the element with the keyboard focus is in a different focus scope (and also that the window doesn't have keyboard focus). We then set the CommandTarget for the menu item and button to the element that has keyboard focus.

A handler isn't required for CanExecute as the command will take care of this with respect to the new CommandTarget.

Run the window again and you'll see that the paste button is enabled for both of the TextBox controls. When you click the button (or menu item) our PreviewCanExecute handler ignores the new keyboard focus and the command is sent to the desired control. 

One drawback of this approach is that keyboard focus isn't returned to the TextBox after the command executes. The CommandTarget remains in place so you can keep pasting and the command remains enabled but you lose the visual cue that lets you know where the target is. I haven't figured out a clean approach to this yet. When I do, I'll update this post. Better yet, if you've figured it out leave a comment.

Use WPF Dispatcher to invoke event handler only when needed

After floundering a bit with the WPF Dispatcher I've come up with a simple way to make sure an event handler executes on the UI thread without paying the overhead of always invoking a delegate.

void someEvent_Handler(object sender, SomeEventEventArgs e)
{
    if (this.Dispatcher.CheckAccess())
    {
        // do work on UI thread
    }
    else
    {
        // or BeginInvoke()
        this.Dispatcher.Invoke(new Action(someEvent_Handler), 
            sender, e);
    }
}

This has the benefit (for me at least) of being very easy to remember. Hook up the event handler and then if there's a chance it could be called from a different thread wrap it using the pattern above. It's easier to read than an anonymous delegate and much faster than defining a specific delegate for the event in question.

I haven't tested the various methods to see which is the fastest yet… will get round to this at some point.

XamlParseException and 256x256 icons

When testing out a WPF app on XP I got an unhelpful XamlParseException error report. 

I was a little puzzled because I was hooking up error reporting in App.xaml.cs:

App.Current.DispatcherUnhandledException += 
    new DispatcherUnhandledExceptionEventHandler(Current_DispatcherUnhandledException);
AppDomain.CurrentDomain.UnhandledException += 
    new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);

My error handler was attempting to create a XAML window to report the error, and evidently this was bombing out as well triggering the good doctor Watson. I added a MessageBox call instead and discovered that the XamlParseException was wrapping a FileFormatException and the stack trace indicated that the problem was with setting the icon for the window. After removing the icon the app started up fine. Weird.

It turns out that WPF chokes on a compressed 256x256 icon on XP and Vista (Windows 7 seems to cope fine). Saving the icon without compression fixes the problem. I use IcoFX and you can set this at Options -> Preferences -> Options -> Compress 256x256 images for Windows Vista. Of course the consequence is that the icon is a couple of hundred kilobytes larger.