Silverlight 4 Default Button Service
Asked Answered
F

5

5

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

Flite answered 21/4, 2010 at 14:37 Comment(0)
B
2

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" ...
Bettis answered 26/5, 2010 at 14:3 Comment(0)
F
8

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>
Flite answered 9/7, 2010 at 13:28 Comment(0)
M
3

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>
Mcphee answered 29/4, 2010 at 5:16 Comment(3)
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 +1Flite
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.Bettis
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.Flite
B
2

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" ...
Bettis answered 26/5, 2010 at 14:3 Comment(0)
F
0

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

Flite answered 28/5, 2010 at 11:9 Comment(0)
O
0

Here is an easy way to wire a default button when enter is pressed:

For security reasons, silverlight will not let you call a button handler directly in code, so we have to create a new routine that can be called, then have the button handler and the form keydown handler both call this routine.

Step 1: make a routine and put everything in it that the button routine had

private void DoSharedRoutine(){ // do something }

Step 2: have button handler call shared routine

private void Login_Click(object sender, System.Windows.RoutedEventArgs e)
    {
        DoSharedRoutine();
    }

Step 3: have keydown handler of your panel that contains your button call the shared routine

private void MyGrid_KeyDown(object sender, System.Windows.Input.KeyEventArgs e)
     {
         if (e.Key == Key.Enter) DoSharedRoutine();
     }
Oracular answered 20/12, 2012 at 12:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.