add:放大镜

This commit is contained in:
2026-05-02 08:28:49 +08:00
parent 16f53acf42
commit 15884c5901
3 changed files with 380 additions and 4 deletions
+278
View File
@@ -0,0 +1,278 @@
using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Threading;
namespace Ink_Canvas.Windows
{
/// <summary>
/// 可拖动、可调整大小的屏幕放大镜窗口。
/// 底层使用 Win32 Magnification API (Magnification.dll) 将窗口当前所在区域下方的屏幕内容放大显示。
/// </summary>
internal class MagnifierWindow : Window
{
#region P/Invoke
private const string MagnifierClassName = "Magnifier";
private const int WS_CHILD = 0x40000000;
private const int WS_VISIBLE = 0x10000000;
[StructLayout(LayoutKind.Sequential)]
private struct RECT { public int left, top, right, bottom; public RECT(int l, int t, int r, int b) { left = l; top = t; right = r; bottom = b; } }
[StructLayout(LayoutKind.Sequential)]
private struct MAGTRANSFORM
{
public float m00, m01, m02;
public float m10, m11, m12;
public float m20, m21, m22;
}
[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr CreateWindowEx(int dwExStyle, string lpClassName, string lpWindowName, int dwStyle,
int x, int y, int nWidth, int nHeight, IntPtr hWndParent, IntPtr hMenu, IntPtr hInstance, IntPtr lpParam);
[DllImport("user32.dll")] private static extern bool DestroyWindow(IntPtr hWnd);
[DllImport("user32.dll")] private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
[DllImport("user32.dll")] private static extern bool InvalidateRect(IntPtr hWnd, IntPtr lpRect, bool bErase);
[DllImport("Magnification.dll")] private static extern bool MagInitialize();
[DllImport("Magnification.dll")] private static extern bool MagUninitialize();
[DllImport("Magnification.dll")] private static extern bool MagSetWindowSource(IntPtr hwnd, RECT rect);
[DllImport("Magnification.dll")] private static extern bool MagSetWindowTransform(IntPtr hwnd, ref MAGTRANSFORM pTransform);
[DllImport("Magnification.dll")] private static extern bool MagSetWindowFilterList(IntPtr hwnd, int dwFilterMode, int count, IntPtr[] pHWND);
private const int MW_FILTERMODE_EXCLUDE = 0;
#endregion
private static MagnifierWindow _instance;
public static bool HasInstance => _instance != null;
private IntPtr _magHwnd;
private HwndSource _hwndSource;
private DispatcherTimer _timer;
private bool _magInitialized;
private float _zoom = 2.0f;
public float Zoom
{
get => _zoom;
set
{
_zoom = Math.Max(1.1f, Math.Min(8.0f, value));
if (_magHwnd != IntPtr.Zero) ApplyTransform();
}
}
public static void Show(float zoom)
{
if (_instance != null)
{
_instance.Activate();
_instance.Zoom = zoom;
return;
}
_instance = new MagnifierWindow { Zoom = zoom };
_instance.Closed += (s, e) => _instance = null;
_instance.Show();
}
public static void HideInstance()
{
_instance?.Close();
}
public static void SetZoom(float zoom)
{
if (_instance != null) _instance.Zoom = zoom;
}
private MagnifierWindow()
{
Title = "聚焦放大镜";
Width = 420;
Height = 280;
MinWidth = 160;
MinHeight = 120;
WindowStyle = WindowStyle.None;
ResizeMode = ResizeMode.CanResizeWithGrip;
Topmost = true;
ShowInTaskbar = false;
AllowsTransparency = false;
Background = new SolidColorBrush(Color.FromRgb(31, 31, 31));
WindowStartupLocation = WindowStartupLocation.CenterScreen;
BuildChrome();
}
private void BuildChrome()
{
var root = new DockPanel { LastChildFill = true };
// 标题栏
var titleBar = new Border
{
Height = 28,
Background = new SolidColorBrush(Color.FromArgb(0xEE, 0x2A, 0x2A, 0x2A)),
Cursor = Cursors.SizeAll
};
DockPanel.SetDock(titleBar, Dock.Top);
titleBar.MouseLeftButtonDown += (s, e) => { if (e.ButtonState == MouseButtonState.Pressed) DragMove(); };
var titleGrid = new Grid();
titleGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
titleGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
var titleText = new TextBlock
{
Text = "聚焦放大镜",
VerticalAlignment = VerticalAlignment.Center,
Margin = new Thickness(10, 0, 0, 0),
Foreground = Brushes.White,
FontSize = 12
};
Grid.SetColumn(titleText, 0);
titleGrid.Children.Add(titleText);
var closeBtn = new Button
{
Content = "✕",
Width = 40,
Height = 28,
Foreground = Brushes.White,
Background = Brushes.Transparent,
BorderThickness = new Thickness(0),
FontSize = 12,
Cursor = Cursors.Arrow
};
closeBtn.Click += (s, e) => Close();
Grid.SetColumn(closeBtn, 1);
titleGrid.Children.Add(closeBtn);
titleBar.Child = titleGrid;
root.Children.Add(titleBar);
// 放大镜承载区(由 Win32 子窗口填充)
_magHost = new MagnifierHost(this);
root.Children.Add(_magHost);
Content = root;
}
private MagnifierHost _magHost;
private class MagnifierHost : HwndHost
{
private readonly MagnifierWindow _owner;
public IntPtr MagHwnd { get; private set; }
public MagnifierHost(MagnifierWindow owner) { _owner = owner; }
protected override HandleRef BuildWindowCore(HandleRef hwndParent)
{
if (!MagInitialize())
{
throw new InvalidOperationException("MagInitialize 失败");
}
_owner._magInitialized = true;
MagHwnd = CreateWindowEx(
0, MagnifierClassName, "ICCMagnifier",
WS_CHILD | WS_VISIBLE,
0, 0, 100, 100,
hwndParent.Handle, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
_owner._magHwnd = MagHwnd;
return new HandleRef(this, MagHwnd);
}
protected override void DestroyWindowCore(HandleRef hwnd)
{
if (hwnd.Handle != IntPtr.Zero)
{
DestroyWindow(hwnd.Handle);
}
MagHwnd = IntPtr.Zero;
}
}
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
_hwndSource = (HwndSource)PresentationSource.FromVisual(this);
ApplyTransform();
// 把本窗口排除在采样源外,避免自我递归
if (_hwndSource != null && _magHwnd != IntPtr.Zero)
{
MagSetWindowFilterList(_magHwnd, MW_FILTERMODE_EXCLUDE, 1, new[] { _hwndSource.Handle });
}
_timer = new DispatcherTimer(DispatcherPriority.Render) { Interval = TimeSpan.FromMilliseconds(33) };
_timer.Tick += OnTick;
_timer.Start();
}
private void ApplyTransform()
{
if (_magHwnd == IntPtr.Zero) return;
var m = new MAGTRANSFORM
{
m00 = _zoom, m01 = 0, m02 = 0,
m10 = 0, m11 = _zoom, m12 = 0,
m20 = 0, m21 = 0, m22 = 1.0f
};
MagSetWindowTransform(_magHwnd, ref m);
}
private void OnTick(object sender, EventArgs e)
{
if (_magHwnd == IntPtr.Zero || _hwndSource == null) return;
// 当前放大区域 = 放大镜承载控件在屏幕上的位置/尺寸
var hostSize = _magHost.RenderSize;
if (hostSize.Width <= 0 || hostSize.Height <= 0) return;
// 承载控件相对窗口的位置
var hostOffset = _magHost.TransformToAncestor(this).Transform(new Point(0, 0));
var screenTopLeft = PointToScreen(hostOffset);
var screenBottomRight = PointToScreen(new Point(hostOffset.X + hostSize.Width, hostOffset.Y + hostSize.Height));
int viewW = (int)(screenBottomRight.X - screenTopLeft.X);
int viewH = (int)(screenBottomRight.Y - screenTopLeft.Y);
if (viewW <= 0 || viewH <= 0) return;
int srcW = Math.Max(1, (int)(viewW / _zoom));
int srcH = Math.Max(1, (int)(viewH / _zoom));
int srcCx = (int)((screenTopLeft.X + screenBottomRight.X) / 2);
int srcCy = (int)((screenTopLeft.Y + screenBottomRight.Y) / 2);
var src = new RECT(srcCx - srcW / 2, srcCy - srcH / 2,
srcCx - srcW / 2 + srcW, srcCy - srcH / 2 + srcH);
MagSetWindowSource(_magHwnd, src);
InvalidateRect(_magHwnd, IntPtr.Zero, false);
}
protected override void OnClosed(EventArgs e)
{
if (_timer != null)
{
_timer.Stop();
_timer.Tick -= OnTick;
_timer = null;
}
if (_magInitialized)
{
MagUninitialize();
_magInitialized = false;
}
base.OnClosed(e);
}
}
}
+56 -4
View File
@@ -266,11 +266,63 @@
Click="InsertImageScreenshotButton_Click"/>
</StackPanel>
<!-- 隐藏占位(保留 .cs 引用的命名元素) -->
<Border x:Name="MagnifierSection" Visibility="Collapsed">
<!-- 区段:聚焦放大镜 -->
<Border x:Name="MagnifierSection">
<StackPanel>
<TextBlock x:Name="MagnifierTitleText" Text="聚焦放大镜"/>
<TextBlock x:Name="MagnifierDescText" Text="功能暂未设计">
<TextBlock x:Name="MagnifierTitleText"
Text="聚焦放大镜"
Style="{StaticResource GameBarSectionTitle}">
<TextBlock.Foreground>
<SolidColorBrush x:Name="MagnifierTitleForeground2Brush" Color="White"/>
</TextBlock.Foreground>
</TextBlock>
<Button x:Name="MagnifierToggleButton"
Content="开启放大镜"
Style="{StaticResource GameBarFlatButton}"
Margin="0,0,0,6"
Click="MagnifierToggleButton_Click"/>
<Border CornerRadius="8" Background="#1AFFFFFF" Padding="6" Margin="0,0,0,4">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
Text="缩放"
FontSize="11"
VerticalAlignment="Center"
Margin="4,0,8,0"
Foreground="White"/>
<Slider x:Name="MagnifierZoomSlider"
Grid.Column="1"
VerticalAlignment="Center"
Stylus.IsPressAndHoldEnabled="False"
Minimum="1.5"
Maximum="6"
Value="2"
TickFrequency="0.5"
IsSnapToTickEnabled="True"
ValueChanged="MagnifierZoomSlider_ValueChanged"/>
<TextBlock x:Name="MagnifierZoomValueText"
Grid.Column="2"
Text="2.0x"
FontSize="11"
FontWeight="SemiBold"
VerticalAlignment="Center"
MinWidth="36"
TextAlignment="Right"
Foreground="#E6FFFFFF"/>
</Grid>
</Border>
<TextBlock x:Name="MagnifierDescText"
Text="放大镜跟随鼠标,再次点击按钮关闭"
FontSize="11"
Margin="2,0,0,0"
TextWrapping="Wrap">
<TextBlock.Foreground>
<SolidColorBrush x:Name="MagnifierDescForegroundBrush" Color="#80FFFFFF"/>
</TextBlock.Foreground>
+46
View File
@@ -1857,6 +1857,46 @@ namespace Ink_Canvas.Windows
#endregion
#region
private void MagnifierToggleButton_Click(object sender, RoutedEventArgs e)
{
try
{
if (MagnifierWindow.HasInstance)
{
MagnifierWindow.HideInstance();
MagnifierToggleButton.Content = "开启放大镜";
}
else
{
MagnifierWindow.Show((float)MagnifierZoomSlider.Value);
if (MagnifierWindow.HasInstance)
{
MagnifierToggleButton.Content = "关闭放大镜";
}
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"切换聚焦放大镜失败: {ex.Message}", LogHelper.LogType.Error);
}
}
private void MagnifierZoomSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
if (MagnifierZoomValueText != null)
{
MagnifierZoomValueText.Text = $"{e.NewValue:0.0}x";
}
if (MagnifierWindow.HasInstance)
{
MagnifierWindow.SetZoom((float)e.NewValue);
}
}
#endregion
#region
/// <summary>
@@ -1865,6 +1905,12 @@ namespace Ink_Canvas.Windows
public void UpdateVisibility(bool isInPPTMode)
{
Visibility = isInPPTMode ? Visibility.Visible : Visibility.Collapsed;
if (!isInPPTMode && MagnifierWindow.HasInstance)
{
MagnifierWindow.HideInstance();
if (MagnifierToggleButton != null)
MagnifierToggleButton.Content = "开启放大镜";
}
}
#endregion