views:

817

answers:

4

For a few months I have been successfully using David Justices Default Button example in my SL 3 app. This approach is based on an attached property.

After upgrading to SL4, the approach no longer works, and I get a XAML exception: "Unknown parser error: Scanner 2148474880"

Has anyone succesfully used this (or any other) default button attached behaviours in SL4?

Is there any other way to achieve default button behaviour in SL4 with the new classes that are available?

Thanks, Mark

+2  A: 

I was really hoping that there would be a out of the box solution for such a common use-case in Silverlight 4, but unfortunately I don't think there is.

There is another Default Button implementation by Patrick Cauldwell. He's also using Attached Properties.

I've tested this in a SL 4 application and it seems to do the job.

You can find the code here: http://www.cauldwell.net/patrick/blog/DefaultButtonSemanticsInSilverlightRevisited.aspx

Edit: I've tweaked David Justice's code to get it working for Silverlight 4. I've just changed the GetDefaultButton and SetDefaultButton to take and return a DefaultButtonService. Usage is the same as noted on his website. This should work for you:

public class DefaultButtonService
    {
        public static DependencyProperty DefaultButtonProperty =
            DependencyProperty.RegisterAttached("DefaultButton",
                                                typeof(Button),
                                                typeof(DefaultButtonService),
                                                new PropertyMetadata(null, DefaultButtonChanged));

        private static void DefaultButtonChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var uiElement = d as UIElement;
            var button = e.NewValue as Button;
            if (uiElement != null && button != null)
            {
                uiElement.KeyUp += (sender, arg) =>
                {
                    if (arg.Key == Key.Enter)
                    {
                        var peer = new ButtonAutomationPeer(button);
                        var invokeProv =
                            peer.GetPattern(PatternInterface.Invoke) as IInvokeProvider;
                        if (invokeProv != null)
                            invokeProv.Invoke();
                    }
                };
            }
        }

        public static DefaultButtonService GetDefaultButton(UIElement obj)
        {
            return (DefaultButtonService)obj.GetValue(DefaultButtonProperty);
        }

        public static void SetDefaultButton(DependencyObject obj, DefaultButtonService button)
        {
            obj.SetValue(DefaultButtonProperty, button);
        }        
    }
Ciaran
Thanks. David's and Patricks approachs are pretty much exactly the same - although I'm pleased to see that the issue isn't with SL4, must a particular scenario related to our app. Thanks for taking time to response +1
Mark Cooper
Shouldn't the "Get/SetDefaultButton" methods return/take a Button instead of a DefaultButtonService? The type in question is the type of the actual value being retrieved/stored in the property. I've made that change myself and it is working now.
Trinition
Yes, Trinition, you are correct. If you would like to add an example I will vote your answer as correct. You can copy my example added from below if you would like.
Mark Cooper
A: 

I extended David's approach by allowing a custom key (defaulted to Enter) to be set in an additional property:

    public static DependencyProperty ButtonKeyProperty = DependencyProperty.RegisterAttached(
         "ButtonKey",
         typeof(Key),
         typeof(Defaults),
         new PropertyMetadata(Key.Enter, ButtonChanged));

    public static void SetButtonKey(DependencyObject dependencyObj, Key key)
    {
        dependencyObj.SetValue(ButtonKeyProperty, key);
    }

    public static Key GetButtonKey(DependencyObject dependencyObj)
    {
        return (Key)dependencyObj.GetValue(ButtonKeyProperty);
    }

I modified the original property to then leverage this property:

    Key key = GetButtonKey(dependencyObj);
    if (button.IsEnabled && keyEvent.Key == key)
        ...

So now, for example, I can use Escape as the key if I want (note I changed the named of the classes and properties):

    ... UI:Defaults.Button="{Binding ElementName=myButton}" UI:Defaults.ButtonKey="Escape" ...
Trinition
A: 

The issue was caused by a poorly typed GetDefaultButton method. This should have been typed as Button, for example using:

    public static Button GetDefaultButton(UIElement obj)
    {
        return (Button)obj.GetValue(DefaultButtonProperty);
    }

instead of

    public static DefaultButtonService GetDefaultButton(UIElement obj)    
    {    
        return (DefaultButtonService)obj.GetValue(DefaultButtonProperty);    
    } 

works as expected.

HTH someone else,
Mark

Mark Cooper
+1  A: 

The final solution for us also had to get around the issue where the backing property was not being updated prior to the button click occuring (as in all MVVM patterns).... Note: peer.SetFocus();

public static class DefaultButtonService
{
    public static DependencyProperty DefaultButtonProperty =
          DependencyProperty.RegisterAttached("DefaultButton",
                                              typeof(Button),
                                              typeof(DefaultButtonService),
                                              new PropertyMetadata(null, DefaultButtonChanged));

    private static void DefaultButtonChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
        var uiElement = d as UIElement;
        var button = e.NewValue as Button;
        if (uiElement != null && button != null) {
            uiElement.KeyUp += (sender, arg) => {
                var peer = new ButtonAutomationPeer(button);

                if (arg.Key == Key.Enter) {
                    peer.SetFocus();
                    uiElement.Dispatcher.BeginInvoke((Action)delegate {

                        var invokeProv =
                            peer.GetPattern(PatternInterface.Invoke) as IInvokeProvider;
                        if (invokeProv != null)
                            invokeProv.Invoke();
                    });
                }
            };
        }

    }

    public static Button GetDefaultButton(UIElement obj) {
        return (Button)obj.GetValue(DefaultButtonProperty);
    }

    public static void SetDefaultButton(DependencyObject obj, Button button) {
        obj.SetValue(DefaultButtonProperty, button);
    }       
}
Mark Cooper