Thursday, April 05, 2007

Binding to binding of two listboxes

Let's imagine following thing. I have list of projects and list of employees, assigned to those projects. Now I want to select some projects and see employees' assignments - who's going to work overtime. Simple, yes? We just create two listboxes. One binded to the list pro projects, and the second is binded to the selected items of first listbox with converter, that calculate unique employees and return calculated result. Something like this. Here we can clearly see, that Employee 12 already forgot how his kids looks like.

 

      <ListBox Name="prjs" SelectionMode="Extended" ItemsSource="{StaticResource projects}"/>

      <ListBox Name="emps1" ItemsSource="{Binding ElementName=prjs, Path=SelectedItems, Converter={StaticResource myConv}}"/>

Do it. You'll notice very fast, that your converter myConv will be called only once, when you have nothing selected, so it simple impossible to convert selected values to something, we need.  You know what, it makes sense, why I should reinitialize converter, if my types are not going to change?

Let's try another trick. We'll put shared datasource in application resource level and on SelectionChanged event put our stuff there. After it we'll just bind second listbox to this source.

 

<l:Employees x:Key="employees"/>

<l:Projects x:Key="projects"/>

 

 

<ListBox Name="prjs" SelectionMode="Extended" ItemsSource="{StaticResource projects}" SelectionChanged="onSelection"/>

<ListBox Name="emps" ItemsSource="{Binding Source={StaticResource employees}}"/>

 

 

void onSelection(object sender, SelectionChangedEventArgs e)

        {

            Employees emps = this.Resources["employees"] as Employees;

 

            if (emps != null)

            {

                for (int i = 0; i < e.RemovedItems.Count; i++)

                {

                    Project p = e.RemovedItems[i] as Project;

                    for (int j = 0; j < p.Employees.Count; j++)

                    {

                        if (emps.Contains(p.Employees[j]))

                        {

                            if(emps[emps.IndexOf(p.Employees[j])].Width == 0)

                            {

                                emps.Remove(p.Employees[j]);

                            }

                            else

                            {

                                emps[emps.IndexOf(p.Employees[j])].Width--;

                            }

                        }

                    }

                }

 

                for (int i = 0; i < e.AddedItems.Count; i++)

                {

                    Project p = e.AddedItems[i] as Project;

                    for (int j = 0; j < p.Employees.Count; j++)

                    {

                        if (!emps.Contains(p.Employees[j]))

                        {

                            emps.Add(p.Employees[j]);

                        }

                        else

                        {

                            emps[emps.IndexOf(p.Employees[j])].Width++;

                        }

                    }

                }

            }

        }

Now it's works. The only thing we have to do is create simple datatemplate to visualize an employee assignment.

 

    <DataTemplate DataType="{x:Type l:Employees}" x:Key="emp">

      <TextBlock FontSize="{Binding Path=Width, Converter={StaticResource wts}}" Text="{Binding Path=Name}"/>

    </DataTemplate>

That's all folks, not always we should go straight-forward to get what we need from WPF, but it's looks like no really obstacles can be found there.

Source code for this article

No comments: