【问题标题】:WPF Prism - Preventing Tab switching using IConfirmNavigationRequest not workingWPF Prism - 使用 IConfirmNavigationRequest 防止选项卡切换不起作用
【发布时间】:2012-11-16 07:37:11
【问题描述】:

当使用 RequestNavigate(即以编程方式)在视图/视图模型之间导航时,会按预期调用相应视图模型上的 IConfirmNavigationRequest 方法。但是,如果您通过单击选项卡来切换 TabControl 区域中的视图,则它不会调用这些方法。

这是预期和接受的行为吗?我能否实现棱镜行为来完成这项工作?

任何建议将不胜感激。

更新

我决定根据 Viktor 的反馈更彻底地解释这个问题。如果用户在屏幕上有未保存的编辑,我想阻止导航。切换标签恕我直言只是另一种导航方式。我希望 Prism 实现是一致的:以编程方式或其他方式导航应该具有相同的行为。

如果我要创建一个带有按钮的 ItemsControl,当单击时使用 RequestNavigate 进行导航(以有效地切换选项卡)它会起作用,但这不是问题的重点。

【问题讨论】:

  • 标签控件中的每个标签是否都有自己的区域?

标签: wpf prism


【解决方案1】:

我想我明白你的意思,我理解你为什么希望它调用 RequestNavigate 方法。

要回答您的问题,是的,这是设计使然,在切换选项卡时不应调用 RequestNavigate。 但是,您可以修改此行为以做您想做的事情。棱镜是开源的。您应该有源代码,您可以将项目添加到您的项目中,并轻松地逐步完成以下代码:

TabControlRegionAdapter - 使区域适应选项卡控件

public class TabControlRegionAdapter : RegionAdapterBase<TabControl>
    {
        /// <summary>
        /// <see cref="Style"/> to set to the created <see cref="TabItem"/>.
        /// </summary>
        public static readonly DependencyProperty ItemContainerStyleProperty =
            DependencyProperty.RegisterAttached("ItemContainerStyle", typeof(Style), typeof(TabControlRegionAdapter), null);

        /// <summary>
        /// Initializes a new instance of the <see cref="TabControlRegionAdapter"/> class.
        /// </summary>
        /// <param name="regionBehaviorFactory">The factory used to create the region behaviors to attach to the created regions.</param>
        public TabControlRegionAdapter(IRegionBehaviorFactory regionBehaviorFactory)
            : base(regionBehaviorFactory)
        {
        }

        /// <summary>
        /// Gets the <see cref="ItemContainerStyleProperty"/> property value.
        /// </summary>
        /// <param name="target">Target object of the attached property.</param>
        /// <returns>Value of the <see cref="ItemContainerStyleProperty"/> property.</returns>
        public static Style GetItemContainerStyle(DependencyObject target)
        {
            if (target == null) throw new ArgumentNullException("target");
            return (Style)target.GetValue(ItemContainerStyleProperty);
        }

        /// <summary>
        /// Sets the <see cref="ItemContainerStyleProperty"/> property value.
        /// </summary>
        /// <param name="target">Target object of the attached property.</param>
        /// <param name="value">Value to be set on the <see cref="ItemContainerStyleProperty"/> property.</param>
        public static void SetItemContainerStyle(DependencyObject target, Style value)
        {
            if (target == null) throw new ArgumentNullException("target");
            target.SetValue(ItemContainerStyleProperty, value);
        }

        /// <summary>
        /// Adapts a <see cref="TabControl"/> 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, TabControl regionTarget)
        {
            if (regionTarget == null) throw new ArgumentNullException("regionTarget");
            bool itemsSourceIsSet = regionTarget.ItemsSource != null;

            if (itemsSourceIsSet)
            {
                throw new InvalidOperationException(Resources.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="TabControl.SelectedItem"/> 
        /// and the <see cref="IRegion.ActiveViews"/> in sync.
        /// </remarks>
        protected override void AttachBehaviors(IRegion region, TabControl regionTarget)
        {
            if (region == null) throw new ArgumentNullException("region");
            base.AttachBehaviors(region, regionTarget);
            if (!region.Behaviors.ContainsKey(TabControlRegionSyncBehavior.BehaviorKey))
            {
                region.Behaviors.Add(TabControlRegionSyncBehavior.BehaviorKey, new TabControlRegionSyncBehavior { 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();
        }
    }

还有,TabControlRegionSyncBehavior。 这是您可以调用 RequestNavigate

 public class TabControlRegionSyncBehavior : RegionBehavior, IHostAwareRegionBehavior
    {
        ///<summary>
        /// The behavior key for this region sync behavior.
        ///</summary>
        public const string BehaviorKey = "TabControlRegionSyncBehavior";

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

        private TabControl hostControl;

        /// <summary>
        /// Gets or sets the <see cref="DependencyObject"/> that the <see cref="IRegion"/> is attached to.
        /// </summary>
        /// <value>A <see cref="DependencyObject"/> that the <see cref="IRegion"/> is attached to.
        /// This is usually a <see cref="FrameworkElement"/> that is part of the tree.</value>
        public DependencyObject HostControl
        {
            get
            {
                return this.hostControl;
            }

            set
            {
                TabControl newValue = value as TabControl;
                if (newValue == null)
                {
                    throw new InvalidOperationException(Resources.HostControlMustBeATabControl);
                }

                if (IsAttached)
                {
                    throw new InvalidOperationException(Resources.HostControlCannotBeSetAfterAttach);
                }

                this.hostControl = newValue;
            }
        }

        /// <summary>
        /// Override this method to perform the logic after the behavior has been attached.
        /// </summary>
        protected override void OnAttach()
        {
            if (this.hostControl == null)
            {
                throw new InvalidOperationException(Resources.HostControlCannotBeNull);
            }

            this.SynchronizeItems();

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

        /// <summary>
        /// Gets the item contained in the <see cref="TabItem"/>.
        /// </summary>
        /// <param name="tabItem">The container item.</param>
        /// <returns>The item contained in the <paramref name="tabItem"/> if it was generated automatically by the behavior; otherwise <paramref name="tabItem"/>.</returns>
        protected virtual object GetContainedItem(TabItem tabItem)
        {
            if (tabItem == null) throw new ArgumentNullException("tabItem");
            if ((bool)tabItem.GetValue(IsGeneratedProperty))
            {
                return tabItem.Content;
            }

            return tabItem;
        }

        /// <summary>
        /// Override to change how TabItem's are prepared for items.
        /// </summary>
        /// <param name="item">The item to wrap in a TabItem</param>
        /// <param name="parent">The parent <see cref="DependencyObject"/></param>
        /// <returns>A tab item that wraps the supplied <paramref name="item"/></returns>
        protected virtual TabItem PrepareContainerForItem(object item, DependencyObject parent)
        {
            TabItem container = item as TabItem;
            if (container == null)
            {
                object dataContext = GetDataContext(item);
                container = new TabItem();
                container.Content = item;
                container.Style = TabControlRegionAdapter.GetItemContainerStyle(parent);
                container.DataContext = dataContext; // To run with SL 2
                container.Header = dataContext; // To run with SL 3                  
                container.SetValue(IsGeneratedProperty, true);
            }

            return container;
        }

        /// <summary>
        /// Undoes the effects of the <see cref="PrepareContainerForItem"/> method.
        /// </summary>
        /// <param name="tabItem">The container element for the item.</param>
        protected virtual void ClearContainerForItem(TabItem tabItem)
        {
            if (tabItem == null) throw new ArgumentNullException("tabItem");
            if ((bool)tabItem.GetValue(IsGeneratedProperty))
            {
                tabItem.Content = null;
            }
        }

        /// <summary>
        /// Creates or identifies the element that is used to display the given item.
        /// </summary>
        /// <param name="item">The item to get the container for.</param>
        /// <param name="itemCollection">The parent's <see cref="ItemCollection"/>.</param>
        /// <returns>The element that is used to display the given item.</returns>
        protected virtual TabItem GetContainerForItem(object item, ItemCollection itemCollection)
        {
            if (itemCollection == null) throw new ArgumentNullException("itemCollection");
            TabItem container = item as TabItem;
            if (container != null && ((bool)container.GetValue(IsGeneratedProperty)) == false)
            {
                return container;
            }

            foreach (TabItem tabItem in itemCollection)
            {
                if ((bool)tabItem.GetValue(IsGeneratedProperty))
                {
                    if (tabItem.Content == item)
                    {
                        return tabItem;
                    }
                }
            }


            return null;
        }

        /// <summary>
        /// Return the appropriate data context.  If the item is a FrameworkElement it cannot be a data context in Silverlight, so we use its data context.
        /// Otherwise, we just us the item as the data context.
        /// </summary>
        private static object GetDataContext(object item)
        {
            FrameworkElement frameworkElement = item as FrameworkElement;
            return frameworkElement == null ? item : frameworkElement.DataContext;
        }

        private void SynchronizeItems()
        {
            List<object> existingItems = new List<object>();
            if (this.hostControl.Items.Count > 0)
            {
                // Control must be empty before "Binding" to a region
                foreach (object childItem in this.hostControl.Items)
                {
                    existingItems.Add(childItem);
                }
            }

            foreach (object view in this.Region.Views)
            {
                TabItem tabItem = this.PrepareContainerForItem(view, this.hostControl);
                this.hostControl.Items.Add(tabItem);
            }

            foreach (object existingItem in existingItems)
            {
                this.Region.Add(existingItem);
            }
        }

        private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            // e.OriginalSource == null, that's why we use sender.
            if (this.hostControl == sender)
            {
                foreach (TabItem tabItem in e.RemovedItems)
                {
                    object item = this.GetContainedItem(tabItem);

                    // 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 (TabItem tabItem in e.AddedItems)
                {
                    object item = this.GetContainedItem(tabItem);
                    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 = this.GetContainerForItem(e.NewItems[0], this.hostControl.Items);
            }
            else if (e.Action == NotifyCollectionChangedAction.Remove
                && this.hostControl.SelectedItem != null
                && e.OldItems.Contains(this.GetContainedItem((TabItem)this.hostControl.SelectedItem)))
            {
                this.hostControl.SelectedItem = null;
            }
        }

        private void OnViewsChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if (e.Action == NotifyCollectionChangedAction.Add)
            {
                int startingIndex = e.NewStartingIndex;
                foreach (object newItem in e.NewItems)
                {
                    TabItem tabItem = this.PrepareContainerForItem(newItem, this.hostControl);
                    this.hostControl.Items.Insert(startingIndex, tabItem);
                }
            }
            else if (e.Action == NotifyCollectionChangedAction.Remove)
            {
                foreach (object oldItem in e.OldItems)
                {
                    TabItem tabItem = this.GetContainerForItem(oldItem, this.hostControl.Items);
                    this.hostControl.Items.Remove(tabItem);
                    this.ClearContainerForItem(tabItem);
                }
            }
        }
    }

当然,您必须弄清楚在哪里调用 RequestNavigate,这样您才能真正取消 TabSelectionChanging。不幸的是,WPF 中不存在此事件。我会求助于Josh Smith How to Prevent a TabItem from changing推荐的技巧

【讨论】:

    【解决方案2】:

    我从您的问题中了解到,您希望切换选项卡调用 IConfirmNavigationRequest。当您从实现此接口的视图/viewModel 导航时,将调用此接口中的方法。

    但是,您在 TabControl 中切换选项卡时遇到的不是导航请求。 TabControl 中的所有视图都已经处理了导航操作,并且所有视图都已经在 TabControl(Your Region) 中。那么当你切换标签时你会做什么呢?您只在您的区域内激活视图。以前活动的视图被停用。

    我真的不知道你想完成什么。我无法想象我为什么要阻止某人切换标签。但是你可以通过使用 IActiveAware 接口来尝试。你可以从这个blog得到想法

    编辑

    1. 实现 OnDeactivate 以询问用户是否要在停用视图之前保存更改

    2. 实现 OnActivate 以调用 RequestNavigate 到现有的视图。你可以阅读关于在 Prism 中导航到现有视图documentation

    3. 禁用所有其他 tabItems 并在保存更改后再次启用它们(不好的方法)

    我真的不是专家,但我认为你没有更多选择了

    【讨论】:

    • 感谢您的回答。我已经更新了我的问题以包含有关用例的更多详细信息。我确实看过 IActiveAware,但它只提供激活的单向通知。因此,您无法使用该界面更改活动视图或阻止其更改。
    • 如果您想阻止用户激活其他 TabItem,您必须将所有 TabItems 的 TabItems 属性 IsEnabled 设置为 false!我不认为这是可取的。
    • @AndreLuus 使用按钮可以得到不同的行为,您可以在命令实现中调用 RequestNavigate 方法,从而可以利用 INavigationAware 和 IConfirmNavigationRequest。因为从 TabControl 中选择不同的 TabItem 您只是更改了您所在区域内的当前活动视图,所以您不能这样做。在博客文章中查看 IsActive 属性的实现。您可以实现 OnDeactivate 方法来询问用户是否要保存更改。
    猜你喜欢
    • 2014-08-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-08
    • 2020-04-19
    • 2013-04-10
    • 2018-05-14
    • 1970-01-01
    相关资源
    最近更新 更多