c# - 如何在 c# 中的另一个程序之上制作非交互式图形叠加?

标签 c# image transparency non-interactive click-through

为了提供一些背景信息,我正在开发一款软件来帮助玩家玩星球大战:旧共和国。该游戏的用户界面功能非常有限,因此我正在开发一个外部应用程序,该应用程序将实时解析日志,并输出视觉线索以帮助用户最大限度地提高游戏性能。例如,如果一个角色获得了某种“buff”,战斗日志就会显示出来,我想在屏幕上放置一个视觉线索(这样用户就不需要注意周围的小图标了屏幕)。

在开始之前,我想为自己创建一些“概念证明”脚本,以弄清楚我将如何处理主要部分。我被困在那里我有我的问题:

我需要能够在游戏的屏幕上显示图形,可能是具有透明度的 PNG 文件。用户需要能够点击该图像,以便他们可以继续与游戏进行交互。我对如何去做这件事有点迷茫。要求是:

  • 显示一张图片(或多张图片,就此而言)
  • 即使没有应用程序“焦点”,也可以将该图像粘贴在其他应用程序之上
  • 使该图像不可交互(或可点击)。
  • 我正在用 C# 开发应用程序

  • 任何关于从哪里开始的指导将不胜感激!

    最佳答案

    我已经开始考虑做一些类似的事情,所以这可能会给你一个开始。

    对于第一个版本,您可能首先查看 Form 的 "AllowTransparency"、"TransparencyKey"和 "TopMost"属性。 .

    (我发现 TransparencyKey 不适用于白色 (255,255,255),但特定的非白色颜色工作正常......不知道为什么)。

    这将作为一个可点击的表单工作,它可以站在其他表单之上……但由于它是透明的,因此您无法在透明部分显示图像。但是,如果您只需要一个适合目标应用程序的 hud,这可能是最简单的方法。

    如果这个顶级表单没有出现在游戏前面……你可以尝试将游戏置于窗口模式。

    在全屏模式下运行时,游戏一般通过ActiveX、Direct3D、OpenGL、DirectDraw等直接绘制到屏幕上。

    在这些之上绘制需要将代码注入(inject) DirectX、OpenGL 或其他引擎的绘制/更新/刷新功能(基本上告诉 DirectX3D 在每个绘制周期结束时绘制您的东西)。有一些现有的软件可以做到这一点:例如,Steam Overlay、fraps、xfire。

    谷歌快速搜索发现“Game Overlay”,虽然我没有下载或尝试过,但它说它可以为你在游戏之上覆盖表单应用程序。

    (似乎该计划隶属于一家刚刚解散的公司,无论如何我似乎无法让它为我工作......)

    可以创建一个不完全透明的表单,但可以通过进行 native Windows 调用来单击通过。我将在接下来的几天内查看是否可以创建一个示例。

    我找到了一个旧的测试项目并清理了一下。

    基本上在运行时,它会在屏幕前面绘制 500 条随机红线,这些线是可点击的。然后它绘制 1000 条随机白线(即删除)。然后重复。

    在编写代码时,我想获得几件事的概念证明:如何能够在表单的整个表面上绘制,如何以编程方式使表单在多个屏幕上成为全尺寸,如何利用后台工作人员,以及此概念证明如何作为透明覆盖层工作。

    指示:

  • 创建一个名为 TranparentOverlay_simpleExample 的新 Windows 窗体项目
  • 在设计 View 中,在 Form1 上设置以下属性:
  • 背景色:白色
  • FormBorderStyle:无
  • 位置:-1280, 0(即屏幕的左上角,一个屏幕可能只有 0,0)
  • TopMost:真
  • 透明键:白色
  • WindowState:最大化

  • 现在输入 Form1 的代码 View 并将其替换为以下内容:
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    using System.Windows.Forms;
    using System.Threading;
    using System.Runtime.InteropServices;
    namespace TransparentOverlay_simpleExample
    {
    
        public partial class Form1 : Form
        {
            BackgroundWorker bw = new BackgroundWorker();
            Random rand = new Random(DateTime.Now.Millisecond);
    
            [DllImport("user32.dll")]
            [return: MarshalAs(UnmanagedType.Bool)]
            static extern bool BringWindowToTop(IntPtr hWnd);
    
            [DllImport("user32.dll")]
            [return: MarshalAs(UnmanagedType.Bool)]
            static extern bool SetForegroundWindow(IntPtr hWnd);
    
            [DllImport("user32.dll", EntryPoint = "SetWindowPos")]
            public static extern IntPtr SetWindowPos(IntPtr hWnd, int hWndInsertAfter, int x, int Y, int cx, int cy, int wFlags);
    
    
    
            [DllImport("user32.dll")]
            static extern int SendMessage(IntPtr hWnd, uint wMsg, UIntPtr wParam, IntPtr lParam); //used for maximizing the screen
    
            const int WM_SYSCOMMAND = 0x0112; //used for maximizing the screen.
            const int myWParam = 0xf120; //used for maximizing the screen.
            const int myLparam = 0x5073d; //used for maximizing the screen.
    
    
            int oldWindowLong;
    
            [Flags]
            enum WindowStyles : uint
            {
                WS_OVERLAPPED = 0x00000000,
                WS_POPUP = 0x80000000,
                WS_CHILD = 0x40000000,
                WS_MINIMIZE = 0x20000000,
                WS_VISIBLE = 0x10000000,
                WS_DISABLED = 0x08000000,
                WS_CLIPSIBLINGS = 0x04000000,
                WS_CLIPCHILDREN = 0x02000000,
                WS_MAXIMIZE = 0x01000000,
                WS_BORDER = 0x00800000,
                WS_DLGFRAME = 0x00400000,
                WS_VSCROLL = 0x00200000,
                WS_HSCROLL = 0x00100000,
                WS_SYSMENU = 0x00080000,
                WS_THICKFRAME = 0x00040000,
                WS_GROUP = 0x00020000,
                WS_TABSTOP = 0x00010000,
    
                WS_MINIMIZEBOX = 0x00020000,
                WS_MAXIMIZEBOX = 0x00010000,
    
                WS_CAPTION = WS_BORDER | WS_DLGFRAME,
                WS_TILED = WS_OVERLAPPED,
                WS_ICONIC = WS_MINIMIZE,
                WS_SIZEBOX = WS_THICKFRAME,
                WS_TILEDWINDOW = WS_OVERLAPPEDWINDOW,
    
                WS_OVERLAPPEDWINDOW = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,
                WS_POPUPWINDOW = WS_POPUP | WS_BORDER | WS_SYSMENU,
                WS_CHILDWINDOW = WS_CHILD,
    
                //Extended Window Styles
    
                WS_EX_DLGMODALFRAME = 0x00000001,
                WS_EX_NOPARENTNOTIFY = 0x00000004,
                WS_EX_TOPMOST = 0x00000008,
                WS_EX_ACCEPTFILES = 0x00000010,
                WS_EX_TRANSPARENT = 0x00000020,
    
                //#if(WINVER >= 0x0400)
    
                WS_EX_MDICHILD = 0x00000040,
                WS_EX_TOOLWINDOW = 0x00000080,
                WS_EX_WINDOWEDGE = 0x00000100,
                WS_EX_CLIENTEDGE = 0x00000200,
                WS_EX_CONTEXTHELP = 0x00000400,
    
                WS_EX_RIGHT = 0x00001000,
                WS_EX_LEFT = 0x00000000,
                WS_EX_RTLREADING = 0x00002000,
                WS_EX_LTRREADING = 0x00000000,
                WS_EX_LEFTSCROLLBAR = 0x00004000,
                WS_EX_RIGHTSCROLLBAR = 0x00000000,
    
                WS_EX_CONTROLPARENT = 0x00010000,
                WS_EX_STATICEDGE = 0x00020000,
                WS_EX_APPWINDOW = 0x00040000,
    
                WS_EX_OVERLAPPEDWINDOW = (WS_EX_WINDOWEDGE | WS_EX_CLIENTEDGE),
                WS_EX_PALETTEWINDOW = (WS_EX_WINDOWEDGE | WS_EX_TOOLWINDOW | WS_EX_TOPMOST),
                //#endif /* WINVER >= 0x0400 */
    
                //#if(WIN32WINNT >= 0x0500)
    
                WS_EX_LAYERED = 0x00080000,
                //#endif /* WIN32WINNT >= 0x0500 */
    
                //#if(WINVER >= 0x0500)
    
                WS_EX_NOINHERITLAYOUT = 0x00100000, // Disable inheritence of mirroring by children
                WS_EX_LAYOUTRTL = 0x00400000, // Right to left mirroring
                //#endif /* WINVER >= 0x0500 */
    
                //#if(WIN32WINNT >= 0x0500)
    
                WS_EX_COMPOSITED = 0x02000000,
                WS_EX_NOACTIVATE = 0x08000000
                //#endif /* WIN32WINNT >= 0x0500 */
    
            }
    
            public enum GetWindowLongConst
            {
                GWL_WNDPROC = (-4),
                GWL_HINSTANCE = (-6),
                GWL_HWNDPARENT = (-8),
                GWL_STYLE = (-16),
                GWL_EXSTYLE = (-20),
                GWL_USERDATA = (-21),
                GWL_ID = (-12)
            }
    
            public enum LWA
            {
                ColorKey = 0x1,
                Alpha = 0x2,
            }
    
            [DllImport("user32.dll", SetLastError = true)]
            static extern int GetWindowLong(IntPtr hWnd, int nIndex);
    
            [DllImport("user32.dll")]
            static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
    
            [DllImport("user32.dll")]
            static extern bool SetLayeredWindowAttributes(IntPtr hwnd, uint crKey, byte bAlpha, uint dwFlags);
    
            /// <summary>
            /// Make the form (specified by its handle) a window that supports transparency.
            /// </summary>
            /// <param name="Handle">The window to make transparency supporting</param>
            public void SetFormTransparent(IntPtr Handle)
            {
                oldWindowLong = GetWindowLong(Handle, (int)GetWindowLongConst.GWL_EXSTYLE);
                SetWindowLong(Handle, (int)GetWindowLongConst.GWL_EXSTYLE, Convert.ToInt32( oldWindowLong | (uint)WindowStyles.WS_EX_LAYERED | (uint)WindowStyles.WS_EX_TRANSPARENT));
            }
    
            /// <summary>
            /// Make the form (specified by its handle) a normal type of window (doesn't support transparency).
            /// </summary>
            /// <param name="Handle">The Window to make normal</param>
            public void SetFormNormal(IntPtr Handle)
            {
                SetWindowLong(Handle, (int)GetWindowLongConst.GWL_EXSTYLE, Convert.ToInt32(oldWindowLong | (uint)WindowStyles.WS_EX_LAYERED));
            }
    
            /// <summary>
            /// Makes the form change White to Transparent and clickthrough-able
            /// Can be modified to make the form translucent (with different opacities) and change the Transparency Color.
            /// </summary>
            public void SetTheLayeredWindowAttribute()
            {
                uint transparentColor = 0xffffffff;
    
                SetLayeredWindowAttributes(this.Handle, transparentColor, 125, 0x2);
    
                this.TransparencyKey = Color.White;
            }
    
            /// <summary>
            /// Finds the Size of all computer screens combined (assumes screens are left to right, not above and below).
            /// </summary>
            /// <returns>The width and height of all screens combined</returns>
            public static Size getFullScreensSize()
            {
                int height = int.MinValue;
                int width = 0;
    
                foreach (Screen screen in System.Windows.Forms.Screen.AllScreens)
                {
                    //take largest height
                    height = Math.Max(screen.WorkingArea.Height, height);
    
                    width += screen.Bounds.Width;
                }
    
                return new Size(width, height);
            }
    
            /// <summary>
            /// Finds the top left pixel position (with multiple screens this is often not 0,0)
            /// </summary>
            /// <returns>Position of top left pixel</returns>
            public static Point getTopLeft()
            {
                int minX = int.MaxValue;
                int minY = int.MaxValue;
    
                foreach (Screen screen in System.Windows.Forms.Screen.AllScreens)
                {
                    minX = Math.Min(screen.WorkingArea.Left, minX);
                    minY = Math.Min(screen.WorkingArea.Top, minY);
                }
    
                return new Point( minX, minY );
            }
    
            public Form1()
            {
                InitializeComponent();
    
                MaximizeEverything();
    
                SetFormTransparent(this.Handle);
    
                SetTheLayeredWindowAttribute();
    
                BackgroundWorker tmpBw = new BackgroundWorker();
                tmpBw.DoWork += new DoWorkEventHandler(bw_DoWork);
    
                this.bw = tmpBw;
    
                this.bw.RunWorkerAsync();
            }
    
            private void MaximizeEverything()
            {
                this.Location = getTopLeft();
                this.Size = getFullScreensSize();
    
                SendMessage(this.Handle, WM_SYSCOMMAND, (UIntPtr)myWParam, (IntPtr)myLparam);
            }
    
            private void bw_DoWork(object sender, DoWorkEventArgs e)
            {
                BackgroundWorker worker = sender as BackgroundWorker;
    
                int numRedLines = 500;
                int numWhiteLines = 1000;
    
                Size fullSize = getFullScreensSize();
                Point topLeft = getTopLeft();
    
                using (Pen redPen = new Pen(Color.Red, 10f), whitePen = new Pen(Color.White, 10f)) {
                    using (Graphics formGraphics = this.CreateGraphics()) {
    
                        while (true) {
    
                            bool makeRedLines = true;
    
                            for (int i = 0; i < numRedLines + numWhiteLines; i++)
                            {
    
                                if (i > numRedLines)
                                {
                                    makeRedLines = false;
                                }
    
                                //Choose points for random lines...but don't draw over the top 100 px of the screen so you can 
                                //still find the stop run button.
                                int pX = rand.Next(0, (-1 * topLeft.X) + fullSize.Width);
                                int pY = rand.Next(100, (-1 * topLeft.Y) + fullSize.Height);
    
                                int qX = rand.Next(0, (-1 * topLeft.X) + fullSize.Width);
                                int qY = rand.Next(100, (-1 * topLeft.Y) + fullSize.Height);
    
                                if (makeRedLines)
                                {
                                    formGraphics.DrawLine(redPen, pX, pY, qX, qY);
                                }
                                else
                                {
                                    formGraphics.DrawLine(whitePen, pX, pY, qX, qY);
                                }
    
                                Thread.Sleep(10);
                            }
                        }
                    }
                }
            }
        }
    }
    

    枚举列表是 native Windows 调用中使用的值,将 White 等 RGB 颜色转换为 uint 会使处理 native Windows 有点痛苦。

    但是,我们现在终于有了一个覆盖所有屏幕的不可见 Canvas ,我们可以像绘制任何其他图形对象一样在它上面进行绘制(因此绘制文本或图片就像绘制线条一样容易)。

    (我认为,如果您为图形对象绘制半透明图片,您可以使自己成为半透明叠加层,而不是完全不透明/透明的叠加层)。

    此示例无法在全屏 3d 游戏上放置叠加层,但适用于在窗口模式下运行的相同游戏。

    (P.S. 我刚刚在军团要塞 2 中测试了这个,它在窗口模式下绘制它,但不是全屏,所以我猜旧共和国会类似)。

    以下链接可能对尝试使用 Direct3D 版本 9、10 和 11 的绘图例程的任何人有用。

    http://spazzarama.com/2011/03/14/c-screen-capture-and-overlays-for-direct3d-9-10-and-11-using-api-hooks/

    https://github.com/spazzarama/Direct3DHook

    它不提供全功能覆盖,但上面的示例项目为我成功地在 Team Fortress 2 上每秒写入了帧数。它有关于如何开始使用它的很好的说明。它应该会引导您完成设置 SlimDX Runtime 的过程。和 EasyHook .

    关于c# - 如何在 c# 中的另一个程序之上制作非交互式图形叠加?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21896080/

    相关文章:

    c# - Linq 中的 ToList 方法

    c# - 如何在 gridview 中动态添加页脚行。带文本框

    python-3.x - 检测黑白文档上的图章形状

    c - 如何在 bmp 图像上叠加文本或标记

    matplotlib - matplotlib 可以生成 EPS10 输出吗?

    c# - 在 ASP.NET C# SQL 中导致问题的文本框

    c# - 更改包含具有 ItemContainerStyle 上的控件模板并绑定(bind)到 CollectionViewSource 的列表框的选项卡时,应用程序崩溃

    Java 将 Image 转换为 BufferedImage

    c# - Silverlight 可以透明播放视频吗?

    java - 使用不显示后面板颜色的透明度覆盖 JButton paintComponent