WPF - Prism with RadTabControl as my region

0

I am trying to use a Telerik RadTabControl as a region in my Prism WPF app. When the application loads, the selected tab's content is loaded correctly. However, the other tabs are not populated with data from their respective viewmodel.

Inspection in the Live Tree Explorer revealed that my DataBindingExpressions are not being evaluated properly. After googling, I found Miroslav's answer here and realized that I need to implement a custom region adapter for RadTabControl.

After adding the code from the above link, I encountered an exception when registering a view in a non-RadTabControl region (see the Initialize() method below).

Exception being thrown:

Prism.Regions.ViewRegistrationException occurred
  HResult=0x80131500
  Message=An exception has occurred while trying to add a view to region 'SecondaryContentRegion'. 
    - The most likely causing exception was was: 'System.InvalidOperationException: Operation is not valid while ItemsSource is in use. Access and modify elements with ItemsControl.ItemsSource instead.
   at System.Windows.Controls.ItemCollection.CheckIsUsingInnerView()
   at System.Windows.Controls.ItemCollection.Remove(Object removeItem)
   at Telerik.Windows.Controls.RadPaneGroup.RemovePane(RadPane pane)
   at Telerik.Windows.Controls.RadPaneGroup.OnItemsChanged(NotifyCollectionChangedEventArgs e)
   at System.Windows.Controls.ItemsControl.OnItemCollectionChanged2(Object sender, NotifyCollectionChangedEventArgs e)
   at System.Collections.Specialized.NotifyCollectionChangedEventHandler.Invoke(Object sender, NotifyCollectionChangedEventArgs e)
   at System.Windows.Data.CollectionView.OnCollectionChanged(NotifyCollectionChangedEventArgs args)
   at System.Windows.Controls.ItemCollection.OnViewCollectionChanged(Object sender, NotifyCollectionChangedEventArgs e)
   at System.Windows.WeakEventManager.ListenerList`1.DeliverEvent(Object sender, EventArgs e, Type managerType)
   at System.Windows.WeakEventManager.DeliverEvent(Object sender, EventArgs args)
   at System.Collections.Specialized.CollectionChangedEventManager.OnCollectionChanged(Object sender, NotifyCollectionChangedEventArgs args)
   at System.Windows.Data.CollectionView.OnCollectionChanged(NotifyCollectionChangedEventArgs args)
   at MS.Internal.Data.EnumerableCollectionView.LoadSnapshot(IEnumerable source)
   at MS.Internal.Data.EnumerableCollectionView.ProcessCollectionChanged(NotifyCollectionChangedEventArgs args)
   at System.Windows.Data.CollectionView.OnCollectionChanged(Object sender, NotifyCollectionChangedEventArgs args)
   at System.Collections.Specialized.NotifyCollectionChangedEventHandler.Invoke(Object sender, NotifyCollectionChangedEventArgs e)
   at Prism.Regions.ViewsCollection.OnCollectionChanged(NotifyCollectionChangedEventArgs e)
   at Prism.Regions.ViewsCollection.SourceCollectionChanged(Object sender, NotifyCollectionChangedEventArgs e)
   at System.Collections.Specialized.NotifyCollectionChangedEventHandler.Invoke(Object sender, NotifyCollectionChangedEventArgs e)
   at System.Collections.ObjectModel.ObservableCollection`1.OnCollectionChanged(NotifyCollectionChangedEventArgs e)
   at System.Collections.ObjectModel.ObservableCollection`1.InsertItem(Int32 index, T item)
   at System.Collections.ObjectModel.Collection`1.Add(T item)
   at Prism.Regions.Region.InnerAdd(Object view, String viewName, IRegionManager scopedRegionManager)
   at Prism.Regions.Region.Add(Object view, String viewName, Boolean createRegionManagerScope)
   at Prism.Regions.Region.Add(Object view)
   at Prism.Regions.Behaviors.AutoPopulateRegionBehavior.AddViewIntoRegion(Object viewToAdd)
   at Prism.Regions.Behaviors.AutoPopulateRegionBehavior.OnViewRegistered(Object sender, ViewRegisteredEventArgs e)'.
    But also check the InnerExceptions for more detail or call .GetRootException(). 
  Source=Prism.Wpf
  StackTrace:
   at Prism.Regions.RegionViewRegistry.OnContentRegistered(ViewRegisteredEventArgs e)
   at Prism.Regions.RegionViewRegistry.RegisterViewWithRegion(String regionName, Func`1 getContentDelegate)
   at Prism.Regions.RegionManager.RegisterViewWithRegion(String regionName, Func`1 getContentDelegate)
   at Hids.Desktop.Core.Services.ControlManager.ActivatePrimaryDisplay(PrimaryDisplay primaryDisplay) in C:\workspace\AKFG\HIDS_PROJECT\HIDS\Hids.Desktop.Core\Services\ControlManager.cs:line 112
   at Hids.Desktop.Core.Services.ControlManager.RegisterPrimaryDisplay(PrimaryDisplay primaryDisplay) in C:\workspace\AKFG\HIDS_PROJECT\HIDS\Hids.Desktop.Core\Services\ControlManager.cs:line 80
   at HuntDefinitionTool.HuntDefinitionToolModule.Initialize() in C:\workspace\AKFG\HIDS_PROJECT\HIDS\Hids.Desktop.HuntDefinitionTool\HuntDefinitionToolModule.cs:line 118
   at Prism.Modularity.ModuleInitializer.Initialize(ModuleInfo moduleInfo)

Inner Exception 1:
InvalidOperationException: Operation is not valid while ItemsSource is in use. Access and modify elements with ItemsControl.ItemsSource instead.

Adapter for RadTabControl:

namespace Telerik.Regions
{
    /// <summary>
    /// Adapter that creates a new <see cref="Region"/> and binds all
    /// the views to the adapted <see cref="RadTabControl"/>.
    /// </summary>
    /// <remarks>
    /// This adapter is needed on Silverlight because the <see cref="RadTabControl"/> doesn't 
    /// automatically create <see cref="RadTabItem"/>s when new items are added to 
    /// the <see cref="ItemsControl.Items"/> collection.
    /// </remarks>
    public class RadTabControlRegionAdapter : RegionAdapterBase<RadTabControl>
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="RadTabControlRegionAdapter"/> class.
        /// </summary>
        /// <param name="regionBehaviorFactory">The factory used to create the region behaviors to attach to the created regions.</param>
        public RadTabControlRegionAdapter(IRegionBehaviorFactory regionBehaviorFactory)
            : base(regionBehaviorFactory)
        {
        }

        /// <summary>
        /// Adapts a <see cref="RadTabControl"/> to an <see cref="IRegion"/>.
        /// </summary>
        /// <param name="region">The new region being used.</param>
        /// <param name="regionTarget">The object to adapt.</param>
        protected override void Adapt(IRegion region, RadTabControl regionTarget)
        {
            bool itemsSourceIsSet = regionTarget.ItemsSource != null;

            if (itemsSourceIsSet)
            {
                throw new InvalidOperationException("ItemsControlHasItemsSourceException");
            }
        }

        /// <summary>
        /// Attach new behaviors.
        /// </summary>
        /// <param name="region">The region being used.</param>
        /// <param name="regionTarget">The object to adapt.</param>
        /// <remarks>
        /// This class attaches the base behaviors and also keeps the <see cref="RadTabControl.SelectedItem"/> 
        /// and the <see cref="IRegion.ActiveViews"/> in sync.
        /// </remarks>
        protected override void AttachBehaviors(IRegion region, RadTabControl regionTarget)
        {
            base.AttachBehaviors(region, regionTarget);
            region.Behaviors.Add(RadTabControlRegionSyncBehavior.BehaviorKey, new RadTabControlRegionSyncBehavior { HostControl = regionTarget });
        }

        /// <summary>
        /// Creates a new instance of <see cref="Region"/>.
        /// </summary>
        /// <returns>A new instance of <see cref="Region"/>.</returns>
        protected override IRegion CreateRegion()
        {
            return new SingleActiveRegion();
        }

        /// <summary>
        /// Behavior that generates <see cref="RadTabItem"/> containers for the added items
        /// and also keeps the <see cref="RadTabControl.SelectedItem"/> and the <see cref="IRegion.ActiveViews"/> in sync.
        /// </summary>
        private class RadTabControlRegionSyncBehavior : RegionBehavior, IHostAwareRegionBehavior
        {
            public const string BehaviorKey = "RadTabControlRegionSyncBehavior";

            private static readonly DependencyProperty IsGeneratedProperty =
                DependencyProperty.RegisterAttached("IsGenerated", typeof(bool), typeof(RadTabControlRegionSyncBehavior), null);

            private RadTabControl hostControl;

            public DependencyObject HostControl
            {
                get
                {
                    return this.hostControl;
                }

                set
                {
                    RadTabControl newValue = value as RadTabControl;
                    if (newValue == null)
                    {
                        throw new InvalidOperationException("HostControlMustBeARadTabControl");
                    }

                    if (IsAttached)
                    {
                        throw new InvalidOperationException("HostControlCannotBeSetAfterAttach");
                    }

                    this.hostControl = newValue;
                }
            }

            protected override void OnAttach()
            {
                if (this.hostControl == null)
                {
                    throw new InvalidOperationException("HostControlCannotBeNull");
                }

                this.hostControl.ItemsSource = this.Region.Views;

                this.hostControl.SelectionChanged += this.OnSelectionChanged;
                this.Region.ActiveViews.CollectionChanged += this.OnActiveViewsChanged;
            }

            private void OnSelectionChanged(object sender, RoutedEventArgs args)
            {
                var e = args as RadSelectionChangedEventArgs;

                // e.OriginalSource == null, that's why we use sender.
                if (this.hostControl == sender)
                {

                    foreach (var item in e.RemovedItems)
                    {
                        // check if the view is in both Views and ActiveViews collections (there may be out of sync)
                        if (this.Region.Views.Contains(item) && this.Region.ActiveViews.Contains(item))
                        {
                            this.Region.Deactivate(item);
                        }
                    }

                    foreach (var item in e.AddedItems)
                    {
                        if (!this.Region.ActiveViews.Contains(item))
                        {
                            this.Region.Activate(item);
                        }
                    }
                }
            }

            private void OnActiveViewsChanged(object sender, NotifyCollectionChangedEventArgs e)
            {
                if (e.Action == NotifyCollectionChangedAction.Add)
                {
                    this.hostControl.SelectedItem = e.NewItems[0];
                }
                else if (e.Action == NotifyCollectionChangedAction.Remove
                    && this.hostControl.SelectedItem != null
                    && e.OldItems.Contains(this.hostControl.SelectedItem))
                {
                    this.hostControl.SelectedItem = null;
                }
            }
        }
    }
}

Registering adapter in bootstrapper:

protected override RegionAdapterMappings ConfigureRegionAdapterMappings()
{
    RegionAdapterMappings regionAdapterMappings = base.ConfigureRegionAdapterMappings();

    if (regionAdapterMappings != null)
    {
        regionAdapterMappings.RegisterMapping(typeof(RadTabControl), Container.Resolve<RadTabControlRegionAdapter>());
    }
    return regionAdapterMappings;
}

Adding views to a region:

public void Initialize()
{
    ...
    _regionManager.RegisterViewWithRegion("PrimaryContentRegion", typeof(HuntAvailabilityControl));
    _regionManager.RegisterViewWithRegion("PrimaryContentRegion", typeof(ApplicationAllocationIssuanceControl));
    ...

    foreach (var secondaryView in primaryDisplay.SecondaryViews)
    {
        _regionManager.RegisterViewWithRegion("SecondaryContentRegion",
            () => ResolveTogglableView(secondaryView)); // Exception is thrown here
        secondaryView.RibbonToggleButton =
            CreateRibbonButtonForView("SecondaryContentRegion", secondaryView);
        _regionManager.RegisterViewWithRegion("HomeRibbonLogsGroup",
            () => secondaryView.RibbonToggleButton);
    }
}

RadTabControl being used a region:

<telerik:RadLayoutControl Grid.Row="2" Orientation="Vertical" SelectedItem="{x:Null}">
    <telerik:LayoutControlGroup>
        <telerik:RadTabControl prism:RegionManager.RegionName="PrimaryContentRegion"
                               ScrollMode="Pixel" />
    </telerik:LayoutControlGroup>
</telerik:RadLayoutControl>

I have found that if I remove the line this.hostControl.ItemsSource = this.Region.Views; from my customer adapter, the exception isn't thrown, but my views are also obviously not in the control. Is this an issue with me using multiple regions, or something else? Thanks for any help.

c#
wpf
telerik
prism
asked on Stack Overflow Jan 16, 2018 by athompsonResDat • edited Jan 16, 2018 by athompsonResDat

0 Answers

Nobody has answered this question yet.


User contributions licensed under CC BY-SA 3.0