using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; namespace BetterGenshinImpact.View.Controls; public partial class CascadeSelector : UserControl { private const double HorizontalMargin = 40; private const double MinFirstLevelWidth = 100; private const double MaxFirstLevelWidth = 300; private const double MinSecondLevelWidth = 100; private const double MaxSecondLevelWidth = 300; private const double MinPopupWidth = 120; private const double MaxPopupWidth = 600; public CascadeSelector() { InitializeComponent(); Loaded += OnLoaded; Unloaded += OnUnloaded; } private void OnLoaded(object sender, RoutedEventArgs e) { UpdateFirstLevelOptions(); UpdatePopupWidth(); } /// /// 控件卸载时清理事件处理器,防止内存泄漏 /// private void OnUnloaded(object sender, RoutedEventArgs e) { RemoveWindowMouseWheelHandler(); } public Dictionary>? CascadeOptions { get { return (Dictionary>?)GetValue(CascadeOptionsProperty); } set { SetValue(CascadeOptionsProperty, value); } } public static readonly DependencyProperty CascadeOptionsProperty = DependencyProperty.Register("CascadeOptions", typeof(Dictionary>), typeof(CascadeSelector), new PropertyMetadata(null, OnCascadeOptionsChanged)); private static void OnCascadeOptionsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var control = (CascadeSelector)d; control.UpdateFirstLevelOptions(); } public List FirstLevelOptions { get { return (List)GetValue(FirstLevelOptionsProperty); } set { SetValue(FirstLevelOptionsProperty, value); } } public static readonly DependencyProperty FirstLevelOptionsProperty = DependencyProperty.Register("FirstLevelOptions", typeof(List), typeof(CascadeSelector), new PropertyMetadata(null)); public List SecondLevelOptions { get { return (List)GetValue(SecondLevelOptionsProperty); } set { SetValue(SecondLevelOptionsProperty, value); } } public static readonly DependencyProperty SecondLevelOptionsProperty = DependencyProperty.Register("SecondLevelOptions", typeof(List), typeof(CascadeSelector), new PropertyMetadata(null, OnSecondLevelOptionsChanged)); private static void OnSecondLevelOptionsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var control = (CascadeSelector)d; control.Dispatcher.BeginInvoke(() => { control.AdjustSecondLevelListWidth(); }, System.Windows.Threading.DispatcherPriority.Render); } public string? SelectedValue { get { return (string?)GetValue(SelectedValueProperty); } set { SetValue(SelectedValueProperty, value); } } public static readonly DependencyProperty SelectedValueProperty = DependencyProperty.Register("SelectedValue", typeof(string), typeof(CascadeSelector), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnSelectedValueChanged)); private static void OnSelectedValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var control = (CascadeSelector)d; control.HandleSelectedValueChanged((string?)e.NewValue); } public string? DefaultValue { get { return (string?)GetValue(DefaultValueProperty); } set { SetValue(DefaultValueProperty, value); } } public static readonly DependencyProperty DefaultValueProperty = DependencyProperty.Register("DefaultValue", typeof(string), typeof(CascadeSelector), new PropertyMetadata(null)); private double MeasureTextWidth(string text, double fontSize) { if (string.IsNullOrEmpty(text)) { return 0; } var formattedText = new FormattedText( text, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, new Typeface(FontFamily, FontStyle, FontWeight, FontStretch), fontSize, Brushes.Black, new NumberSubstitution(), TextFormattingMode.Display, VisualTreeHelper.GetDpi(this).PixelsPerDip); return formattedText.WidthIncludingTrailingWhitespace; } private void AdjustFirstLevelListWidth() { if (FirstLevelOptions == null || FirstLevelOptions.Count == 0) { return; } double maxWidth = MinFirstLevelWidth; foreach (var option in FirstLevelOptions) { double textWidth = MeasureTextWidth(option, FontSize) + HorizontalMargin; if (textWidth > maxWidth) { maxWidth = textWidth; } } if (maxWidth > MaxFirstLevelWidth) { maxWidth = MaxFirstLevelWidth; } var grid = PopupBorder?.Child as Grid; var firstColumn = grid?.ColumnDefinitions[0]; if (firstColumn != null) { firstColumn.Width = new GridLength(maxWidth); } } private void AdjustSecondLevelListWidth() { if (SecondLevelOptions == null || SecondLevelOptions.Count == 0) { UpdatePopupWidth(); return; } double maxWidth = MinSecondLevelWidth; foreach (var option in SecondLevelOptions) { double textWidth = MeasureTextWidth(option, FontSize) + HorizontalMargin; if (textWidth > maxWidth) { maxWidth = textWidth; } } if (maxWidth > MaxSecondLevelWidth) { maxWidth = MaxSecondLevelWidth; } var grid = PopupBorder?.Child as Grid; var thirdColumn = grid?.ColumnDefinitions[2]; if (thirdColumn != null) { thirdColumn.Width = new GridLength(maxWidth); } Dispatcher.BeginInvoke(() => UpdatePopupWidth(), System.Windows.Threading.DispatcherPriority.Render); } private void UpdatePopupWidth() { var grid = PopupBorder?.Child as Grid; if (grid == null || grid.ColumnDefinitions.Count < 3) { return; } double totalWidth = 0; var firstColumn = grid.ColumnDefinitions[0]; var secondColumn = grid.ColumnDefinitions[1]; var thirdColumn = grid.ColumnDefinitions[2]; if (firstColumn.Width.IsAuto) { firstColumn.Width = new GridLength(MinFirstLevelWidth); } totalWidth += firstColumn.Width.Value; totalWidth += secondColumn.Width.Value; totalWidth += thirdColumn.Width.Value; totalWidth += 8; if (totalWidth < MinPopupWidth) { totalWidth = MinPopupWidth; } if (totalWidth > MaxPopupWidth) { totalWidth = MaxPopupWidth; } PopupBorder.Width = totalWidth; } private void UpdateFirstLevelOptions() { if (CascadeOptions != null) { FirstLevelOptions = CascadeOptions.Keys.ToList(); Dispatcher.BeginInvoke(() => { AdjustFirstLevelListWidth(); UpdatePopupWidth(); }, System.Windows.Threading.DispatcherPriority.Render); } else { FirstLevelOptions = new List(); } } private void HandleSelectedValueChanged(string? newValue) { if (string.IsNullOrEmpty(newValue) || CascadeOptions == null) { return; } foreach (var kvp in CascadeOptions) { if (kvp.Value.Contains(newValue)) { FirstLevelListView.SelectedItem = kvp.Key; SecondLevelOptions = kvp.Value; SecondLevelListView.SelectedItem = newValue; break; } } } private void FirstLevelListView_SelectionChanged(object sender, SelectionChangedEventArgs e) { if (FirstLevelListView.SelectedItem is string selectedFirstLevel) { if (CascadeOptions != null && CascadeOptions.TryGetValue(selectedFirstLevel, out var secondLevelOptions)) { SecondLevelOptions = secondLevelOptions; SecondLevelListView.SelectedItem = null; } } } private void SecondLevelListView_SelectionChanged(object sender, SelectionChangedEventArgs e) { if (SecondLevelListView.SelectedItem is string selectedSecondLevel) { SelectedValue = selectedSecondLevel; if (MainToggle.IsChecked == true) { MainToggle.IsChecked = false; } } } /// /// Popup 打开时添加全局滚轮事件拦截 /// private void MainPopup_Opened(object sender, EventArgs e) { var window = Window.GetWindow(this); if (window != null) { window.PreviewMouseWheel -= Window_PreviewMouseWheel; window.PreviewMouseWheel += Window_PreviewMouseWheel; } } /// /// Popup 关闭时移除全局滚轮事件拦截 /// private void MainPopup_Closed(object sender, EventArgs e) { RemoveWindowMouseWheelHandler(); } /// /// 移除窗口级滚轮事件处理器 /// private void RemoveWindowMouseWheelHandler() { var window = Window.GetWindow(this); if (window != null) { window.PreviewMouseWheel -= Window_PreviewMouseWheel; } } /// /// 全局滚轮事件处理,当 Popup 打开时拦截所有滚轮事件 /// private void Window_PreviewMouseWheel(object sender, MouseWheelEventArgs e) { if (MainPopup.IsOpen) { e.Handled = true; var scrollViewer1 = FindScrollViewer(FirstLevelListView); var scrollViewer2 = FindScrollViewer(SecondLevelListView); if (scrollViewer1 != null && scrollViewer1.IsMouseOver) { scrollViewer1.ScrollToVerticalOffset(scrollViewer1.VerticalOffset - e.Delta / 2.0); return; } if (scrollViewer2 != null && scrollViewer2.IsMouseOver) { scrollViewer2.ScrollToVerticalOffset(scrollViewer2.VerticalOffset - e.Delta / 2.0); return; } } } /// /// 处理 Popup 内的鼠标滚轮事件,防止滚动穿透到外部页面 /// private void PopupBorder_PreviewMouseWheel(object sender, MouseWheelEventArgs e) { e.Handled = true; var scrollViewer1 = FindScrollViewer(FirstLevelListView); var scrollViewer2 = FindScrollViewer(SecondLevelListView); if (scrollViewer1 != null && scrollViewer1.IsMouseOver) { scrollViewer1.ScrollToVerticalOffset(scrollViewer1.VerticalOffset - e.Delta / 2.0); return; } if (scrollViewer2 != null && scrollViewer2.IsMouseOver) { scrollViewer2.ScrollToVerticalOffset(scrollViewer2.VerticalOffset - e.Delta / 2.0); return; } } /// /// 在视觉树中查找 ScrollViewer /// private ScrollViewer? FindScrollViewer(DependencyObject parent) { if (parent == null) return null; for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++) { var child = VisualTreeHelper.GetChild(parent, i); if (child is ScrollViewer scrollViewer) { return scrollViewer; } var result = FindScrollViewer(child); if (result != null) { return result; } } return null; } }