diff --git a/Ink Canvas/Windows/MagnifierWindow.cs b/Ink Canvas/Windows/MagnifierWindow.cs
new file mode 100644
index 00000000..d8e035d3
--- /dev/null
+++ b/Ink Canvas/Windows/MagnifierWindow.cs
@@ -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
+{
+ ///
+ /// 可拖动、可调整大小的屏幕放大镜窗口。
+ /// 底层使用 Win32 Magnification API (Magnification.dll) 将窗口当前所在区域下方的屏幕内容放大显示。
+ ///
+ 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);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ink Canvas/Windows/PPTQuickPanel.xaml b/Ink Canvas/Windows/PPTQuickPanel.xaml
index 434e15a5..1f0f4a88 100644
--- a/Ink Canvas/Windows/PPTQuickPanel.xaml
+++ b/Ink Canvas/Windows/PPTQuickPanel.xaml
@@ -266,11 +266,63 @@
Click="InsertImageScreenshotButton_Click"/>
-
-
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Ink Canvas/Windows/PPTQuickPanel.xaml.cs b/Ink Canvas/Windows/PPTQuickPanel.xaml.cs
index 7dfeee0e..0d958594 100644
--- a/Ink Canvas/Windows/PPTQuickPanel.xaml.cs
+++ b/Ink Canvas/Windows/PPTQuickPanel.xaml.cs
@@ -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 e)
+ {
+ if (MagnifierZoomValueText != null)
+ {
+ MagnifierZoomValueText.Text = $"{e.NewValue:0.0}x";
+ }
+ if (MagnifierWindow.HasInstance)
+ {
+ MagnifierWindow.SetZoom((float)e.NewValue);
+ }
+ }
+
+ #endregion
+
#region 公开方法
///
@@ -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