在WPF / Prism

时间:2017-09-05 11:20:01

标签: c# wpf mvvm ninject prism

我的大多数视图模型都在WPF项目上使用Prism的EventAggregator订阅了一个公共事件。基本上,声音命令在视图上触发此事件,并且作为响应,视图将向文本到语音模块发布包含其特定消息的另一事件。 但是,当我实现这一点时,我意识到当使用RegionManager的RequestNavigate切换到另一个视图时,之前的视图模型仍然以某种方式处于活动状态。当我触发最近视图的公共事件时,也会触发上一个视图。

简化示例:

  1. 从View 1
  2. 开始
  3. 触发常见事件
  4. 回复:来自View 1的消息
  5. RequestNavigate to View 2
  6. 触发常见事件
  7. 响应:来自View 2的消息,然后来自View 1的消息
  8. RequestNavigate to View 3
  9. 触发常见事件
  10. 响应:来自View 3,然后是View 2,然后是View 1
  11. 的消息
  12. 我在View 1,View 2和View 3的常见事件上放置了断点,每次从视图中收到消息时,都会触发其断点。

    我想要的是简单的:当我切换视图时,我不希望以前的ViewModel(也可能是View)仍然以某种方式处于活动状态。更好的是他们被垃圾收集,因为我也有一些奇怪的情况,通过再次导航到View 1,View 2和View 1,View 1的消息被发送两次(并且它的断点也被击中两次),所以我甚至不确定是否创建了多个ViewModel引用,这可能会导致内存泄漏。

    我尝试通过创建另一个仅包含基本要素的项目来重现此行为,因此这里是代码。我使用Visual Studio 2017和.net framework 4.5.2和Ninject。

    Shell.xaml

    <Window x:Class="PrismTest.Shell"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:prsm="http://prismlibrary.com/"
            mc:Ignorable="d">
        <Grid>
            <ContentControl Name="MainRegion" prsm:RegionManager.RegionName="MainRegion" />
        </Grid>
    </Window>
    

    NinjectPrismBootstrapper.cs

    public class NinjectPrismBootstrapper : NinjectBootstrapper
        {
            protected override void InitializeModules()
            {
                base.InitializeModules();
    
                // Text to speech
                Kernel.Bind<SpeechSynthesizer>().ToSelf().InSingletonScope();
                Kernel.Bind<INarrator>().To<StandardNarrator>().InSingletonScope();
                Kernel.Bind<INarratorEventManager>().To<NarratorEventManager>().InSingletonScope();
    
                // View models
                Kernel.Bind<MainPageViewModel>().ToSelf();
                Kernel.Bind<SecondPageViewModel>().ToSelf();
    
                // Views
                Kernel.Bind<object>().To<MainPageView>().InTransientScope().Named(typeof(MainPageView).Name);
                Kernel.Bind<object>().To<SecondPageView>().InTransientScope().Named(typeof(SecondPageView).Name);
    
                Kernel.Bind<Shell>().ToSelf();
    
                var narratorEventManager = Kernel.Get<INarratorEventManager>();
    
                var regionManager = Kernel.Get<IRegionManager>();
                regionManager.RegisterViewWithRegion("MainRegion", typeof(MainPageView));
            }
    
            protected override DependencyObject CreateShell()
            {
                return (Shell)Kernel.GetService(typeof(Shell));
            }
    
            protected override void InitializeShell()
            {
                base.InitializeShell();
                Application.Current.MainWindow = (Shell)this.Shell;
                Application.Current.MainWindow.Show();
            }
        }
    

    MainPageView.xaml(我的起始页)

    <UserControl x:Class="PrismTest.Views.MainPageView"
                 namespaces...>
        <StackPanel>
            <TextBlock Text="Main page"/>
            <Button Content="Narrator speaks" Command="{Binding Path=NarratorSpeaksCommand}" />
            <Button Content="Next page" Command="{Binding Path=GoToNextPageCommand}"/>
        </StackPanel>
    </UserControl>
    

    MainPageView.xaml.cs

    public partial class MainPageView : UserControl
        {
            public MainPageView(MainPageViewModel dataContext)
            {
                InitializeComponent();
    
                this.DataContext = dataContext;
            }
        }
    

    MainPageViewModel(MainPageView的视图模型)

    public class MainPageViewModel : BindableBase, IRegionMemberLifetime, INavigationAware
        {
            private readonly IEventAggregator _eventAggregator;
            private readonly IRegionManager _regionManager;
    
            public DelegateCommand GoToNextPageCommand { get; private set; }
            public DelegateCommand NarratorSpeaksCommand { get; private set; }
    
            public MainPageViewModel(IEventAggregator eventAggregator, IRegionManager regionManager)
            {
                _eventAggregator = eventAggregator;
                _regionManager = regionManager;
    
                ConfigureCommands();
    
                //The original common event triggered by a vocal command is simulated in this project by simply clicking on a button
                _eventAggregator.GetEvent<CommonEventToAllViews>().Subscribe(NarratorSpeaks);
            }
    
            private void ConfigureCommands()
            {
                GoToNextPageCommand = new DelegateCommand(GoToNextPage);
                NarratorSpeaksCommand = new DelegateCommand(ClickPressed);
            }
    
            private void GoToNextPage()
            {
                _regionManager.RequestNavigate("MainRegion", new Uri("SecondPageView", UriKind.Relative));
            }
    
            private void ClickPressed()
            {
                _eventAggregator.GetEvent<CommonEventToAllViews>().Publish();
            }
    
            private void NarratorSpeaks()
            {
                _eventAggregator.GetEvent<NarratorSpeaksEvent>().Publish("Main page");
            }
        }
    

    我不需要为SecondPageViewModel和SecondPageView设置代码,因为除了RequestNavigate将用户发送回MainPageView并且其NarratorSpeaks方法发送不同的字符串之外,它是完全相同的代码。

    我尝试了什么:

    1)使MainPageViewModel和SecondPageViewModel继承IRegionMemberLifetime并将KeepAlive设置为false

    2)继承INavigationAware并在IsNavigationTarget方法中返回false

    3)将其添加到INAVigationAware的OnNavigatedFrom方法中:

    public void OnNavigatedFrom(NavigationContext navigationContext)
            {
                var region = _regionManager.Regions["MainRegion"];
                var view = region.Views.Single(v => v.GetType().Name == "MainPageView");
                region.Deactivate(view);
            }
    

    值得注意的是:即使没有停用部分,如果我在var region = _regionManager.Regions之后放置一个断点[&#34; MainRegion&#34;];并检查region.views,无论我切换多少视图,只有一个结果。

    当我来回切换视图时,没有任何效果,事件会在之前的视图中被触发。 所以,我在这里不知所措。我不确定这是否是我在Ninject中注册视图和ViewModel的方式触发了这个或其他什么,但如果有人有建议,我很乐意接受它。

    谢谢!

1 个答案:

答案 0 :(得分:1)

过去我遇到过类似的问题。您是否考虑过导航时取消订阅活动?