I'm using MVVM to create an application which switches between multiple views. The views are instantiated through a ContentControl
like this:
<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>
Where the underlying ViewModel looks something like this:
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;
}
}
}
The problem is that in some of my Views, I need to subscribe to PropertyChanged
notifications on their respective ViewModels so that I can change things in my View that can't be bound easily. This results in a "memory leak" (insofar as a memory leak can exist in C#) because the ContentControl
creates a new View every time and it never gets cleaned up due to those event handlers still having references in the ViewModel.
I tried cleaning up all event subscribers to the ViewModel when the View changes, but quite apart from the fact that it results in view cleanup code inside the ViewModel, it also had unintended consequences, and made some of my functionality stop working.
Is there a way for me to tell my views to stop subscribing to events? Or, should I find a way to bind them (such as creating a custom control with DependencyProperties which can be bound).