Caleb 的个人资料Caleb's Ramblings日志列表 工具 帮助

Caleb's Ramblings

2月18日

WPF RoutedCommands not routing correctly in a ContextMenu

LiveJournal Tags: ,,,

I recently ran into a few problems with RoutedCommands in a ContextMenu I was adding to an application.  The problem was that when the ContextMenu was opened it wasn’t always finding the command bindings that I had in the parent Window (it would sometimes) and so the MenuItem was disabled.  After searching the web for some answers I found a few people that had also noticed this problem and they had a proposed work around.  You can find the work around here.  However the work around provided didn’t fit well with how I was setting up the ContextMenu so I decided to dig a little deeper.  After about 8 hours I found the reason and a solution to the problem that is far easier (at least for me) to put in place.

Before we go too far though I’ll provide some code so that you can replicate the issue for your self.  If you start with a blank WPF application project called RoutedCommandInContextMenu then you can simply add the following code to the listed files.

Add to Window1.xaml

<Window x:Class="RoutedCommandInContextMenu.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:RoutedCommandInContextMenu"
        Title="Window1"
        Height="300"
        Width="300">
    <ScrollViewer>
        <TextBlock Text="fooooobaaaaaar">        
            <TextBlock.ContextMenu>            
                <ContextMenu>                
                    <MenuItem Header="Foo"
                              Command="{x:Static local:MyCommands.FooBar}" />            
                </ContextMenu>        
            </TextBlock.ContextMenu>
        </TextBlock>
    </ScrollViewer>
</Window>

Add to Window1.cs

    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
            CommandBindings.Add(new CommandBinding(MyCommands.FooBar, FooExecuted, CanFooExecute));
        }

        public void FooExecuted(object sender, ExecutedRoutedEventArgs e)
        {
            MessageBox.Show("Foo!");
        }

        public void CanFooExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = true;
        }
    }

    public static class MyCommands 
    { 
        public static RoutedCommand FooBar = new RoutedCommand();     
    }

If you then run this application and right click (without doing anything else first) you will see that the “Foo” menu item is disabled in the context menu.  You will also note that if you left click the non border area of the window and then right click again the “Foo” menu item is enabled.

When I first noticed this behaviour I thought that perhaps the MenuItem was simply not calling the CanExecute method of the command.  So I made a simple WrappedCommand class that implemented ICommand and used that as the command instead.  I was then able to put a break in the can execute method of my wrapped command and see that it was indeed being called.  I could also see that the underlying routed command was returning false for its CanExecute even though the Window’s command binding was never being called.  Clearly there is something going on with the routing of the command.

ContextMenus are implemented as a separate window which is managed by the Popup class.  Because of this they have a different Visual Tree.  Their Logical Tree is also separate from the Window they were launched from.  There are one or two situations where it looks like the Popup might report itself to be a child of the launching Window, but it doesn’t in the case of our ContextMenu (even when it is working in our example).

It turns out that in when a UIElement is determining if a routed command can execute or not it uses an internal method on the CommandManager class called OnCanExecute. In here we can see (with enough analasis) that the method searches for a command binding within the focus scope of the sender (the element that raised the event), then if it can’t find one it will transfer the event to the parent focus scope (assuming there is one) to see if it has any CommandBindings registered.  This is how the routed command execution travels from the ContextMenu to the Window when things are working.  The thing is that it will start routing the event in the parent scope from whatever has the logical focus in that scope, this is where our problem lies.  Until we click the window (actually the ScrollViewer to be precise) the current logical focus is null, which means that the CommandManager doesn’t know where to start routing the event in the parent focus scope so it doesn’t transfer the event.  Once we click the ScrollViewer the logical focus scope is set to the ScrollViewer so the CommandManager transfers execution of the routed event to the scroll viewer which then finds the Window’s command bindings and everything is dandy.

To fix this issue you can simply add a call to Focus() in the Window’s constructor like so:

public Window1() { InitializeComponent(); CommandBindings.Add(new CommandBinding(MyCommands.FooBar, FooExecuted, CanFooExecute)); Focus(); // Alternativly you could use // SetValue(FocusManager.FocusedElementProperty, this);

// Another alternative is to use the following on the Window element in XAML

// FocusManager.FocusedElement="{Binding RelativeSource={x:Static RelativeSource.Self}, Mode=OneTime}" }

I believe that this is a bug in the framework as the documentation on Logical Focus states the following:

An element that has keyboard focus will also have logical focus for the focus scope it belongs to; therefore, setting focus on an element with the Focus method on the Keyboard class or the base element classes will attempt to give the element keyboard focus and logical focus.

You can demonstrate that this statement in the documentation is correct by adding the following code to the Window1 class and commenting out the Focus() call in the constructor.

protected override void OnContextMenuOpening(ContextMenuEventArgs e) { Debug.Assert(Keyboard.FocusedElement != this ||
Keyboard.FocusedElement == FocusManager.GetFocusedElement(this));
base.OnContextMenuOpening(e); }

Now when you right click the window the assertion will fail, because FocusManager.GetFocusedElement() is returning null, but the Keyboard is saying that the Window has the focus.

So there you go, after a lot of stepping through framework code, pulling my hair out and staying up into the wee hours of the morning, I was finally able to figure out what was going on.  I have raised this as a bug on MS connect so if you would like to rate it that might be nice.  The bug can be found here: https://connect.microsoft.com/feedback/ViewFeedback.aspx?FeedbackID=415864&SiteID=212.

Also when writing this post I found the following article on WPF command routing which explains a few other interesting uses for focus scopes which I found interesting: http://www.aspfree.com/c/a/.NET/More-on-Commands-Input-and-the-WPF/2/

2月9日

How to set a binding on a DependencyObject from code.

LiveJournal Tags: ,,

Sometimes it is useful to be able to set bindings on DependencyObjects in code.  If the object you are working in derives from FrameworkElement you can just use the object’s SetBinding method, but what if the object is derived from something else such as a Freezable?  The answer is that you use the static BindingOperations classes' SetBinding method, which unsurprisingly is what the implementation of SetBinding on FrameworkElement calls to do its work anyway.

2月4日

Scratch Pad projects are a great idea.

LiveJournal Tags: ,

For that last half a year or so I have been using a solution that I call scratch pad to try out ideas.  For example if I have an idea for some sort of crazy layout panel that I want to try out, but don’t wont to mess around with the project that it is destined for, then I make a new project (or reuse an existing one) in my ScratchPad.sln and try out the idea.  This has worked really well for me, because I typically find it hard to decide on the best way to do something to start with which makes me procrastinate doing it.  Since the scratch pad doesn’t matter and I don’t have to worry about making a mess it is much easier for me to just jump in and try something out.

2月2日

WPF Designer Frustrations

LiveJournal Tags: ,,,

Lately I have been increasingly frustrated with the designers for WPF.  I would not be at all surprised to find that I would have some of the same frustrations with the Windows forms designer, but some of the frustrations are more WPF specific.  The real problem seems to be that as soon as you do something non trivial the designer will break even though the code runs perfectly fine.  Here are some of the things that are giving me grief at the moment.

  1. I moved a style out of a UserControl into a separate ResourceDictionary that is merged into the Application’s resource dictionary.  The designer can still load the user control, but if I try to load the designer for the Window that uses this UserControl it says it can’t find the style.  If I change this style reference to a DynamicResource reference instead of a StaticResource reference then the Visual Studio designer will load, but not Blend.
  2. I am using an IoC container (Castle Windsor) in some of my domain objects to provide instances of classes which inherit a certain interface.  The view model which I am using instantiates some of these domain objects and BOOM! the designer can’t cope with setting up the IoC container because it can’t find the config file.  If I hard code the location of the config file in (not desirable, but possibly a liveable workaround when I am in design mode) then I get a different error saying that it couldn’t load the Castle assemblies.  I can’t see why this should happen because they required assemblies are added as references in the project.

The designers are very useful when they are working as they enable you to quickly try out loads of different brushes for styles without having to start the app up each time, but increasingly I find my self fighting with the designer to get it to show me the thing I am interested in so I can work on it.  Maybe I am using the wrong approach to things, but there seems very little documentation of a better way to work with them while actually showing the data you are interested in. 

What would be truly awesome is if you could attach a designer program to a running application and muck around with the properties of the objects while the application was running.  That way you wouldn’t have the problem of not being able to see the working data from the database or some other datasource.

2月1日

How To find the config file path for you application.

LiveJournal Tags: ,,

Today I was trying to solve an issue with nunit not setting the correct path for the assembly I was testing’s config file.  Well it wasn’t setting the expected path anyway.  It took me a little while to find out where the application was looking for the config file.  Eventually I discovered the following property: AppDomain.CurrentDomain.SetupInformation.ConfigurationFile.  Hopefully this can be of some use to you in the future.

 

Vear Caleb

职业
地点
兴趣
尚未添加列表。