使用SharpDX的HUD基础知识:如何在屏幕表面上绘制



场景

我计划学习如何为第三方PC游戏(免费,合法)开发自定义HUD的基础知识,我的目的是开发一个应用程序,该应用程序将在屏幕上绘制/显示其他信息(通过读取mem地址)。

所以我研究了专业项目,我发现 TurboHUD 使用 SharpDX 库使用基于 DirectX 的库在屏幕上绘制对象/叠加层/文本,结果非常好(在屏幕上绘制多个对象时,它不会随时失去性能),但是,由于该项目的作者没有提供了解他们如何做到的来源,因此我正在尝试自己学习使用相同的专业技术。

问题

如果我走错了路,或者我错过了比 SharpDX 更好的替代方案来开发它,请告诉我。

我的主要问题是:

在 C# 中,或者最好是 VB.Net,如何使用 SharpDX 在屏幕上有效地绘制自定义字符串?

请注意,我可以将表单的oppacity设置为0,但我认为它应该以正确的方式存在,我要求知道直接在"桌面"屏幕上绘制的正确方法。

我的期望是启动该PC游戏,然后启动我的自定义HUD,它将在"桌面"屏幕表面上绘制以添加有关游戏的其他信息,我希望您理解我。

研究

我应该澄清一下,我对这种 DirectX 库完全没有经验,我正在使用 SharpDX 示例包来尝试了解它的用法。

由于所有示例都是 C# 中,因此在 VB.Net 下学习其用法更加困难。

SharpDX示例包中有一个"AdvancedTextRenderingApp"C#项目,但顾名思义,它是一个高级示例,并且它还实例了一个自定义表单(SharpDX.Windows.RenderForm)以在该表单上绘制。

这是我提到的 C# 项目的 VB.Net 代码转换:

http://pastebin.com/KG2c3v09


更新:

只是为了评论我所做的一项研究:

我最近发现了这个有用的 GitHub 存储库,但是,它无法在 Visual Studio 2015 中编译(因为缺少命名空间使用,添加时会产生更多编译器错误),它也面向高级 SharpDX 用户,在分析完整示例后,我仍然不知道如何在第 3 部分进程窗口的表面上编写/绘制......C#语法也让我很难全面理解SharpDX的用法,因为作者也对各种各样的SharpDX成员进行了自定义实现,然后......我对所有这些例子都感到迷茫。

官方的 SharpDX 样本是另一个似乎非常有用的东西......也许对于高级用户。一些示例似乎演示了如何呈现自定义窗口/表面(使用 500 行乏味且难以理解的代码来执行此操作。他们的"Hello world"样本对我来说是一场噩梦),但是,正如我在问题中解释的那样,我想实现的是绘制另一个进程的现有窗口的表面,并且我知道可能为此我需要使用 SharpDX 从头开始渲染"表面",然后将其定位在目标窗口中, 然后使表面不可见,然后在上面画画,但我不知道该怎么做这些事情。

我花了一段时间才找到如何在 XNA 中加载字体以绘制文本,但一切正常。

您需要 4 件事:

  1. 使表单最顶层
  2. 将空气动力学玻璃风格扩展到整体形式(使透明度发挥作用)
  3. 初始化 XNA,Microsoft XNA Game Studio 4.0
  4. 绘制纹理和文本

一个限制

游戏不得处于全屏模式。与涡轮HUD限制相同

Form1.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.IO;
using System.Windows.Forms;
using System.Threading;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using System.Runtime.InteropServices;
using System.Drawing;
namespace XNATransparentWindow
{
    public partial class Form1 : Form
    {
        private ContentBuilder contentBuilder;
        public Form1()
        {
            InitializeComponent();
            TopMost = true;
            FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
            MARGINS margins = new MARGINS();
            margins.leftWidth = 0;
            margins.rightWidth = 0;
            margins.topHeight = this.Width;
            margins.bottomHeight = this.Height;
            // Extend aero glass style to whole form
            DwmExtendFrameIntoClientArea(this.Handle, ref margins);
            //Load XNA directX
            this.contentBuilder = new ContentBuilder();
            graphicsDeviceService = GraphicsDeviceService.AddRef(Handle, ClientSize.Width, ClientSize.Height);
            //Register the service, so components like ContentManager can find it.
            services.AddService<IGraphicsDeviceService>(graphicsDeviceService);
            //Get the Graphics device
            dev = graphicsDeviceService.GraphicsDevice;
            if(dev == null){/*error message*/}
            //Load texture
            int bufferSize;
            System.IO.MemoryStream memoryStream;
            Bitmap img;
            using (img  = new Bitmap(@"C:....png"))
            {
                bufferSize = img.Height * img.Width * 4;
                memoryStream = new System.IO.MemoryStream(bufferSize);
                img.Save(memoryStream, System.Drawing.Imaging.ImageFormat.Png);
                memoryStream.Seek(0, SeekOrigin.Begin);
                texture = Texture2D.FromStream(dev, memoryStream, img.Width, img.Height, false);
                memoryStream.Close();
                if(texture == null){/*error message*/}
            }
            //Create sprite
            s_Batch = new SpriteBatch(dev);
            if(s_Batch == null){/*error message*/}
            FontPos = new Vector2(270.0F, 110.0F);
            //Load font
            contentManager = new ContentManager(services, this.contentBuilder.OutputDirectory);
            this.contentBuilder.Clear();
            this.contentBuilder.Add(@"C:...my_font1.spritefont","my_font1", "FontDescriptionImporter", "FontDescriptionProcessor");
            //Build spritefont to get the .xbn file
            string error = this.contentBuilder.Build();
            //If build fail
            if (!String.IsNullOrEmpty(error))
            {
                MessageBox.Show(error);
                return;
            }
            //Load the .xbn file
            Font1 = contentManager.Load<SpriteFont>("my_font1");
            if(Font1 == null){/*error message*/}
        }
        [DllImport("dwmapi.dll")]
        static extern int DwmExtendFrameIntoClientArea(IntPtr hWnd, ref MARGINS margin);
        [DllImport("user32.dll")]
        static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, Int32 wParam, Int32 lParam);
        [StructLayout(LayoutKind.Sequential)]
        public struct MARGINS
        {
            public int leftWidth;
            public int rightWidth;
            public int topHeight;
            public int bottomHeight;
        }
        public ServiceContainer Services
        {
            get { return services; }
        }
        ServiceContainer services = new ServiceContainer();
        GraphicsDevice dev;
        SpriteFont Font1;
        Vector2 FontPos;
        SpriteBatch s_Batch;
        Texture2D texture;
        ContentManager contentManager;
        GraphicsDeviceService graphicsDeviceService;
        private const UInt32 WM_NCLBUTTONDOWN = 0xA1;
        private const Int32 HTCAPTION = 0x2;
        private void Form1_MouseDown(object sender, MouseEventArgs e)
        {
            if (e.Button == System.Windows.Forms.MouseButtons.Right)
            {
                this.Close();
            }
            else //to move the form
            {
                this.Capture = false;
                SendMessage(this.Handle, WM_NCLBUTTONDOWN, HTCAPTION, 0);
            }
        }
        private void Form1_Paint(object sender, PaintEventArgs e)
        {
            //There are two buffers. One offscreen-backbuffer and the 
            //frontbuffer which is the actual form in this example. The
            //drawings are done to the backbuffer and in the end the two
            //buffers flip. The backbuffer becomes frontbuffer and the
            //frontbuffer becomes backbuffer.
            //The drawing should start when the last resource is loaded.
            //Since Font1 is the last one in this example I check for this
            if (Font1 == null)
            {
                return;
            }
            //clear the backbuffer with transparent color.
            dev.Clear(Microsoft.Xna.Framework.Color.Transparent);
            //Do all your drawings here
            //draw texture and text with the sprite
            s_Batch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend);
            s_Batch.Draw(texture, new Microsoft.Xna.Framework.Rectangle(0, 0, this.Width, this.Height), Microsoft.Xna.Framework.Color.White);
            s_Batch.DrawString(Font1, @"XNA FRAMEWORK", FontPos, Microsoft.Xna.Framework.Color.Black);
            s_Batch.End();
            //here the flip is performed
            dev.Present();
        }
        //Release resources
        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            graphicsDeviceService.GraphicsDevice.Dispose();
            graphicsDeviceService.Release(true);
            s_Batch.Dispose();
            texture.Dispose();
        }
    }
}

以下类只是从我发现的示例 XNA 4.0 内容编译器复制粘贴(略有调整)。它们仅用于加载字体以绘制文本:

GraphicsDeviceService.cs

#region File Description
//-----------------------------------------------------------------------------
// GraphicsDeviceService.cs
//
// Microsoft XNA Community Game Platform
// Copyright (C) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
#endregion
#region Using Statements
using System;
using System.Threading;
using Microsoft.Xna.Framework.Graphics;
#endregion
// The IGraphicsDeviceService interface requires a DeviceCreated event, but we
// always just create the device inside our constructor, so we have no place to
// raise that event. The C# compiler warns us that the event is never used, but
// we don't care so we just disable this warning.
#pragma warning disable 67
namespace XNATransparentWindow
{
    /// <summary>
    /// Helper class responsible for creating and managing the GraphicsDevice.
    /// All GraphicsDeviceControl instances share the same GraphicsDeviceService,
    /// so even though there can be many controls, there will only ever be a single
    /// underlying GraphicsDevice. This implements the standard IGraphicsDeviceService
    /// interface, which provides notification events for when the device is reset
    /// or disposed.
    /// </summary>
    class GraphicsDeviceService : IGraphicsDeviceService
    {
        #region Fields
        // Singleton device service instance.
        static GraphicsDeviceService singletonInstance;
        // Keep track of how many controls are sharing the singletonInstance.
        static int referenceCount;
        #endregion
        /// <summary>
        /// Constructor is private, because this is a singleton class:
        /// client controls should use the public AddRef method instead.
        /// </summary>
        GraphicsDeviceService(IntPtr windowHandle, int width, int height)
        {
            parameters = new PresentationParameters();
            parameters.BackBufferWidth = Math.Max(width, 1);
            parameters.BackBufferHeight = Math.Max(height, 1);
            parameters.BackBufferFormat = SurfaceFormat.Vector4; // SurfaceFormat.Color;
            parameters.DeviceWindowHandle = windowHandle;
            parameters.PresentationInterval = PresentInterval.Immediate;
            parameters.IsFullScreen = false;
            graphicsDevice = new GraphicsDevice(GraphicsAdapter.DefaultAdapter, GraphicsProfile.Reach, parameters);
        }
        /// <summary>
        /// Gets a reference to the singleton instance.
        /// </summary>
        public static GraphicsDeviceService AddRef(IntPtr windowHandle,
                                                   int width, int height)
        {
            // Increment the "how many controls sharing the device" reference count.
            if (Interlocked.Increment(ref referenceCount) == 1)
            {
                // If this is the first control to start using the
                // device, we must create the singleton instance.
                singletonInstance = new GraphicsDeviceService(windowHandle,
                                                              width, height);
            }
            return singletonInstance;
        }

        /// <summary>
        /// Releases a reference to the singleton instance.
        /// </summary>
        public void Release(bool disposing)
        {
            // Decrement the "how many controls sharing the device" reference count.
            if (Interlocked.Decrement(ref referenceCount) == 0)
            {
                // If this is the last control to finish using the
                // device, we should dispose the singleton instance.
                if (disposing)
                {
                    if (DeviceDisposing != null)
                        DeviceDisposing(this, EventArgs.Empty);
                    graphicsDevice.Dispose();
                }
                graphicsDevice = null;
            }
        }

        /// <summary>
        /// Resets the graphics device to whichever is bigger out of the specified
        /// resolution or its current size. This behavior means the device will
        /// demand-grow to the largest of all its GraphicsDeviceControl clients.
        /// </summary>
        public void ResetDevice(int width, int height)
        {
            if (DeviceResetting != null)
                DeviceResetting(this, EventArgs.Empty);
            parameters.BackBufferWidth = Math.Max(parameters.BackBufferWidth, width);
            parameters.BackBufferHeight = Math.Max(parameters.BackBufferHeight, height);
            graphicsDevice.Reset(parameters);
            if (DeviceReset != null)
                DeviceReset(this, EventArgs.Empty);
        }
        /// <summary>
        /// Gets the current graphics device.
        /// </summary>
        public GraphicsDevice GraphicsDevice
        {
            get { return graphicsDevice; }
        }
        GraphicsDevice graphicsDevice;
        // Store the current device settings.
        PresentationParameters parameters;
        // IGraphicsDeviceService events.
        public event EventHandler<EventArgs> DeviceCreated;
        public event EventHandler<EventArgs> DeviceDisposing;
        public event EventHandler<EventArgs> DeviceReset;
        public event EventHandler<EventArgs> DeviceResetting;
    }
}

ServiceContainer.cs

#region File Description
//-----------------------------------------------------------------------------
// ServiceContainer.cs
//
// Microsoft XNA Community Game Platform
// Copyright (C) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
#endregion
#region Using Statements
using System;
using System.Collections.Generic;
#endregion
namespace XNATransparentWindow
{
    /// <summary>
    /// Container class implements the IServiceProvider interface. This is used
    /// to pass shared services between different components, for instance the
    /// ContentManager uses it to locate the IGraphicsDeviceService implementation.
    /// </summary>
    public class ServiceContainer : IServiceProvider
    {
        Dictionary<Type, object> services = new Dictionary<Type, object>();

        /// <summary>
        /// Adds a new service to the collection.
        /// </summary>
        public void AddService<T>(T service)
        {
            services.Add(typeof(T), service);
        }
        /// <summary>
        /// Looks up the specified service.
        /// </summary>
        public object GetService(Type serviceType)
        {
            object service;
            services.TryGetValue(serviceType, out service);
            return service;
        }
    }
}

ContentBuilder.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Diagnostics;
using Microsoft.Build.Construction;
using Microsoft.Build.Evaluation;
using Microsoft.Build.Execution;
using Microsoft.Build.Framework;
namespace XNATransparentWindow
{
    public class ContentBuilder : IDisposable
    {
        #region Fields
        // What importers or processors should we load?
        const string xnaVersion = ", Version=4.0.0.0, PublicKeyToken=842cf8be1de50553";
        static string[] pipelineAssemblies =
        {
            "Microsoft.Xna.Framework.Content.Pipeline.FBXImporter" + xnaVersion,
            "Microsoft.Xna.Framework.Content.Pipeline.XImporter" + xnaVersion,
            "Microsoft.Xna.Framework.Content.Pipeline.TextureImporter" + xnaVersion,
            "Microsoft.Xna.Framework.Content.Pipeline.EffectImporter" + xnaVersion,
            "Microsoft.Xna.Framework.Content.Pipeline.AudioImporters" + xnaVersion,
            "Microsoft.Xna.Framework.Content.Pipeline.VideoImporters" + xnaVersion,
            // If you want to use custom importers or processors from
            // a Content Pipeline Extension Library, add them here.
            //
            // If your extension DLL is installed in the GAC, you should refer to it by assembly
            // name, eg. "MyPipelineExtension, Version=1.0.0.0, PublicKeyToken=1234567812345678".
            //
            // If the extension DLL is not in the GAC, you should refer to it by
            // file path, eg. "c:/MyProject/bin/MyPipelineExtension.dll".
        };
        // MSBuild objects used to dynamically build content.
        Project buildProject;
        ProjectRootElement projectRootElement;
        BuildParameters buildParameters;
        List<ProjectItem> projectItems = new List<ProjectItem>();
        //ErrorLogger errorLogger;
        // Temporary directories used by the content build.
        string buildDirectory;
        string processDirectory;
        string baseDirectory;
        // Generate unique directory names if there is more than one ContentBuilder.
        static int directorySalt;
        // Have we been disposed?
        bool isDisposed;
        #endregion
        #region Properties
        /// Gets the output directory, which will contain the generated .xnb files.
        public string OutputDirectory
        {
            get { return Path.Combine(buildDirectory, "bin/Content"); }
        }
        #endregion
        #region Initialization
        /// Creates a new content builder.
        public ContentBuilder()
        {
            CreateTempDirectory();
            CreateBuildProject();
        }
        /// Finalizes the content builder.
        ~ContentBuilder()
        {
            Dispose(false);
        }
        /// Disposes the content builder when it is no longer required.
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        /// Implements the standard .NET IDisposable pattern.
        protected virtual void Dispose(bool disposing)
        {
            if (!isDisposed)
            {
                isDisposed = true;
                DeleteTempDirectory();
            }
        }
        #endregion
        #region MSBuild
        /// Creates a temporary MSBuild content project in memory.
        void CreateBuildProject()
        {
            string projectPath = Path.Combine(buildDirectory, "content.contentproj");
            string outputPath = Path.Combine(buildDirectory, "bin");
            // Create the build project.
            projectRootElement = ProjectRootElement.Create(projectPath);
            // Include the standard targets file that defines how to build XNA Framework content.
            projectRootElement.AddImport("$(MSBuildExtensionsPath)\Microsoft\XNA Game Studio\" +
                                         "v4.0\Microsoft.Xna.GameStudio.ContentPipeline.targets");
            buildProject = new Project(projectRootElement);
            buildProject.SetProperty("XnaPlatform", "Windows");
            buildProject.SetProperty("XnaProfile", "Reach");
            buildProject.SetProperty("XnaFrameworkVersion", "v4.0");
            buildProject.SetProperty("Configuration", "Release");
            buildProject.SetProperty("OutputPath", outputPath);
            // Register any custom importers or processors.
            foreach (string pipelineAssembly in pipelineAssemblies)
            {
                buildProject.AddItem("Reference", pipelineAssembly);
            }
            // Hook up our custom error logger.
            //errorLogger = new ErrorLogger();
            buildParameters = new BuildParameters(ProjectCollection.GlobalProjectCollection);
            //buildParameters.Loggers = new ILogger[] { errorLogger };
        }
        /// Adds a new content file to the MSBuild project. The importer and
        /// processor are optional: if you leave the importer null, it will
        /// be autodetected based on the file extension, and if you leave the
        /// processor null, data will be passed through without any processing.
        public void Add(string filename, string name, string importer, string processor)
        {
            ProjectItem item = buildProject.AddItem("Compile", filename)[0];
            item.SetMetadataValue("Link", Path.GetFileName(filename));
            item.SetMetadataValue("Name", name);
            if (!string.IsNullOrEmpty(importer))
                item.SetMetadataValue("Importer", importer);
            if (!string.IsNullOrEmpty(processor))
                item.SetMetadataValue("Processor", processor);
            projectItems.Add(item);
        }
        /// Removes all content files from the MSBuild project.
        public void Clear()
        {
            buildProject.RemoveItems(projectItems);
            projectItems.Clear();
        }
        /// Builds all the content files which have been added to the project,
        /// dynamically creating .xnb files in the OutputDirectory.
        /// Returns an error message if the build fails.
        public string Build()
        {
            // Create and submit a new asynchronous build request.
            BuildManager.DefaultBuildManager.BeginBuild(buildParameters);
            BuildRequestData request = new BuildRequestData(buildProject.CreateProjectInstance(), new string[0]);
            BuildSubmission submission = BuildManager.DefaultBuildManager.PendBuildRequest(request);
            submission.ExecuteAsync(null, null);
            // Wait for the build to finish.
            submission.WaitHandle.WaitOne();
            BuildManager.DefaultBuildManager.EndBuild();
            // If the build failed, return an error string.
            if (submission.BuildResult.OverallResult == BuildResultCode.Failure)
            {
                //return string.Join("n", errorLogger.Errors.ToArray());
            }
            return null;
        }
        #endregion
        #region Temp Directories
        /// Creates a temporary directory in which to build content.
        void CreateTempDirectory()
        {
            // Start with a standard base name:
            //
            //  %temp%WinFormsContentLoading.ContentBuilder
            baseDirectory = Path.Combine(Path.GetTempPath(), GetType().FullName);
            // Include our process ID, in case there is more than
            // one copy of the program running at the same time:
            //
            //  %temp%WinFormsContentLoading.ContentBuilder<ProcessId>
            int processId = Process.GetCurrentProcess().Id;
            processDirectory = Path.Combine(baseDirectory, processId.ToString());
            // Include a salt value, in case the program
            // creates more than one ContentBuilder instance:
            //
            //  %temp%WinFormsContentLoading.ContentBuilder<ProcessId><Salt>
            directorySalt++;
            buildDirectory = Path.Combine(processDirectory, directorySalt.ToString());
            // Create our temporary directory.
            Directory.CreateDirectory(buildDirectory);
            PurgeStaleTempDirectories();
        }
        /// <summary>
        /// Deletes our temporary directory when we are finished with it.
        /// </summary>
        void DeleteTempDirectory()
        {
            Directory.Delete(buildDirectory, true);
            // If there are no other instances of ContentBuilder still using their
            // own temp directories, we can delete the process directory as well.
            if (Directory.GetDirectories(processDirectory).Length == 0)
            {
                Directory.Delete(processDirectory);
                // If there are no other copies of the program still using their
                // own temp directories, we can delete the base directory as well.
                if (Directory.GetDirectories(baseDirectory).Length == 0)
                {
                    Directory.Delete(baseDirectory);
                }
            }
        }

        /// <summary>
        /// Ideally, we want to delete our temp directory when we are finished using
        /// it. The DeleteTempDirectory method (called by whichever happens first out
        /// of Dispose or our finalizer) does exactly that. Trouble is, sometimes
        /// these cleanup methods may never execute. For instance if the program
        /// crashes, or is halted using the debugger, we never get a chance to do
        /// our deleting. The next time we start up, this method checks for any temp
        /// directories that were left over by previous runs which failed to shut
        /// down cleanly. This makes sure these orphaned directories will not just
        /// be left lying around forever.
        /// </summary>
        void PurgeStaleTempDirectories()
        {
            // Check all subdirectories of our base location.
            foreach (string directory in Directory.GetDirectories(baseDirectory))
            {
                // The subdirectory name is the ID of the process which created it.
                int processId;
                if (int.TryParse(Path.GetFileName(directory), out processId))
                {
                    try
                    {
                        // Is the creator process still running?
                        Process.GetProcessById(processId);
                    }
                    catch (ArgumentException)
                    {
                        // If the process is gone, we can delete its temp directory.
                        Directory.Delete(directory, true);
                    }
                }
            }
        }
        #endregion
    }
}

您正在加载的字体是一个 xml 文件:any_name.spritefont

例:

<?xml version="1.0" encoding="utf-8"?>
<!--
This file contains an xml description of a font, and will be read by the XNA
Framework Content Pipeline. Follow the comments to customize the appearance
of the font in your game, and to change the characters which are available to draw
with.
-->
<XnaContent xmlns:Graphics="Microsoft.Xna.Framework.Content.Pipeline.Graphics">
  <Asset Type="Graphics:FontDescription">
    <!--
    Modify this string to change the font that will be imported.
    -->
    <FontName>Segoe UI Mono</FontName>
    <!--
    Size is a float value, measured in points. Modify this value to change
    the size of the font.
    -->
    <Size>14</Size>
    <!--
    Spacing is a float value, measured in pixels. Modify this value to change
    the amount of spacing in between characters.
    -->
    <Spacing>0</Spacing>
    <!--
    UseKerning controls the layout of the font. If this value is true, kerning information
    will be used when placing characters.
    -->
    <UseKerning>true</UseKerning>
    <!--
    Style controls the style of the font. Valid entries are "Regular", "Bold", "Italic",
    and "Bold, Italic", and are case sensitive.
    -->
    <Style>Regular</Style>
    <!--
    If you uncomment this line, the default character will be substituted if you draw
    or measure text that contains characters which were not included in the font.
    -->
    <!-- <DefaultCharacter>*</DefaultCharacter> -->
    <!--
    CharacterRegions control what letters are available in the font. Every
    character from Start to End will be built and made available for drawing. The
    default range is from 32, (ASCII space), to 126, ('~'), covering the basic Latin
    character set. The characters are ordered according to the Unicode standard.
    See the documentation for more information.
    -->
    <CharacterRegions>
      <CharacterRegion>
        <Start>&#32;</Start>
        <End>&#126;</End>
      </CharacterRegion>
    </CharacterRegions>
  </Asset>
</XnaContent>

如果您希望表单是点击式的,那么您需要添加一个随机的要形成和以下形状的透明度关键颜色和:

[DllImport("user32.dll", EntryPoint="GetWindowLong")]
static extern IntPtr GetWindowLongPtr(IntPtr hWnd, int nIndex);
[DllImport("user32.dll")]
static extern int SetWindowLong(IntPtr hWnd, int nIndex, IntPtr dwNewLong);

public Form1()

IntPtr initialStyle = GetWindowLongPtr(this.Handle, -20);
SetWindowLong(this.Handle, -20, (IntPtr)((int)initialStyle | 0x20));

请随时纠正我犯的任何错误。

最新更新