增加ViewModelBase 生命周期控制View
This commit is contained in:
1 parent
30a6a5ea24
commit
9b9e4cbd82
8 files changed
+163
-8
No files matched your search
@@ -32,7 +32,7 @@ public static class IDialogServiceExtension
|
||||
/// <summary>
|
||||
/// 确定取消对话框 ViewModel
|
||||
/// </summary>
|
||||
public class ConfirmDialogViewModel : BindableBase, IDialogAware
|
||||
public class ConfirmDialogViewModel : ViewModelBase, IDialogAware
|
||||
{
|
||||
public ConfirmDialogViewModel()
|
||||
{
|
||||
@@ -286,4 +286,4 @@ public abstract class ConfirmDialogViewModel<TParameter, TResult> : ConfirmDialo
|
||||
//public abstract class ConfirmDialogParameterViewModel : ConfirmDialogViewModel<ConfirmDialogParameterViewModel.Parameter>
|
||||
//{
|
||||
// public abstract class Parameter;
|
||||
//}
|
||||
//}
|
||||
@@ -0,0 +1,85 @@
|
||||
using Prism.Services.Dialogs;
|
||||
using System.ComponentModel;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Store.Management.Framework.Controls;
|
||||
|
||||
internal static class ViewLifecycle
|
||||
{
|
||||
// 卸载时 DataContext 可能已改变,记录实际收到 OnLoaded 的实例。
|
||||
private static readonly DependencyProperty LoadedViewModelProperty =
|
||||
DependencyProperty.RegisterAttached("LoadedViewModel", typeof(ViewModelBase), typeof(ViewLifecycle));
|
||||
|
||||
internal static void Register()
|
||||
{
|
||||
foreach (Type type in new[] { typeof(UserControl), typeof(Window) })
|
||||
{
|
||||
FrameworkElement.DataContextProperty.OverrideMetadata(type,
|
||||
new FrameworkPropertyMetadata(null, OnDataContextChanged));
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnDataContextChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (sender is Window window)
|
||||
{
|
||||
if (e.OldValue is ViewModelBase)
|
||||
{
|
||||
window.Activated -= OnActivated;
|
||||
window.Deactivated -= OnDeactivated;
|
||||
window.Closing -= OnClosing;
|
||||
window.Closed -= OnClosed;
|
||||
}
|
||||
if (e.NewValue is ViewModelBase)
|
||||
{
|
||||
window.Activated += OnActivated;
|
||||
window.Deactivated += OnDeactivated;
|
||||
window.Closing += OnClosing;
|
||||
window.Closed += OnClosed;
|
||||
}
|
||||
if (window is IDialogWindow) return; // Prism 弹窗由内容页接收加载与卸载。
|
||||
}
|
||||
var view = (FrameworkElement)sender;
|
||||
if (e.OldValue is ViewModelBase)
|
||||
{
|
||||
view.Loaded -= OnLoaded;
|
||||
view.Unloaded -= OnUnloaded;
|
||||
}
|
||||
if (e.NewValue is ViewModelBase)
|
||||
{
|
||||
view.Loaded += OnLoaded;
|
||||
view.Unloaded += OnUnloaded;
|
||||
}
|
||||
if (view.IsLoaded || view.GetValue(LoadedViewModelProperty) != null)
|
||||
UpdateViewModel(view);
|
||||
}
|
||||
|
||||
private static void OnLoaded(object sender, RoutedEventArgs e)
|
||||
=> UpdateViewModel((FrameworkElement)sender);
|
||||
|
||||
private static void OnActivated(object? sender, EventArgs e) => (((Window)sender!).DataContext as ViewModelBase)?.OnActivated();
|
||||
private static void OnDeactivated(object? sender, EventArgs e) => (((Window)sender!).DataContext as ViewModelBase)?.OnDeactivated();
|
||||
private static void OnClosing(object? sender, CancelEventArgs e) => (((Window)sender!).DataContext as ViewModelBase)?.OnClosing(e);
|
||||
private static void OnClosed(object? sender, EventArgs e) => (((Window)sender!).DataContext as ViewModelBase)?.OnClosed();
|
||||
|
||||
private static void UpdateViewModel(FrameworkElement view)
|
||||
{
|
||||
// 共享父级 DataContext 的页眉、导航栏等子控件不拥有页面生命周期。
|
||||
var source = DependencyPropertyHelper.GetValueSource(view, FrameworkElement.DataContextProperty);
|
||||
var next = source.BaseValueSource == BaseValueSource.Inherited ? null : view.DataContext as ViewModelBase;
|
||||
var previous = (ViewModelBase?)view.GetValue(LoadedViewModelProperty);
|
||||
if (ReferenceEquals(previous, next)) return;
|
||||
view.SetValue(LoadedViewModelProperty, next);
|
||||
previous?.OnUnloaded();
|
||||
next?.OnLoaded();
|
||||
}
|
||||
|
||||
private static void OnUnloaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var view = (FrameworkElement)sender;
|
||||
var previous = (ViewModelBase?)view.GetValue(LoadedViewModelProperty);
|
||||
view.ClearValue(LoadedViewModelProperty);
|
||||
previous?.OnUnloaded();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using Prism.Mvvm;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Store.Management.Framework.Controls;
|
||||
|
||||
public abstract class ViewModelBase : BindableBase
|
||||
{
|
||||
protected internal virtual void OnLoaded() { }
|
||||
protected internal virtual void OnUnloaded() { }
|
||||
protected internal virtual void OnActivated() { }
|
||||
protected internal virtual void OnDeactivated() { }
|
||||
protected internal virtual void OnClosing(CancelEventArgs e) { }
|
||||
protected internal virtual void OnClosed() { }
|
||||
}
|
||||
@@ -26,6 +26,8 @@ public abstract class FrameworkApplication : PrismApplication
|
||||
private IStatusBarService StatusBarService;
|
||||
private IMessageBoxService MessageBoxService;
|
||||
private static bool FatalShutdown = false;
|
||||
static FrameworkApplication() => Controls.ViewLifecycle.Register();
|
||||
|
||||
protected FrameworkApplication()
|
||||
{
|
||||
DispatcherUnhandledException += OnDispatcherUnhandledException;
|
||||
|
||||
@@ -2,12 +2,12 @@ using Prism.Commands;
|
||||
using MAX.Notification;
|
||||
using Store.Management.Notifications;
|
||||
using Prism.Ioc;
|
||||
using Prism.Mvvm;
|
||||
using Store.Management.Framework.Controls;
|
||||
using Prism.Services.Dialogs;
|
||||
using Store.Management.Services;
|
||||
using Store.Management.Views;
|
||||
namespace Store.Management.ViewModels;
|
||||
public sealed class FrameworkTestPageViewModel : BindableBase
|
||||
public sealed class FrameworkTestPageViewModel : ViewModelBase
|
||||
{
|
||||
private readonly IContainerProvider container;
|
||||
private readonly SingletonProbe singleton;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using Store.Management.Framework.Services;
|
||||
using Prism.Mvvm;
|
||||
using Store.Management.Framework.Controls;
|
||||
using Prism.Regions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -9,7 +9,7 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace Store.Management.ViewModels
|
||||
{
|
||||
partial class MainViewModel : BindableBase
|
||||
partial class MainViewModel : ViewModelBase
|
||||
{
|
||||
private readonly IRegionManager _regionManager;
|
||||
public IStatusBarService StatusBar { get; }
|
||||
|
||||
@@ -12,7 +12,7 @@ using System.Windows.Controls.Primitives;
|
||||
|
||||
namespace Store.Management.ViewModels;
|
||||
|
||||
public sealed class UserManagementPageViewModel(IUserService userService, IDialogService dialogs, IStatusBarService statusBar) : BindableBase
|
||||
public sealed class UserManagementPageViewModel(IUserService userService, IDialogService dialogs, IStatusBarService statusBar) : ViewModelBase
|
||||
{
|
||||
public const int KeywordLimit = 100;
|
||||
private string appliedKeyword = "";
|
||||
@@ -37,6 +37,10 @@ public sealed class UserManagementPageViewModel(IUserService userService, IDialo
|
||||
|
||||
public AsyncDelegateCommand LoginCommand => field ??= new(LoginAsync);
|
||||
|
||||
protected override void OnLoaded()
|
||||
{
|
||||
base.OnLoaded();
|
||||
}
|
||||
private async Task LoginAsync()
|
||||
{
|
||||
ButtonResult result = ButtonResult.None;
|
||||
|
||||
@@ -58,8 +58,46 @@ class Program
|
||||
Console.WriteLine("PASS: LoginViewModel -> ILoginService -> AuthApi, retry, password contract and cancellation.");
|
||||
return 0;
|
||||
}
|
||||
App app = args.Contains("--status-bar") ? new App() : new TestApp(client);
|
||||
App app = args.Contains("--status-bar") || args.Contains("--view-lifecycle") ? new App() : new TestApp(client);
|
||||
app.InitializeComponent();
|
||||
if (args.Contains("--view-lifecycle"))
|
||||
{
|
||||
app.ShutdownMode = ShutdownMode.OnExplicitShutdown;
|
||||
var pageVm = new LifecycleProbe();
|
||||
var windowVm = new LifecycleProbe();
|
||||
var page = new UserControl { DataContext = pageVm, Content = new UserControl() };
|
||||
var window = new Window { DataContext = windowVm, Content = page };
|
||||
window.Show();
|
||||
app.Dispatcher.Invoke(() => { }, DispatcherPriority.ApplicationIdle);
|
||||
Check(pageVm.Loads == 1 && windowVm.Loads == 1, $"窗口和页面各加载一次:{windowVm.Loads}/{pageVm.Loads}");
|
||||
window.Content = null;
|
||||
app.Dispatcher.Invoke(() => { }, DispatcherPriority.ApplicationIdle);
|
||||
Check(pageVm.Unloads == 1, "移出页面触发卸载");
|
||||
window.Content = page;
|
||||
app.Dispatcher.Invoke(() => { }, DispatcherPriority.ApplicationIdle);
|
||||
Check(pageVm.Loads == 2, "重新加入页面再次加载");
|
||||
var replacement = new LifecycleProbe();
|
||||
page.DataContext = replacement;
|
||||
Check(pageVm.Unloads == 2 && replacement.Loads == 1, "已加载的页面更换 ViewModel 后转移通知");
|
||||
windowVm.CancelClose = true;
|
||||
window.Close();
|
||||
Check(window.IsVisible && windowVm.Closes == 0, "ViewModel 可以取消关闭");
|
||||
windowVm.CancelClose = false;
|
||||
window.Close();
|
||||
Check(windowVm.Closes == 1, "窗口关闭后通知 ViewModel");
|
||||
var dialogVm = new LifecycleProbe();
|
||||
var dialog = new DialogWindow
|
||||
{
|
||||
DataContext = dialogVm,
|
||||
Content = new UserControl { DataContext = dialogVm }
|
||||
};
|
||||
dialog.Show();
|
||||
app.Dispatcher.Invoke(() => { }, DispatcherPriority.ApplicationIdle);
|
||||
Check(dialogVm.Loads == 1, "Prism 弹窗宿主和内容共享 ViewModel 时只加载一次");
|
||||
dialog.Close();
|
||||
Console.WriteLine("PASS: view and window lifecycle.");
|
||||
return 0;
|
||||
}
|
||||
int exitCode = 0;
|
||||
app.Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, new Action(async () =>
|
||||
{
|
||||
@@ -227,6 +265,18 @@ class Program
|
||||
static void Check(bool success, string message) { if (!success) throw new Exception(message); }
|
||||
}
|
||||
|
||||
sealed class LifecycleProbe : ViewModelBase
|
||||
{
|
||||
public int Loads { get; private set; }
|
||||
public int Unloads { get; private set; }
|
||||
public int Closes { get; private set; }
|
||||
public bool CancelClose { get; set; }
|
||||
protected override void OnLoaded() => Loads++;
|
||||
protected override void OnUnloaded() => Unloads++;
|
||||
protected override void OnClosing(System.ComponentModel.CancelEventArgs e) => e.Cancel = CancelClose;
|
||||
protected override void OnClosed() => Closes++;
|
||||
}
|
||||
|
||||
sealed class LoginHandler : HttpMessageHandler
|
||||
{
|
||||
public int RequestCount { get; private set; }
|
||||
|
||||
Reference in new issue
Block a user