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

如何在C#中实现全屏应用运行时仍能在显示器上绘制自定义十字准星

如何在C#中实现全屏应用运行时仍能在显示器上绘制自定义十字准星

嘿,我明白你的问题了——像CS 1.6这类老游戏的全屏模式会独占显示输出,直接把画面渲染到显存里,你之前用桌面DC绘制的内容会被完全覆盖掉。别担心,我们换个思路,用一个始终置顶的透明顶层窗口来承载十字准星,这样就能穿透全屏应用显示,而且不会干扰游戏的鼠标操作。

问题根源

你之前的代码是通过GetDC(IntPtr.Zero)获取桌面设备上下文来绘制,但全屏DirectX/OpenGL应用会绕过桌面窗口管理器(DWM),直接控制显示适配器,所以桌面DC的绘制内容会被覆盖。而顶层透明窗口是作为独立窗口存在,系统会保证它在所有窗口之上(包括全屏应用)。

解决方案:改造为透明顶层窗口

我们需要修改你的Form,让它变成一个覆盖整个屏幕、完全透明(除了十字准星)、不拦截鼠标输入的始终置顶窗口,然后在这个窗口上绘制十字准星。

以下是修改后的完整代码:

using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace AimChanger
{
    public partial class Form1 : Form
    {
        private bool dragging = false;
        private Point dragCursorPoint;
        private Point dragFormPoint;
        private Timer drawTimer;

        // 用于设置窗口扩展样式
        [DllImport("user32.dll")]
        private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
        [DllImport("user32.dll")]
        private static extern int GetWindowLong(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 int LWA_COLORKEY = 0x1;
        private const int LWA_ALPHA = 0x2;

        public Form1()
        {
            InitializeComponent();
            // 初始化透明顶层窗口设置
            SetupOverlayWindow();
            // 保留你原来的UI交互逻辑
            pictureBox2.Paint += PictureBox2_Paint;
            pictureBox2.MouseDown += PictureBox2_MouseDown;
            pictureBox2.MouseMove += PictureBox2_MouseMove;
            pictureBox2.MouseUp += PictureBox2_MouseUp;
            ApplyRoundedCornersToPictureBox();
            textBox1.Text = trackBar1.Value.ToString();
            textBox2.Text = trackBar2.Value.ToString();
            textBox3.Text = trackBar3.Value.ToString();
            textBox4.Text = trackBar4.Value.ToString();
            textBox5.Text = trackBar5.Value.ToString();
            textBox6.Text = trackBar6.Value.ToString();
            textBox7.Text = trackBar7.Value.ToString();
            textBox8.Text = trackBar8.Value.ToString();
            checkBox2.CheckedChanged += checkBox2_CheckedChanged;

            // 设置绘制定时器
            drawTimer = new Timer() { Interval = 16 };
            drawTimer.Tick += (s, e) => { this.Invalidate(); }; // 触发窗口重绘
            drawTimer.Start();
        }

        private void SetupOverlayWindow()
        {
            // 设置窗口为无边框、始终置顶
            this.FormBorderStyle = FormBorderStyle.None;
            this.TopMost = true;
            // 让窗口覆盖整个主屏幕
            this.Bounds = Screen.PrimaryScreen.Bounds;
            // 设置扩展样式:分层窗口 + 透明(不拦截鼠标)
            int exStyle = GetWindowLong(this.Handle, GWL_EXSTYLE);
            exStyle |= WS_EX_LAYERED | WS_EX_TRANSPARENT;
            SetWindowLong(this.Handle, GWL_EXSTYLE, exStyle);
            // 设置窗口完全透明(我们会在Paint事件里绘制十字准星)
            SetLayeredWindowAttributes(this.Handle, 0, 255, LWA_ALPHA);
            // 确保窗口在屏幕分辨率变化时自动调整大小
            Screen.PrimaryScreen.DeviceChanged += (s, e) => { this.Bounds = Screen.PrimaryScreen.Bounds; };
        }

        // 在窗口的Paint事件里绘制十字准星
        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);
            if (checkBox1.Checked)
            {
                DrawCustomCrosshair(e.Graphics);
            }
        }

        private void DrawCustomCrosshair(Graphics g)
        {
            int screenCenterX = this.Bounds.Width / 2;
            int screenCenterY = this.Bounds.Height / 2;
            int size = trackBar1.Value;
            int thickness = trackBar2.Value;
            int gap = trackBar3.Value;
            int alpha = trackBar4.Value;
            int outline = trackBar5.Value;
            Color crosshairColor = Color.FromArgb(alpha, trackBar6.Value, trackBar7.Value, trackBar8.Value);
            g.SmoothingMode = SmoothingMode.AntiAlias;
            
            // 绘制描边
            if (outline > 0)
            {
                using (Pen outlinePen = new Pen(Color.FromArgb(alpha, Color.Black), thickness + outline * 2))
                {
                    g.DrawLine(outlinePen, screenCenterX - size - gap, screenCenterY, screenCenterX - gap, screenCenterY);
                    g.DrawLine(outlinePen, screenCenterX + gap, screenCenterY, screenCenterX + size + gap, screenCenterY);
                    g.DrawLine(outlinePen, screenCenterX, screenCenterY - size - gap, screenCenterX, screenCenterY - gap);
                    g.DrawLine(outlinePen, screenCenterX, screenCenterY + gap, screenCenterX, screenCenterY + size + gap);
                }
            }
            // 绘制十字准星主体
            using (Pen crosshairPen = new Pen(crosshairColor, thickness))
            {
                g.DrawLine(crosshairPen, screenCenterX - size - gap, screenCenterY, screenCenterX - gap, screenCenterY);
                g.DrawLine(crosshairPen, screenCenterX + gap, screenCenterY, screenCenterX + size + gap, screenCenterY);
                g.DrawLine(crosshairPen, screenCenterX, screenCenterY - size - gap, screenCenterX, screenCenterY - gap);
                g.DrawLine(crosshairPen, screenCenterX, screenCenterY + gap, screenCenterX, screenCenterY + size + gap);
            }
            // 绘制中心点
            if (checkBox2.Checked)
            {
                int dotSize = 5;
                using (Brush dotBrush = new SolidBrush(Color.FromArgb(alpha, trackBar6.Value, trackBar7.Value, trackBar8.Value)))
                {
                    g.FillEllipse(dotBrush, screenCenterX - dotSize / 2, screenCenterY - dotSize / 2, dotSize, dotSize);
                }
            }
        }

        // 以下保留你原来的其他方法,不需要修改
        private void PictureBox2_Paint(object sender, PaintEventArgs e)
        {
            DrawGradientText(e.Graphics, pictureBox2.ClientRectangle);
        }
        [DllImport("Gdi32.dll", EntryPoint = "CreateRoundRectRgn")]
        private static extern IntPtr CreateRoundRectRgn(int nLeftRect, int nTopRect, int nRightRect, int nBottomRect, int nWidthEllipse, int nHeightEllipse);
        private void ApplyRoundedCornersToPictureBox()
        {
            int radius = 30;
            IntPtr region = CreateRoundRectRgn(0, 0, pictureBox3.Width, pictureBox3.Height, radius, radius);
            pictureBox3.Region = Region.FromHrgn(region);
        }
        private void DrawGradientText(Graphics g, Rectangle bounds)
        {
            string text1 = "Crosshair";
            string text2 = "Changer";
            Font font = new Font("Consolas", 18, FontStyle.Bold);
            Point text1Location = new Point(bounds.Left + 10, bounds.Top + 15);
            Point text2Location = new Point(bounds.Left + 135, bounds.Top + 15);
            using (SolidBrush whiteBrush = new SolidBrush(Color.White))
            {
                g.SmoothingMode = SmoothingMode.AntiAlias;
                g.DrawString(text1, font, whiteBrush, text1Location);
            }
            Rectangle gradientRect = new Rectangle(text2Location, new Size(200, 50));
            Color[] colors = { Color.Purple, Color.Red, Color.Orange };
            using (LinearGradientBrush brush = new LinearGradientBrush(gradientRect, Color.White, Color.Red, LinearGradientMode.Horizontal))
            {
                ColorBlend blend = new ColorBlend
                {
                    Colors = colors,
                    Positions = new float[] { 0f, 0.5f, 1f }
                };
                brush.InterpolationColors = blend;
                g.DrawString(text2, font, brush, text2Location);
            }
        }
        private void ApplyRoundedCorners()
        {
            int radius = 50;
            Region = new Region(new GraphicsPath());
            GraphicsPath path = new GraphicsPath();
            path.AddArc(0, 0, radius, radius, 180, 90);
            path.AddArc(Width - radius, 0, radius, radius, 270, 90);
            path.AddArc(Width - radius, Height - radius, radius, radius, 0, 90);
            path.AddArc(0, Height - radius, radius, radius, 90, 90);
            path.CloseFigure();
            Region = new Region(path);
        }
        private void Form1_Load(object sender, EventArgs e)
        {
            ApplyRoundedCorners();
        }
        private void Form1_Resize(object sender, EventArgs e)
        {
            ApplyRoundedCorners();
        }
        private void PictureBox2_MouseDown(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                dragging = true;
                dragCursorPoint = Cursor.Position;
                dragFormPoint = this.Location;
            }
        }
        private void PictureBox2_MouseMove(object sender, MouseEventArgs e)
        {
            if (dragging)
            {
                Point diff = new Point(Cursor.Position.X - dragCursorPoint.X, Cursor.Position.Y - dragCursorPoint.Y);
                this.Location = new Point(dragFormPoint.X + diff.X, dragFormPoint.Y + diff.Y);
            }
        }
        private void PictureBox2_MouseUp(object sender, MouseEventArgs e)
        {
            dragging = false;
        }
        private void label2_Click(object sender, EventArgs e)
        {
            Environment.Exit(0);
        }
        private void label3_Click(object sender, EventArgs e)
        {
            this.WindowState = FormWindowState.Minimized;
        }
        private void trackBar1_Scroll(object sender, EventArgs e)
        {
            textBox1.Text = trackBar1.Value.ToString();
        }
        private void trackBar2_Scroll(object sender, EventArgs e)
        {
            textBox2.Text = trackBar2.Value.ToString();
        }
        private void trackBar3_Scroll(object sender, EventArgs e)
        {
            textBox3.Text = trackBar3.Value.ToString();
        }
        private void trackBar4_Scroll(object sender, EventArgs e)
        {
            textBox4.Text = trackBar4.Value.ToString();
        }
        private void trackBar5_Scroll(object sender, EventArgs e)
        {
            textBox5.Text = trackBar5.Value.ToString();
        }
        private void checkBox2_CheckedChanged(object sender, EventArgs e)
        {
            this.Invalidate();
        }
    }
}

关键修改点说明

  1. 窗口样式设置:通过SetWindowLong给窗口添加WS_EX_LAYERED(分层窗口,支持透明)和WS_EX_TRANSPARENT(让窗口不拦截鼠标输入,游戏能正常接收点击)样式。
  2. 窗口大小与置顶:设置窗口无边框、覆盖整个屏幕,并且始终置顶,确保它在全屏应用之上。
  3. 绘制逻辑迁移:把原来的桌面DC绘制改成在窗口的OnPaint事件里绘制,这样十字准星会直接画在透明窗口上,不会被全屏应用覆盖。
  4. 定时器触发重绘:用定时器每隔16ms触发窗口重绘,保证十字准星的实时性(和游戏帧率匹配)。

额外注意事项

  • 如果你的系统开启了全屏优化,可能需要在游戏的属性里关闭它,确保窗口能正常显示在游戏之上。
  • 部分反作弊软件可能会检测这类顶层窗口,CS 1.6这类老游戏一般没问题,但如果遇到检测,可能需要用更底层的DirectOverlay方案,但那会复杂很多。

备注:内容来源于stack exchange,提问作者kidprogramer

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.14 09:59:35