There is an application with a set of periodically opening / closing tabs on the tape. The main view model contains information about the list of tabs, the selected tab and the command for creating new tabs:
public sealed class MainVm : ViewModel { // создает новую вкладку, добавляет ее в коллекцию и делает выбранной public ICommand OpenNewTabCommand { get; } public ObservableCollection<TabVm> Tabs { get; } public TabVm SelectedTab { get; set; } // PropertyChanged }
The tab view model provides the name and command to close the tab:
public sealed class TabVm : ViewModel { private bool isClosed; private void HandleClose() { Parent.SelectedTab = Parent.Tabs.FirstOrDefault(_ => _ != this); Parent.Tabs.Remove(this); isClosed = true; } private bool CanClose() { if (isClosed) { Trace.WriteLine($"{Name}: вызван CanClose после закрытия вкладки"); } return true; } public MainVm Parent { get; } public string Name { get; } public ICommand CloseCommand { get; } public TabVm(MainVm parent, string name) { Parent = parent; Name = name; CloseCommand = new RelayCommand(HandleClose, CanClose); } }
ICommand implementation:
public sealed class RelayCommand : ICommand { private readonly Action execute; private readonly Func<bool> canExecute; public RelayCommand(Action execute, Func<bool> canExecute) { this.execute = execute; this.canExecute = canExecute; } public event EventHandler CanExecuteChanged { add { CommandManager.RequerySuggested += value; } remove { CommandManager.RequerySuggested -= value; } } public bool CanExecute(object parameter) { return canExecute(); } public void Execute(object parameter) { execute(); } }
Main window view:
<RibbonWindow ...> <Ribbon ItemsSource="{Binding Tabs}" SelectedItem="{Binding SelectedTab}"> <Ribbon.ApplicationMenu> <RibbonApplicationMenu> <RibbonApplicationMenuItem Header="Новая вкладка" Command="{Binding OpenNewTabCommand}"/> </RibbonApplicationMenu> </Ribbon.ApplicationMenu> <Ribbon.TabHeaderTemplate> <DataTemplate> <StackPanel Orientation="Horizontal"> <TextBlock Text="{Binding Name}"/> <Button Content="Close" Command="{Binding CloseCommand}"/> </StackPanel> </DataTemplate> </Ribbon.TabHeaderTemplate> </Ribbon> </RibbonWindow>
If I create a new tab, and then close it, then in the Output there will be messages about calling CanClose for the already closed tab. All of this will continue until the garbage collector reaches the tab view model. This is probably due to the subscription to the RequerySuggested event of the CommandManager in the command.
Are there any suggestions for circumventing this situation?
One option is to make the command Disposable. Within the team itself, collect subscribers into the list, and in Dispose, unsubscribe. Not a good option, because you have to either pull Dispose through reflection automatically, or watch it all and release it manually.