功能更新
This commit is contained in:
265
Melskin/Assists/BehaviorAssist.cs
Normal file
265
Melskin/Assists/BehaviorAssist.cs
Normal file
@@ -0,0 +1,265 @@
|
||||
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
|
||||
{
|
||||
/// <summary>
|
||||
/// 控件行为的附加属性类
|
||||
/// </summary>
|
||||
public class BehaviorAssist
|
||||
{
|
||||
#region Popup
|
||||
|
||||
/// <summary>
|
||||
/// 表示是否使 Popup 控件模拟原生平台的弹出行为。当设置为 true 时,Popup 将会在用户点击外部区域或窗口失去焦点时自动关闭,从而提供更接近于原生应用程序的用户体验。
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty SimulateNativeBehaviorProperty =
|
||||
DependencyProperty.RegisterAttached(
|
||||
"SimulateNativeBehavior", typeof(bool), typeof(BehaviorAssist),
|
||||
new PropertyMetadata(false, OnSimulateNativeBehaviorChanged));
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="element"></param>
|
||||
/// <param name="value"></param>
|
||||
public static void SetSimulateNativeBehavior(DependencyObject element, bool value) => element.SetValue(SimulateNativeBehaviorProperty, value);
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="element"></param>
|
||||
/// <returns></returns>
|
||||
[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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 【新增】一个集中的清理方法,确保所有附加的处理器都被移除。
|
||||
/// </summary>
|
||||
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
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定对象的滑动动画启用状态。
|
||||
/// </summary>
|
||||
/// <param name="obj">要获取属性值的对象。</param>
|
||||
/// <returns>如果为 true,则表示启用了滑动动画;否则为 false。</returns>
|
||||
[AttachedPropertyBrowsableForType(typeof(ListBox))]
|
||||
public static bool GetEnableSlideAnimation(DependencyObject obj)
|
||||
{
|
||||
return (bool)obj.GetValue(EnableSlideAnimationProperty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置指定对象的滑动动画启用状态。
|
||||
/// </summary>
|
||||
/// <param name="obj">要设置属性值的对象。</param>
|
||||
/// <param name="value">一个布尔值,表示是否启用滑动动画。如果为 true,则启用滑动动画;否则不启用。</param>
|
||||
public static void SetEnableSlideAnimation(DependencyObject obj, bool value)
|
||||
{
|
||||
obj.SetValue(EnableSlideAnimationProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用于控制 ListBox 控件是否启用滑动动画效果的依赖属性。通过设置此属性,可以为 ListBox 添加平滑的滑动过渡效果,从而增强用户体验。
|
||||
/// </summary>
|
||||
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<ItemsPresenter>();
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user