Page 1 of 1

WPF Build An Application 2, Commanding and Enums

#1 andrewsw  Icon User is online

  • Fire giant boob nipple gun!
  • member icon

Reputation: 3354
  • View blog
  • Posts: 11,353
  • Joined: 12-December 12

Posted 13 May 2014 - 01:31 PM

This continues from Part 1 and uses Commands rather than event-handling (code-behind) to handle the Button clicks. It also led me to resolve the problem of the enumerated Priorities being hard-coded in the XAML (see the last section).

Commanding Overview :MSDN
Curtis's tutorial was very helpful: Basic WPF Databinding :DIC

(There are a few other links listed near the top of Part 1.)

The main aim of this part is that ToDo.xaml.cs will look like this:
// using statements omitted
namespace ToDoApplication.Views {
    /// <summary>
    /// Interaction logic for ToDo.xaml
    /// </summary>
    public partial class ToDo : UserControl {

        public ToDo() {
            InitializeComponent();
        }
    }
}


No code-behind! An additional benefit is seen in this screenshot:

Attached Image

The Next and Last Buttons are automatically disabled if we are already on the last ToDo item.

Commands for Navigation

Creating Custom Commands is not necessarily complicated, but it doesn't help that there are a few ways to do this: creating a static class, RelayCommands, putting code directly in the Window's class-file, etc..

One issue that I wanted to avoid though, was that, with a more standard approach, we might end up with a separate Class for each Command that we create. This seems messy because the navigation Buttons essentially behave the same. Creating a RelayCommand promises a solution to this but I couldn't find a comprehensive, or clear, tutorial for it. (It's used in the Prism project.)

This tutorial is useful though: ICommand is like a chocolate cake

When implementing the ICommand interface it is possible to supply parameters to the methods. This looked promising as I could then supply parameters of 'First', 'Last', etc., and use the same Command for all the navigation Buttons. Again, finding decent material for this was proving tricky.

In the end I realised that we could supply parameters to an ICommand's constructor, that it could store as field-values and use to tailor its behaviour.

Curtis's tutorial (linked above) was very useful to me. His example subscribes to a Contact's PropertyChanged event to determine whether the Command is available (CanExecute). Because we are working with a ToDoList (an ObservableCollection) we, instead, subscribe to this list's CurrentChanged event. We are not interested (currently) in changes to the current ToDo item, we just need to be notified when the user moves to a different ToDo item.

At some point it may also be necessary to consider the ObservableCollection<T>.Collectionchanged Event:

MSDN said:

Occurs when an item is added, removed, changed, moved, or the entire list is refreshed.


As things stand, when we Add a new ToDo item we also move the CurrentPosition to this item, triggering the CurrentChanged event, and our Application doesn't currently have the functionality to delete or move items.




First, let's create an enumeration (an enum) in MyObservableCollection.cs (still within the namespace):
    public enum Navigation {
        First, Previous, Next, Last, Add
    }


I originally thought about using integer values -2,-1,0,1,2, where 0 means 'Add' and -2 means 'First', but an enumeration is much more professional.

Right-click our Models folder to create a new class name NavigationCommand.cs:
using System;
using System.Windows.Input;

namespace ToDoApplication.Models {
    class NavigationCommand : ICommand {
        private readonly MyObservableCollection<ToDo> _list;
        private readonly Navigation _request;
        private bool _canExecute;

        public NavigationCommand(MyObservableCollection<ToDo> list, Navigation request) {
            _list = list;
            _request = request;
            System.Windows.Data.CollectionViewSource.GetDefaultView(_list).CurrentChanged += 
                NavigationCommand_CurrentChanged;
            if (_request == Navigation.Add) _canExecute = true;
        }

        void NavigationCommand_CurrentChanged(object sender, EventArgs e) {
            switch (_request) {
                case Navigation.First:
                    _canExecute = _list.CanMoveBack();
                    break;
                case Navigation.Previous:
                    _canExecute = _list.CanMoveBack();
                    break;
                case Navigation.Next:
                    _canExecute = _list.CanMoveForward();
                    break;
                case Navigation.Last:
                    _canExecute = _list.CanMoveForward();
                    break;
                case Navigation.Add:
                    _canExecute = true;
                    break;
                default:
                    break;
            }
            OnCanExecuteChanged();
        }

        private void OnCanExecuteChanged() {
            if (CanExecuteChanged != null) CanExecuteChanged(this, new EventArgs());
        }
        public bool CanExecute(object parameter) {
            return _canExecute;
        }

        public event EventHandler CanExecuteChanged;

        public void Execute(object parameter) {
            switch (_request) {
                case Navigation.First:
                    _list.MoveFirst();
                    break;
                case Navigation.Previous:
                    _list.MovePrevious();
                    break;
                case Navigation.Next:
                    _list.MoveNext();
                    break;
                case Navigation.Last:
                    _list.MoveLast();
                    break;
                case Navigation.Add:
                    _list.Add(new ToDoApplication.Models.ToDo());
                    _list.MoveLast();
                    break;
                default:
                    break;
            }
        }
    }
}


Let's break it down:
    class NavigationCommand : ICommand {
        private readonly MyObservableCollection<ToDo> _list;
        private readonly Navigation _request;
        private bool _canExecute;

        public NavigationCommand(MyObservableCollection<ToDo> list, Navigation request) {
            _list = list;
            _request = request;
            System.Windows.Data.CollectionViewSource.GetDefaultView(_list).CurrentChanged += 
                NavigationCommand_CurrentChanged;
            if (_request == Navigation.Add) _canExecute = true;
        }


When our model instantiates one of these Commands it will supply the ToDoList as an argument and one of the enums, to indicate which navigation-request (First, Last, etc.) it relates to. We store these values as fields and subscribe to the List's, DefaultView's CurrentChanged event.

We want the 'New' Button to be immediately available so change the boolean value of _canExecute to true (from its default of false). The other Buttons will be disabled, but this is appropriate as our application starts with a new, empty, list. [If we later add the ability to load a ToDoList from somewhere then we will need to re-examine this behaviour.]
        void NavigationCommand_CurrentChanged(object sender, EventArgs e) {
            switch (_request) {
                case Navigation.First:
                    _canExecute = _list.CanMoveBack();
                    break;
                case Navigation.Previous:
                    _canExecute = _list.CanMoveBack();
                    break;
                case Navigation.Next:
                    _canExecute = _list.CanMoveForward();
                    break;
                case Navigation.Last:
                    _canExecute = _list.CanMoveForward();
                    break;
                case Navigation.Add:
                    _canExecute = true;
                    break;
                default:
                    break;
            }
            OnCanExecuteChanged();
        }


This will execute when the CurrentPosition of our list is changed. It uses the methods we created in our MyObservableCollection class to determine whether _canExecute should be true or false, according to which navigation-request this is for.



Code Re-Use:

Our ICommand is not very re-usable as it depends on MyObservableCollection<ToDo> (and Navigation). However, with a little effort, I believe we could (possibly) package MyObservableCollection<>, the Navigation enum, and this ICommand together, to be able to use them in other projects.

Well, possibly, because Commands tend to be quite specific to the Application. Nevertheless, our trio is quite re-usable as it is: all we need to do is change 'ToDo' to some other type.




OnCanExecuteChanged() is called at the end of this code:
        private void OnCanExecuteChanged() {
            if (CanExecuteChanged != null) CanExecuteChanged(this, new EventArgs());
        }


All this does is to notify the Command that the CanExecute state has changed, causing the following to be re-evaluated:
        public bool CanExecute(object parameter) {
            return _canExecute;
        }


This, in turn, will either disable, or enable, the Buttons.

If CanExecute is true and the user clicks a Button, the Execute code is run:
        public void Execute(object parameter) {
            switch (_request) {
                case Navigation.First:
                    _list.MoveFirst();
                    break;
                case Navigation.Previous:
                    _list.MovePrevious();
                    break;
                case Navigation.Next:
                    _list.MoveNext();
                    break;
                case Navigation.Last:
                    _list.MoveLast();
                    break;
                case Navigation.Add:
                    _list.Add(new ToDoApplication.Models.ToDo());
                    _list.MoveLast();
                    break;
                default:
                    break;
            }
        }


This is the same code that we had in the code-behind but it is no longer necessary to check CanMoveBack() or CanMoveForward(); if the appropriate one weren't true then CanExecute would be false, and Execute wouldn't be called.

Now we need to modify ToDoList.cs to create and store appropriate instances of our Command-class:
using System;
using System.Windows.Input;

namespace ToDoApplication.Models {
    public class ToDoList : MyObservableCollection<ToDo> {
        public ICommand CommandFirst { get; private set; }
        public ICommand CommandPrevious { get; private set; }
        public ICommand CommandNext { get; private set; }
        public ICommand CommandLast { get; private set; }
        public ICommand CommandAdd { get; private set; }

        public ToDoList()
            : this("Your Title Here.") {
        }
        public ToDoList(string title)
            : base() {
            this.Add(new ToDo(title));
            CommandFirst = new NavigationCommand(this, Navigation.First);
            CommandPrevious = new NavigationCommand(this, Navigation.Previous);
            CommandNext = new NavigationCommand(this, Navigation.Next);
            CommandLast = new NavigationCommand(this, Navigation.Last);
            CommandAdd = new NavigationCommand(this, Navigation.Add);
        }
    }
}


The Commands need to be publicly available as properties so that we can call them from the XAML.

So, finally, we modify ToDo.xaml to call our Commands:
        <StackPanel Grid.Row="5" Grid.Column="0" Grid.ColumnSpan="5" Orientation="Horizontal" 
                HorizontalAlignment="Center">
            <Button Content="First" Name="btnFirst" Command="{Binding Path=CommandFirst}" 
                    CommandParameter="models:Navigation.First" />
            <Button Content="Previous" Name="btnPrevious" Command="{Binding Path=CommandPrevious}" 
                    CommandParameter="models:Navigation.Previous" />
            <Button Content="Next" Name="btnNext" Command="{Binding Path=CommandNext}" 
                    CommandParameter="models:Navigation.Next" />
            <Button Content="Last" Name="btnLast" Command="{Binding Path=CommandLast}" 
                    CommandParameter="models:Navigation.Last" />
            <Button Content="New" Name="btnNew" Command="{Binding Path=CommandAdd}" 
                    CommandParameter="models:Navigation.Add" />
        </StackPanel>


(Don't forget to remove CommonNavHandler() from ToDo.xaml.cs - we don't need it anymore.)

Edited: The CommandParameters can be removed from this code, they aren't needed.

Notice that the Paths do not include a slash "/". This would reference the current-item, but the Commands are available directly in the ToDoList-DataContext.




Use Enums in ComboBox

We will remove the hard-coded values 'High,Medium,Low' from the XAML and read them directly from the Priorities enum. There seems to be a variety of (somewhat complicated) ways to do this. I am using the answer found here: SO topic.

Modify ToDo.xaml to add a namespace reference for System and a new (UserControl) Resource:
<UserControl x:Class="ToDoApplication.Views.ToDo"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:models="clr-namespace:ToDoApplication.Models" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:system="clr-namespace:System;assembly=mscorlib"
             mc:Ignorable="d">
    <UserControl.Resources>
        <ObjectDataProvider x:Key="dataFromEnum" MethodName="GetValues"
                            ObjectType="{x:Type system:Enum}">
            <ObjectDataProvider.MethodParameters>
                <x:Type TypeName="models:Priorities" />
            </ObjectDataProvider.MethodParameters>
        </ObjectDataProvider>
    </UserControl.Resources>
    <Grid>


ObjectDataProvider Class :MSDN

MSDN said:

Wraps and creates an object that you can use as a binding source.


Briefly, this provider has the name (Key) 'dataFromEnum' and uses the 'GetValues' method on an enum; MethodParameters specifies our 'models:Priorities' enum as the values (the Type) to act upon.

Now we can modify the ComboBox to use this provider, and populate the ComboBox-Items:
        <ComboBox Grid.Row="4" Grid.Column="1" Grid.ColumnSpan="2" x:Name="cboPriority" 
                  SelectedValue="{Binding Path=/Priority, Mode=TwoWay}" 
                  ItemsSource="{Binding Source={StaticResource dataFromEnum}}">
        </ComboBox>


Personally, I'm not going to worry about ObjectDataProvider too much at this stage. I'm sure we'll encounter it again at some point.

This post has been edited by andrewsw: 16 May 2014 - 08:59 AM


Is This A Good Question/Topic? 1
  • +

Replies To: WPF Build An Application 2, Commanding and Enums

#2 farrell2k  Icon User is offline

  • D.I.C Lover
  • member icon

Reputation: 823
  • Posts: 2,533
  • Joined: 29-July 11

Posted 14 May 2014 - 01:50 PM

These tutorials just get no love :( +1 for the effort. They are all very well done. I have bookmarked them for when I have the time and energy to attempt to decipher wpf. :)
Was This Post Helpful? 0
  • +
  • -

#3 andrewsw  Icon User is online

  • Fire giant boob nipple gun!
  • member icon

Reputation: 3354
  • View blog
  • Posts: 11,353
  • Joined: 12-December 12

Posted 14 May 2014 - 02:32 PM

Thank you for the support, and interest, farrell2k ;)

Yes, I encourage comments or suggestions. It is a big subject though, and a long tutorial-sequence, so feedback/responses will take longer than it does for shorter, and more focussed, tutorials.
Was This Post Helpful? 0
  • +
  • -

Page 1 of 1