Notify ViewModel when TabControl selection changes (get viewmodel of selected tabitem)

Topics: Prism v4 - WPF 4
Mar 13, 2013 at 10:29 AM
Edited Mar 13, 2013 at 10:30 AM
Hi!

Note: I've posted this thread also to Telerik's WPF forum but I thought that I'd would be appropriate to also post it here since I think I can get good support from you guys, hope you don't mind :)

I'm using RadTabControl as a Prism region and Tab items are successfully created. I have a Shell window which has a RadTabControl. Can I somehow get the view/viewmodel of the selected TabItem (tab's content) when TabControl's selected item changes so that I could call a method of that view model (to send an event with current navigation service)?

All of this is because I'm using PRISM RegionNavigationService in the Shell's viewmodel and I want each tab to be able to publish their NavigationService via EventAggregator event when tab is selected.

Background: I'm creating a dashboard application that has navigation bar with back/forward buttons and I want to be able to navigate back/forward under each Tab which has a scoped region.

Now I get this navigation service for a Tab when I navigate to it the first time (using NavigatedTo event) but I also need to get navigation service again when selecting tab later.

Other ways of doing this are also accepted of course :)

Br,

Kalle
Developer
Mar 13, 2013 at 7:11 PM
Hi Kalle,

As a possible approach, I believe you could take advantage of the region in the RadTabControl to react when the selected tab changes. The Region class has an ActiveViews collection containing the active views in the corresponding region. This collection exposes a CollectionChanged event to which you can subscribe to be notified of changes:
this.regionManager.Regions["TabRegion"].ActiveViews.CollectionChanged += ActiveViews_CollectionChanged;
Based on my understanding, the actives view in the region are in sync with the visible views in the control. Therefore, when a tab is selected in the TabControl the previous view will be removed from the ActiveViews and the selected one will be added to it (raising the CollectionChanged event). Using this, you can check if the new item added to the collection is the view of the subscribed view model (for example by checking the DataContext property) and then react accordingly. For example:
void ActiveViews_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
    if (e.Action == NotifyCollectionChangedAction.Add)
    {
        foreach (var item in e.NewItems)
        {
            FrameworkElement view = item as FrameworkElement;
            if (view != null && view.DataContext == this)
            {
                // Do work
            }
        }
    }   
}
I hope you find this useful,

Damian Cherubini
http://blogs.southworks.net/dcherubini
Mar 14, 2013 at 7:58 AM
Edited Mar 14, 2013 at 8:03 AM
Hi Damian,

I'm not sure if I can use this in my case or maybe I didn't understand it completely but I try to describe my problem better (let me know if your suggestion should do the trick):

The TabItem in the TabRegion is DashboardTabView which has a ContentControl. Now, when I add the tabitem to TabRegion (using navigation) I also navigate another view (MissingMeasurementView) to it's TabContentRegion using scoped region manager:
            // Navigate to DashboardTabView with a title for the Tab item
            this.regionManager.Regions[RegionNames.TabRegion].RequestNavigate(
                new Uri("DashboardTabView?createRegionManagerScope=true&title=" + TitleText, UriKind.Relative),
                (result) =>
                {
                    // Get scoped region manager and use it to navigate to sub view
                    var myRegionManager = result.ExtractRegionManager();
                    myRegionManager.RequestNavigate(RegionNames.TabContentRegion, 
                        new Uri("MissingMeasurementView?view=1", UriKind.Relative)
                        );
                });
After the Tab is opened I can see the MissingMeasurementView and from that view I can navigate to next view, for example, View2. And from View2 I can navigate back to MissingMeasurementView using Shell's navigation back button. This is all working nicely. I publish current navigationContext.NavigationService on each OnNavigatedTo event so that my application's Shell can offer Back/Forward navigation buttons.
        public override void OnNavigatedTo(NavigationContext navigationContext)
        {
            // get navigation service
            NavigationService = navigationContext.NavigationService;

            // Send event with Navigation service to handle back/forward navigation under this Tab
            var dashboardEvent = this.eventAggregator.GetEvent<DashboardOpenTabEvent>();
            DashboardTabOpenParams eventParams = new DashboardTabOpenParams();
            eventParams.NavigationService = NavigationService;
            dashboardEvent.Publish(eventParams);
        }
Problem that I'm facing with this is that if user changes selected Tab and creates another instance of this same Tab (DashboardTabView) the Shell does not get old Tab's navigation service anymore because OnNavigatedTo does not seem to trigger when I change selected Tab but only when I create the Tab and navigate inside the Tab. So basically navigation works only for the most recently created Tab.

That is why I wanted to somehow know which Tab is open and get that Tab's navigation service published via EventAggregator. One way that I came up with was to use TabItem's IsSelected property and bind it to a property in DashboardTabViewModel. In DashboardTabViewModel I could then send navigation service event in property's Setter but DashboardTabViewModel does not know about scoped region's navigation service. It only knows about TabControl's TabRegion navigation service which is not the one I want to use. I want to use TabContentRegion's navigation service which, I believe, is not available in DashboardTabViewModel.

So I would need to get scoped region's navigation service in DashboardTabViewModel to go with this IsSelected property binding approach.

Hope this makes more clear what I'm trying to accomplish.
Mar 14, 2013 at 10:55 AM
Edited Mar 14, 2013 at 10:56 AM
Hi!

It seems I was able to get it working after all using this IsSelected property binding approach :)

I created a property IsTabSelected to DashboardTabViewModel and bind that to TabItem's IsSelected property and after that get the navigation service from scoped region manager only when IsTabSelected == true and publish it in an event.
        private bool bIsTabSelected;
        public bool IsTabSelected
        {
            get { return bIsTabSelected; }
            set
            {
                bIsTabSelected = value;
                RaisePropertyChanged("IsTabSelected");
                if(value == true)
                    SendNavigationServiceEvent();
            }
        }

        private void SendNavigationServiceEvent()
        {
            if (this.RegionManager == null) return;

            // Send event with Navigation service to handle back/forward navigation under this Tab
            var dashboardEvent = this.eventAggregator.GetEvent<DashboardOpenTabEvent>();
            DashboardTabOpenParams eventParams = new DashboardTabOpenParams();
            eventParams.NavigationService = this.RegionManager.Regions[RegionNames.TabContentRegion].NavigationService;
            dashboardEvent.Publish(eventParams);
        }
I'm still open for other possible solutions for this but for now this is enough for me.

Br,

Kalle