« Http Modules, Basic Authentication and IISI, Model (Part I - An Entity Wrapper) »

Command pattern in Silverlight

05/14/09

Permalink 08:50:10 pm, Categories: Architecture

Contrary to WPF, Silverlight doesn't really support Commands in the xaml. For example, the Button class doesn't have a Dependency Property called Command that allows to bind to a command. The command pattern is at the heart of the Model-View-ViewModel pattern (M-V-VM), and this pattern is probably the most important in WPF and Silverlight. Thus, we had to find a way to have it in Silverlight!

Follow up:

My teammate Roman found an example involving Attached Dependency Properties. This is an important implementation, because it allows one to put commands on any type of existing controls, without having to change this control, as long as one codes so-called behaviors. However, I thought that this approach was somewhat overkill to some extent, especially when it comes to binding the button to an existing command exposed by the ViewModel, as advised by the M-V-VM pattern. In fact, with the Attached Dependency Property solution, the command has to live in a Command Store, actually making this property more "detached" than Attached :-)

So I looked at the problem and decided to implement the commands pretty much the same way they're implemented in WPF, with Dependency Properties. However, as opposed to Attached properties, the dependency property has to be xaml'ed in the class that's the target of the property, so I started by deriving a class from the Button class, and put this class in the Omniscient.Foundation.Contrib.Silverlight.CommandControls namespace. That gives us a Button to use in xaml that sports a property Command and a property CommandParameter. The nice part is that it's somewhat better than the actual WPF implementation, since reassigning the CommandParameter's value (either from an animation, some action, etc) re-evaluates the CanExecute value, affecting the button's IsEnabled value.

As needs emerge, we will add controls to Contrib. I invite anyone to add classes and send me patches!

Here's the Button code:

using System.Windows;
using System.Windows.Input;
namespace Omniscient.Foundation.Contrib.Silverlight.CommandControls
{
    public class Button: System.Windows.Controls.Button
    {
        public static readonly DependencyProperty CommandProperty;
        public static readonly DependencyProperty CommandParameterProperty;

        static Button()
        {
            CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(Button),
                new PropertyMetadata(null, new PropertyChangedCallback(OnCommandChanged)));

            CommandParameterProperty = DependencyProperty.Register("CommandParam", typeof(object), typeof(Button),
                new PropertyMetadata(null, new PropertyChangedCallback(OnCommandParamChanged)));
        }

        public Button()
            : base()
        {
            this.Click += new RoutedEventHandler(CommandButton_Click);
        }

        void CommandButton_Click(object sender, RoutedEventArgs e)
        {
            if (Command != null) Command.Execute(this.CommandParam);
        }

        public ICommand Command
        {
            get { return (ICommand)GetValue(CommandProperty); }
            set { SetValue(CommandProperty, value); }
        }

        public object CommandParam
        {
            get { return GetValue(CommandParameterProperty); }
            set { SetValue(CommandParameterProperty, value); }
        }

        private static void OnCommandChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            Button button = (Button)sender;
            button.UpdateCanExecute();
        }

        private static void OnCommandParamChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            Button button = (Button)sender;
            button.UpdateCanExecute();
        }

        private void UpdateCanExecute()
        {
            bool canExecute = true;

            if (Command == null) canExecute = false;
            if (canExecute) canExecute = Command.CanExecute(CommandParam);
            this.IsEnabled = canExecute;
        }
    }
}


Here's an example in a simple View:

<UserControl x:Class="SilverlightApplication6.Views.NamedView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
             xmlns:input="clr-namespace:SilverlightApplication6.Inputs"
             Width="400" Height="300">
    <StackPanel x:Name="LayoutRoot" Background="White">
        <TextBox Text="{Binding Name, Mode=TwoWay}" Background="WhiteSmoke" />
        <input:CommandButton Content="Change Text" Command="{Binding ChangeText}" CommandParam="{Binding NewName}" />
    </StackPanel>
</UserControl>

2 comments

Comment from: Rodney Stephens [Visitor]
***--
Can't get it to work. Command property always returns nothing. Here is my code. Any help would be appreciated.
Namespace CommandControls
Public Class CommandButton : Inherits System.Windows.Controls.Button
Public Shared ReadOnly CommandProperty As DependencyProperty
Public Shared ReadOnly CommandParameterProperty As DependencyProperty

Shared Sub New()
CommandProperty = DependencyProperty.Register("Command", GetType(ICommand), GetType(CommandButton), _
New PropertyMetadata(Nothing, New PropertyChangedCallback(AddressOf OnCommandChanged)))

CommandParameterProperty = DependencyProperty.Register("CommandParam", GetType(Object), GetType(CommandButton), _
New PropertyMetadata(Nothing, New PropertyChangedCallback(AddressOf OnCommandParamChanged)))
End Sub

Public Sub New()
MyBase.New()

AddHandler Me.Click, New RoutedEventHandler(AddressOf CommandButton_Click)
End Sub

Private Sub CommandButton_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
If (Command IsNot Nothing) Then
Command.Execute(Me.CommandParam)
End If
End Sub

Public Property Command() As ICommand
Get
Return DirectCast(GetValue(CommandProperty), ICommand)
End Get
Set(ByVal Value As ICommand)
SetValue(CommandProperty, Value)
End Set
End Property

Public Property CommandParam() As Object
Get
Return GetValue(CommandParameterProperty)
End Get
Set(ByVal Value As Object)
SetValue(CommandParameterProperty, Value)
End Set
End Property

Private Shared Sub OnCommandChanged(ByVal sender As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
Dim button As CommandButton = DirectCast(sender, CommandButton)
button.UpdateCanExecute()
End Sub

Private Shared Sub OnCommandParamChanged(ByVal sender As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
Dim button As CommandButton = DirectCast(sender, CommandButton)
button.UpdateCanExecute()
End Sub

Private Sub UpdateCanExecute()
Dim canExecute As Boolean = True

If Command Is Nothing Then
canExecute = False
End If

If canExecute Then
canExecute = Command.CanExecute(CommandParam)
Me.IsEnabled = canExecute
End If
End Sub
End Class
End Namespace
12/16/09 @ 17:45
Comment from: Dave [Member] Email
When you set the datacontext, is the command object instantiated? That's a common mistake. Here's some code:

Partial Public Class MainPage
Inherits UserControl

Private _command As Command

Public Sub New()
InitializeComponent()

'wrong...
'Me.DataContext = Me
'_command = New Command()

_command = New Command()
Me.DataContext = Me
End Sub

Public ReadOnly Property Command() As ICommand
Get
Return _command
End Get
End Property
End Class
12/16/09 @ 19:47

This post has 19 feedbacks awaiting moderation...

Leave a comment


Your email address will not be revealed on this site.

Your URL will be displayed.
PoorExcellent
(Line breaks become <br />)
(Name, email & website)
(Allow users to contact you through a message form (your email will not be revealed.)
September 2010
Sun Mon Tue Wed Thu Fri Sat
 << <   > >>
      1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30    

Here on this blog you'll find continuous thoughts and information about Omniscient's Foundation framework.

Search

The requested Blog doesn't exist any more!

XML Feeds

powered by b2evolution free blog software