View model disposal is now done via a lambda method with captured variables rather than by tagging the view

This commit is contained in:
Markus Ewald 2025-06-19 14:02:02 +02:00
parent 07be49c952
commit f6457b1909
3 changed files with 39 additions and 39 deletions

View File

@ -57,9 +57,8 @@ static void Main() {
Application.EnableVisualStyles(); Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false); Application.SetCompatibleTextRenderingDefault(false);
using(var windowManager = new WindowManager()) { var windowManager = new WindowManager();
Application.Run(windowManager.OpenRoot<MainViewModel>()); Application.Run(windowManager.OpenRoot<MainViewModel>());
}
} }
``` ```

View File

@ -21,6 +21,10 @@ using System;
using System.ComponentModel; using System.ComponentModel;
using System.Windows.Forms; using System.Windows.Forms;
#if NET6_0_OR_GREATER
using System.Runtime.Versioning;
#endif
using Nuclex.Support; using Nuclex.Support;
namespace Nuclex.Windows.Forms.Views { namespace Nuclex.Windows.Forms.Views {
@ -28,6 +32,9 @@ namespace Nuclex.Windows.Forms.Views {
/// <summary> /// <summary>
/// Base class for MVVM user controls that act as views connected to a view model /// Base class for MVVM user controls that act as views connected to a view model
/// </summary> /// </summary>
#if NET6_0_OR_GREATER
[SupportedOSPlatform("windows")]
#endif
public class ViewControl : UserControl, IView { public class ViewControl : UserControl, IView {
/// <summary>Initializes a new view control</summary> /// <summary>Initializes a new view control</summary>

View File

@ -29,6 +29,7 @@ using System.Runtime.Versioning;
using Nuclex.Support; using Nuclex.Support;
using Nuclex.Windows.Forms.AutoBinding; using Nuclex.Windows.Forms.AutoBinding;
using Nuclex.Windows.Forms.Views; using Nuclex.Windows.Forms.Views;
using static System.Windows.Forms.VisualStyles.VisualStyleElement;
namespace Nuclex.Windows.Forms { namespace Nuclex.Windows.Forms {
@ -125,11 +126,10 @@ namespace Nuclex.Windows.Forms {
window.Closed += this.rootWindowClosedDelegate; window.Closed += this.rootWindowClosedDelegate;
// If we either created the view model or the user explicitly asked us to // If we either created the view model or the user explicitly asked us to
// dispose his view model, tag the window so that we know to dispose it // dispose his view model, prepare the window so that it will dispose its
// when we're done (but still allow the user to change his mind) // view model when the window is done.
if((viewModel == null) || disposeOnClose) { if((viewModel == null) || disposeOnClose) {
window.Tag = "DisposeViewModelOnClose"; // TODO: Wrap SetProp() instead? setupViewModelDisposal(window);
//window.SetValue(DisposeViewModelOnCloseProperty, true);
} }
window.Show(); window.Show();
@ -158,11 +158,10 @@ namespace Nuclex.Windows.Forms {
try { try {
// If we either created the view model or the user explicitly asked us to // If we either created the view model or the user explicitly asked us to
// dispose his view model, tag the window so that we know to dispose it // dispose his view model, prepare the window so that it will dispose its
// when we're done (but still allow the user to change his mind) // view model when the window is done.
if((viewModel == null) || disposeOnClose) { if((viewModel == null) || disposeOnClose) {
window.Tag = "DisposeViewModelOnClose"; // TODO: Wrap SetProp() instead? setupViewModelDisposal(window);
//window.SetValue(DisposeViewModelOnCloseProperty, true);
} }
DialogResult result = window.ShowDialog(this.activeWindow); DialogResult result = window.ShowDialog(this.activeWindow);
@ -178,14 +177,6 @@ namespace Nuclex.Windows.Forms {
window.Activated -= this.rootWindowActivatedDelegate; window.Activated -= this.rootWindowActivatedDelegate;
ActiveWindow = window.Owner; ActiveWindow = window.Owner;
if(shouldDisposeViewModelOnClose(window)) {
IView windowAsView = window as IView;
if(windowAsView != null) {
object viewModelAsObject = windowAsView.DataContext;
windowAsView.DataContext = null;
disposeIfDisposable(viewModelAsObject);
}
}
disposeIfDisposable(window); disposeIfDisposable(window);
} }
} }
@ -331,6 +322,10 @@ namespace Nuclex.Windows.Forms {
return Activator.CreateInstance(type); return Activator.CreateInstance(type);
} }
protected virtual object CreateServiceScope() {
return null;
}
/// <summary>Called when one of the application's root windows is closed</summary> /// <summary>Called when one of the application's root windows is closed</summary>
/// <param name="sender">Window that has been closed</param> /// <param name="sender">Window that has been closed</param>
/// <param name="arguments">Not used</param> /// <param name="arguments">Not used</param>
@ -339,20 +334,11 @@ namespace Nuclex.Windows.Forms {
closedWindow.Closed -= this.rootWindowClosedDelegate; closedWindow.Closed -= this.rootWindowClosedDelegate;
closedWindow.Activated -= this.rootWindowActivatedDelegate; closedWindow.Activated -= this.rootWindowActivatedDelegate;
// If the view model was created just for this view or if the user asked us
// to dispose of his view model, do so now.
if(shouldDisposeViewModelOnClose(closedWindow)) {
IView windowAsView = closedWindow as IView;
if(windowAsView != null) {
object viewModelAsObject = windowAsView.DataContext;
windowAsView.DataContext = null;
disposeIfDisposable(viewModelAsObject);
}
}
lock(this) { lock(this) {
ActiveWindow = null; ActiveWindow = null;
} }
//disposeIfDisposable(closedWindow);
} }
/// <summary>Called when one of the application's root windows is activated</summary> /// <summary>Called when one of the application's root windows is activated</summary>
@ -402,18 +388,26 @@ namespace Nuclex.Windows.Forms {
} }
} }
/// <summary>Determines if the view owns the view model</summary> /// <summary>Attaches a view model disposer to a control</summary>
/// <param name="view">View that will be checked for ownership</param> /// <param name="control">
/// <returns>True if the view owns the view model</returns> /// Control whose view model will be disposed when it is itself disposed
private static bool shouldDisposeViewModelOnClose(Control view) { /// </param>
string tagAsString = view.Tag as string; private void setupViewModelDisposal(Control control) {
if(tagAsString != null) { IView controlAsView = control as IView;
return tagAsString.Contains("DisposeViewModelOnClose"); if(controlAsView != null) {
} else { IDisposable disposableViewModel = controlAsView.DataContext as IDisposable;
return false; if(disposableViewModel != null) {
control.Disposed += delegate(object sender, EventArgs arguments) {
disposableViewModel.Dispose();
controlAsView.DataContext = null;
};
} }
} }
//window.Tag = "DisposeViewModelOnClose"; // TODO: Wrap SetProp() instead?
//window.SetValue(DisposeViewModelOnCloseProperty, true);
}
/// <summary>Filters a list of types to contain only those in a specific namespace</summary> /// <summary>Filters a list of types to contain only those in a specific namespace</summary>
/// <param name="exportedTypes">List of exported types that will be filtered</param> /// <param name="exportedTypes">List of exported types that will be filtered</param>
/// <param name="filteredNamespace"> /// <param name="filteredNamespace">