How can I bind a second ItemsControl to the Items collection?

Topics: Getting Started
Oct 11, 2011 at 8:27 PM

I'm following the step-by-step instructions for Coproject. I've successfully used Conductor<T>.Collection.OneActive to build a Collection of Items (Screens): 

    [Export(typeof(IShell))]
    public class ShellViewModel : Conductor<IModule>.Collection.OneActive, IShell
    {
        [ImportingConstructor]
        public ShellViewModel([ImportMany]IEnumerable<Lazy<IModule, IModuleMetadata>> moduleHandles)        
        {
            var modules = from h in moduleHandles orderby h.Metadata.Order select h.Value;
            Items.AddRange(modules);
        }

I see the magic happen...by simply naming my ItemsControl "Items" I get a tabbed list of screen names and each one is activated as I click it's tab. Great!

Now I'd like to bind another ItemsControl to the exact same Items collection. How is this done? I'd like the same behavior on the second control - click to activate the selected screen. Also, how would I manually activate one of these same screens? If one screen in the Items collection is named Customers, what code goes in the ShellViewModel to activate the Customers screen?

Thanks in advance.

Oct 11, 2011 at 10:08 PM

If you have another (Items)Control in the same view bound to the same collection, you can't (obviously) give it the same name; hence you have to use a regular binding for it.
Use conventions even for this control *could* be probably obtained tweaking ViewModelBinder.HandleUnmatchedElements delegate; it is called by CM after  all conventions have been applied, and let
you to deal with named control which haven't been handled by CM.
It's likely a not trivial amount of work, and I never actually tried to do it.

About activating a particular item in the Items collection, you just have to call Conductor's ActivateItem method, passing the VM to activate:

var vm = this.Items.SingleOrDefault(x=>x.DisplayName.Equals("Customers")); //just a sample
this.ActivateItem( vm );

Oct 12, 2011 at 5:33 PM
Edited Oct 13, 2011 at 12:08 PM

I added regular binding on my second items control (and it works well) but I need help with the screen activation. How do I modify this to activate the selected screen?

                    <ListBox  ItemsSource="{Binding Items}">
                        <ListBox.ItemTemplate>
                            <DataTemplate>
                                <Grid>
                                    <HyperlinkButton Content="{Binding Path=DisplayName}" />
                                </Grid>
                            </DataTemplate>
                        </ListBox.ItemTemplate>
                    </ListBox>

and what goes in the ShellViewModel code behind?

Also, I ran into a problem using your ActivateItem code. DisplayName is not recognized as a property of x which is an IModule (as in Coproject details here). What am I missing? Perhaps I need a cast? Should I modify IModule to include DisplayName? Furthermore what allows the ListBox above to bind to Items.DisplayName while Item x in the lambda expression has no DisplayName? Are there two different Items collections?

I really appreciate your help.

Oct 12, 2011 at 10:12 PM

The first row of my code was actually just a sample: I didn't know how do you wanted to select the screen to activate, so I wrote a sample condition.
My bad: since your class is a Conductor<IModule>, members of Items collection doesn't expose DisplayName.

Anyway, to have the conductor activate the item currently selected in the ListBox, you don't need any additional code in your VM.
You just have to set a TwoWay binding between the SelectedItem property of the ListBox and the ActiveItem property of the VM:

<ListBox ItemsSource="{Binding Items}" SelectedItem="{Binding ActiveItem, Mode=TwoWay}">
	...
</ListBox>

CM takes care of running child VM lifecycle methos (OnActivate and OnDeactivate) even if you directly set ActiveItem property (through binding, in this case) instead of calling ActivateItem method.