From 49619149195ff7533464e9c3a0ffd03080044bbe Mon Sep 17 00:00:00 2001 From: ShrlAlgo Date: Mon, 23 Feb 2026 19:14:40 +0800 Subject: [PATCH] =?UTF-8?q?=E5=91=BD=E4=BB=A4=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Melskin/Controls/MultiComboBox.xaml.cs | 14 ++-- Melskin/Controls/SearchableComboBox.cs | 14 ++-- Melskin/Utilities/RelayCommand.cs | 90 ++++---------------------- Melskin/Utilities/RelayCommand{T}.cs | 82 +++++++++++++++++++++++ 4 files changed, 109 insertions(+), 91 deletions(-) create mode 100644 Melskin/Utilities/RelayCommand{T}.cs diff --git a/Melskin/Controls/MultiComboBox.xaml.cs b/Melskin/Controls/MultiComboBox.xaml.cs index 4c732ad..77bea31 100644 --- a/Melskin/Controls/MultiComboBox.xaml.cs +++ b/Melskin/Controls/MultiComboBox.xaml.cs @@ -355,8 +355,7 @@ public class MultiComboBox : Control { base.OnApplyTemplate(); - if (listBox != null) - listBox.SelectionChanged -= ListBox_SelectionChanged; + listBox?.SelectionChanged -= ListBox_SelectionChanged; listBox = GetTemplateChild("PART_ListBox") as ListBox; filterTextBox = GetTemplateChild("PART_FilterTextBox") as TextBox; @@ -564,8 +563,7 @@ public class MultiComboBox : Control private static void OnSelectionModeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var c = (MultiComboBox)d; - if (c.listBox != null) - c.listBox.SelectionMode = c.SelectionMode; + c.listBox?.SelectionMode = c.SelectionMode; c.isUpdatingSelection = true; try @@ -620,7 +618,7 @@ public class MultiComboBox : Control // 1. 移除 ListBox 中有但源中没有的 (倒序遍历以安全删除) for (int i = controlList.Count - 1; i >= 0; i--) { - if (!sourceSet.Contains(controlList[i])) + if (!sourceSet.Contains(controlList[i]!)) { controlList.RemoveAt(i); } @@ -763,13 +761,13 @@ public class MultiComboBox : Control } // 处理第一个元素 - if (IsMatch(firstItem, filter!, propDesc)) filtered.Add(firstItem); + if (IsMatch(firstItem, filter!, propDesc)) filtered.Add(firstItem!); } // 处理剩余元素 while (enumerator.MoveNext()) { - if (IsMatch(enumerator.Current, filter, propDesc)) + if (IsMatch(enumerator.Current, filter!, propDesc)) filtered.Add(enumerator.Current); } @@ -791,6 +789,6 @@ public class MultiComboBox : Control value = item.ToString(); } - return value != null && value.IndexOf(filter, StringComparison.OrdinalIgnoreCase) >= 0; + return value != null && value.Contains(filter); } } \ No newline at end of file diff --git a/Melskin/Controls/SearchableComboBox.cs b/Melskin/Controls/SearchableComboBox.cs index 0852f0b..f0f3de4 100644 --- a/Melskin/Controls/SearchableComboBox.cs +++ b/Melskin/Controls/SearchableComboBox.cs @@ -13,7 +13,7 @@ namespace Melskin.Controls /// public class SearchableComboBox : ComboBox { - private TextBox editableTextBox; + private TextBox? editableTextBox; private bool isInternalOperation; static SearchableComboBox() @@ -31,11 +31,13 @@ namespace Melskin.Controls public override void OnApplyTemplate() { base.OnApplyTemplate(); - if (GetTemplateChild("PART_EditableTextBox") is TextBox _editableTextBox) + editableTextBox = GetTemplateChild("PART_EditableTextBox") as TextBox; + + if (editableTextBox != null) { - _editableTextBox.TextChanged += OnEditableTextBoxTextChanged; + editableTextBox.TextChanged += OnEditableTextBoxTextChanged; // 点击时全选 - _editableTextBox.GotFocus += (s, e) => _editableTextBox.SelectAll(); + editableTextBox.GotFocus += (s, e) => editableTextBox.SelectAll(); } } @@ -54,7 +56,7 @@ namespace Melskin.Controls private void OnEditableTextBoxTextChanged(object sender, TextChangedEventArgs e) { if (isInternalOperation) return; - + if (editableTextBox == null) return; string searchText = editableTextBox.Text; string selectedText = GetItemDisplayText(SelectedItem); @@ -75,7 +77,7 @@ namespace Melskin.Controls string itemText = GetItemDisplayText(item); // 只要包含关键字就显示(忽略大小写) - return itemText.IndexOf(searchText, StringComparison.OrdinalIgnoreCase) >= 0; + return itemText.Contains(searchText); }; // 自动打开下拉框 diff --git a/Melskin/Utilities/RelayCommand.cs b/Melskin/Utilities/RelayCommand.cs index 7d4652d..2c94dc3 100644 --- a/Melskin/Utilities/RelayCommand.cs +++ b/Melskin/Utilities/RelayCommand.cs @@ -7,10 +7,10 @@ namespace Melskin.Utilities; internal class RelayCommand : ICommand { private readonly Action execute; - private readonly Predicate? _canExecute; + private readonly Predicate? canExecute; /// /// 当命令的可执行状态更改时发生的事件。 - /// 此事件通知UI,当命令的CanExecute方法返回值发生变化时,需要重新查询命令的状态。 + /// 广播此事件通知UI,当命令的CanExecute方法返回值发生变化时,需要重新查询命令的状态。 /// public event EventHandler? CanExecuteChanged { @@ -18,16 +18,23 @@ internal class RelayCommand : ICommand remove => CommandManager.RequerySuggested -= value; } /// - /// 一个简单的ICommand实现,用于传递动作。此命令始终可以执行。 + /// 初始化 RelayCommand 的新实例。 /// - public RelayCommand(Action execute) => this.execute = execute; + /// 命令执行的逻辑。 + /// 判断命令是否可以执行的逻辑(可选)。如果为 null,则命令始终可执行。 + /// execute 为 null 时抛出。 + public RelayCommand(Action execute, Predicate? canExecute = null) + { + this.execute = execute ?? throw new ArgumentNullException(nameof(execute)); + this.canExecute = canExecute; + } /// /// 判断命令是否可以执行。 /// /// 传递给命令的参数。 /// 返回一个布尔值,指示命令是否能够执行。对于此实现,总是返回true。 - public bool CanExecute(object? parameter) => _canExecute == null || _canExecute(parameter); + public bool CanExecute(object? parameter) => canExecute == null || canExecute(parameter); /// /// 执行命令。 @@ -36,79 +43,8 @@ internal class RelayCommand : ICommand public void Execute(object? parameter) => execute(parameter); /// - /// 通知命令管理器重新查询此命令的 CanExecute 状态。 + /// 通知命令管理器重新查询此命令的 CanExecute 状态,手动更新,应对一些特殊场景,如倒计时功能结束,手动调用刷新按钮状态。 /// public void RaiseCanExecuteChanged() => CommandManager.InvalidateRequerySuggested(); } -/// -/// 一个简单的ICommand实现,用于传递动作。此命令始终可以执行。 -/// -internal class RelayCommand : ICommand -{ - private readonly Action execute; - private readonly Predicate? canExecute; - - /// - /// 一个简单的ICommand实现,用于传递动作。此命令始终可以执行。 - /// - public RelayCommand(Action execute, Predicate? canExecute = null) - { - this.execute = execute ?? throw new ArgumentNullException(nameof(execute)); - this.canExecute = canExecute; - } - - /// - /// 当命令的可执行状态更改时触发的事件。 - /// 此事件允许UI元素订阅并响应命令可执行性变化,从而能够适时更新其状态(如启用或禁用按钮)。 - /// 通过将此事件与CommandManager.RequerySuggested关联,可以确保每当应用程序中的命令状态可能改变时,UI都会自动检查命令是否仍然可执行。 - /// - public event EventHandler? CanExecuteChanged - { - add => CommandManager.RequerySuggested += value; - remove => CommandManager.RequerySuggested -= value; - } - - /// - /// 判断命令是否可以执行。 - /// - /// 传递给命令的参数。 - /// 返回一个布尔值,指示命令是否能够执行。对于此实现,总是返回true。 - public bool CanExecute(object? parameter) - { - if (canExecute == null) return true; - - // 使用安全的类型获取机制,防止 InvalidCastException - T? validParameter = GetSafeParameter(parameter); - return canExecute(validParameter); - } - - /// - /// 执行命令。 - /// - /// 传递给命令的参数。 - public void Execute(object? parameter) - { - T? validParameter = GetSafeParameter(parameter); - execute(validParameter); - } - - /// - /// 通知命令管理器重新查询此命令的CanExecute状态。 - /// 此方法用于在命令执行条件可能已更改时更新UI。 - /// - public void RaiseCanExecuteChanged() => CommandManager.InvalidateRequerySuggested(); - /// - /// 安全地将 object 参数转换为泛型 T。如果类型不匹配,则返回 T 的默认值。 - /// - private static T? GetSafeParameter(object? parameter) - { - if (parameter is T tParam) - { - return tParam; - } - - // 应对 WPF 绑定时可能会传入 null 的情况,特别是在 T 为值类型时 - return default; - } -} diff --git a/Melskin/Utilities/RelayCommand{T}.cs b/Melskin/Utilities/RelayCommand{T}.cs new file mode 100644 index 0000000..ce2fa9a --- /dev/null +++ b/Melskin/Utilities/RelayCommand{T}.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Input; + +namespace Melskin.Utilities +{ + /// + /// 一个简单的ICommand实现,用于传递动作。此命令始终可以执行。 + /// + internal class RelayCommand : ICommand + { + private readonly Action execute; + private readonly Predicate? canExecute; + + /// + /// 一个简单的ICommand实现,用于传递动作。此命令始终可以执行。 + /// + public RelayCommand(Action execute, Predicate? canExecute = null) + { + this.execute = execute ?? throw new ArgumentNullException(nameof(execute)); + this.canExecute = canExecute; + } + + /// + /// 当命令的可执行状态更改时触发的事件。 + /// 广播此事件允许UI元素订阅并响应命令可执行性变化,从而能够适时更新其状态(如启用或禁用按钮)。 + /// 通过将此事件与CommandManager.RequerySuggested关联,可以确保每当应用程序中的命令状态可能改变时,UI都会自动检查命令是否仍然可执行。 + /// + public event EventHandler? CanExecuteChanged + { + add => CommandManager.RequerySuggested += value; + remove => CommandManager.RequerySuggested -= value; + } + + /// + /// 判断命令是否可以执行。 + /// + /// 传递给命令的参数。 + /// 返回一个布尔值,指示命令是否能够执行。对于此实现,总是返回true。 + public bool CanExecute(object? parameter) + { + if (canExecute == null) return true; + + // 使用安全的类型获取机制,防止 InvalidCastException + T? validParameter = GetSafeParameter(parameter); + return canExecute(validParameter); + } + + /// + /// 执行命令。 + /// + /// 传递给命令的参数。 + public void Execute(object? parameter) + { + T? validParameter = GetSafeParameter(parameter); + execute(validParameter); + } + + /// + /// 通知命令管理器重新查询此命令的CanExecute状态。 + /// 此方法用于在命令执行条件可能已更改时更新UI,手动更新,应对一些特殊场景,如倒计时功能结束,手动调用刷新按钮状态 + /// + public void RaiseCanExecuteChanged() => CommandManager.InvalidateRequerySuggested(); + /// + /// 安全地将 object 参数转换为泛型 T。如果类型不匹配,则返回 T 的默认值。 + /// + private static T? GetSafeParameter(object? parameter) + { + if (parameter is T tParam) + { + return tParam; + } + + // 应对 WPF 绑定时可能会传入 null 的情况,特别是在 T 为值类型时 + return default; + } + } + +}