Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Silverlight 4 Default Button Service

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

like image 993
Mark Cooper Avatar asked Apr 21 '10 14:04

Mark Cooper


3 Answers

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();

Edit: Added XAML example.

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);
    }       
}

How to apply in XAML:

<StackPanel>
    <TextBox DinnerConfig:DefaultButtonService.DefaultButton="{Binding ElementName=MyButton}"
                Text="Press Enter" />
    <Button x:Name="MyButton"
            Content="Click me" />
</StackPanel>
like image 162
Mark Cooper Avatar answered Oct 27 '22 17:10

Mark Cooper


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:

Edit2: Added XAML example for clarity.

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);
        }        
    }

How to apply in XAML:

<StackPanel>
    <TextBox DinnerConfig:DefaultButtonService.DefaultButton="{Binding ElementName=MyButton}"
                Text="Press Enter" />
    <Button x:Name="MyButton"
            Content="Click me" />
</StackPanel>
like image 29
Ciaran O'Neill Avatar answered Oct 27 '22 17:10

Ciaran O'Neill


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" ...
like image 23
Trinition Avatar answered Oct 27 '22 18:10

Trinition