SelectorItemsSourceSyncBehavior vs. ItemsSource

Topics: Prism v4 - WPF 4
Mar 21, 2013 at 10:54 AM
A question on the implementation of SelectorItemsSourceSyncBehavior:

Why is the collection Selector.Items "manually" kept in sync with the observable collection this.Region.Views?

Why isn't the Selector.ItemsSource simply bound to that collection?

I did the following change in the OnAttach method:
protected override void OnAttach()
        {
...
            //this.SynchronizeItems();
...
            //this.Region.Views.CollectionChanged += this.Views_CollectionChanged;
            this.hostControl.SetBinding(Selector.ItemsSourceProperty, new Binding { Source = this.Region.Views });
        }
This results in one failing unit test: AddingViewToTwoRegionsThrows:
Test method Microsoft.Practices.Prism.Tests.Regions.Behaviors.SelectorItemsSourceSyncRegionBehaviorFixture.AddingViewToTwoRegionsThrows did not throw expected exception System.InvalidOperationException.
The reason is that the (dummy) Button in the test is now not actually added a visual tree.

Why am I even investigating this?

I would like the views in a TabControl to only Load at the moment the tab gets activated
(and unload when the TabItem gets un-selected).
This works well when binding an observable collection of Views to the ItemsSource.
When manually adding and removing the Views in the actual Items collection, all TabItems get loaded when the TabControl gets Loaded.
Actions during the OnLoaded of the Views are thus executed too early.
This also results in an unpredictable sequence of Loaded and Unloaded events: activating an inactive TabItem, loads it twice without ever being unloaded in between.

I'm considering overriding this implementation with our own, using MEF.
Anyone strongly advising against this?

Thanks in advance!
jan
Developer
Mar 21, 2013 at 8:28 PM
Hi Jan,

As far as I know, the Prism library was designed so that significant components of it could be extended or replaced to fit the functionality required in your application. Taking this into account, there is no advice against replacing the SelectorItemsSourceSyncBehavior.

Regarding the implementation of directly mapping the ItemsSource to the Views collection, this is the approach used for regions defined in ItemsControls. The difference between a region in a Selector and in an ItemsControl is that the latest one does not need to synchronize the active view with the control (because all views are considered as active.) This might be the reason behind the difference between the two approaches.

I am not aware of how much you have customized you behavior, but take into account that besides mapping the ItemsSource to the Views collection you will also need to keep in sync the region's active view and the control's selected item manually (including the deactivation of non selected views in the region).

Also, based on my understanding, I believe that data binding can swallow exceptions. Hence, the failing test could be related to this.

Regards,

Damian Cherubini
http://blogs.southworks.net/dcherubini
Mar 22, 2013 at 8:39 AM
Edited Mar 22, 2013 at 10:25 AM
Thanks for the reply and pointing me to the active view!

As far as I can see, the synchronization of the SelectedItem and active view is handled separately from the synchronization of the actual Items collection and Views.

In SelectorItemsSourceSyncBehavior.OnAttach() the first is handled by these 2 subscriptions
this.hostControl.SelectionChanged += this.HostControlSelectionChanged;
this.Region.ActiveViews.CollectionChanged += this.ActiveViews_CollectionChanged;
while the second is handled by these 2 calls:
this.SynchronizeItems();
...
this.Region.Views.CollectionChanged += this.Views_CollectionChanged;
I see no reason why the second cannot be solved using a binding.

I intend to modify the behavior only with respect to the second synchronisation of the actual Items collection and Views.
In fact my implementation is just a copy of the Prism's own 'SelectorItemsSourceSyncBehavior' (including the Copyright), with the modifications listed in my first post.

EDIT: Unfortunately SelectorRegionAdapter.AttachBehaviors references new SelectorItemsSourceSyncBehavior() (line 61) directly, so I'll have to replace the default 'SelectorRegionAdapter' as well.

jan


PS: For completeness, this is my OnAttach() method:
    protected override void OnAttach()
    {
      bool itemsSourceIsSet = this.hostControl.ItemsSource != null;
#if !SILVERLIGHT
      itemsSourceIsSet = itemsSourceIsSet || (BindingOperations.GetBinding(this.hostControl, ItemsControl.ItemsSourceProperty) != null);
#endif
      if (itemsSourceIsSet)
      {
        throw new InvalidOperationException(Resources.ItemsControlHasItemsSourceException);
      }

      this.hostControl.SelectionChanged += this.HostControlSelectionChanged;
      this.Region.ActiveViews.CollectionChanged += this.ActiveViews_CollectionChanged;

      this.hostControl.SetBinding(Selector.ItemsSourceProperty, new Binding { Source = this.Region.Views });
    }