• [Unity 3d] 使用 Unity 开发无边框、可拖拽、缩放、置顶、最小化的应用


    文章出处:
    https://www.jianshu.com/p/c81342e96def

    using System;
    using System.Runtime.InteropServices;
    using System.Text;
    using UnityEngine;
    
    public static class PInvoke
    {
        static IntPtr ptr;
        public static IntPtr UnityHWnd
        {
            get
            {
                if (ptr == null || ptr == IntPtr.Zero)
                {
                    ptr = GetUnityWindow();
                }
                return ptr;
            }
        }
    
        #region 常量
        //https://docs.microsoft.com/zh-cn/windows/win32/winmsg/window-styles
        public const ulong WS_MAXIMIZEBOX = 0x00010000L; //最大化的按钮禁用
        public const ulong WS_DLGFRAME = 0x00400000L; //不现实边框
        public const ulong WS_SIZEBOX = 0x00040000L; //调大小的边框
        public const ulong WS_BORDER = 0x00800000L; //边框
        public const ulong WS_CAPTION = 0x00C00000L; //标题栏
    
        // Retreives pointer to WindowProc function.
        public const int GWLP_WNDPROC = -4; //Windows 绘制方法的指针
        public const int WM_SIZING = 0x214;
        public const int WS_POPUP = 0x800000;
        public const int GWL_STYLE = -16;
        //边框参数
        public const uint SWP_SHOWWINDOW = 0x0040;
        public const uint SWP_NOMOVE = 0x0002;
        public const int SW_SHOWMINIMIZED = 2;//(最小化窗口)
        // Name of the Unity window class used to find the window handle.
        public const string UNITY_WND_CLASSNAME = "UnityWndClass";
        #endregion
    
        #region Win32 API
        // Passes message information to the specified window procedure.
        [DllImport("user32.dll")]
        public static extern IntPtr CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
    
        //获得窗口样式
        [DllImport("user32.dll", EntryPoint = "GetWindowLongPtr")]
        public static extern IntPtr GetWindowLongPtr(IntPtr hwnd, int nIndex);
        // Retrieves the dimensions of the bounding rectangle of the specified window.
        // The dimensions are given in screen coordinates that are relative to the upper-left corner of the screen.
        [DllImport("user32.dll", SetLastError = true)]
        public static extern bool GetWindowRect(IntPtr hwnd, ref RECT lpRect);
        // Retrieves the coordinates of a window's client area. The client coordinates specify the upper-left
        // and lower-right corners of the client area. Because client coordinates are relative to the upper-left
        // corner of a window's client area, the coordinates of the upper-left corner are (0,0).
        [DllImport("user32.dll")]
        public static extern bool GetClientRect(IntPtr hWnd, ref RECT lpRect);
    
        // 改变指定窗口的属性 ,该函数还在额外窗口内存中的指定偏移处设置一个值。
        [DllImport("user32.dll", EntryPoint = "SetWindowLongPtr", CharSet = CharSet.Auto)]
        public static extern IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr dwNewLong);
    
        //设置当前窗口的显示状态
        [DllImport("user32.dll")]
        public static extern bool ShowWindow(System.IntPtr hwnd, int nCmdShow);
    
        //设置窗口位置,大小
        [DllImport("user32.dll")]
        public static extern bool SetWindowPos(IntPtr hWnd, int hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
    
        // 通过将每个窗口的句柄依次传递给应用程序定义的回调函数,枚举与线程关联的所有非子窗口。
        [DllImport("user32.dll")]
        private static extern bool EnumThreadWindows(uint dwThreadId, EnumWindowsProc lpEnumFunc, IntPtr lParam);
        private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
        //检索调用线程的线程标识符。
        [DllImport("kernel32.dll")]
        private static extern uint GetCurrentThreadId();
        // 检索指定窗口所属的类的名称。
        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern int GetClassName(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
    
    
        //窗口拖动
        [DllImport("user32.dll")]
        public static extern bool ReleaseCapture();
        [DllImport("user32.dll")]
        public static extern bool SendMessage(IntPtr hwnd, int wMsg, int wParam, int lParam);
    
    
        #endregion
        #region Static Function
        //最小化窗口
        //具体窗口参数看这     https://msdn.microsoft.com/en-us/library/windows/desktop/ms633548(v=vs.85).aspx
        public static void SetMinWindows()
        {
            if (!Application.isEditor)
            {
                ShowWindow(UnityHWnd, SW_SHOWMINIMIZED);
            }
        }
    
        //拖动窗口
        public static void DragWindow()
        {
            ReleaseCapture();
            SendMessage(UnityHWnd, 0xA1, 0x02, 0);
            SendMessage(UnityHWnd, 0x0202, 0, 0);
        }
    
        public static IntPtr GetUnityWindow()
        {
            var unityHWnd = IntPtr.Zero;
            EnumThreadWindows(GetCurrentThreadId(), (hWnd, lParam) =>
            {
                var classText = new StringBuilder(UNITY_WND_CLASSNAME.Length + 1);
                GetClassName(hWnd, classText, classText.Capacity);
    
                if (classText.ToString() == UNITY_WND_CLASSNAME)
                {
                    unityHWnd = hWnd;
                    return false;
                }
                return true;
            }, IntPtr.Zero);
            return unityHWnd;
        }
    
        #endregion
        #region Assistant
        /// 
        /// WinAPI RECT definition.
        /// 
        [StructLayout(LayoutKind.Sequential)]
        public struct RECT
        {
            public int Left;
            public int Top;
            public int Right;
            public int Bottom;
            public override string ToString()
            {
                return "left = {Left}\nright = {Right}\ntop = {Top}\nbottom = {Bottom}";
            }
        }
        #endregion
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147

    实现:

    1. 无边框

      [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSplashScreen)]
      static void InitAppWindow()
      {
      if (Application.isEditor) return;
      var dwStyles = GetWindowLongPtr(UnityHWnd, GWL_STYLE);
      var sty = ((ulong)dwStyles);
      sty &= ~(WS_CAPTION| WS_DLGFRAME)&WS_POPUP;
      SetWindowLongPtr(UnityHWnd, GWL_STYLE, (IntPtr)sty);
      }
      注意:设置无边框后,任务栏点击无法实现 App 最小化,如果有解决方案欢迎提 PR

    2. 最小化

      //最小化窗口
      //具体窗口参数看这 https://msdn.microsoft.com/en-us/library/windows/desktop/ms633548(v=vs.85).aspx
      public static void SetMinWindows()
      {
      if (!Application.isEditor)
      {
      ShowWindow(UnityHWnd, SW_SHOWMINIMIZED);
      }
      }
      //设置当前窗口的显示状态
      [DllImport(“user32.dll”)]
      public static extern bool ShowWindow(System.IntPtr hwnd, int nCmdShow);

    Tips: 代码中用到的 win32 api 以及常量请参阅本文配套 GitHub 仓库:点我。

    1. 缩放

    using UnityEngine;
    using UnityEngine.EventSystems;
    using static PInvoke;

    public class WindowResizeHandler : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler, IBeginDragHandler, IEndDragHandler, IDragHandler
    {
    bool isDragging = false;
    bool isInsideOfHandler = false;
    public Vector2 hotspot = Vector2.zero;

    // Minimum and maximum values for window width/height in pixel.
    [SerializeField]
    private int minWidthPixel = 768;
    [SerializeField]
    private int maxWidthPixel = 2048;
    
    public Texture2D wnes;
    private float aspect = 16 / 9f;
    void IBeginDragHandler.OnBeginDrag(PointerEventData eventData) => isDragging = eventData.pointerId==-1;
    void IDragHandler.OnDrag(PointerEventData eventData) => WindowProcess(eventData);
    void IEndDragHandler.OnEndDrag(PointerEventData eventData)
    {
        isDragging = false;
        if (!isInsideOfHandler)
        {
            Cursor.SetCursor(default, default, CursorMode.Auto);
        }
    }
    void IPointerEnterHandler.OnPointerEnter(PointerEventData eventData)
    {
        isInsideOfHandler = true;
        Cursor.SetCursor(wnes, hotspot, CursorMode.Auto);
    }
    void IPointerExitHandler.OnPointerExit(PointerEventData eventData)
    {
        isInsideOfHandler = false;
        if (!isDragging)
        {
            Cursor.SetCursor(default, default, CursorMode.Auto);
        }
    }
    private void WindowProcess(PointerEventData eventData)
    {
        if (Application.isEditor || eventData.pointerId != -1) return;
        RECT rc = default;
        GetWindowRect(UnityHWnd, ref rc);
        int newWidth = Mathf.Clamp(rc.Right - rc.Left + Mathf.RoundToInt(eventData.delta.x), minWidthPixel, maxWidthPixel);
        int newHeight = Mathf.RoundToInt(newWidth / aspect);
        SetWindowPos(UnityHWnd, 0, rc.Left, rc.Top, newWidth, newHeight, SWP_SHOWWINDOW);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    }

    Tips: 代码中用到的 win32 api 以及常量请参阅本文配套 GitHub 仓库:点我。

    1. 拖拽窗体

    using UnityEngine;
    using UnityEngine.EventSystems;
    using UnityEngine.UI;
    using static PInvoke;
    [RequireComponent(typeof(Graphic))]
    public class WindowMoveHandler : MonoBehaviour,IPointerDownHandler,IPointerUpHandler,IPointerExitHandler
    {
    static bool isDrag = false;
    void IPointerDownHandler.OnPointerDown(PointerEventData eventData) => isDrag = eventData.pointerId==-1;
    void IPointerExitHandler.OnPointerExit(PointerEventData eventData) => isDrag = false;
    void IPointerUpHandler.OnPointerUp(PointerEventData eventData) => isDrag = !(eventData.pointerId==-1);
    private void Update()
    {
    if (!Application.isEditor&&isDrag)
    {
    DragWindow();
    }
    }
    }

    //拖动窗口
    public static void DragWindow()
    {
        ReleaseCapture();
        SendMessage(UnityHWnd, 0xA1, 0x02, 0);
        SendMessage(UnityHWnd, 0x0202, 0, 0);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    1. 置顶窗体(Topmost)

      // 应用窗口置顶
      public static void SetTopmost(bool isTopmost)
      {
      if (!Application.isEditor)
      {
      int ptr = isTopmost ? -1 : -2;
      SetWindowPos(UnityHWnd, ptr, 0, 0, 0, 0, 1 | 2 | 64);//0x0040
      }
      else
      {
      Debug.LogWarning($“{nameof(PInvoke)}: 为避免编辑器行为异常,请打包 exe 后测试!”);
      }
      }
      Tips: 代码中用到的 win32 api 以及常量请参阅本文配套 GitHub 仓库:点我。

    扩展阅读:
    System-Tray-Icon-For-Unity - 为 Unity 开发的 App 提供 System Tray Icon,后面计划使用 Unity MenuItem 的方式,现在使用起来感觉不怎么便利。
    UnitySkipSplash - 几句话跳过 Unity Logo 闪屏界面,别问我为何这么 big 胆,仅供学习用途嘛。
    Simple-Customize-ERP-System - 本文配套的 GitHub 仓库。
    写到最后:
    版权所有,转载请注明出处!

    作者:雨落随风
    链接:https://www.jianshu.com/p/c81342e96def
    来源:简书
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 相关阅读:
    【C++】栈~~(很详细哦)
    基于Javaweb的商铺租赁管理系统/租赁管理系统
    计算机网络——网络协议
    37 | 什么是SLI、SLO、SLA
    使用 WebGL 为 HTML5 游戏创建逼真的地形
    数据分析案例-基于snownlp模型的MatePad11产品用户评论情感分析(文末送书)
    勒索病毒基础介绍,值得收藏
    ChatGPT:你的数字生活助手
    在K8S中,静态、动态、自主式Pod有何区别?
    10款实用的市场分析工具,你知道几个?
  • 原文地址:https://blog.csdn.net/weixin_40012419/article/details/126007945