【问题标题】:.NET Equivalent of Snipping Tool.NET 等效的截图工具
【发布时间】:2011-03-08 14:47:45
【问题描述】:

我正在寻找与 Snipping Tool 执行相同功能的 .NET 代码 - 捕获屏幕区域。 我相信它使用钩子。知道它如何突出显示所选片段会很有趣。

更新: 找到http://www.codeproject.com/KB/vb/Screen_Shot.aspx。尽管人们说它缺少一些用于正确编译的重要文件。

【问题讨论】:

    标签: .net screen screenshot capture


    【解决方案1】:

    截图工具效果在 Windows 窗体中实现并不难。向您的项目添加一个新表单并将其命名为“SnippingTool”。使代码如下所示:

    using System;
    using System.Drawing;
    using System.Drawing.Drawing2D;
    using System.Windows.Forms;
    
    namespace WindowsFormsApplication1 {
        public partial class SnippingTool : Form {
            public static Image Snip() {
                var rc = Screen.PrimaryScreen.Bounds;
                using (Bitmap bmp = new Bitmap(rc.Width, rc.Height, System.Drawing.Imaging.PixelFormat.Format32bppPArgb)) {
                    using (Graphics gr = Graphics.FromImage(bmp))
                        gr.CopyFromScreen(0, 0, 0, 0, bmp.Size);
                    using (var snipper = new SnippingTool(bmp)) {
                        if (snipper.ShowDialog() == DialogResult.OK) {
                            return snipper.Image;
                        }
                    }
                    return null;
                }
            }
    
            public SnippingTool(Image screenShot) {
                InitializeComponent();
                this.BackgroundImage = screenShot;
                this.ShowInTaskbar = false;
                this.FormBorderStyle = FormBorderStyle.None;
                this.WindowState = FormWindowState.Maximized;
                this.DoubleBuffered = true;
            }
            public Image Image { get; set; }
    
            private Rectangle rcSelect = new Rectangle();
            private Point pntStart;
    
            protected override void OnMouseDown(MouseEventArgs e) {
                // Start the snip on mouse down
                if (e.Button != MouseButtons.Left) return;
                pntStart = e.Location;
                rcSelect = new Rectangle(e.Location, new Size(0, 0));
                this.Invalidate();
            }
            protected override void OnMouseMove(MouseEventArgs e) {
                // Modify the selection on mouse move
                if (e.Button != MouseButtons.Left) return;
                int x1 = Math.Min(e.X, pntStart.X);
                int y1 = Math.Min(e.Y, pntStart.Y);
                int x2 = Math.Max(e.X, pntStart.X);
                int y2 = Math.Max(e.Y, pntStart.Y);
                rcSelect = new Rectangle(x1, y1, x2 - x1, y2 - y1);
                this.Invalidate();
            }
            protected override void OnMouseUp(MouseEventArgs e) {
                // Complete the snip on mouse-up
                if (rcSelect.Width <= 0 || rcSelect.Height <= 0) return;
                Image = new Bitmap(rcSelect.Width, rcSelect.Height);
                using (Graphics gr = Graphics.FromImage(Image)) {
                    gr.DrawImage(this.BackgroundImage, new Rectangle(0, 0, Image.Width, Image.Height),
                        rcSelect, GraphicsUnit.Pixel);
                }
                DialogResult = DialogResult.OK;
            }
            protected override void OnPaint(PaintEventArgs e) {
                // Draw the current selection
                using (Brush br = new SolidBrush(Color.FromArgb(120, Color.White))) {
                    int x1 = rcSelect.X; int x2 = rcSelect.X + rcSelect.Width;
                    int y1 = rcSelect.Y; int y2 = rcSelect.Y + rcSelect.Height;
                    e.Graphics.FillRectangle(br, new Rectangle(0, 0, x1, this.Height));
                    e.Graphics.FillRectangle(br, new Rectangle(x2, 0, this.Width - x2, this.Height));
                    e.Graphics.FillRectangle(br, new Rectangle(x1, 0, x2 - x1, y1));
                    e.Graphics.FillRectangle(br, new Rectangle(x1, y2, x2 - x1, this.Height - y2));
                }
                using (Pen pen = new Pen(Color.Red, 3)) {
                    e.Graphics.DrawRectangle(pen, rcSelect);
                }
            }
            protected override bool ProcessCmdKey(ref Message msg, Keys keyData) {
                // Allow canceling the snip with the Escape key
                if (keyData == Keys.Escape) this.DialogResult = DialogResult.Cancel;
                return base.ProcessCmdKey(ref msg, keyData);
            }
        }
    }
    

    用法:

    var bmp = SnippingTool.Snip();
    if (bmp != null) {
        // Do something with the bitmap
        //...
    }
    

    【讨论】:

    • Hans,你能帮忙使这个工具与辅助显示器兼容吗?尝试添加屏幕参数,并根据它调整sniper位置,但得到了意想不到的结果。
    • 您希望我如何处理“意外结果”?提出一个新问题并真正记录您的问题。
    【解决方案2】:

    这是修改后的@Hans 版本,兼容多台显示器并与 DPI 缩放配合良好(在 Windows 7 和 Windows 10 上测试)。

    public sealed partial class SnippingTool : Form
    {
        public static event EventHandler Cancel;
        public static event EventHandler AreaSelected;
        public static Image Image { get; set; }
    
        private static SnippingTool[] _forms;
        private Rectangle _rectSelection;
        private Point _pointStart;
    
        public SnippingTool(Image screenShot, int x, int y, int width, int height)
        {
            InitializeComponent();
            BackgroundImage = screenShot;
            BackgroundImageLayout = ImageLayout.Stretch;
            ShowInTaskbar = false;
            FormBorderStyle = FormBorderStyle.None;
            StartPosition = FormStartPosition.Manual;
            SetBounds(x, y, width, height);
            WindowState = FormWindowState.Maximized;
            DoubleBuffered = true;
            Cursor = Cursors.Cross;
            TopMost = true;
        }
    
        private void OnCancel(EventArgs e)
        {
            Cancel?.Invoke(this, e);
        }
    
        private void OnAreaSelected(EventArgs e)
        {
            AreaSelected?.Invoke(this, e);
        }
    
        private void CloseForms()
        {
            for (int i = 0; i < _forms.Length; i++)
            {
                _forms[i].Dispose();
            }
        }
    
        public static void Snip()
        {
            var screens = ScreenHelper.GetMonitorsInfo();
            _forms = new SnippingTool[screens.Count];
            for (int i = 0; i < screens.Count; i++)
            {
                int hRes = screens[i].HorizontalResolution;
                int vRes = screens[i].VerticalResolution;
                int top = screens[i].MonitorArea.Top;
                int left = screens[i].MonitorArea.Left;
                var bmp = new Bitmap(hRes, vRes, PixelFormat.Format32bppPArgb);
                using (var g = Graphics.FromImage(bmp))
                {
                    g.CopyFromScreen(left, top, 0, 0, bmp.Size);
                }
                _forms[i] = new SnippingTool(bmp, left, top, hRes, vRes);
                _forms[i].Show();
            }
        }
    
        #region Overrides
        protected override void OnMouseDown(MouseEventArgs e)
        {
            // Start the snip on mouse down
            if (e.Button != MouseButtons.Left)
            {
                return;
            }
            _pointStart = e.Location;
            _rectSelection = new Rectangle(e.Location, new Size(0, 0));
            Invalidate();
        }
    
        protected override void OnMouseMove(MouseEventArgs e)
        {
            // Modify the selection on mouse move
            if (e.Button != MouseButtons.Left)
            {
                return;
            }
            int x1 = Math.Min(e.X, _pointStart.X);
            int y1 = Math.Min(e.Y, _pointStart.Y);
            int x2 = Math.Max(e.X, _pointStart.X);
            int y2 = Math.Max(e.Y, _pointStart.Y);
            _rectSelection = new Rectangle(x1, y1, x2 - x1, y2 - y1);
            Invalidate();
        }
    
        protected override void OnMouseUp(MouseEventArgs e)
        {
            // Complete the snip on mouse-up
            if (_rectSelection.Width <= 0 || _rectSelection.Height <= 0)
            {
                CloseForms();
                OnCancel(new EventArgs());
                return;
            }
            Image = new Bitmap(_rectSelection.Width, _rectSelection.Height);
            var hScale = BackgroundImage.Width / (double)Width;
            var vScale = BackgroundImage.Height / (double)Height;
            using (Graphics gr = Graphics.FromImage(Image))
            {
    
                gr.DrawImage(BackgroundImage, 
                    new Rectangle(0, 0, Image.Width, Image.Height),
                    new Rectangle((int)(_rectSelection.X * hScale), (int)(_rectSelection.Y * vScale), (int)(_rectSelection.Width * hScale), (int)(_rectSelection.Height * vScale)),  
                    GraphicsUnit.Pixel);
            }
            CloseForms();
            OnAreaSelected(new EventArgs());
        }
    
        protected override void OnPaint(PaintEventArgs e)
        {
            // Draw the current selection
            using (Brush br = new SolidBrush(Color.FromArgb(120, Color.White)))
            {
                int x1 = _rectSelection.X;
                int x2 = _rectSelection.X + _rectSelection.Width;
                int y1 = _rectSelection.Y;
                int y2 = _rectSelection.Y + _rectSelection.Height;
                e.Graphics.FillRectangle(br, new Rectangle(0, 0, x1, Height));
                e.Graphics.FillRectangle(br, new Rectangle(x2, 0, Width - x2, Height));
                e.Graphics.FillRectangle(br, new Rectangle(x1, 0, x2 - x1, y1));
                e.Graphics.FillRectangle(br, new Rectangle(x1, y2, x2 - x1, Height - y2));
            }
            using (Pen pen = new Pen(Color.Red, 2))
            {
                e.Graphics.DrawRectangle(pen, _rectSelection);
            }
        }
    
        protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
        {
            // Allow canceling the snip with the Escape key
            if (keyData == Keys.Escape)
            {
                Image = null;
                CloseForms();
                OnCancel(new EventArgs());
            }
            return base.ProcessCmdKey(ref msg, keyData);
        }
        #endregion
    }
    

    用法:

    SnippingTool.AreaSelected += OnAreaSelected; 
    SnippingTool.Snip();
    
    private static void OnAreaSelected(object sender, EventArgs e)
    {
        var bmp = SnippingTool.Image;
        // Do something with the bitmap
        //...
    }
    

    请注意,您需要一个帮助类来获取实际的显示器分辨率并避免 DPI 缩放问题。 这是代码:

    public class DeviceInfo
    {
        public string DeviceName { get; set; }
        public int VerticalResolution { get; set; }
        public int HorizontalResolution { get; set; }
        public Rectangle MonitorArea { get; set; }
    }
    public static class ScreenHelper
    {
        private const int DektopVertRes = 117;
        private const int DesktopHorzRes = 118;
        [StructLayout(LayoutKind.Sequential)]
        internal struct Rect
        {
            public int left;
            public int top;
            public int right;
            public int bottom;
        }
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        internal struct MONITORINFOEX
        {
            public int Size;
            public Rect Monitor;
            public Rect WorkArea;
            public uint Flags;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
            public string DeviceName;
        }
        private delegate bool MonitorEnumDelegate(IntPtr hMonitor, IntPtr hdcMonitor, ref Rect lprcMonitor, IntPtr dwData);
        [DllImport("user32.dll")]
        private static extern bool EnumDisplayMonitors(IntPtr hdc, IntPtr lprcClip, MonitorEnumDelegate lpfnEnum, IntPtr dwData);
        [DllImport("gdi32.dll")]
        private static extern IntPtr CreateDC(string lpszDriver, string lpszDevice, string lpszOutput, IntPtr lpInitData);
        [DllImport("user32.dll", CharSet = CharSet.Unicode)]
        private static extern bool GetMonitorInfo(IntPtr hMonitor, ref MONITORINFOEX lpmi);
        [DllImport("User32.dll")]
        private static extern int ReleaseDC(IntPtr hwnd, IntPtr dc);
        [DllImport("gdi32.dll")]
        private static extern int GetDeviceCaps(IntPtr hdc, int nIndex);
    
        private static List<DeviceInfo> _result;
    
        public static List<DeviceInfo> GetMonitorsInfo()
        {
            _result = new List<DeviceInfo>();
            EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, MonitorEnum, IntPtr.Zero);
            return _result;
        }
    
        private static bool MonitorEnum(IntPtr hMonitor, IntPtr hdcMonitor, ref Rect lprcMonitor, IntPtr dwData)
        {
            var mi = new MONITORINFOEX();
            mi.Size = Marshal.SizeOf(typeof(MONITORINFOEX));
            bool success = GetMonitorInfo(hMonitor, ref mi);
            if (success)
            {
                var dc = CreateDC(mi.DeviceName, mi.DeviceName, null, IntPtr.Zero);
                var di = new DeviceInfo
                {
                    DeviceName = mi.DeviceName,
                    MonitorArea = new Rectangle(mi.Monitor.left, mi.Monitor.top, mi.Monitor.right-mi.Monitor.right, mi.Monitor.bottom-mi.Monitor.top),
                    VerticalResolution = GetDeviceCaps(dc, DektopVertRes),
                    HorizontalResolution = GetDeviceCaps(dc, DesktopHorzRes)
                };
                ReleaseDC(IntPtr.Zero, dc);
                _result.Add(di);
            }
            return true;
        }
    }
    

    这里是完整的source code

    【讨论】:

    • 它可以,但是在高 Dpi 下截取图像时图像模糊,如果保存它,图像太小,尝试修复它但无法修复。 @thepirat000
    • @Arvand 你的 DPI 配置是什么?
    • 3840x2160,文字大小设置为 250% [4K 显示]
    • @thepirat000 谢谢,效果很好!!我想知道如何修改编。能够通过两台显示器进行剪辑??实际上,您的工具无法离开选定的监视器。
    【解决方案3】:

    它需要一个全屏截图,然后(可能)复制它,应用半透明效果并显示它。当您单击并拖动它时,它可以覆盖原始捕获的相应区域。

    您可以使用CopyFromScreen()GDI API 获取屏幕截图。

    【讨论】:

    • 感谢您的精彩回答。这就说得通了。也许您知道如何将“白化”效果应用于位图?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-02-22
    • 2021-06-20
    • 1970-01-01
    相关资源
    最近更新 更多