Tuesday, June 13, 2017

WPF Tip #13 - Extended WPF Toolkit - ColorCanvas with MVVMLight, Binding and EventToCommand

In Tip #12, we used a ColorCanvas control in a SplitButton to change the Background color of the SplitButton using an event in the code behind. In this tip, we will do the same thing with data binding and commands using MVVMLight.

The first step is to add the MVVMLight NuGet package to the project:

image

Adding the package will add and update several classes in the project. We now have a MainViewModel and ViewModelLocator inside a ViewModel folder. The locator class exposes each ViewModel in the project so they can be bound in your views. To make the locator discoverable to the views in the project, MVVMLight added a new resource to the App.xaml:

<Application.Resources>
   <ResourceDictionary>
     <vm:ViewModelLocator x:Key="Locator" d:IsDataSource="True" xmlns:vm="clr-namespace:WpfApp1.ViewModel" />
   </ResourceDictionary>
</Application.Resources>

Now lets start with the changes we have to make. First remove the event handler from the MainWindow.xaml.cs. The next step is to replace that functionality with some decoupled logic in the MainViewModel. Two things are needed:

  • A ButtonBrush property to bind the SplitButton's Background property.
  • A ChangeBrushColorCommand RelayCommand to bind to the ColorChanged event of the ColorCanvas.

An OnColorChanged method is added to do the work needed when the RelayCommand is invoked. These are hooked up in the constructor, along with some default colors for the SplitButton. I made the button different colors at design-time and runtime as a way to validate that my binding code isn't broken. Here's the complete MainViewModel implementation:

public class MainViewModel : ViewModelBase
{
     public MainViewModel()
     {
         if (IsInDesignMode)
         {
             ButtonBrush = new SolidColorBrush(Colors.LightGray);
         }
         else
         {
             ButtonBrush = new SolidColorBrush(Colors.LightSkyBlue);

            ChangeBrushColorCommand = new RelayCommand<Color?>(OnColorChanged);
         }
     }

    public SolidColorBrush ButtonBrush { get; set; }

    internal void OnColorChanged(Color? p_Param)
     {
         if (p_Param.HasValue)
         {
             ButtonBrush = new SolidColorBrush(p_Param.Value);
         }
         else
         {
             ButtonBrush = new SolidColorBrush(Colors.Transparent);
         }

        RaisePropertyChanged(nameof(ButtonBrush));
     }

    public RelayCommand<Color?> ChangeBrushColorCommand { get; private set; }
}

The next thing we need is a converter class to convert the event args of the ColorChanged event to the Color? parameter type of our RelayCommand. SelectedColorChangedToColorConverter implements the IEventArgsConverter in MvvmLight. It's similar to value converters you may have created for data binding in WPF (see WPF Tip #4).

public class SelectedColorChangedToColorConverter : IEventArgsConverter
{
     public object Convert(object value, object parameter)
     {
         var args = (RoutedPropertyChangedEventArgs<Color?>)value;

        return args.NewValue ?? Colors.Transparent;
     }
}

Finally, lets make a few changes to the MainWindow view.

Set the DataContext for the Window using the ViewModelLocator:

DataContext="{Binding Main, Source={StaticResource Locator}}"

Add the converter to the Window.Resources:

<Window.Resources>
     <ResourceDictionary>
         <local:SelectedColorChangedToColorConverter x:Key="SelectedColorChangedToColorConverter" />
     </ResourceDictionary>
</Window.Resources>

Update the SplitButton to bind its Background property and the ColorCanvas to use the MVVMLight EventToCommand trigger, hooking up our RelayCommand through the converter.

<tk:SplitButton Content="Pick a Color" Height="32" Width="120" Background="{Binding ButtonBrush}">
     <tk:SplitButton.DropDownContent>
         <tk:ColorCanvas x:Name="MainColorCanvas">
             <i:Interaction.Triggers>
                 <i:EventTrigger EventName="SelectedColorChanged">
                     <command:EventToCommand Command="{Binding ChangeBrushColorCommand, Mode=OneWay}"
                                             EventArgsConverter="{StaticResource SelectedColorChangedToColorConverter}"
                                             PassEventArgsToCommand="True"/>
                 </i:EventTrigger>
             </i:Interaction.Triggers>
         </tk:ColorCanvas>
     </tk:SplitButton.DropDownContent>
</tk:SplitButton>

That's it!. Same functionality, but now with better separation between view and code.

Happy coding! (P.S. - I hope to get some proper code formatting hooked up to Blogger soon… or maybe a new blogging platform. Thanks for bearing with me.)


No comments:

Post a Comment