WPF截图应用:矩形坐标转屏幕坐标问题求助
问题原因分析
你的截图区域偏移/仅捕获部分内容,核心问题有两个:
- WPF与GDI+的坐标体系不匹配:WPF使用设备无关像素(DIP),而
System.Drawing.CopyFromScreen使用物理屏幕像素,当系统启用高DPI缩放(如125%、150%)时,两者坐标会出现偏移。 - 坐标转换对象错误:你用Window的
PointToScreen方法转换Canvas内部坐标,虽然Canvas占满窗口,但DPI缩放或窗口布局差异会导致转换后的屏幕坐标不准确。
解决方案
1. 获取DPI缩放比例
添加方法获取当前窗口的DPI缩放因子,用于将WPF的DIP坐标转换为物理像素:
private double GetDpiScale() { var source = PresentationSource.FromVisual(this); if (source?.CompositionTarget != null) { return source.CompositionTarget.TransformToDevice.M11; } return 1.0; }
2. 修正坐标获取与转换逻辑
直接在鼠标事件中获取屏幕坐标(而非Canvas内部坐标),再转换为Canvas坐标用于绘制选区,最后通过DPI缩放得到物理像素坐标用于截图:
完整修正后的C#代码
using System; using System.Drawing; using System.Windows; using System.Windows.Input; namespace ScreenCapture { public partial class MainWindow : Window { private System.Windows.Shapes.Rectangle DragRectangle = null; private System.Windows.Point StartPoint, LastPoint; public MainWindow() { InitializeComponent(); } private void Window_PreviewKeyDown(object sender, KeyEventArgs e) { if (e.Key == Key.Escape) { this.Close(); e.Handled = true; } } private void canvas_MouseDown(object sender, MouseButtonEventArgs e) { // 直接获取屏幕坐标 StartPoint = Mouse.GetPosition(null); LastPoint = StartPoint; DragRectangle = new System.Windows.Shapes.Rectangle { Width = 1, Height = 1, Stroke = System.Windows.Media.Brushes.Red, StrokeThickness = 1, Cursor = Cursors.Cross }; canvas.Children.Add(DragRectangle); // 转换为Canvas内部坐标,用于绘制选区 var canvasStart = canvas.PointFromScreen(StartPoint); Canvas.SetLeft(DragRectangle, canvasStart.X); Canvas.SetTop(DragRectangle, canvasStart.Y); canvas.MouseMove += canvas_MouseMove; canvas.MouseUp += canvas_MouseUp; canvas.CaptureMouse(); } private void canvas_MouseMove(object sender, MouseEventArgs e) { if (DragRectangle == null) return; LastPoint = Mouse.GetPosition(null); var canvasLast = canvas.PointFromScreen(LastPoint); var canvasStart = canvas.PointFromScreen(StartPoint); // 更新选区尺寸与位置 DragRectangle.Width = Math.Abs(canvasLast.X - canvasStart.X); DragRectangle.Height = Math.Abs(canvasLast.Y - canvasStart.Y); Canvas.SetLeft(DragRectangle, Math.Min(canvasLast.X, canvasStart.X)); Canvas.SetTop(DragRectangle, Math.Min(canvasLast.Y, canvasStart.Y)); } private void canvas_MouseUp(object sender, MouseButtonEventArgs e) { if (DragRectangle == null) return; canvas.ReleaseMouseCapture(); double dpiScale = GetDpiScale(); // 计算物理屏幕像素坐标 int x = (int)(Math.Min(LastPoint.X, StartPoint.X) * dpiScale); int y = (int)(Math.Min(LastPoint.Y, StartPoint.Y) * dpiScale); int width = (int)(Math.Abs(LastPoint.X - StartPoint.X) * dpiScale); int height = (int)(Math.Abs(LastPoint.Y - StartPoint.Y) * dpiScale); if (width > 0 && height > 0) { CaptureScreen(x, y, width, height); } this.Close(); } private double GetDpiScale() { var source = PresentationSource.FromVisual(this); if (source?.CompositionTarget != null) { return source.CompositionTarget.TransformToDevice.M11; } return 1.0; } private void CaptureScreen(int x, int y, int width, int height) { using (Bitmap bitmap = new Bitmap(width, height)) { using (Graphics g = Graphics.FromImage(bitmap)) { // 直接使用物理像素坐标捕获屏幕 g.CopyFromScreen(x, y, 0, 0, new System.Drawing.Size(width, height)); } string desktopPath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop); string filePath = System.IO.Path.Combine(desktopPath, $"Screenshot_{DateTime.Now:yyyyMMdd_HHmmss}.png"); bitmap.Save(filePath); MessageBox.Show($"Screenshot saved to {filePath}"); } } } }
3. XAML代码无需修改
保持你原有的XAML布局即可,因为窗口已经设置为全屏透明,Canvas占满整个窗口。
内容的提问来源于stack exchange,提问作者pcinfogmach
相关产品推荐
相关产品推荐

