Skip to content

14 Add dynamic property

Matthieu MEZIL (MSFT) edited this page Jun 8, 2015 · 1 revision

In previous workshops, you used a master detail in MainWindow with employees and customers using a ComboBox for employees.

Now imagine that instead of picking the customers for one employee, you want to pick customers for many employees.

To do this, you will use a CheckedListBox like this

Now the question is: how to manage the CheckedListBox binding with MVVM.

The "normal" way to do it is to use a class with two properties: the employee and a boolean.

So it means creating a new class. Then having a collection of this class but then, when you want to add or delete an employee, you have to synchronize this collection too.

So WAQS provides a way based on PropertyDescriptor to dynamically add a new bindable property to a type or some instances.

After updating MainWindow.xaml like this, add a collection of Employee in MainWidowViewModel.cs

private HashSet<Employee> _selectedEmployees = new HashSet<Employee>();

Then update the LoadCustomerAsync method to use it

public async Task LoadCustomersAsync()
{
    Customers = null;
    var selectedEmployees = _selectedEmployees.ToList();
    if (selectedEmployees.Any())
    {
        var customers = await (from c in _context.Customers.AsAsyncQueryable()
                                from e in _context.Employees.AsAsyncQueryable()
                                where selectedEmployees.Contains(e)
                                where e.AlreadySoldTo(c)
                                let totalSpent = c.TotalSpent
                                orderby totalSpent descending
                                select new CustomerInfo
                                {
                                    Id = c.Id,
                                    Name = c.FullName,
                                    TotalSpent = totalSpent
                                }).ExecuteAsync();
        if (selectedEmployees.Count == _selectedEmployees.Count && selectedEmployees.All(e => _selectedEmployees.Contains(e)))
        {
            Customers = customers;
        }
    }
}

Finally, add the PropertyDescriptor in the constructor:

_context.AddProperty<Employee, bool>("IsSelected", 
    e => _selectedEmployees.Contains(e), 
    (e, value) =>
        {
            if (value)
            {
                _selectedEmployees.Add(e);
            }
            else
            {
                _selectedEmployees.Remove(e);
            }
            Customers = null;
            LoadCustomersAsync().ConfigureAwait(true);
        });

Note how easy it is. You define the type it applies to, the result type, the name, the get and the set.

And then, you can bind on it:

<ListBox ItemsSource="{Binding Employees}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <StackPanel Orientation="Horizontal">
                <CheckBox IsChecked="{Binding IsSelected}" />
                <TextBlock Text="{Binding FullName}" />
            </StackPanel>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

You can download the result here.

Clone this wiki locally