优化了PickerWindow的表现效果

This commit is contained in:
__Chisato__
2025-01-06 18:35:11 +08:00
parent a7fae09464
commit 63d7d230dc
4 changed files with 307 additions and 53 deletions

View File

@@ -1,37 +1,183 @@
<Window x:Class="BetterGenshinImpact.View.PickerWindow" <Window x:Class="BetterGenshinImpact.View.PickerWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="选择捕获窗口" Title="选择捕获窗口"
Width="800" Width="800"
Height="450" Height="450"
FontFamily="{DynamicResource TextThemeFontFamily}" WindowStartupLocation="CenterScreen"
mc:Ignorable="d"> Background="#FF1E1E1E">
<Grid Background="Black">
<Window.Resources>
<!-- ListBoxItem Fluent UI 样式 -->
<Style TargetType="ListBoxItem">
<Setter Property="Margin" Value="8,4" />
<Setter Property="Padding" Value="12" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Border x:Name="Border"
Background="{TemplateBinding Background}"
CornerRadius="8"
BorderThickness="1"
BorderBrush="Transparent">
<Grid>
<Border x:Name="SelectionBackground"
Background="#30FFFFFF"
CornerRadius="8"
Opacity="0"/>
<ContentPresenter />
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="SelectionBackground" Property="Opacity" Value="0.1" />
<Setter TargetName="Border" Property="BorderBrush" Value="#20FFFFFF" />
</Trigger>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="SelectionBackground" Property="Opacity" Value="0.2" />
<Setter TargetName="Border" Property="BorderBrush" Value="#40FFFFFF" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="*" /> <RowDefinition Height="*" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<StackPanel Grid.Row="0" <!-- 标题区域 -->
Margin="5,0" <Border Grid.Row="0"
Background="White" Background="#FF252525"
Orientation="Horizontal"> Padding="24,20">
<TextBlock FontSize="20" Text="双击选中要捕获的窗口" /> <Grid>
</StackPanel> <Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListBox x:Name="WindowList" <TextBlock Text="选择捕获窗口"
Grid.Row="1" FontSize="24"
Padding="0,10" FontWeight="SemiBold"
BorderThickness="0"> Foreground="#FFFFFF"/>
<ListBox.ItemTemplate>
<DataTemplate> <TextBlock Grid.Row="1"
<ContentControl MouseDoubleClick="WindowsOnMouseDoubleClick"> Text="双击选中要捕获的窗口"
<TextBlock FontSize="14" Text="{Binding Name}" /> Margin="0,8,0,0"
</ContentControl> Foreground="#99FFFFFF"
</DataTemplate> FontSize="14"/>
</ListBox.ItemTemplate> </Grid>
</ListBox> </Border>
<ScrollViewer Grid.Row="1"
PanningMode="VerticalOnly"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListBox x:Name="WindowList"
Background="Transparent"
BorderThickness="0"
MouseDoubleClick="WindowsOnMouseDoubleClick"
VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.VirtualizationMode="Recycling"
ScrollViewer.CanContentScroll="False">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Padding" Value="0"/>
<Setter Property="Margin" Value="4"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Border x:Name="Border"
Background="{TemplateBinding Background}"
CornerRadius="8"
BorderThickness="1"
BorderBrush="Transparent">
<ContentPresenter/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.TargetName="Border"
Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)"
To="#22FFFFFF"
Duration="0:0:0.2">
<ColorAnimation.EasingFunction>
<ExponentialEase EasingMode="EaseOut"/>
</ColorAnimation.EasingFunction>
</ColorAnimation>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.TargetName="Border"
Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)"
To="#00000000"
Duration="0:0:0.2">
<ColorAnimation.EasingFunction>
<ExponentialEase EasingMode="EaseOut"/>
</ColorAnimation.EasingFunction>
</ColorAnimation>
</Storyboard>
</BeginStoryboard>
</Trigger.ExitActions>
</Trigger>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="Border" Property="Background" Value="#33FFFFFF"/>
<Setter TargetName="Border" Property="BorderBrush" Value="#44FFFFFF"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Margin="12,8" Height="48">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- 内容层 -->
<Border Grid.Column="0"
Width="40"
Height="40"
Background="#22FFFFFF"
CornerRadius="4"
Margin="0,0,12,0">
<Image Width="24"
Height="24"
Source="{Binding Icon}"
RenderOptions.BitmapScalingMode="HighQuality"/>
</Border>
<StackPanel Grid.Column="1"
VerticalAlignment="Center">
<TextBlock Text="{Binding Name}"
FontSize="14"
FontWeight="SemiBold"
Foreground="#FFFFFF"/>
<TextBlock Text="{Binding ProcessName}"
FontSize="12"
Foreground="#99FFFFFF"
Margin="0,4,0,0"/>
</StackPanel>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</ScrollViewer>
</Grid> </Grid>
</Window> </Window>

View File

@@ -7,71 +7,160 @@ using System.Windows;
using System.Windows.Input; using System.Windows.Input;
using System.Windows.Interop; using System.Windows.Interop;
using Vanara.PInvoke; using Vanara.PInvoke;
using System.Windows.Media;
using System.Collections.Generic;
using System.Windows.Media.Imaging;
using System.Runtime.InteropServices;
namespace BetterGenshinImpact.View; namespace BetterGenshinImpact.View;
public partial class PickerWindow : Window public partial class PickerWindow : Window
{ {
private static readonly string[] _ignoreProcesses = ["applicationframehost", "shellexperiencehost", "systemsettings", "winstore.app", "searchui"]; private static readonly string[] _ignoreProcesses = ["applicationframehost", "shellexperiencehost", "systemsettings", "winstore.app", "searchui"];
private bool _isSelected = false;
public PickerWindow() public PickerWindow()
{ {
InitializeComponent(); InitializeComponent();
this.InitializeDpiAwareness(); this.InitializeDpiAwareness();
Loaded += OnLoaded; Loaded += OnLoaded;
} }
public class CapturableWindow
{
public string Name { get; set; }
public string ProcessName { get; set; }
public IntPtr Handle { get; set; }
public ImageSource Icon { get; set; }
}
private void OnLoaded(object sender, RoutedEventArgs e) private void OnLoaded(object sender, RoutedEventArgs e)
{ {
FindWindows(); FindWindows();
} }
public IntPtr PickCaptureTarget(IntPtr hWnd) public bool PickCaptureTarget(IntPtr hWnd,out IntPtr PickedWindow)
{ {
new WindowInteropHelper(this).Owner = hWnd; new WindowInteropHelper(this).Owner = hWnd;
ShowDialog(); ShowDialog();
if(!_isSelected)
return ((CapturableWindow?)WindowList.SelectedItem)?.Handle ?? IntPtr.Zero; {
PickedWindow = IntPtr.Zero;
return false;
}
PickedWindow = ((CapturableWindow?)WindowList.SelectedItem)?.Handle ?? IntPtr.Zero;
return true;
} }
private unsafe void FindWindows() private unsafe void FindWindows()
{ {
var wih = new WindowInteropHelper(this); var wih = new WindowInteropHelper(this);
var windows = new List<CapturableWindow>();
User32.EnumWindows((hWnd, lParam) => User32.EnumWindows((hWnd, lParam) =>
{ {
// ignore invisible windows if (!User32.IsWindowVisible(hWnd) || wih.Handle == hWnd)
if (!User32.IsWindowVisible(hWnd))
return true; return true;
// ignore untitled windows
var title = new StringBuilder(1024); var title = new StringBuilder(1024);
_ = User32.GetWindowText(hWnd, title, title.Capacity); _ = User32.GetWindowText(hWnd, title, title.Capacity);
if (string.IsNullOrWhiteSpace(title.ToString())) if (string.IsNullOrWhiteSpace(title.ToString()))
return true; return true;
// ignore me
if (wih.Handle == hWnd)
return true;
_ = User32.GetWindowThreadProcessId(hWnd, out var processId); _ = User32.GetWindowThreadProcessId(hWnd, out var processId);
// ignore by process name
var process = Process.GetProcessById((int)processId); var process = Process.GetProcessById((int)processId);
if (_ignoreProcesses.Contains(process.ProcessName.ToLower())) if (_ignoreProcesses.Contains(process.ProcessName.ToLower()))
return true; return true;
WindowList.Items.Add(new CapturableWindow // 获取窗口图标
var icon = GetWindowIcon((IntPtr)hWnd);
windows.Add(new CapturableWindow
{ {
Handle = (IntPtr)hWnd, Handle = (IntPtr)hWnd,
Name = $"{title} ({process.ProcessName}.exe)" Name = title.ToString(),
ProcessName = process.ProcessName,
Icon = icon
}); });
return true; return true;
}, IntPtr.Zero); }, IntPtr.Zero);
WindowList.ItemsSource = windows;
}
private ImageSource GetWindowIcon(IntPtr hWnd)
{
try
{
const int ICON_BIG = 1; // WM_GETICON large icon constant
const int ICON_SMALL = 0; // WM_GETICON small icon constant
const int GCL_HICON = -14; // GetClassLong index for icon
// 尝试获取窗口大图标
var iconHandle = User32.SendMessage(hWnd, User32.WindowMessage.WM_GETICON, (IntPtr)ICON_BIG, IntPtr.Zero);
if (iconHandle == IntPtr.Zero)
{
// 尝试获取窗口小图标
iconHandle = User32.SendMessage(hWnd, User32.WindowMessage.WM_GETICON, (IntPtr)ICON_SMALL, IntPtr.Zero);
}
if (iconHandle == IntPtr.Zero)
{
// 尝试获取窗口类图标
iconHandle = User32.GetClassLong(hWnd, GCL_HICON);
}
if (iconHandle != IntPtr.Zero)
{
return Imaging.CreateBitmapSourceFromHIcon(
iconHandle,
Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions());
}
}
catch (Exception ex)
{
Debug.WriteLine($"获取窗口图标失败: {ex.Message}");
}
// 如果获取失败返回一个默认图标或null
return null;
}
private bool IsGenshinWindow(string windowName)
{
// 判断是否包含原神相关的进程名 TODO更加健壮的判断
return windowName == "原神";
}
private bool AskIsThisGenshinImpact(string windowName)
{
var res = MessageBox.Question(
$"""
这看起来不像是原神,确定要选择这个窗口吗?
当前选择的窗口:{windowName}
""",
"确认选择",
MessageBoxButton.YesNo,
MessageBoxResult.No
);
return res == MessageBoxResult.Yes;
} }
private void WindowsOnMouseDoubleClick(object sender, MouseButtonEventArgs e) private void WindowsOnMouseDoubleClick(object sender, MouseButtonEventArgs e)
{ {
var selectedWindow = WindowList.SelectedItem as CapturableWindow;
if (selectedWindow == null) return;
// 如果不是原神窗口,询问用户是否确认
if (!IsGenshinWindow(selectedWindow.Name))
{
if (!AskIsThisGenshinImpact(selectedWindow.Name))
{
return;
}
}
_isSelected = true;
Close(); Close();
} }
} }

View File

@@ -139,29 +139,42 @@ public partial class HomePageViewModel : ObservableObject, INavigationAware, IVi
private void OnStartCaptureTest() private void OnStartCaptureTest()
{ {
var picker = new PickerWindow(); var picker = new PickerWindow();
var hWnd = picker.PickCaptureTarget(new WindowInteropHelper(UIDispatcherHelper.MainWindow).Handle);
if (hWnd != IntPtr.Zero) if (picker.PickCaptureTarget(new WindowInteropHelper(UIDispatcherHelper.MainWindow).Handle, out var hWnd))
{ {
var captureWindow = new CaptureTestWindow(); if (hWnd != IntPtr.Zero)
captureWindow.StartCapture(hWnd, Config.CaptureMode.ToCaptureMode()); {
captureWindow.Show(); var captureWindow = new CaptureTestWindow();
captureWindow.StartCapture(hWnd, Config.CaptureMode.ToCaptureMode());
captureWindow.Show();
}
else
{
MessageBox.Error("选择的窗体句柄为空");
}
} }
} }
[RelayCommand] [RelayCommand]
private void OnManualPickWindow() private void OnManualPickWindow()
{ {
var picker = new PickerWindow(); var picker = new PickerWindow();
var hWnd = picker.PickCaptureTarget(new WindowInteropHelper(UIDispatcherHelper.MainWindow).Handle); if(picker.PickCaptureTarget(new WindowInteropHelper(UIDispatcherHelper.MainWindow).Handle,out var hWnd))
if (hWnd != IntPtr.Zero)
{ {
_hWnd = hWnd; if (hWnd != IntPtr.Zero)
Start(hWnd); {
} _hWnd = hWnd;
else Start(hWnd);
{ }
MessageBox.Error("选择的窗体句柄为空!"); else
{
MessageBox.Error("选择的窗体句柄为空!");
}
} }
} }
[RelayCommand] [RelayCommand]
@@ -181,8 +194,13 @@ public partial class HomePageViewModel : ObservableObject, INavigationAware, IVi
var hWnd = SystemControl.FindGenshinImpactHandle(); var hWnd = SystemControl.FindGenshinImpactHandle();
if (hWnd == IntPtr.Zero) if (hWnd == IntPtr.Zero)
{ {
if (Config.GenshinStartConfig.LinkedStartEnabled && !string.IsNullOrEmpty(Config.GenshinStartConfig.InstallPath)) if (Config.GenshinStartConfig.LinkedStartEnabled)
{ {
if (string.IsNullOrEmpty(Config.GenshinStartConfig.InstallPath))
{
MessageBox.Error("没有找到原神的安装路径");
return;
}
hWnd = await SystemControl.StartFromLocalAsync(Config.GenshinStartConfig.InstallPath); hWnd = await SystemControl.StartFromLocalAsync(Config.GenshinStartConfig.InstallPath);
if (hWnd != IntPtr.Zero) if (hWnd != IntPtr.Zero)
{ {

1
View/PickerWindow.xaml Normal file
View File

@@ -0,0 +1 @@