gpt4 book ai didi

WPF Prism - 使用 IConfirmNavigationRequest 防止选项卡切换不起作用

转载 作者:行者123 更新时间:2023-12-01 09:34:09 25 4
gpt4 key购买 nike

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

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

如有任何建议,我们将不胜感激。

更新

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

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

最佳答案

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

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

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 推荐的技巧

关于WPF Prism - 使用 IConfirmNavigationRequest 防止选项卡切换不起作用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13412315/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com