mirror of
https://github.com/MaaAssistantArknights/MaaAssistantArknights.git
synced 2026-07-18 02:10:21 +08:00
rft: 将 PropertyDependsOnViewModel 改为静态工具类
This commit is contained in:
153
src/MaaWpfGui/Utilities/PropertyDependsOnHelper.cs
Normal file
153
src/MaaWpfGui/Utilities/PropertyDependsOnHelper.cs
Normal file
@@ -0,0 +1,153 @@
|
||||
// <copyright file="PropertyDependsOnHelper.cs" company="MaaAssistantArknights">
|
||||
// Part of the MaaWpfGui project, maintained by the MaaAssistantArknights team (Maa Team)
|
||||
// Copyright (C) 2021-2025 MaaAssistantArknights Contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License v3.0 only as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY
|
||||
// </copyright>
|
||||
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace MaaWpfGui.Utilities;
|
||||
|
||||
/// <summary>
|
||||
/// 提供对 PropertyDependsOnAttribute 的支持:当源属性改变时,自动通知依赖的属性。
|
||||
/// 这是一个静态工具类,可以被 ViewModel 和 Screen 使用。
|
||||
/// </summary>
|
||||
public static class PropertyDependsOnHelper
|
||||
{
|
||||
// 存储每个实例的属性依赖关系:使用 ConditionalWeakTable 避免内存泄漏
|
||||
private static readonly ConditionalWeakTable<object, Dictionary<string, List<string>>> _instanceDependencies = new();
|
||||
|
||||
// 存储每个实例的事件处理器,以便在需要时可以取消订阅
|
||||
private static readonly ConditionalWeakTable<object, PropertyChangedEventHandler> _instanceHandlers = new();
|
||||
|
||||
/// <summary>
|
||||
/// 扫描指定实例的属性,查找 PropertyDependsOnAttribute 并设置通知。
|
||||
/// 从派生类型的构造函数中调用此方法。
|
||||
/// </summary>
|
||||
/// <param name="instance">要实现属性依赖的实例(必须实现 INotifyPropertyChanged)</param>
|
||||
/// <exception cref="ArgumentNullException">当 instance 为 null 时抛出</exception>
|
||||
/// <exception cref="ArgumentException">当 instance 不实现 INotifyPropertyChanged 时抛出</exception>
|
||||
public static void InitializePropertyDependencies(object instance)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(instance);
|
||||
|
||||
if (instance is not INotifyPropertyChanged notifyPropertyChanged)
|
||||
{
|
||||
throw new ArgumentException("实例必须实现 INotifyPropertyChanged 接口", nameof(instance));
|
||||
}
|
||||
|
||||
var type = instance.GetType();
|
||||
var properties = type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
|
||||
|
||||
var propertyDependencies = new Dictionary<string, List<string>>(); // key: source property name, value: dependent property names
|
||||
|
||||
foreach (var property in properties)
|
||||
{
|
||||
var dependsOnAttributes = property.GetCustomAttributes<PropertyDependsOnAttribute>(true);
|
||||
foreach (var attribute in dependsOnAttributes.Where(i => i.PropertyNames.Length > 0))
|
||||
{
|
||||
foreach (var key in attribute.PropertyNames)
|
||||
{
|
||||
if (!propertyDependencies.TryGetValue(key, out var value))
|
||||
{
|
||||
value = [];
|
||||
propertyDependencies[key] = value;
|
||||
}
|
||||
|
||||
if (propertyDependencies.TryGetValue(property.Name, out var values) && values.Contains(key))
|
||||
{
|
||||
throw new ArgumentException($"属性 {key} 依赖于属性 {property.Name}, 但它们之间存在循环依赖关系");
|
||||
}
|
||||
|
||||
value.Add(property.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (propertyDependencies.Count > 0)
|
||||
{
|
||||
_instanceDependencies.Add(instance, propertyDependencies);
|
||||
|
||||
void handler(object? sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (sender != instance)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(e.PropertyName) || !propertyDependencies.TryGetValue(e.PropertyName, out var dependentProperties))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var dependentProperty in dependentProperties)
|
||||
{
|
||||
NotifyPropertyChange(instance, dependentProperty);
|
||||
}
|
||||
}
|
||||
|
||||
_instanceHandlers.Add(instance, handler);
|
||||
notifyPropertyChanged.PropertyChanged += handler;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取消指定实例的属性依赖初始化,移除事件订阅。
|
||||
/// </summary>
|
||||
/// <param name="instance">要取消初始化的实例</param>
|
||||
public static void UninitializePropertyDependencies(object instance)
|
||||
{
|
||||
if (instance == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (instance is INotifyPropertyChanged notifyPropertyChanged && _instanceHandlers.TryGetValue(instance, out var handler))
|
||||
{
|
||||
notifyPropertyChanged.PropertyChanged -= handler;
|
||||
_instanceHandlers.Remove(instance);
|
||||
}
|
||||
|
||||
_instanceDependencies.Remove(instance);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通知属性改变。尝试使用 NotifyOfPropertyChange 方法(Stylet),如果不存在则直接触发 PropertyChanged 事件。
|
||||
/// </summary>
|
||||
private static void NotifyPropertyChange(object instance, string propertyName)
|
||||
{
|
||||
// 首先尝试使用反射调用 NotifyOfPropertyChange 方法(Stylet 的 PropertyChangedBase 和 Screen 都有这个方法)
|
||||
var type = instance.GetType();
|
||||
var method = type.GetMethod("NotifyOfPropertyChange", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof(string) }, null);
|
||||
|
||||
if (method != null)
|
||||
{
|
||||
method.Invoke(instance, new object[] { propertyName });
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果没有 NotifyOfPropertyChange 方法,尝试直接触发 PropertyChanged 事件
|
||||
if (instance is INotifyPropertyChanged notifyPropertyChanged)
|
||||
{
|
||||
var eventField = type.GetField("PropertyChanged", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public);
|
||||
if (eventField?.GetValue(instance) is PropertyChangedEventHandler eventHandler)
|
||||
{
|
||||
eventHandler.Invoke(instance, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace MaaWpfGui.ViewModels
|
||||
/// <summary>
|
||||
/// Represents a grouped log card that contains several <see cref="LogItemViewModel"/>.
|
||||
/// </summary>
|
||||
public class LogCardViewModel : PropertyDependsOnViewModel
|
||||
public class LogCardViewModel : PropertyChangedBase
|
||||
{
|
||||
public ObservableCollection<LogItemViewModel> Items { get; } = new();
|
||||
|
||||
@@ -40,7 +40,7 @@ namespace MaaWpfGui.ViewModels
|
||||
|
||||
public LogCardViewModel()
|
||||
{
|
||||
InitializePropertyDependencies();
|
||||
PropertyDependsOnHelper.InitializePropertyDependencies(this);
|
||||
|
||||
// Keep StartTime/EndTime in sync when Items changes or an item's Time updates.
|
||||
Items.CollectionChanged += Items_CollectionChanged;
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace MaaWpfGui.ViewModels;
|
||||
/// <summary>
|
||||
/// The view model of log item.
|
||||
/// </summary>
|
||||
public class LogItemViewModel : PropertyDependsOnViewModel
|
||||
public class LogItemViewModel : PropertyChangedBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LogItemViewModel"/> class.
|
||||
@@ -38,7 +38,7 @@ public class LogItemViewModel : PropertyDependsOnViewModel
|
||||
/// <param name="toolTip">The toolTip</param>
|
||||
public LogItemViewModel(string content, string color = UiLogColor.Message, string weight = "Regular", string dateFormat = "", bool showTime = true, ToolTip? toolTip = null)
|
||||
{
|
||||
InitializePropertyDependencies();
|
||||
PropertyDependsOnHelper.InitializePropertyDependencies(this);
|
||||
|
||||
if (string.IsNullOrEmpty(dateFormat))
|
||||
{
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
// <copyright file="PropertyDependsOnViewModel.cs" company="MaaAssistantArknights">
|
||||
// Part of the MaaWpfGui project, maintained by the MaaAssistantArknights team (Maa Team)
|
||||
// Copyright (C) 2021-2025 MaaAssistantArknights Contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License v3.0 only as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY
|
||||
// </copyright>
|
||||
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using MaaWpfGui.Utilities;
|
||||
using Stylet;
|
||||
|
||||
namespace MaaWpfGui.ViewModels
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides support for PropertyDependsOnAttribute: when a source property changes,
|
||||
/// dependent properties are automatically notified.
|
||||
/// </summary>
|
||||
public abstract class PropertyDependsOnViewModel : PropertyChangedBase
|
||||
{
|
||||
private readonly Dictionary<string, List<string>> _propertyDependencies = []; // key: source property name, value: dependent property names
|
||||
|
||||
/// <summary>
|
||||
/// Scan for PropertyDependsOnAttribute on this instance's properties and wire notifications.
|
||||
/// Call this from the derived type's constructor.
|
||||
/// </summary>
|
||||
protected void InitializePropertyDependencies()
|
||||
{
|
||||
var type = GetType();
|
||||
var properties = type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
|
||||
|
||||
foreach (var property in properties)
|
||||
{
|
||||
var dependsOnAttributes = property.GetCustomAttributes<PropertyDependsOnAttribute>(true);
|
||||
foreach (var attribute in dependsOnAttributes.Where(i => i.PropertyNames.Length > 0))
|
||||
{
|
||||
foreach (var key in attribute.PropertyNames)
|
||||
{
|
||||
if (!_propertyDependencies.TryGetValue(key, out var value))
|
||||
{
|
||||
value = [];
|
||||
_propertyDependencies[key] = value;
|
||||
}
|
||||
|
||||
if (_propertyDependencies.TryGetValue(property.Name, out var values) && values.Contains(key))
|
||||
{
|
||||
throw new ArgumentException($"属性 {key} 依赖于属性 {property.Name}, 但它们之间存在循环依赖关系");
|
||||
}
|
||||
|
||||
value.Add(property.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PropertyChanged += OnPropertyChanged;
|
||||
}
|
||||
|
||||
private void OnPropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (string.IsNullOrEmpty(e.PropertyName) || !_propertyDependencies.TryGetValue(e.PropertyName, out var value))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var dependentProperty in value)
|
||||
{
|
||||
NotifyOfPropertyChange(dependentProperty);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,11 +29,11 @@ using Stylet;
|
||||
|
||||
namespace MaaWpfGui.ViewModels;
|
||||
|
||||
public abstract class TaskViewModel : PropertyDependsOnViewModel
|
||||
public abstract class TaskViewModel : PropertyChangedBase
|
||||
{
|
||||
protected TaskViewModel()
|
||||
{
|
||||
InitializePropertyDependencies();
|
||||
PropertyDependsOnHelper.InitializePropertyDependencies(this);
|
||||
}
|
||||
|
||||
protected T? GetTaskConfig<T>()
|
||||
|
||||
Reference in New Issue
Block a user