using System; using System.ComponentModel; using System.Reflection; using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Data; using System.Windows.Input; using System.Windows.Media; using System.Windows.Shell; namespace ModernWpf.Controls { // ========================================== // 1. 附加属性帮助类 (新增 Prefix) // ========================================== public class ElementHelper : DependencyObject { // ... 原有的属性保持不变 (Watermark, Icon, CornerRadius, Header) ... public static readonly DependencyProperty WatermarkProperty = DependencyProperty.RegisterAttached("Watermark", typeof(string), typeof(ElementHelper), new PropertyMetadata("")); public static void SetWatermark(DependencyObject element, string value) => element.SetValue(WatermarkProperty, value); public static string GetWatermark(DependencyObject element) => (string)element.GetValue(WatermarkProperty); public static readonly DependencyProperty IconProperty = DependencyProperty.RegisterAttached("Icon", typeof(Geometry), typeof(ElementHelper), new PropertyMetadata(null)); public static void SetIcon(DependencyObject element, Geometry value) => element.SetValue(IconProperty, value); public static Geometry GetIcon(DependencyObject element) => (Geometry)element.GetValue(IconProperty); public static readonly DependencyProperty CornerRadiusProperty = DependencyProperty.RegisterAttached("CornerRadius", typeof(CornerRadius), typeof(ElementHelper), new PropertyMetadata(new CornerRadius(4))); public static void SetCornerRadius(DependencyObject element, CornerRadius value) => element.SetValue(CornerRadiusProperty, value); public static CornerRadius GetCornerRadius(DependencyObject element) => (CornerRadius)element.GetValue(CornerRadiusProperty); public static readonly DependencyProperty HeaderProperty = DependencyProperty.RegisterAttached("Header", typeof(string), typeof(ElementHelper), new PropertyMetadata("")); public static void SetHeader(DependencyObject element, string value) => element.SetValue(HeaderProperty, value); public static string GetHeader(DependencyObject element) => (string)element.GetValue(HeaderProperty); // === 新增 Prefix 属性 (支持文字或UI元素) === public static readonly DependencyProperty PrefixProperty = DependencyProperty.RegisterAttached("Prefix", typeof(object), typeof(ElementHelper), new PropertyMetadata(null)); public static void SetPrefix(DependencyObject element, object value) => element.SetValue(PrefixProperty, value); public static object GetPrefix(DependencyObject element) => element.GetValue(PrefixProperty); } // ========================================== // 2. ModernWindow (保持不变) // ========================================== public class ModernWindow : Window { static ModernWindow() { DefaultStyleKeyProperty.OverrideMetadata(typeof(ModernWindow), new FrameworkPropertyMetadata(typeof(ModernWindow))); } public ModernWindow() { var chrome = new WindowChrome { CaptionHeight = 32, ResizeBorderThickness = new Thickness(4), // 让系统边框可见,从而触发 Win11 原生圆角和阴影 GlassFrameThickness = new Thickness(1), UseAeroCaptionButtons = false }; WindowChrome.SetWindowChrome(this, chrome); CommandBindings.Add(new CommandBinding(SystemCommands.CloseWindowCommand, (s, e) => SystemCommands.CloseWindow(this))); CommandBindings.Add(new CommandBinding(SystemCommands.MaximizeWindowCommand, (s, e) => SystemCommands.MaximizeWindow(this))); CommandBindings.Add(new CommandBinding(SystemCommands.MinimizeWindowCommand, (s, e) => SystemCommands.MinimizeWindow(this))); CommandBindings.Add(new CommandBinding(SystemCommands.RestoreWindowCommand, (s, e) => SystemCommands.RestoreWindow(this))); } public override void OnApplyTemplate() { base.OnApplyTemplate(); if (GetTemplateChild("PART_TitleBar") is Border titleBar) { titleBar.MouseLeftButtonDown += (s, e) => { if (e.ClickCount == 2) { if (ResizeMode != ResizeMode.NoResize) WindowState = WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized; } else { DragMove(); } }; } } } public class SearchableComboBox : ComboBox { private TextBox _editableTextBox; private bool _isInternalOperation; static SearchableComboBox() { DefaultStyleKeyProperty.OverrideMetadata(typeof(SearchableComboBox), new FrameworkPropertyMetadata(typeof(SearchableComboBox))); } public SearchableComboBox() { IsEditable = true; IsTextSearchEnabled = false; StaysOpenOnEdit = true; } public override void OnApplyTemplate() { base.OnApplyTemplate(); _editableTextBox = GetTemplateChild("PART_EditableTextBox") as TextBox; if (_editableTextBox != null) { _editableTextBox.TextChanged += OnEditableTextBoxTextChanged; // 点击时全选 _editableTextBox.GotFocus += (s, e) => _editableTextBox.SelectAll(); } } protected override void OnSelectionChanged(SelectionChangedEventArgs e) { if (_isInternalOperation) return; _isInternalOperation = true; base.OnSelectionChanged(e); // 重点 1:选中项改变后,必须彻底清空过滤器,否则下次打开下拉框只剩下一项 this.Items.Filter = null; _isInternalOperation = false; } private void OnEditableTextBoxTextChanged(object sender, TextChangedEventArgs e) { if (_isInternalOperation) return; string searchText = _editableTextBox.Text; string selectedText = GetItemDisplayText(SelectedItem); // 重点 2:断开锁定逻辑 // 如果文本框的内容和当前选中项的文本不一致,说明用户正在打字替换内容 if (SelectedItem != null && searchText != selectedText) { _isInternalOperation = true; SelectedItem = null; // 必须将 SelectedItem 设为 null,否则 WPF 会强行还原文本 _isInternalOperation = false; } // 重点 3:执行过滤 this.Items.Filter = item => { if (string.IsNullOrEmpty(searchText)) return true; string itemText = GetItemDisplayText(item); // 只要包含关键字就显示(忽略大小写) return itemText.IndexOf(searchText, StringComparison.OrdinalIgnoreCase) >= 0; }; // 自动打开下拉框 if (!IsDropDownOpen && _editableTextBox.IsFocused && !string.IsNullOrEmpty(searchText)) { IsDropDownOpen = true; } } private string GetItemDisplayText(object item) { if (item == null) return string.Empty; if (item is ComboBoxItem cbi) return cbi.Content?.ToString() ?? string.Empty; // 兼容 DisplayMemberPath 或 TextSearch.TextPath string path = DisplayMemberPath; if (string.IsNullOrEmpty(path)) path = TextSearch.GetTextPath(this); if (!string.IsNullOrEmpty(path)) { var prop = item.GetType().GetProperty(path); if (prop != null) return prop.GetValue(item)?.ToString() ?? string.Empty; } return item.ToString(); } } public class ModernPasswordBox : Control { static ModernPasswordBox() { DefaultStyleKeyProperty.OverrideMetadata(typeof(ModernPasswordBox), new FrameworkPropertyMetadata(typeof(ModernPasswordBox))); } public string Password { get => (string)GetValue(PasswordProperty); set => SetValue(PasswordProperty, value); } public static readonly DependencyProperty PasswordProperty = DependencyProperty.Register("Password", typeof(string), typeof(ModernPasswordBox), new PropertyMetadata(string.Empty)); // 辅助属性:标记是否为空,用于 XAML 控制水印显隐 public bool IsEmpty { get => (bool)GetValue(IsEmptyProperty); private set => SetValue(IsEmptyProperty, value); } public static readonly DependencyProperty IsEmptyProperty = DependencyProperty.Register("IsEmpty", typeof(bool), typeof(ModernPasswordBox), new PropertyMetadata(true)); public override void OnApplyTemplate() { base.OnApplyTemplate(); var pb = GetTemplateChild("PART_PasswordBox") as PasswordBox; var tb = GetTemplateChild("PART_TextBox") as TextBox; var btn = GetTemplateChild("PART_ToggleBtn") as ToggleButton; if (pb != null && tb != null && btn != null) { pb.PasswordChanged += (s, e) => { if (pb.Visibility == Visibility.Visible) { Password = pb.Password; IsEmpty = string.IsNullOrEmpty(pb.Password); } }; tb.TextChanged += (s, e) => { if (tb.Visibility == Visibility.Visible) { Password = tb.Text; IsEmpty = string.IsNullOrEmpty(tb.Text); } }; btn.Checked += (s, e) => { tb.Text = pb.Password; tb.Visibility = Visibility.Visible; pb.Visibility = Visibility.Collapsed; }; btn.Unchecked += (s, e) => { pb.Password = tb.Text; pb.Visibility = Visibility.Visible; tb.Visibility = Visibility.Collapsed; }; } } } }