| « Http Modules, Basic Authentication and IIS | I, Model (Part I - An Entity Wrapper) » |
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>
This post has 19 feedbacks awaiting moderation...