diff --git a/src/MaaWpfGui/Utilities/PropertyDependsOnHelper.cs b/src/MaaWpfGui/Utilities/PropertyDependsOnHelper.cs
new file mode 100644
index 0000000000..5ae7099ad5
--- /dev/null
+++ b/src/MaaWpfGui/Utilities/PropertyDependsOnHelper.cs
@@ -0,0 +1,153 @@
+//
+// 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
+//
+
+#nullable enable
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+
+namespace MaaWpfGui.Utilities;
+
+///
+/// 提供对 PropertyDependsOnAttribute 的支持:当源属性改变时,自动通知依赖的属性。
+/// 这是一个静态工具类,可以被 ViewModel 和 Screen 使用。
+///
+public static class PropertyDependsOnHelper
+{
+ // 存储每个实例的属性依赖关系:使用 ConditionalWeakTable 避免内存泄漏
+ private static readonly ConditionalWeakTable>> _instanceDependencies = new();
+
+ // 存储每个实例的事件处理器,以便在需要时可以取消订阅
+ private static readonly ConditionalWeakTable _instanceHandlers = new();
+
+ ///
+ /// 扫描指定实例的属性,查找 PropertyDependsOnAttribute 并设置通知。
+ /// 从派生类型的构造函数中调用此方法。
+ ///
+ /// 要实现属性依赖的实例(必须实现 INotifyPropertyChanged)
+ /// 当 instance 为 null 时抛出
+ /// 当 instance 不实现 INotifyPropertyChanged 时抛出
+ 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>(); // key: source property name, value: dependent property names
+
+ foreach (var property in properties)
+ {
+ var dependsOnAttributes = property.GetCustomAttributes(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;
+ }
+ }
+
+ ///
+ /// 取消指定实例的属性依赖初始化,移除事件订阅。
+ ///
+ /// 要取消初始化的实例
+ 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);
+ }
+
+ ///
+ /// 通知属性改变。尝试使用 NotifyOfPropertyChange 方法(Stylet),如果不存在则直接触发 PropertyChanged 事件。
+ ///
+ 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));
+ }
+ }
+ }
+}
+
diff --git a/src/MaaWpfGui/ViewModels/LogCardViewModel.cs b/src/MaaWpfGui/ViewModels/LogCardViewModel.cs
index 688896e499..45903b06ab 100644
--- a/src/MaaWpfGui/ViewModels/LogCardViewModel.cs
+++ b/src/MaaWpfGui/ViewModels/LogCardViewModel.cs
@@ -23,7 +23,7 @@ namespace MaaWpfGui.ViewModels
///
/// Represents a grouped log card that contains several .
///
- public class LogCardViewModel : PropertyDependsOnViewModel
+ public class LogCardViewModel : PropertyChangedBase
{
public ObservableCollection 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;
diff --git a/src/MaaWpfGui/ViewModels/LogItemViewModel.cs b/src/MaaWpfGui/ViewModels/LogItemViewModel.cs
index 1b5645d4f0..7c017bf45b 100644
--- a/src/MaaWpfGui/ViewModels/LogItemViewModel.cs
+++ b/src/MaaWpfGui/ViewModels/LogItemViewModel.cs
@@ -25,7 +25,7 @@ namespace MaaWpfGui.ViewModels;
///
/// The view model of log item.
///
-public class LogItemViewModel : PropertyDependsOnViewModel
+public class LogItemViewModel : PropertyChangedBase
{
///
/// Initializes a new instance of the class.
@@ -38,7 +38,7 @@ public class LogItemViewModel : PropertyDependsOnViewModel
/// The toolTip
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))
{
diff --git a/src/MaaWpfGui/ViewModels/PropertyDependsOnViewModel.cs b/src/MaaWpfGui/ViewModels/PropertyDependsOnViewModel.cs
deleted file mode 100644
index 13d9b977ae..0000000000
--- a/src/MaaWpfGui/ViewModels/PropertyDependsOnViewModel.cs
+++ /dev/null
@@ -1,81 +0,0 @@
-//
-// 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
-//
-
-#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
-{
- ///
- /// Provides support for PropertyDependsOnAttribute: when a source property changes,
- /// dependent properties are automatically notified.
- ///
- public abstract class PropertyDependsOnViewModel : PropertyChangedBase
- {
- private readonly Dictionary> _propertyDependencies = []; // key: source property name, value: dependent property names
-
- ///
- /// Scan for PropertyDependsOnAttribute on this instance's properties and wire notifications.
- /// Call this from the derived type's constructor.
- ///
- 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(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);
- }
- }
- }
-}
diff --git a/src/MaaWpfGui/ViewModels/TaskViewModel.cs b/src/MaaWpfGui/ViewModels/TaskViewModel.cs
index 55b4af0f4c..70ac052173 100644
--- a/src/MaaWpfGui/ViewModels/TaskViewModel.cs
+++ b/src/MaaWpfGui/ViewModels/TaskViewModel.cs
@@ -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()