Ordering items in Regions?

Sep 30, 2008 at 5:58 AM

Is there any guidance on ordering items in a region? For example if I have a StatusBar region and 3 modules that each wish to add a StatusBarItem. However regardless of module load order I want to make sure that the StatusBarItems are ordered moduleA, moduleB, moduleC, etc.

I've haven't found anything in the docs or any posts online that seems to talk about this. I played around with the source and came up with the following kludge, but it doesn't feel right.

I created the following:

ISequencedControl  -- add a Sequence value
SequencedItemsControlRegionAdapter : ItemsControlRegionAdapter  -- override CreateRegion() to return a SequencedRegion
SequencedRegion : AllActiveRegion  -- override ItemMetadataCollection to return a SequencedObservableCollection
SequencedObservableCollection<T> : ObservableCollection<T> where T : ItemMetadata  -- override InsertItem to insert in order of sequence and call base.OnCollectionChanged(NotifyCollectionChangedAction.Reset)

The SequencedObservableCollection really feels wrong. especially since it looks like I have to call the OnCollectionChanged(reset) to force the UI to actually update. I have also considered getting rid of SequencedObservableCollection and in Sequenced region listening to the OnCollectionChanged event and on adds, re-evaluate and move items around. Also feels not right.

Anyone have any other ideas? I'd be happy to find out that I've reinvented the wheel because I missed some simple way of doing this.
Sep 30, 2008 at 2:19 PM
Edited Sep 30, 2008 at 2:56 PM
This can be done via configuration - I think you'll find the following very interesting:

+ Development Activities
   + How to: Dynamically load modules
      + Using the Configuration Module Enumerator

Specifically, the <modules><module><dependencies><dependency moduleName="Module B"> element

Edited:  the configuration doesn't provide your "regardless of module load order"; however it does provide a work-around to ensure your modules add menu items in a specified order.

Sep 30, 2008 at 5:47 PM

That would work if modules loaded items in chunks.   (e.g. itemA1, itemA2, itemB1, itemB2) but I really need is dynamic sequencing.

itemA1 - 50
itemA2 - 10
itemB1 - 5
itemB2 - 25

then the layout should be: itemB1, itemA2, itemB2, itemA1

The method I described above does this, it just has that code smell to it that is not right. I am starting to think the other option I listed (listening to the change notifications and kludging the order after the fact) might be a bit cleaner, but still not ideal.

Sep 30, 2008 at 9:22 PM
Edited Sep 30, 2008 at 9:24 PM

At a high level, looking at the code provided, your solution doesn't seem so kludgy; I'd probably call it more creative - particularly since it works.   An approach I might take when/if I run into this requirement, particularly if you don't give us your code;)  is to follow in MEF's (Managed Extensibility Framework) footsteps.

In the absence of regions, the following is one of the ways the MEF team populates a view.  I saw in their solution (default: below) a possible alternate solution to your sequencing issue.  In the status bars presenter - pull your items into a collection, order by sequence number and then populate your status bar (perhaps this is how you are currently doing it?).   The presenter can handle any events to update the status bar as applicable (if new modules are added after populated)

Primer on MEF follows along with a code snippet from their XFileExplorer sample project follows:

External modules will contain an Export attribute with the same contract name shown below in the Import attribute; there will be multiple exports.  The following Import attribute pulls all of the exports it finds into a _views collection which is used below.  Note: The Export methods also have attributes which define their order in the universe.

private ExportCollection<UserControl, IFileExplorerViewMetadata> _views = null;

// Reload all views
foreach (var view in _views.OrderBy(i => i.MetadataView.DockId))
    //Get the instance of current view
    var childPane = view.GetExportedObject();

    //Dock the view properly according to its metadata
    switch (view.MetadataView.Docking)
        case Dock.Top:
            TopPanel.Children.Add(childPane); break;
        case Dock.Bottom:
            BottomPanel.Children.Add(childPane); break;
        case Dock.Left:
            LeftPanel.Children.Add(childPane); break;
            TabItem item = RightPanelTabs.Items.Cast<TabItem>()
                .FirstOrDefault(i => (int)i.Tag == view.MetadataView.DockId);
            if (item != null)
                (item.Content as DockPanel).Children.Add(childPane);

Oct 1, 2008 at 11:44 PM

The idea you posted doesn't seem bad to me. We actually left the ItemMetadataCollection property virtual (even though its protected) for cases exactly like these.
What it does feel a little odd for me is the OnCollectionChanged(reset) call. You must call OnCollectionChanged, but maybe you could provide a more specific parameter (instead of Reset use Add, and tell it the index where it was added).

You're not saying how are you storing the order value to know in what position to add the view in the collection. I'm curious to know if you are using an attached property in ItemMetadata. The only reason ItemMetadata is a DependencyObject is to allow extending the metadata properties on a view, for cases like these.

I hope this helps ease your concerns on this solution.

Julian Dominguez
Oct 3, 2008 at 8:20 PM
At the moment the Sequence value comes from the interface ISequenceControl that I created. I subclass StatusBarItem -> SequencedStatusBarItem.

I'm researching moving our winforms/CAB app over to prism, but this is my first week digging into WPF/prism so far. I'll look into the DependencyObject a bit more, and try to post code on Monday.

Thanks for the help.
Oct 5, 2008 at 11:17 PM
Had some "ah-ha" moments this weekend on DependencyProperties/AttachedProperties.

1) Created an attached Sequence property
2)  this.statusBar.Items.SortDescriptions.Add(new SortDescription("Sequence", ListSortDirection.Ascending));

Seems to work fine.

I started looking into the SortDescriptors a bit more and found this post:

I'm expanding it a bit and can replace #2 above with a snip of XAML which seems much cleaner.

<StatusBar Name="statusBar"
   cal:RegionManager.RegionName="{x:Static infc:RegionNames.StatusBar}"