mirror of
https://github.com/babalae/better-genshin-impact.git
synced 2026-03-15 07:43:20 +08:00
优化了PickerWindow的表现效果
This commit is contained in:
@@ -1,37 +1,183 @@
|
||||
<Window x:Class="BetterGenshinImpact.View.PickerWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
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="选择捕获窗口"
|
||||
Width="800"
|
||||
Height="450"
|
||||
FontFamily="{DynamicResource TextThemeFontFamily}"
|
||||
mc:Ignorable="d">
|
||||
<Grid Background="Black">
|
||||
WindowStartupLocation="CenterScreen"
|
||||
Background="#FF1E1E1E">
|
||||
|
||||
<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>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<StackPanel Grid.Row="0"
|
||||
Margin="5,0"
|
||||
Background="White"
|
||||
Orientation="Horizontal">
|
||||
<TextBlock FontSize="20" Text="双击选中要捕获的窗口" />
|
||||
</StackPanel>
|
||||
<!-- 标题区域 -->
|
||||
<Border Grid.Row="0"
|
||||
Background="#FF252525"
|
||||
Padding="24,20">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<ListBox x:Name="WindowList"
|
||||
Grid.Row="1"
|
||||
Padding="0,10"
|
||||
BorderThickness="0">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<ContentControl MouseDoubleClick="WindowsOnMouseDoubleClick">
|
||||
<TextBlock FontSize="14" Text="{Binding Name}" />
|
||||
</ContentControl>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
<TextBlock Text="选择捕获窗口"
|
||||
FontSize="24"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="#FFFFFF"/>
|
||||
|
||||
<TextBlock Grid.Row="1"
|
||||
Text="双击选中要捕获的窗口"
|
||||
Margin="0,8,0,0"
|
||||
Foreground="#99FFFFFF"
|
||||
FontSize="14"/>
|
||||
</Grid>
|
||||
</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>
|
||||
</Window>
|
||||
</Window>
|
||||
@@ -7,71 +7,160 @@ using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Interop;
|
||||
using Vanara.PInvoke;
|
||||
using System.Windows.Media;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace BetterGenshinImpact.View;
|
||||
|
||||
public partial class PickerWindow : Window
|
||||
{
|
||||
private static readonly string[] _ignoreProcesses = ["applicationframehost", "shellexperiencehost", "systemsettings", "winstore.app", "searchui"];
|
||||
|
||||
private bool _isSelected = false;
|
||||
public PickerWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
this.InitializeDpiAwareness();
|
||||
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)
|
||||
{
|
||||
FindWindows();
|
||||
}
|
||||
|
||||
public IntPtr PickCaptureTarget(IntPtr hWnd)
|
||||
public bool PickCaptureTarget(IntPtr hWnd,out IntPtr PickedWindow)
|
||||
{
|
||||
new WindowInteropHelper(this).Owner = hWnd;
|
||||
ShowDialog();
|
||||
|
||||
return ((CapturableWindow?)WindowList.SelectedItem)?.Handle ?? IntPtr.Zero;
|
||||
if(!_isSelected)
|
||||
{
|
||||
PickedWindow = IntPtr.Zero;
|
||||
return false;
|
||||
}
|
||||
PickedWindow = ((CapturableWindow?)WindowList.SelectedItem)?.Handle ?? IntPtr.Zero;
|
||||
return true;
|
||||
}
|
||||
|
||||
private unsafe void FindWindows()
|
||||
{
|
||||
var wih = new WindowInteropHelper(this);
|
||||
var windows = new List<CapturableWindow>();
|
||||
|
||||
User32.EnumWindows((hWnd, lParam) =>
|
||||
{
|
||||
// ignore invisible windows
|
||||
if (!User32.IsWindowVisible(hWnd))
|
||||
if (!User32.IsWindowVisible(hWnd) || wih.Handle == hWnd)
|
||||
return true;
|
||||
|
||||
// ignore untitled windows
|
||||
var title = new StringBuilder(1024);
|
||||
_ = User32.GetWindowText(hWnd, title, title.Capacity);
|
||||
if (string.IsNullOrWhiteSpace(title.ToString()))
|
||||
return true;
|
||||
|
||||
// ignore me
|
||||
if (wih.Handle == hWnd)
|
||||
return true;
|
||||
|
||||
_ = User32.GetWindowThreadProcessId(hWnd, out var processId);
|
||||
|
||||
// ignore by process name
|
||||
var process = Process.GetProcessById((int)processId);
|
||||
if (_ignoreProcesses.Contains(process.ProcessName.ToLower()))
|
||||
return true;
|
||||
|
||||
WindowList.Items.Add(new CapturableWindow
|
||||
// 获取窗口图标
|
||||
var icon = GetWindowIcon((IntPtr)hWnd);
|
||||
|
||||
windows.Add(new CapturableWindow
|
||||
{
|
||||
Handle = (IntPtr)hWnd,
|
||||
Name = $"{title} ({process.ProcessName}.exe)"
|
||||
Name = title.ToString(),
|
||||
ProcessName = process.ProcessName,
|
||||
Icon = icon
|
||||
});
|
||||
|
||||
return true;
|
||||
}, 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)
|
||||
{
|
||||
var selectedWindow = WindowList.SelectedItem as CapturableWindow;
|
||||
if (selectedWindow == null) return;
|
||||
|
||||
// 如果不是原神窗口,询问用户是否确认
|
||||
if (!IsGenshinWindow(selectedWindow.Name))
|
||||
{
|
||||
if (!AskIsThisGenshinImpact(selectedWindow.Name))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
_isSelected = true;
|
||||
Close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,29 +139,42 @@ public partial class HomePageViewModel : ObservableObject, INavigationAware, IVi
|
||||
private void OnStartCaptureTest()
|
||||
{
|
||||
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();
|
||||
captureWindow.StartCapture(hWnd, Config.CaptureMode.ToCaptureMode());
|
||||
captureWindow.Show();
|
||||
if (hWnd != IntPtr.Zero)
|
||||
{
|
||||
var captureWindow = new CaptureTestWindow();
|
||||
captureWindow.StartCapture(hWnd, Config.CaptureMode.ToCaptureMode());
|
||||
captureWindow.Show();
|
||||
}
|
||||
else
|
||||
{
|
||||
MessageBox.Error("选择的窗体句柄为空");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void OnManualPickWindow()
|
||||
{
|
||||
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))
|
||||
{
|
||||
_hWnd = hWnd;
|
||||
Start(hWnd);
|
||||
}
|
||||
else
|
||||
{
|
||||
MessageBox.Error("选择的窗体句柄为空!");
|
||||
if (hWnd != IntPtr.Zero)
|
||||
{
|
||||
_hWnd = hWnd;
|
||||
Start(hWnd);
|
||||
}
|
||||
else
|
||||
{
|
||||
MessageBox.Error("选择的窗体句柄为空!");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
@@ -181,8 +194,13 @@ public partial class HomePageViewModel : ObservableObject, INavigationAware, IVi
|
||||
var hWnd = SystemControl.FindGenshinImpactHandle();
|
||||
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);
|
||||
if (hWnd != IntPtr.Zero)
|
||||
{
|
||||
|
||||
1
View/PickerWindow.xaml
Normal file
1
View/PickerWindow.xaml
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
Reference in New Issue
Block a user