using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Controls.Primitives; using System.Windows.Input; using System.Windows.Media.Animation; using Melskin.Controls; using Melskin.Extensions; namespace Melskin.Assists { /// /// 控件行为的附加属性类 /// public class BehaviorAssist { #region Popup /// /// 表示是否使 Popup 控件模拟原生平台的弹出行为。当设置为 true 时,Popup 将会在用户点击外部区域或窗口失去焦点时自动关闭,从而提供更接近于原生应用程序的用户体验。 /// public static readonly DependencyProperty SimulateNativeBehaviorProperty = DependencyProperty.RegisterAttached( "SimulateNativeBehavior", typeof(bool), typeof(BehaviorAssist), new PropertyMetadata(false, OnSimulateNativeBehaviorChanged)); /// /// /// /// /// public static void SetSimulateNativeBehavior(DependencyObject element, bool value) => element.SetValue(SimulateNativeBehaviorProperty, value); /// /// /// /// /// [AttachedPropertyBrowsableForType(typeof(ColorPicker))] [AttachedPropertyBrowsableForType(typeof(AutoComplete))] [AttachedPropertyBrowsableForType(typeof(Cascader))] [AttachedPropertyBrowsableForType(typeof(ComboBox))] [AttachedPropertyBrowsableForType(typeof(DatePicker))] [AttachedPropertyBrowsableForType(typeof(MultiComboBox))] [AttachedPropertyBrowsableForType(typeof(TimePicker))] public static bool GetSimulateNativeBehavior(DependencyObject element) => (bool)element.GetValue(SimulateNativeBehaviorProperty); // 私有状态属性的定义保持不变 private static readonly DependencyProperty PreviewMouseDownHandlerProperty = DependencyProperty.RegisterAttached("PreviewMouseDownHandler", typeof(MouseButtonEventHandler), typeof(BehaviorAssist)); private static readonly DependencyProperty WindowEventHandlerProperty = DependencyProperty.RegisterAttached("WindowEventHandler", typeof(EventHandler), typeof(BehaviorAssist)); private static readonly DependencyProperty PreviewMouseWheelHandlerProperty = DependencyProperty.RegisterAttached("PreviewMouseWheelHandler", typeof(MouseWheelEventHandler), typeof(BehaviorAssist)); private static void OnSimulateNativeBehaviorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (d is not Popup popup) return; if ((bool)e.NewValue) { popup.Opened += OnPopupOpened; popup.Closed += OnPopupClosed; } else { popup.Opened -= OnPopupOpened; popup.Closed -= OnPopupClosed; } } private static void OnPopupOpened(object? sender, EventArgs e) { if (sender is not Popup popup || !GetSimulateNativeBehavior(popup)) return; var window = Window.GetWindow(popup); if (window == null) return; // 【防御性编程】在添加任何处理器之前,先执行一次清理,防止因竞争条件导致的泄漏。 CleanupHandlers(popup, window); // --- 添加自动关闭逻辑 --- EventHandler windowEventHandler = (_, _) => { popup.Dispatcher.BeginInvoke(new Action(() => { popup.IsOpen = false; })); }; popup.SetValue(WindowEventHandlerProperty, windowEventHandler); window.Deactivated += windowEventHandler; window.LocationChanged += windowEventHandler; // --- 添加鼠标点击外部逻辑 --- MouseButtonEventHandler mouseDownHandler = (_, _) => { if (popup.IsMouseOver || popup.PlacementTarget is { IsMouseOver: true }) { return; } popup.IsOpen = false; }; popup.SetValue(PreviewMouseDownHandlerProperty, mouseDownHandler); window.AddHandler(UIElement.PreviewMouseDownEvent, mouseDownHandler, true); // --- 添加鼠标滚轮拦截逻辑 --- MouseWheelEventHandler mouseWheelHandler = (_, args) => { if (!popup.IsMouseOver) { args.Handled = true; } }; popup.SetValue(PreviewMouseWheelHandlerProperty, mouseWheelHandler); window.AddHandler(UIElement.PreviewMouseWheelEvent, mouseWheelHandler, true); } private static void OnPopupClosed(object? sender, EventArgs e) { if (sender is not Popup popup || !GetSimulateNativeBehavior(popup)) return; var window = Window.GetWindow(popup); // 窗口可能在Popup关闭时已经不存在了 if (window != null) { CleanupHandlers(popup, window); } } /// /// 【新增】一个集中的清理方法,确保所有附加的处理器都被移除。 /// private static void CleanupHandlers(Popup popup, Window window) { if (popup.GetValue(WindowEventHandlerProperty) is EventHandler windowEventHandler) { window.Deactivated -= windowEventHandler; window.LocationChanged -= windowEventHandler; popup.ClearValue(WindowEventHandlerProperty); } if (popup.GetValue(PreviewMouseDownHandlerProperty) is MouseButtonEventHandler mouseDownHandler) { window.RemoveHandler(UIElement.PreviewMouseDownEvent, mouseDownHandler); popup.ClearValue(PreviewMouseDownHandlerProperty); } if (popup.GetValue(PreviewMouseWheelHandlerProperty) is MouseWheelEventHandler mouseWheelHandler) { window.RemoveHandler(UIElement.PreviewMouseWheelEvent, mouseWheelHandler); popup.ClearValue(PreviewMouseWheelHandlerProperty); } } #endregion #region ListBox /// /// 获取指定对象的滑动动画启用状态。 /// /// 要获取属性值的对象。 /// 如果为 true,则表示启用了滑动动画;否则为 false。 [AttachedPropertyBrowsableForType(typeof(ListBox))] public static bool GetEnableSlideAnimation(DependencyObject obj) { return (bool)obj.GetValue(EnableSlideAnimationProperty); } /// /// 设置指定对象的滑动动画启用状态。 /// /// 要设置属性值的对象。 /// 一个布尔值,表示是否启用滑动动画。如果为 true,则启用滑动动画;否则不启用。 public static void SetEnableSlideAnimation(DependencyObject obj, bool value) { obj.SetValue(EnableSlideAnimationProperty, value); } /// /// 用于控制 ListBox 控件是否启用滑动动画效果的依赖属性。通过设置此属性,可以为 ListBox 添加平滑的滑动过渡效果,从而增强用户体验。 /// public static readonly DependencyProperty EnableSlideAnimationProperty = DependencyProperty.RegisterAttached("EnableSlideAnimation", typeof(bool), typeof(BehaviorAssist), new PropertyMetadata(false, OnEnableSlideAnimationChanged)); private static void OnEnableSlideAnimationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (d is not ListBox listBox) return; if ((bool)e.NewValue) { listBox.Loaded += ListBox_Loaded; listBox.SelectionChanged += ListBox_SelectionChanged; } else { listBox.Loaded -= ListBox_Loaded; listBox.SelectionChanged -= ListBox_SelectionChanged; } } private static void ListBox_Loaded(object sender, RoutedEventArgs e) { if (sender is ListBox listBox && listBox.IsVisible) { UpdateIndicator(listBox, false); } } private static void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e) { if (sender is ListBox { IsLoaded: true } listBox) { UpdateIndicator(listBox, true); } } private static void UpdateIndicator(ListBox? listBox, bool useAnimation) { if (listBox?.SelectedItem == null) return; var indicator = listBox.Template.FindName("PART_SelectionIndicator", listBox) as FrameworkElement; var itemsPresenter = listBox.FindVisualChild(); if (indicator == null || itemsPresenter == null) return; var selectedItemUI = listBox.ItemContainerGenerator.ContainerFromItem(listBox.SelectedItem) as FrameworkElement; if (selectedItemUI == null) return; var position = selectedItemUI.TransformToAncestor(itemsPresenter).Transform(new Point(0, 0)); indicator.Width = selectedItemUI.ActualWidth; indicator.Height = selectedItemUI.ActualHeight; var transform = indicator.RenderTransform as TranslateTransform; // 如果 transform 为 null,或者它是一个被冻结的(只读)实例, // 那么我们就需要创建一个新的、可写的实例来替换它。 if (transform == null || transform.IsFrozen) { transform = new TranslateTransform(); indicator.RenderTransform = transform; } if (useAnimation) { var animationX = new DoubleAnimation(position.X, new Duration(TimeSpan.FromMilliseconds(300))) { EasingFunction = new CubicEase { EasingMode = EasingMode.EaseOut } }; var animationY = new DoubleAnimation(position.Y, new Duration(TimeSpan.FromMilliseconds(300))) { EasingFunction = new CubicEase { EasingMode = EasingMode.EaseOut } }; transform.BeginAnimation(TranslateTransform.XProperty, animationX); transform.BeginAnimation(TranslateTransform.YProperty, animationY); } else { // 现在 transform 保证是可写的,所以这里不会再抛出异常 transform.X = position.X; transform.Y = position.Y; } } #endregion } }