feat: JS级联选择 和 秘境选择 滚轮事件禁止穿透至其他窗口 (#2828)

This commit is contained in:
DarkFlameMaster
2026-02-24 15:21:20 +08:00
committed by GitHub
parent cac0dfc754
commit 4aaca366f0
4 changed files with 264 additions and 12 deletions

View File

@@ -44,7 +44,9 @@
PlacementTarget="{Binding ElementName=MainToggle}"
StaysOpen="False"
AllowsTransparency="True"
PopupAnimation="Slide">
PopupAnimation="Slide"
Opened="MainPopup_Opened"
Closed="MainPopup_Closed">
<Border x:Name="PopupBorder"
Background="{DynamicResource ApplicationBackgroundBrush}"
BorderBrush="{DynamicResource SurfaceStrokeColorDefaultBrush}"
@@ -55,7 +57,8 @@
MinHeight="100"
MaxHeight="300"
MinWidth="120"
MaxWidth="600">
MaxWidth="600"
PreviewMouseWheel="PopupBorder_PreviewMouseWheel">
<Border.Effect>
<DropShadowEffect BlurRadius="10" ShadowDepth="2" Direction="270" Color="Black" Opacity="0.2"/>
</Border.Effect>
@@ -70,7 +73,8 @@
ItemsSource="{Binding FirstLevelOptions, ElementName=Root}"
BorderThickness="0"
SelectionChanged="FirstLevelListView_SelectionChanged"
ScrollViewer.VerticalScrollBarVisibility="Auto">
ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.CanContentScroll="False">
<ui:ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" Margin="8,4"/>
@@ -86,7 +90,8 @@
ItemsSource="{Binding SecondLevelOptions, ElementName=Root}"
BorderThickness="0"
SelectionChanged="SecondLevelListView_SelectionChanged"
ScrollViewer.VerticalScrollBarVisibility="Auto">
ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.CanContentScroll="False">
<ui:ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" Margin="8,4"/>

View File

@@ -1,8 +1,10 @@
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;
@@ -21,6 +23,7 @@ public partial class CascadeSelector : UserControl
{
InitializeComponent();
Loaded += OnLoaded;
Unloaded += OnUnloaded;
}
private void OnLoaded(object sender, RoutedEventArgs e)
@@ -29,6 +32,14 @@ public partial class CascadeSelector : UserControl
UpdatePopupWidth();
}
/// <summary>
/// 控件卸载时清理事件处理器,防止内存泄漏
/// </summary>
private void OnUnloaded(object sender, RoutedEventArgs e)
{
RemoveWindowMouseWheelHandler();
}
public Dictionary<string, List<string>>? CascadeOptions
{
get { return (Dictionary<string, List<string>>?)GetValue(CascadeOptionsProperty); }
@@ -277,4 +288,112 @@ public partial class CascadeSelector : UserControl
}
}
}
/// <summary>
/// Popup 打开时添加全局滚轮事件拦截
/// </summary>
private void MainPopup_Opened(object sender, EventArgs e)
{
var window = Window.GetWindow(this);
if (window != null)
{
window.PreviewMouseWheel -= Window_PreviewMouseWheel;
window.PreviewMouseWheel += Window_PreviewMouseWheel;
}
}
/// <summary>
/// Popup 关闭时移除全局滚轮事件拦截
/// </summary>
private void MainPopup_Closed(object sender, EventArgs e)
{
RemoveWindowMouseWheelHandler();
}
/// <summary>
/// 移除窗口级滚轮事件处理器
/// </summary>
private void RemoveWindowMouseWheelHandler()
{
var window = Window.GetWindow(this);
if (window != null)
{
window.PreviewMouseWheel -= Window_PreviewMouseWheel;
}
}
/// <summary>
/// 全局滚轮事件处理,当 Popup 打开时拦截所有滚轮事件
/// </summary>
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;
}
}
}
/// <summary>
/// 处理 Popup 内的鼠标滚轮事件,防止滚动穿透到外部页面
/// </summary>
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;
}
}
/// <summary>
/// 在视觉树中查找 ScrollViewer
/// </summary>
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;
}
}

View File

@@ -38,12 +38,16 @@
</ToggleButton.Template>
</ToggleButton>
<Popup IsOpen="{Binding IsChecked, ElementName=MainToggle, Mode=TwoWay}"
<Popup x:Name="MainPopup"
IsOpen="{Binding IsChecked, ElementName=MainToggle, Mode=TwoWay}"
PlacementTarget="{Binding ElementName=MainToggle}"
StaysOpen="False"
AllowsTransparency="True"
PopupAnimation="Slide">
<Border Background="{DynamicResource ApplicationBackgroundBrush}"
PopupAnimation="Slide"
Opened="MainPopup_Opened"
Closed="MainPopup_Closed">
<Border x:Name="PopupBorder"
Background="{DynamicResource ApplicationBackgroundBrush}"
BorderBrush="{DynamicResource SurfaceStrokeColorDefaultBrush}"
BorderThickness="1"
CornerRadius="8"
@@ -51,7 +55,8 @@
Padding="4"
MinHeight="200"
MaxHeight="400"
Width="350">
Width="350"
PreviewMouseWheel="PopupBorder_PreviewMouseWheel">
<Border.Effect>
<DropShadowEffect BlurRadius="10" ShadowDepth="2" Direction="270" Color="Black" Opacity="0.2"/>
</Border.Effect>
@@ -63,10 +68,12 @@
</Grid.ColumnDefinitions>
<!-- Countries -->
<ui:ListView ItemsSource="{Binding Countries, ElementName=Root}"
<ui:ListView x:Name="CountriesListView"
ItemsSource="{Binding Countries, ElementName=Root}"
SelectedItem="{Binding SelectedCountry, ElementName=Root, Mode=TwoWay}"
BorderThickness="0"
ScrollViewer.VerticalScrollBarVisibility="Auto">
ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.CanContentScroll="False">
<ui:ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" Margin="8,4"/>
@@ -78,13 +85,14 @@
<Rectangle Grid.Column="1" Width="1" Fill="{DynamicResource SurfaceStrokeColorDefaultBrush}" Margin="2,0"/>
<!-- Domains -->
<ui:ListView Grid.Column="2"
<ui:ListView Grid.Column="2" x:Name="DomainsListView"
ItemsSource="{Binding FilteredDomains, ElementName=Root}"
SelectedValue="{Binding SelectedDomain, ElementName=Root, Mode=TwoWay}"
SelectedValuePath="Item2.Name"
BorderThickness="0"
SelectionChanged="DomainListView_SelectionChanged"
ScrollViewer.VerticalScrollBarVisibility="Auto">
ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.CanContentScroll="False">
<ui:ListView.ItemTemplate>
<DataTemplate>
<TextBlock Margin="8,4">

View File

@@ -1,9 +1,12 @@
using System;
using BetterGenshinImpact.GameTask.AutoTrackPath.Model;
using BetterGenshinImpact.GameTask.Common.Element.Assets;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
namespace BetterGenshinImpact.View.Controls;
@@ -13,6 +16,15 @@ public partial class DomainSelector : UserControl
{
InitializeComponent();
Countries = MapLazyAssets.Instance.CountryToDomains.Keys.Reverse().ToList();
Unloaded += OnUnloaded;
}
/// <summary>
/// 控件卸载时清理事件处理器,防止内存泄漏
/// </summary>
private void OnUnloaded(object sender, RoutedEventArgs e)
{
RemoveWindowMouseWheelHandler();
}
public List<string> Countries
@@ -95,4 +107,112 @@ public partial class DomainSelector : UserControl
MainToggle.IsChecked = false;
}
}
/// <summary>
/// Popup 打开时添加全局滚轮事件拦截
/// </summary>
private void MainPopup_Opened(object sender, EventArgs e)
{
var window = Window.GetWindow(this);
if (window != null)
{
window.PreviewMouseWheel -= Window_PreviewMouseWheel;
window.PreviewMouseWheel += Window_PreviewMouseWheel;
}
}
/// <summary>
/// Popup 关闭时移除全局滚轮事件拦截
/// </summary>
private void MainPopup_Closed(object sender, EventArgs e)
{
RemoveWindowMouseWheelHandler();
}
/// <summary>
/// 移除窗口级滚轮事件处理器
/// </summary>
private void RemoveWindowMouseWheelHandler()
{
var window = Window.GetWindow(this);
if (window != null)
{
window.PreviewMouseWheel -= Window_PreviewMouseWheel;
}
}
/// <summary>
/// 全局滚轮事件处理,当 Popup 打开时拦截所有滚轮事件
/// </summary>
private void Window_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
if (MainPopup.IsOpen)
{
e.Handled = true;
var scrollViewer1 = FindScrollViewer(CountriesListView);
var scrollViewer2 = FindScrollViewer(DomainsListView);
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;
}
}
}
/// <summary>
/// 处理 Popup 内的鼠标滚轮事件,防止滚动穿透到外部页面
/// </summary>
private void PopupBorder_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
e.Handled = true;
var scrollViewer1 = FindScrollViewer(CountriesListView);
var scrollViewer2 = FindScrollViewer(DomainsListView);
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;
}
}
/// <summary>
/// 在视觉树中查找 ScrollViewer
/// </summary>
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;
}
}