You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何开发类似Epic Pen的屏幕透明绘图应用?

实现类似Epic Pen的屏幕绘图应用解决方案

一、实现MSPaint风格的笔触绘制效果

要模拟连续流畅的笔触,核心是实时追踪鼠标轨迹并绘制连续线条,而非仅生成静态图形:

  • 维护两个集合:_currentStroke记录当前正在绘制的轨迹点,_allStrokes存储已完成的所有笔触
  • 鼠标按下时启动轨迹记录,移动过程中持续添加点并触发窗体重绘,鼠标松开时完成当前笔触的保存
  • 绘制时使用DrawLines连接轨迹点,开启抗锯齿(SmoothingMode.AntiAlias)让笔触边缘更自然

二、解决透明窗体点击穿透问题

放弃TransparencyKey方案,改用**分层窗口(Layered Window)**实现透明且保留鼠标交互:

  • 通过Win32 API设置窗体扩展样式为WS_EX_LAYERED,支持分层绘制
  • 结合WS_EX_TRANSPARENT样式控制鼠标事件穿透:绘图模式下移除该样式,让窗体接收鼠标事件;光标模式下添加该样式,允许事件传递到下层窗口
  • 使用SetLayeredWindowAttributes设置窗体透明度,确保背景透明但绘制内容可见

三、绘图/光标模式切换逻辑

通过全局快捷键或UI按钮触发模式切换:

  • 绘图模式:显示专用光标(如十字光标),移除WS_EX_TRANSPARENT样式,允许窗体捕获鼠标事件进行绘制
  • 光标模式:恢复系统默认光标,添加WS_EX_TRANSPARENT样式,让鼠标事件穿透到下层窗口,同时暂停绘制逻辑

关键代码示例

窗体初始化与Win32 API导入

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using System.Runtime.InteropServices;

public class ScreenDrawForm : Form
{
    private List<Point> _currentStroke = new List<Point>();
    private List<List<Point>> _allStrokes = new List<List<Point>>();
    private Pen _drawPen = new Pen(Color.Red, 5);
    private bool _isDrawing = false;
    private bool _isDrawMode = true;

    // Win32 API声明
    [DllImport("user32.dll")]
    private static extern IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr dwNewLong);

    [DllImport("user32.dll")]
    private static extern IntPtr GetWindowLongPtr(IntPtr hWnd, int nIndex);

    [DllImport("user32.dll")]
    private static extern bool SetLayeredWindowAttributes(IntPtr hWnd, uint crKey, byte bAlpha, uint dwFlags);

    private const int GWL_EXSTYLE = -20;
    private const int WS_EX_LAYERED = 0x80000;
    private const int WS_EX_TRANSPARENT = 0x20;
    private const uint LWA_ALPHA = 0x2;

    public ScreenDrawForm()
    {
        FormBorderStyle = FormBorderStyle.None;
        WindowState = FormWindowState.Maximized;
        TopMost = true;
        // 初始化分层窗口
        var exStyle = GetWindowLongPtr(Handle, GWL_EXSTYLE);
        SetWindowLongPtr(Handle, GWL_EXSTYLE, (IntPtr)(exStyle.ToInt64() | WS_EX_LAYERED));
        SetLayeredWindowAttributes(Handle, 0, 255, LWA_ALPHA);
        Cursor = Cursors.Cross;

        // 注册F1为模式切换快捷键
        RegisterHotKey(Handle, 1, 0, (int)Keys.F1);
    }

鼠标事件与绘制逻辑

protected override void OnMouseDown(MouseEventArgs e)
    {
        if (_isDrawMode)
        {
            _isDrawing = true;
            _currentStroke.Clear();
            _currentStroke.Add(e.Location);
        }
        base.OnMouseDown(e);
    }

    protected override void OnMouseMove(MouseEventArgs e)
    {
        if (_isDrawing && _isDrawMode)
        {
            _currentStroke.Add(e.Location);
            Invalidate();
        }
        base.OnMouseMove(e);
    }

    protected override void OnMouseUp(MouseEventArgs e)
    {
        if (_isDrawing && _isDrawMode)
        {
            _currentStroke.Add(e.Location);
            _allStrokes.Add(new List<Point>(_currentStroke));
            _isDrawing = false;
            Invalidate();
        }
        base.OnMouseUp(e);
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        base.OnPaint(e);
        e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
        // 绘制所有已完成的笔触
        foreach (var stroke in _allStrokes)
        {
            if (stroke.Count > 1)
                e.Graphics.DrawLines(_drawPen, stroke.ToArray());
        }
        // 绘制当前正在进行的笔触
        if (_currentStroke.Count > 1)
            e.Graphics.DrawLines(_drawPen, _currentStroke.ToArray());
    }

模式切换实现

protected override void WndProc(ref Message m)
    {
        const int WM_HOTKEY = 0x0312;
        if (m.Msg == WM_HOTKEY && m.WParam.ToInt32() == 1)
        {
            ToggleMode();
        }
        base.WndProc(ref m);
    }

    private void ToggleMode()
    {
        _isDrawMode = !_isDrawMode;
        var exStyle = GetWindowLongPtr(Handle, GWL_EXSTYLE);
        if (_isDrawMode)
        {
            Cursor = Cursors.Cross;
            // 移除透明穿透样式,允许接收鼠标事件
            SetWindowLongPtr(Handle, GWL_EXSTYLE, (IntPtr)(exStyle.ToInt64() & ~WS_EX_TRANSPARENT));
        }
        else
        {
            Cursor = Cursors.Default;
            // 添加透明穿透样式,让鼠标事件传递到下层窗口
            SetWindowLongPtr(Handle, GWL_EXSTYLE, (IntPtr)(exStyle.ToInt64() | WS_EX_TRANSPARENT));
        }
    }

    [DllImport("user32.dll")]
    private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, int vk);

    [DllImport("user32.dll")]
    private static extern bool UnregisterHotKey(IntPtr hWnd, int id);

    protected override void Dispose(bool disposing)
    {
        UnregisterHotKey(Handle, 1);
        base.Dispose(disposing);
    }
}

内容的提问来源于stack exchange,提问作者user123

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 15:55:19