视图卸载时调用LoadedCommand

时间:2020-07-27 08:11:00

标签: c# wpf .net-core prism

.net Core 3.1 WPF应用程序中有一个行为,在显示视图之后,该行为在ViewModel中调用了一个命令。

public class LoadedBehavior
{
   public static DependencyProperty LoadedCommandProperty
      = DependencyProperty.RegisterAttached(
         "LoadedCommand",
         typeof(ICommand),
         typeof(LoadedBehavior),
         new PropertyMetadata(null, OnLoadedCommandChanged));

   private static void OnLoadedCommandChanged
      (DependencyObject depObj, DependencyPropertyChangedEventArgs e)
   {
      if (depObj is FrameworkElement frameworkElement && e.NewValue is ICommand)
      {
         frameworkElement.Loaded
            += (o, args) => { (e.NewValue as ICommand)?.Execute(null); };
      }
   }

   public static ICommand GetLoadedCommand(DependencyObject depObj)
   {
      return (ICommand)depObj.GetValue(LoadedCommandProperty);
   }

   public static void SetLoadedCommand(
      DependencyObject depObj,
      ICommand value)
   {
      depObj.SetValue(LoadedCommandProperty, value);
   }
}

此行为附在视图内:

behaviors:LoadedBehavior.LoadedCommand="{Binding LoadedCommand}"

我正在与Prisms RegionManager一起将我的视图注入视图内的特定区域。现在,当我尝试注入新视图时,将从旧视图中加载的命令再次调用。这似乎来自行为。

为了更好地理解,这也是调用代码以在特定区域内显示新视图的代码

public class NavigationService
{
   private readonly IServiceLocator _serviceLocator;
   private readonly IRegionManager _regionManager;

   public NavigationService(IServiceLocator serviceLocator, IRegionManager regionManager)
   {
      _serviceLocator = serviceLocator;
      _regionManager = regionManager;
   }

   public void Navigate(string regionName, object view)
   {
      RemoveAllViews(regionName);
      _regionManager.AddToRegion(regionName, view);
   }

   public void Navigate<T>(string regionName) where T : FrameworkElement
   {
      var view = _serviceLocator.GetInstance<T>();
      Navigate(regionName, view);
   }

   public void RemoveAllViews(string regionName)
   {
      _regionManager.Regions[regionName].RemoveAll();
   }
}

谁能告诉我,我在这里做错了什么?还是这种行为不可行?

编辑

发布此消息后,我立即发现了问题:多次调用了Loaded Command。这似乎是由于此视图的内容更改引起的。因此,每次我添加一个新视图时,父视图都将其称为已加载事件。是否只有在显示视图后才能运行命令?

1 个答案:

答案 0 :(得分:1)

Loaded事件对于仅在加载控件时一次意图触发动作是非常不可靠的。从Loaded的{​​{1}}事件的reference开始。

由于用户启动的系统主题更改,

已加载和已卸载都可能在控件上引发。主题更改会导致控件模板和所包含的可视化树无效,从而使整个控件卸载和重新加载。 因此,不能假定仅在通过导航到该页面的页面首次加载页面时发生加载。

在Prism中,您可以通过创建自定义区域行为来对导航进行操作。在您的示例中,一旦视图添加到区域中,您想在视图模型上执行命令。创建一个所有目标视图模型都实现的接口,并使用一个命令,该命令应在第一次显示视图时执行。

FrameworkElement

创建一个区域行为,当将视图添加到区域时,该行为将监视区域的public interface IInitializableViewModel { ICommand Initialize { get; } } 集合并执行一次命令。它将检查每个视图的数据上下文,如果实现了该接口,则该命令不为null,并且该命令可以执行。

Views

将Prism应用程序中的自定义区域行为添加到区域行为集合中。

public class InitializableDataContextRegionBehavior : RegionBehavior
{
   public const string BehaviorKey = nameof(InitializableDataContextRegionBehavior);

   protected override void OnAttach()
   {
      Region.Views.CollectionChanged += OnViewsCollectionChanged;
   }

   private void OnViewsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
   {
      if (e.Action == NotifyCollectionChangedAction.Add)
      {
         foreach (var frameworkElement in e.NewItems.OfType<FrameworkElement>())
         {
            if (frameworkElement.DataContext is IInitializableViewModel initializableViewModel &&
                initializableViewModel.Initialize != null &&
                initializableViewModel.Initialize.CanExecute(null))
            {
               initializableViewModel.Initialize.Execute(null);
            }
         }
      }
   }
}

将相应视图添加到任何区域时,每个视图模型上的命令将仅执行一次。出于演示目的,在这里使用界面更加容易,但是您还可以为命令创建一个附加属性,该属性附加到视图并绑定到视图模型。