237 lines
10 KiB
C#
237 lines
10 KiB
C#
|
|
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; };
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|