David Sackstein's Blog

"The more that you learn, the more places you'll go.”, Dr. Seuss

April 2009 - Posts

ISynchronizedInvoke and WPF

I have seen a lot of discussion out there about why Dispatchers in WPF don’t implement ISynchronizedInvoke.

I don’t have the answer, but I can offer you an extension method I use to simplify the synchronization of all those asynchronous events onto a window thread.

Here is the extension method:

    public static class Extensions

    {

        static public void InvokeIfRequired(this Dispatcher dispatcher, Action action)

        {

            if (dispatcher.CheckAccess())

            {

                action();

            }

            else

            {

                dispatcher.BeginInvoke(DispatcherPriority.Normal, action);

            }

        }

    }

And you can use it like this:

    void task_CompletedEvent(object sender, EventArgs args)

    {

        // Possibly called from another thread

 

        Dispatcher.InvokeIfRequired(() =>

        {

            // You can access the UI here, we are in the window thread

        });

    }

A Validated TextBox as a WPF UserControl

In this post I will be describing a WPF UserControl that I wrote to handle the validation of a text box.

The complete source code can be downloaded here.

Requirements

I would like to be able to use the control like this:

      <local:ValidatedTextBox x:Name="tbAnInteger">

         <local:ValidatedTextBox.Text>

            <Binding Path="AnInteger" Mode="TwoWay"

                     UpdateSourceTrigger="PropertyChanged">

               <Binding.ValidationRules>

                  <local:RangeValidationRule MinValue="3" MaxValue="50" />

                  <local:DivisibleValidationRule Divisor="4"/>

               </Binding.ValidationRules>

            </Binding>

         </local:ValidatedTextBox.Text>

      </local:ValidatedTextBox>

and have the control handle the display of any error message as a label next to the textbox and as a tooltip.

I would like to be able to add any number of validation rules (though usually one would be enough) and be able to ask explicitly in code whether the control validates.

As the XAML shows, the validation rules themselves are not part of the control, but can be arbitrarily built and used by the client code.

Implementation

The implementation is fairly straightforward (like everything in WPF – it looks really easy).

Here is the XAML:

<UserControl x:Class="ValidationSample.ValidatedTextBox"

            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

            xmlns:local="clr-namespace:ValidationSample">

   <Validation.ErrorTemplate>

      <ControlTemplate>

         <DockPanel LastChildFill="True"

                   Background="Transparent"

                   ToolTipService.InitialShowDelay="0"

                   ToolTipService.ShowDuration="2000"

                   ToolTip="{Binding

                             ElementName=myAdorner,

                             Path=AdornedElement.(Validation.Errors)[0].ErrorContent}">

            <TextBlock DockPanel.Dock="Right"

                      VerticalAlignment="Center"

                      Text="{Binding

                             ElementName=myAdorner,

                             Path=AdornedElement.(Validation.Errors)[0].ErrorContent}">

            </TextBlock>

            <AdornedElementPlaceholder Name="myAdorner"/>

         </DockPanel>

      </ControlTemplate>

   </Validation.ErrorTemplate>

   <TextBox Text="{Binding

                 RelativeSource={

                     RelativeSource FindAncestor,

                     AncestorType={x:Type local:ValidatedTextBox}

                 },

                 Path=Text,

                 UpdateSourceTrigger=PropertyChanged}">

   </TextBox>

</UserControl>

The control consists only of a TextBox and an ErrorTemplate (which is an attached property of the Validation class).

The Text property of the TextBox is bound to the Text (dependency) property of the ValidatedTextBox. The ErrorTemplate kicks in when there are one or more validation errors. Then it displays text and a tooltip describing the error.

This is the code behind implementation of the ValidatedTextBox.

    public partial class ValidatedTextBox : UserControl

    {

        public ValidatedTextBox()

        {

            InitializeComponent();

        }

 

        public string Text

        {

            get { return (string)GetValue(TextProperty); }

            set { SetValue(TextProperty, value); }

        }

 

        public static readonly DependencyProperty TextProperty =

            DependencyProperty.Register("Text", typeof(string),

                typeof(ValidatedTextBox), new UIPropertyMetadata(""));

 

    }

As you can see, I only exposed a Text property (as a Dependency Property). Of course, you could expose other properties too, or even expose the internal TextBox itself.

To test the control, I wrote the following validation rules:

    public class RangeValidationRule : ValidationRule

    {

        public int MinValue { get; set; }

        public int MaxValue { get; set; }

 

        public override ValidationResult Validate(

            object value, System.Globalization.CultureInfo cultureInfo)

        {

            int intValue;

 

            string text = String.Format ("Must be between {0} and {1}",

                                          MinValue, MaxValue);

            if (! Int32.TryParse(value.ToString(), out intValue))

                return new ValidationResult(false, "Not an integer");

            if (intValue < MinValue)

                return new ValidationResult(false, "To small. " + text);

            if (intValue > MaxValue)

                return new ValidationResult(false, "To large. " + text);

            return ValidationResult.ValidResult;

        }

    }

 

    public class DivisibleValidationRule : ValidationRule

    {

        public int Divisor { get; set; }

        public override ValidationResult Validate(

            object value, System.Globalization.CultureInfo cultureInfo)

        {

            int intValue;

 

            if (!Int32.TryParse(value.ToString(), out intValue))

                return new ValidationResult(false, "Not an integer");

            if (intValue % Divisor != 0)

                return new ValidationResult(false,

                                      "Not divisible by " + Divisor);

            return ValidationResult.ValidResult;

        }

    }

These rules are just examples.

I added an instance of each rule to the ValidationRules collection of the Binding of the Text property of the ValidatedTextBox.

Note, that if both validation rules fail, only the first error (in the order that the rules appear in the collection) will be displayed.

Check Validity in Code

Let’s say you have an OK button which you would like enabled only if a ValidatedTextBox validates.

I decided to use some Command Binding for that.

Here is my XAML for the Main Window:

<Window x:Class="ValidationSample.MainWindow"

       xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

       xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

       xmlns:local="clr-namespace:ValidationSample"

       Title="Main Window"

       Height="300" Width="300"

       Loaded="Window_Loaded">

   <Window.CommandBindings>

      <CommandBinding Command="Save"

                     CanExecute="CommandBinding_CanExecute"

                     Executed="CommandBinding_Executed" />

   </Window.CommandBindings>

   <Window.Resources>

   </Window.Resources>

   <StackPanel>

      <local:ValidatedTextBox x:Name="tbAnInteger">

         <local:ValidatedTextBox.Text>

            <Binding Path="AnInteger"

                     Mode="TwoWay"

                     UpdateSourceTrigger="PropertyChanged">

               <Binding.ValidationRules>

                  <local:RangeValidationRule MinValue="3" MaxValue="50" />

                  <local:DivisibleValidationRule Divisor="4"/>

               </Binding.ValidationRules>

            </Binding>

         </local:ValidatedTextBox.Text>

      </local:ValidatedTextBox>

      <Button Command="Save">Text</Button>

   </StackPanel>

</Window>

I bound the application command “Save” adding handlers for CanExecute and Executed and associated the Save button with the Save ApplicationCommand.

The code behind for these handlers is:

        private void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)

        {

            e.CanExecute = ! Validation.GetHasError(tbAnInteger);

        }

 

        private void CommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)

        {

            Close();

        }

As you can see, Validation.GetHasErrors allows me to check if any of the validation rules fails.

So, when there are any validation errors, the Save button is not enabled.

The complete source code can be downloaded here.

Comments are welcome!

Aggregate CheckBox for DataGridCheckBoxColumn (Part 3)

I described the problem at hand in the first post in this series.

In the previous post I described the high level design of the source code that demonstrates my solution.

In this post, I will describe the EmployeeUserControl and summarize.

You can download the complete source code for the article here.

DataBinding Design

First, let’s decide what should be bound to what.

From the very start we made the fairly obvious decision to use an ObservableCollection<Employee> as out ItemsSource. Combined with the implementation of INotifyPropertyChanged by Employee and this simple XAML we get two-way data binding between the Employees and their properties and rows in the DataGrid.

        <StackPanel>

            <tk:DataGrid x:Name="gridEmployeeList"

                        AutoGenerateColumns="False"

                        >

                <tk:DataGrid.Columns>

                    <tk:DataGridCheckBoxColumn

                           Header="Manager"

                           HeaderStyle="{StaticResource CheckBoxHeaderStyle}"

                           Binding="{Binding Path=IsManager, Mode=TwoWay}"

                   />

                    <tk:DataGridTextColumn

                           Header="Name"

                           Width="*"

                           Binding="{Binding Path=Name, Mode=TwoWay}"

                   />

                </tk:DataGrid.Columns>

            </tk:DataGrid>

        </StackPanel>

Now we need to bind a new CheckBox (the aggregate) to all the CheckBoxes in the first column. As we shall see, this can be done using a MultiBinding object and an appropriate IMultivalueConverter that converts the values of the source to a single aggregate value for the target and vice versa.

Though binding properties of controls to properties of other controls is certainly feasible, it requires that we find each of the check boxes on each row and add it to the binding. Finding controls on the grid, is tricky and slow, and in my opinion, should be avoided if possible.

In the case at hand we can use the IsManager property of each Employee instead, because, due to two way data binding, these are always synchronized with their bound CheckBox in the DataGrid. So, this is what I did.

The result is a clean design in which our business objects, represented by a ObserverableCollection<Employee>, are at the center and all bound elements of the UI bind (two-way) directly to these business objects (and not to each other).

So far, we have been talking about an aggregate CheckBox, but have not created it in XAML or in code. Let’s do that next.

Adding the CheckBox to the Column Header

I added a CheckBox using a ControlTemplate.

The ControlTemplate is contained in a style called “CheckBoxHeaderStyle” designed for controls of type “DataGridColumnHeader”. It is assigned to the HeaderStyle property of the first column.

This the XAML for the EmployeeListControl with the CheckBox.

<UserControl

   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

   xmlns:tk="clr-namespace:Microsoft.Windows.Controls;assembly=WPFToolkit"

   xmlns:tkp="clr-namespace:Microsoft.Windows.Controls.Primitives;assembly=WPFToolkit"

   xmlns:local="clr-namespace:UserControls"

       x:Class="UserControls.EmployeeListControl"

       Loaded ="UserControl_Loaded"

       >

    <UserControl.Resources>

        <Style x:Key="CheckBoxHeaderStyle" TargetType="{x:Type tkp:DataGridColumnHeader}">

            <Setter Property="Template">

                <Setter.Value>

                    <ControlTemplate TargetType="{x:Type tkp:DataGridColumnHeader}">

                        <StackPanel Orientation="Horizontal" VerticalAlignment="Center">

                            <CheckBox x:Name="cbCheckAll"

                                     IsThreeState="True" VerticalAlignment="Center"/>

                            <Label Content="{Binding}"></Label>

                        </StackPanel>

                    </ControlTemplate>

                </Setter.Value>

            </Setter>

        </Style>

    </UserControl.Resources>

 

    <StackPanel x:Name="panel">

        <tk:DataGrid x:Name="gridEmployeeList"

                    AutoGenerateColumns="False"

                    >

            <tk:DataGrid.Columns>

                <tk:DataGridCheckBoxColumn

                       Header="Manager"

                       HeaderStyle="{StaticResource CheckBoxHeaderStyle}"

                       Binding="{Binding Path=IsManager, Mode=TwoWay}"

               />

                <tk:DataGridTextColumn

                       Header="Name"

                       Width="*"

                       Binding="{Binding Path=Name, Mode=TwoWay}"

               />

            </tk:DataGrid.Columns>

        </tk:DataGrid>

    </StackPanel>

</UserControl>

Note that the new CheckBox, hereinafter also known as “cbCheckAll” has IsThreeState set to True.

The required behavior is as follows.

  1. When IsManager is true for all Employees, cbCheckAll.IsChecked will be True.
  2. When IsManager is false for all Employees, cbCheckAll.IsChecked will be False.
  3. Otherwise, cbCheckAll.IsChecked should be null (it’s third state).

On the other hand, when the user clicks on cbCheckAll the result should be either checked or unchecked. The third state has no meaning in this scenario..

OK. So now we have a CheckBox and we know what we need to data bind. Now lets get a reference to the CheckBox and set it all up.

Getting a Reference to a Templated Control

Hmmm. Wouldn’t it be wonderful if we could just have a member called cbCheckAll?

Well, unfortunately, that is not possible for controls that are defined in a template.

OK, so maybe we can use the “FindName” function of the Template class to get hold of the CheckBox dynamically?

Now that “could” work in principle, but there is a problem of timing here. If you try to do this in the constructor or even in the Loaded handler of the user control, you will find that the call returns null.

The reason for this is that cbCheckAll is situated in a DataGridColumnHeadersPresenter which derives from ItemsControl. As you may know, ItemsControls generate their item containers dynamically, and asynchronously. The recommended way to retrieve items or containers from a ItemsControls is to subscribe to the events of the ItemContainerGenerator as shown in the code below.

    public partial class EmployeeListControl : UserControl

    {

        public EmployeeListControl()

        {

            InitializeComponent();

        }

 

        #region The aggregate CheckBox

 

        protected CheckBox cbCheckAll;

 

        private void UserControl_Loaded(object sender, RoutedEventArgs args)

        {

            // The aggregate checkBox is in the DataGridColumnHeadersPresenter

            // which is an ItemsControl.

 

            DataGridColumnHeadersPresenter presenter =

               GetDataGridColumnHeadersPresenter();

 

            // Items in an ItemsControls are generated "asynchronously"

            // by an ItemContainerGenerator.

            // So, to find an item, we subscribe to the StatusChanged event of the

            // ItemContainerGenerator.

 

            presenter.ItemContainerGenerator.StatusChanged +=

               ColumnHeadersGenerator_StatusChanged;

        }

 

        private DataGridColumnHeadersPresenter GetDataGridColumnHeadersPresenter()

        {

            // The DataGrid has a ScrollViewer which contains the

            // PART_ColumnHeadersPresenter

 

            ScrollViewer scrollViewer =

                gridEmployeeList.Template.FindName(

                    "DG_ScrollViewer", gridEmployeeList) as ScrollViewer;

 

            Debug.Assert(scrollViewer != null);

 

            // DG_ScrollViewer contains a DataGridColumnHeadersPresenter

            // called PART_ColumnHeadersPresenter

 

            DataGridColumnHeadersPresenter presenter =

                scrollViewer.Template.FindName(

                     "PART_ColumnHeadersPresenter", scrollViewer)

                          as DataGridColumnHeadersPresenter;

 

            Debug.Assert(presenter != null);

 

            return presenter;

        }

 

        private void ColumnHeadersGenerator_StatusChanged(object sender, EventArgs args)

        {

            ItemContainerGenerator itemGenerator = sender as ItemContainerGenerator;

 

            // Handle the case when the containers have been generated.

 

            if (itemGenerator.Status == GeneratorStatus.ContainersGenerated)

            {

                // The containers in the DataGridColumnHeadersPresenter

                // are the DataGridColumnHeader objects for each row.

                // Find the check box in the header of the first column.

 

                DataGridColumnHeader header =

                   (DataGridColumnHeader)itemGenerator.ContainerFromIndex(0);

 

                cbCheckAll = header.Template.FindName("cbCheckAll", header) as CheckBox;

                Debug.Assert(cbCheckAll != null);

 

                ConfigureCheckBox();

 

                // GeneratorStatus.ContainersGenerated is triggered a number of times,

                // we only use the first - so we uninstall the delegate in the first.

 

                itemGenerator.StatusChanged -= ColumnHeadersGenerator_StatusChanged;

            }

        }

In the Loaded event handler, we find the DataGridColumnHeadersPresenter which is responsible for the layout of the column headers across the top of the DataGrid. We then subscribe to the StatusChanged event of the ItemContainerGenerator of the “presenter”.

The ColumnHeadersGenerator_StatusChanged event handler does nothing until the status of the generator is ContainersGenerated. A status of ContainersGenerated indicates that all the item containers controls have been created. The item container controls for this presenter are of type DataGridColumnHeader.

So, at this point we can ask the ItemContainerGenerator the first DataGridColumnHeader, which corresponds to the DataGridCheckBoxColumn, and then, finally, we can use the FindName method on the Template property of the ColumnHeader to dynamically get the required reference to the cbCheckAll CheckBox.

Oh, and before we return from this function, we unsubscribe the event handler from the StatusChanged event of the ItemContainerGenerator because our task here is done.

Now don’t tell me that was easy!

Not only was it not easy, but you may be quite depressed at this point, asking yourself, “How does he know all of that?”, and, “What, I need to learn and memorize the intricate structure of each control in order to perform such simple customizations in code?”

Well, I can comfort you on one count. I didn’t memorize these details and you also don’t need to. There is a great tool out there to visually guide you through the structure, if you need it. Its called Mole and you can download it here.

And yet, your alarm is not entirely unjustified. The way I worked with Mole to write this code was not efficient at all.

I had to write some skeleton code, run the debugger with a breakpoint set somewhere suitable and to use Mole to view the structure and jot it down somewhere. Then I wrote some code to navigate through the elements and ran it again. Each time, I needed to check with Mole to see whether I was getting nearer the control I was looking for, or not.

This observation is one example of what I meant when I said that sometimes in WPF you have to work very hard to get some very basic features.

OK, that’s enough ranting on! We now have our cbCheckBox. Let’s see what need’s to be done with it.

Configuring the Aggregate CheckBox

There are three configurations we need to implement:

  1. Add an event handler to the IsChecked event of cbCheckAll to disable the indeterminate state when being clicked by the user.
  2. Assume that the MultiBinding (checkBinding ) object has been prepared elsewhere in the code we need to use the CheckBox’s SetBinding method to bind it to the output of the MultiBinding object.
  3. We also set the IsEnabled property of cbCheckAll so that the aggregate CheckBox is disabled when there are no items in the list.

Here is the code:

        MultiBinding checkBinding;

 

        protected virtual void ConfigureCheckBox()

        {

            // Configure the check box:

            // Skip the tri state when clicked by the user, and bind to multiBinding

 

            cbCheckAll.Click +=

                (s, e) =>

                {

                    if (cbCheckAll.IsChecked == null)

                    {

                        cbCheckAll.IsChecked = false;

                    }

                };

            if (checkBinding != null)

            {

                cbCheckAll.SetBinding(CheckBox.IsCheckedProperty, checkBinding);

                cbCheckAll.IsEnabled = Employees.Count > 0;

            }

        }

Now let’s see how the MultiBinding object is prepared.

Setting Up The Binding

The most important part of the data binding plumbing in this project is the MultiBinding object. The MultiBinding object is basically a collection of individual bindings and a converter (a IMultiValueConverter, to be precise).

In order to convert the IsMAnager property of all items in the collection to one aggregate value, and vice versa, we must add exactly one binding object for each Employee object in the collection. The job of the converter is to Convert, upon request, the group of values of the IsManager property for all Employees in the collection to the required aggregate value and vice versa.

Here is the code for the CheckConverter class:

    class CheckConverter : IMultiValueConverter

    {

        #region IMultiValueConverter Members

 

        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)

        {

            int checkedCount = values.Count((o) => (bool)o);

            if (checkedCount == 0)

                return false;

            if (checkedCount == values.Length)

                return true;

            return null;

        }

 

        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)

        {

            object[] values = new object[targetTypes.Length];

            for (int i = 0; i < values.Length; i++)

            {

                values[i] = value;

            }

            return values;

        }

 

        #endregion

    }

OK. Now how are we going to make sure that there exactly one binding object in the Bindings collection of the MultBinding object for every Employee?

Easy! Whenever the Employees collection is set we will create a new MultiBinding adding a Binding for each Employee. In addition, we will subscribe to the CollectionChanged event of the collection and in our handler, update the MultiBinding object whenever Employees are added or removed from the collection.

Here you go:

        MultiBinding checkBinding;

        CheckConverter converter = new CheckConverter();

        #region Bind Employee collection to MultiBinding

 

        public ObservableCollection<Employee> Employees

        {

            get { return gridEmployeeList.ItemsSource as ObservableCollection<Employee>; }

            set

            {

                BindCollection(value);

 

                if (Employees != null)

                {

                    Employees.CollectionChanged -= CollectionChanged;

                }

 

                gridEmployeeList.ItemsSource = value;

 

                if (Employees != null)

                {

                    Employees.CollectionChanged += CollectionChanged;

                }

            }

        }

 

        private void BindCollection(ObservableCollection<Employee> value)

        {

            checkBinding = new MultiBinding();

            checkBinding.Converter = converter;

 

            foreach (Employee employee in value)

            {

                Binding binding = new Binding("IsManager");

                binding.Source = employee;

                checkBinding.Bindings.Add(binding);

            }

 

            if (cbCheckAll != null)

            {

                cbCheckAll.SetBinding(CheckBox.IsCheckedProperty, checkBinding);

                cbCheckAll.IsEnabled = value.Count > 0;

            }

        }

 

        void CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)

        {

            BindCollection(Employees);

        }     

 

Now looking at this code, you might be thinking: “Why do I have to rebuild the MultiBinding object each time the collection changes? The CollectionChanged event handler takes by argument an NotifyCollectionChangedEventArgs object that provides a NewItems and an OldItems property. Why cant I just use this and add and remove Bindings from the Bindings collection of the MultiBinding object?

Well, it turns out that once a binding has been used, (e.g. participated in SetBinding) it can no longer be modified. If you try you will get an exception with this message: “Binding cannot be changed after being used”.

Note that while binding the IsChecked property of the cbCheckAll CheckBox we also determine the IsEnabled property. It makes no sense to be able to check and uncheck the cbCheckAll CheckBox  when the collection is empty.

One final improvement that I added was Click Once Editing.

Click Once Editing

By default, you will find that in order to select a CheckBox in a DataGridCheckBoxColumn you need to click once to select the row and a second time to check or uncheck.

In order to enable one click editing I copied the solution I found here..

Summary

Though the problem at hand was a simple one, the code is not simple.

These were the main challenges:

 

Problem 1:
Getting hold of a reference to the aggregate CheckBox when it is in a templated Column Header.

 

Solution:
Find the ItemsControl ancestor of the control, and subscribe to the StatusChanged of the ItemContainerGenerator.

Problem 2:
Setting up a MultBinding object and keeping it synchronized with the source collection.

 

Solution:
Create a new MultiBinding object every time the source collection is set and every time the collection is modified.

 

Problem 3: Enabling Click Once Editing

Implement the solution found here.

Aggregate CheckBox for DataGridCheckBoxColumn (Part 2)

In the previous post I described the requirement to create an aggregate CheckBox for a DataGridCheckBoxColumn.

You can download the source code for my solution here.

In this post I describe the high level design of my solution as documentation for the source code. If you are looking for the WPF techniques themselves, you can skip to the next post (Part 3).

Setting the Scene

The project contains three assemblies:

  1. PresentationLayer: This is a WPF window application.
  2. UserControls: A class library that hosts the EmployeeListControl.
  3. BusinessLayer: A class library that hosts the Employee class.

BusinessLayer

The only class in this library is the Employee class. As you can see I preferred the INotifyPropertyChanged approach over Dependency Properties as this is a business object.

    public class Employee : INotifyPropertyChanged

    {

        private bool isManager;

        public bool IsManager

        {

            get { return isManager; }

            set {

                isManager = value;

                RaisePropertyChanged("IsManager");

            }

        }

 

        private string name;

        public string Name

        {

            get { return name; }

            set {

                name = value;

                RaisePropertyChanged("Name");

            }

        }

 

        #region INotifyPropertyChanged Members

 

        public event PropertyChangedEventHandler PropertyChanged;

 

        #endregion

 

        void RaisePropertyChanged(string propertyName)

        {

            if (PropertyChanged != null)

                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));

        }

    }

UserControls

I am going to place all the UI elements required for displaying a collection of Employees in a UserControl called EmployeeListControl. The interface between the PresentationLayer and the UserControl will be expressed in terms of business objects. “Here is a collection of Employees, present them to the user and allow him to edit the collection and the Employees”.

This is one of the nice side-effects of databinding. One module can concentrate on the data, the other on the UI and databinding synchronizes them behind the scenes.

It’s often best to appreciate the interface of a module (in this case, the EmployeeListControl) from the outside, by seeing how its used. The interface of the module seen this way is a requirements document.

So, let’s take a quick look at the PresentationLayer and then drill down into the EmployeeListControl.

PresentationLayer

This assembly contains one window (MainWindow) that hosts two buttons and an EmployeeListControl. This is the XAML for the MainWindow:

<Window x:Class="PresentationLayer.MainWindow"

   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

   xmlns:uc="clr-namespace:UserControls;assembly=UserControls"

       Width="200"

       Height="300"

       ResizeMode="NoResize"

       Loaded="Window_Loaded">

    <StackPanel>

        <Grid>

            <Grid.ColumnDefinitions>

                <ColumnDefinition/>

                <ColumnDefinition/>

            </Grid.ColumnDefinitions>

            <Button Grid.Column="0" Click="btnFill_Click">Fill</Button>

            <Button Grid.Column="1" Click="btnCheck_Click">Check All</Button>

        </Grid>

        <uc:EmployeeListControl x:Name="ctlEmployeeList"/>

    </StackPanel>

</Window>

And this is the code behind:

    public partial class MainWindow : Window

    {

        // Use of ObservableCollection allows bound controls to react to changes to the collection

 

        ObservableCollection<Employee> employees;

 

        public MainWindow()

        {

            InitializeComponent();

        }

 

        private void Window_Loaded(object sender, RoutedEventArgs e)

        {

            employees = new ObservableCollection<Employee>();

            ctlEmployeeList.Employees = employees;

        }

 

        private void btnFill_Click(object sender, RoutedEventArgs e)

        {

            Button btn = sender as Button;

 

            // Shows that ObservableCollection allows bound controls to react to changes to the collection

 

            if (btn.Content.ToString() == "Fill")

            {

                employees.Add(new Employee { IsManager = true, Name = "Bill" });

                employees.Add(new Employee { IsManager = true, Name = "Tom" });

                employees.Add(new Employee { IsManager = false, Name = "***" });

                employees.Add(new Employee { IsManager = true, Name = "Harry" });

                employees.Add(new Employee { IsManager = false, Name = "Alice" });

 

                btn.Content = "Empty";

            }

            else

            {

                employees.Clear();

                btn.Content = "Fill";

            }

        }

 

        private void btnCheck_Click(object sender, RoutedEventArgs e)

        {

            Button btn = sender as Button;

 

            // Demonstrate that INotifyPropertyChanged on Employee allows bound controls

            // to react to changes to its properties

 

            if (btn.Content.ToString() == "Check All")

            {

                foreach (Employee employee in employees)

                {

                    employee.IsManager = true;

                }

                btn.Content = "Uncheck All";

            }

            else

            {

                foreach (Employee employee in employees)

                {

                    employee.IsManager = false;

                }

                btn.Content = "Check All";

            }

        }

    }

As you can see, (in the Window_Loaded handler) the only interface with the EmployeeListControl is the Employees property which exposes an ObservableCollection<Employee>.

The affect of the “Fill” button on the number of entries in the EmployeeListControl is implemented solely by adding or removing items for the collection.

Similarly, the affect of the “Check All” button on the entries in the rows of the EmployeeListControl is implemented solely by setting properties of Employee objects in the collection.

OK. I think that sets the scene. In the next post we will take a detailed look at the EmployeeListControl which implements an aggregate CheckBox in a Column Header which allows you to check / uncheck the IsManager property for all employees in the collection.

Aggregate CheckBox for DataGridCheckBoxColumn (Part 1)

It often amazes me to see how easily one can accomplish so much with WPF.

And yet, let’s admit it, its can sometimes be darned hard to achieve some fairly trivial things.

Here is a simple problem I worked quite hard on until I had a solution I was happy with. I boiled it down to a few specific techniques that can be reused, so I think they are worth sharing.

You can download the source code for this article here.

Background

Let’s say you have a business object (say Employee) with a boolean property (say IsManager). You have a collection of these objects and you want to bind them to the DataGrid of the WPFToolkit. A natural choice for representing  a boolean property in the DataGrid is the DataGridCheckBoxColumn and using fairly rudimentary XAML you can two-way bind your collection to the grid.

   <StackPanel>

        <tk:DataGrid x:Name="gridEmployeeList"

                    AutoGenerateColumns="False"

                    >

            <tk:DataGrid.Columns>

                <tk:DataGridCheckBoxColumn

                       Header="Manager"

                       Binding="{Binding Path=IsManager, Mode=TwoWay}"

               />

                <tk:DataGridTextColumn

                       Header="Name"

                       Width="*"

                       Binding="{Binding Path=Name, Mode=TwoWay}"

               />

            </tk:DataGrid.Columns>

        </tk:DataGrid>

    </StackPanel>

(The ‘tk’ prefix is mapped to the Microsoft.Windows.Controls namespace in the WPFToolkit assembly.)

OK. You do have to write some code in order for changes to an Employee to propagate to UI. You have a choice. Either make those IsManager and Name properties Dependency Properties or implement INotifyPropertyChanged and raise the PropertyChanged event when those properties change.

As these are business objects with no dependence on UI or WPF, I prefer not to force them to inherit the DependencyObject base class so, in this article, I will be implementing the INotifyPropertyChanged interface.

The Problem

Now, here is my trivial problem.

I would like to have a CheckBox, let’s call it an aggregate CheckBox, that I can use to check and uncheck the CheckBoxes on all rows (in the DataGridCheckBoxColumn) with one click. This is quite a basic requirement for any application that will be used to present more than a few records.

A natural place to put the aggregate CheckBox would be in the Column Header above the DataGridCheckBoxColumn.

So, go on, show me how easy it is to do that with the WPF DataGrid : )

I will describe my solution in the next post…