【问题标题】:How do I tell my Views when they should unsubscribe from events? [duplicate]我如何告诉我的视图何时应该取消订阅事件? [复制]
【发布时间】:2018-05-25 15:17:15
【问题描述】:

我正在使用 MVVM 创建一个在多个视图之间切换的应用程序。视图通过ContentControl 实例化,如下所示:

<ContentControl Name="DynamicViewControl" Content="{Binding }">
    <ContentControl.Resources>
         <DataTemplate x:Key="PageOneTemplate">
             <pages:PageOne DataContext="{Binding PageOneViewModel}"/>
         </DataTemplate>
         <DataTemplate x:Key="PageTwoTemplate">
             <pages:PageTwo DataContext="{Binding PageTwoViewModel}"/>
         </DataTemplate>
         <!-- And so on... -->
    </ContentControl.Resources>
    <ContentControl.Style>
        <Style TargetType="{x:Type ContentControl}">
            <Style.Triggers>
                 <DataTrigger Binding="{Binding CurrentPage}" Value="{x:Static model:Pages.PageOne}">
                       <Setter Property="ContentTemplate" Value="{StaticResource PageOneTemplate}"/>
                 </DataTrigger>
                 <DataTrigger Binding="{Binding CurrentPage}" Value="{x:Static model:Pages.PageTwo}">
                    <Setter Property="ContentTemplate" Value="{StaticResource PageTwoTemplate}"/>
                 </DataTrigger>           
                 <!-- And so on... -->     
            </Style.Triggers>
        </Style>
    </ContentControl.Style>
</ContentControl>

底层 ViewModel 如下所示:

public enum Pages {
    PageOne,
    PageTwo,
    // etc...
}

public class PageViewModel : ObservableObject {
    private Pages currentPage = Pages.PageOne;

    public PageViewModel() {
        PageOneViewModel= new PageOneViewModel(Model);
        PageTwoViewModel= new PageTwoViewModel(Model);
        // And so on...

        NavButtonCommand = new RelayCommand(NavButton);
        PreviousButtonCommand = new RelayCommand(PreviousButton);
    }

    public PageModel Model { get; } = new PageModel();

    /// <summary>Gets or sets the current page.</summary>
    public Pages CurrentPage {
        get => currentPage;
        set {
            currentPage = value;
            NotifyPropertyChanged();
        }
    }

    public DataSelectionViewModel PageOneViewModel { get; }

    public ProfileSelectionViewModel PageTwoViewModel { get; }

    public ICommand NavButtonCommand { get; }

    public ICommand PreviousButtonCommand { get; }

    // This isn't my actual page change logic, just some code with a 
    // similar effect to get my point across
    private void NavButton(object param) {
        int next = (int)CurrentPage + 1;
        if Enum.IsDefined(typeof(Pages), next) {
            CurrentPage = (Pages)next;
        }
    }

    private void PreviousButton(object param) {
        int previous = (int)CurrentPage - 1;
        if Enum.IsDefined(typeof(Pages), previous) {
            CurrentPage = (Pages)previous;
        }
    }
}

问题是,在我的一些视图中,我需要在它们各自的视图模型上订阅PropertyChanged 通知,以便我可以更改视图中无法轻松绑定的内容。这会导致“内存泄漏”(就 C# 中可能存在内存泄漏而言),因为 ContentControl 每次都会创建一个新视图,并且由于那些事件处理程序仍然在 ViewModel 中具有引用,因此它永远不会被清理。

我尝试在 View 更改时清理 ViewModel 的所有事件订阅者,但除了会导致 ViewModel 内的视图清理代码这一事实之外,它还会产生意想不到的后果,并使我的一些功能停止工作。

有没有办法让我的观点停止订阅事件?或者,我是否应该找到一种方法来绑定它们(例如创建一个可以绑定的具有 DependencyProperties 的自定义控件)。

【问题讨论】:

  • 您的视图模型也订阅了哪些事件?
  • 视图正在订阅视图模型上的PropertyChanged 通知。
  • 我并不一定认为仅仅因为一个问题具有相同的答案就认为它是同一个问题,但是它可能会帮助未来的访问者参考类似的问题。

标签: c# wpf mvvm


【解决方案1】:

我找到了答案,比我想象的要快。大多数 WPF 控件执行它的方式是Weak Event Pattern。此模式允许您订阅具有弱引用的事件。解决方案是像这样更改行:

model.PropertyChanged += Model_PropertyChanged;

类似这样的东西:

PropertyChangedEventManager.AddHandler(model, Model_PropertyChanged, "MyProperty");

这样,即使 ViewModel 的生命周期比视图长,任何引用都只会是弱引用,从而允许垃圾收集器出现并清理对象,即使它的事件订阅还没有被清理。

【讨论】:

猜你喜欢
  • 1970-01-01
  • 2015-03-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-08-03
  • 2019-10-25
  • 1970-01-01
  • 2021-03-03
相关资源
最近更新 更多