Windows桌面开发之窗口

日常使用windows操作系统,会打开很多个应用程序。每一个应用程序都有很多窗口,提供给用户操作。但是窗体这个概念其实是很深刻的一个概念,这篇文章从windows开发的角度,介绍窗体。
窗体可以理解成屏幕上的一块儿矩形区域,应用程序使用这片区域接收用户输入,并将结果输出到这块儿矩形区域。
一个应用程序启动时,必须至少有一个窗体作为主窗体。
注意,虽然操作系统可以同时打开多个窗口,但某一具体时刻,只能有一个窗口接收用户输入。

 

窗体提供以下能力

  • Functions
    • 提供一些函数,可以创建、销毁、移动、拉伸窗体
      • 常见的API
      • CreateWindow, CloseWindow, DestroyWindow
      • MoveWindow, SetWindowPos
  • Messages
    • 提供一些消息,发送这些消息给窗体,可以获得窗体的一些信息,也可以改变窗体的一些设置
      • 常见的消息
      • WM_GETTEXT
      • WM_SETTEXT
  • Notifications
    • 窗体自身发生改变,主动发送消息,告知应用程序。例如窗体被移动、拉伸时,会发送消息
    • 常见的消息
    • WM_CREATE, WM_CLOSE, WM_DESTROY
    • WM_MOVE, WM_MOVING
    • WM_SIZE, WM_SIZING
窗体具有一个唯一标识符,通过这标识符(integer identifier),可以找到窗体并向其发送消息,指示其活动
 

窗体的结构图

窗体的位置始终基于屏幕的左上角定位

窗体重要概念

  • 窗体句柄
    • 创建完窗体之后,函数返回一个句柄,用来唯一标识此窗体
    • 窗体句柄的类型是HWND
    • 通过方法FindWindow可以找到符合条件的所有窗体的句柄
    • 通过方法IsWindow可传入句柄,判断是否指向一个合法窗体

窗体类

  • 窗体类像是模板一样,构造一个新的窗体只能从已经注册过的类去构建。但是可以注册自定义的窗体类。
  • 窗体类分为以下三大类
      • 系统类
        • 应用程序启动时,系统类自动复制到当前应用程序,供应用程序使用,但不是所有类都能创建窗体
        • 这些系统类可以使用
          • Button
          • ComboBox
          • Edit
          • ListBox
          • MDIClient
          • ScrollBar
          • Static
      • 应用全局类
        • 一个dll使用RegisterClassEx方法,并添加CS_GLOBALCLASS样式,不需要时使用UnregisterClass方法
      • 应用本地类
        • 注册它的模块销毁时,此类自动被销毁
应用顺序
  • 先在应用本地类列表中查找
  • 如未找到,再到应用全局类查找
  • 如未找到,再到系统类中查找
窗体类的属性
  • Class Name
    • 使用类名作为唯一区分的标志,WNDCLASSEX结构中的lpszClassName
    • 一个进程内部,类名是唯一的
  • Window Procedure Address
    • 系统把消息发到这里,窗体需要处理,例如绘制客户端区域内容
    • 例子,一个简单的处理消息函数
  • LRESULT CALLBACK MainWndProc(HWND hwnd, // handle to window
        UINT uMsg,        // message identifier
        WPARAM wParam,    // first message parameter
        LPARAM lParam)    // second message parameter
    { 
        switch (uMsg) 
        { 
            case WM_CREATE: 
                // Initialize the window. 
                return 0; 
            case WM_PAINT: 
                // Paint the window's client area. 
                return 0; 
            case WM_SIZE: 
                // Set the size and position of the window. 
                return 0; 
            case WM_DESTROY: 
                // Clean up window-specific data objects. 
                return 0; 
            default: 
                return DefWindowProc(hwnd, uMsg, wParam, lParam); 
        } 
        return 0; 
    } 
    • Instance Handle
      • WNDCLASSEX结构中的hInstance,用来指向应用程序
    • Class Cursor
      • 鼠标移动进入窗体的客户区域时,cursor变成什么样子
      • 先使用LoadCursor方法加载特定的游标,然后设置WNDCLASSEX结构中的hInstance
      • 也可以在接收到WM_MOUSEMOVE消息的时候,调用SetCursor方法改变cursor
    • Class Icons
      • 图标是指切换当前应用的缩略图和任务栏下面显示的图片
    • Class Background Brush
    • Class Menu
    • Class Styles
      • WNDCLASSEXA结构
        • cbSize
        • style
        • lpfnWndProc
        • cbClsExtra
        • cbWndExtra
        • hInstance
        • hIcon
        • hCursor
        • hbrBackground
        • lpszMenuName
        • lpszClassName
        • hIconSm
    • Classes and Device Contexts
    • Extra Class Memory
    • Extra Window Memory  

    窗体的分类

    应用程序可以同时打开多个窗体,窗体之间的关系如下:
    • Overlapped Window
      • 顶级窗体,作为应用程序的主窗体。可以在使用 CreateWindowEx 方法构造窗体时,设置 WS_OVERLAPPED,或者WS_OVERLAPPEDWINDOW的样式,来构造这种窗体。
        • Popup Window
        • 这是一种特殊的Overlapped Window,显示在主窗体之外的临时窗体。通常用做对话框或者消息框。
    • Child Window
      • 应用程序除过Overlapped Window,剩下的就是Child Window。这种窗体必须具有父窗体。
      • 与此同时,它们没有顶级窗体所具有的Title Bar Min Max Button,只有Client区域。 它具有的样式是WS_CHILD。

    窗体之间的关系

    • 父子关系
      • 一个应用首先打开顶级窗体,随后打开子窗体,子窗体可能又会打开子窗体。它们之间的关系就是父子关系。
      • Parent窗体和Child窗体具有如下关系:
        • 一个父窗体可以拥有多个子窗体,但子窗体只能属于一个父窗体。如果将当前窗体的父窗体指定为空,它将脱离应用程序,成为桌面的子窗体。
        • 可以通过GetParent,SetParent 修改当前窗体的父窗体。
        • EnumChildWindows可以用来枚举当前窗体的直接子窗体,子窗体可以继续枚举属于它的子窗体。
        • 父窗体将会裁剪子窗体超出自己可视区域的部分
        • 行为特点
          • 父窗体可见时,子窗体才有可能可见。并且是父窗体先显示出来,接着子窗体才显示出来
          • 父窗体隐藏时,先把所有子窗体都隐藏掉,最后再隐藏父窗体
          • 父窗体移动时,子窗体跟着移动,移动完之后子窗体重新绘制内容区域,子窗体永远基于父窗体左上角定位
          • 父窗体z-order增大时,子窗体的z-order也跟着增大
          • 父窗体销毁时,优先销毁掉所有子窗体,然后再销毁父窗体
          • 父窗体通过给子窗体发送消息,使得子窗体状态改变,从而产生变化。
          • 子窗体与父窗体重叠的区域,windows只会把消息发送子窗体,除非子窗体被禁用。反过来,子窗体在自身状态改变时,又会主动发消息给父窗体,告知其状态变更,以便父窗体采取行动。
      • 没有父窗体的窗体叫做顶级窗体,本质上来说,顶级窗体的父窗体就是桌面窗体,通过EnumWindows方法可以找到所有屏幕上的顶级窗体
    • 从属关系
      • 两个窗体之间还可以建立拥有和从属的关,关系服从以下约束
        • 被拥有者的z-order比拥有者大
        • 拥有者销毁时,被拥有者也自动被销毁
        • 拥有者隐藏时,被拥有者也被隐藏
        • 只有Overlapped Window可以拥有窗体
    • 前台与后台关系
      • 用户正在操作的那个窗口,处于激活状态,是前台窗口,反之就是后台窗口
      • 用户可以通过 Alt + Tab 切换前台窗口
      • 可以通过API GetForegroundWindow,SetForegroundWindow 获取和设置前台窗口
    • 位置关系
      • 操作系统维护z-order列表,z-order大的窗口将遮挡住z-order小的窗口
      • 可以通过API移动z-order,BringWindowToTop, SetWindowPos, DeferWindowPos
      • 用户也可以通过激活窗口,调整z-order

    窗体状态

    • Acitve
      • 被激活的顶级窗口,也是前台窗口
    • Disabled
      • EnableWindow 用来禁用一个窗口
      • IsWindowEnabled 用来判断一个窗口是否是禁用状态
      • 当父窗体禁用时,无法获得键盘焦点。即使此时子窗体已经获得键盘焦点,也会立刻失去
    • Visible
      • 通过设置 WS_VISIBLE 样式来控制,设置完这个样式,系统先发送WM_SHOWWINDOW消息给窗体,接着显示窗体
      • IsWindowVisible可以用来检测窗体是否可见,方法ShowWindow可以控制窗体显示隐藏
      • 可见不一定意味着屏幕中能看到它,有可能此窗体被其它窗体遮挡,也有可能它的父窗体不可见,还有可能是它的位置在屏幕外侧
    • Minimized
      • 拥有WS_MAXIMIZE 样式
    • Maximized
      • 拥有WS_MINIMIZE 样式
      • 关闭就是最小化,因此可以调用CloseWindow来使得窗体最小化
    • Restored
      • 恢复到原先的大小和位置

    小贴士

    系统在执行最大化、最小化、恢复之前,会先发送WM_QUERYOPEN 消息给窗体,如果窗体处理函数返回false,则不执行这个命令
     
    构造窗体时,通过指定CW_USEDEFAULT样式,让系统决定窗体位置和大小
     
    策略如下:
    针对于顶级窗体,如果当前应用程序还没有构造过一个顶级窗体,那么就基于屏幕的左上角进行定位。
    如果已经构造过一个顶级窗体,就根据这个顶级窗体的左上角进行定位。宽高也是,如果已经构造过顶级窗体,则使用这个顶级窗体的宽高。
    如果为构造过,则系统计算一个合适的宽高应用给这个顶级窗体。
    对于子窗体或者弹出式窗体,系统会给一个默认最小的宽高。
     
    系统检测到用户点击了最大化、最小化等等按钮时,发送WM_SYSCOMMAND消息,并且能够拿到以下command
    SC_CLOSE SC_MAXIMIZE SC_MINIMIZE SC_MOVE SC_RESTORE SC_SIZE

    窗体大小位置

    可以使用以下方法操作窗体
    • SetWindowPlacement 窗体最大化、最小化的位置、恢复时的位置和大小、窗体状态
    • MoveWindow 改变窗体位置
    • SetWindowPos 改变窗体位置,同时还可以改变窗体的显示状态
    • GetWindowRect 返回窗体的矩形区域大小,所有窗体都是基于屏幕左上角
      • ScreenToClient 和 MapWindowPoints 可以求出相对于父窗体的位置信息
      • GetClientRect 返回窗体的可视区域,位置始终是0,0,宽高是有效的
    窗体改变时,系统发送以下消息给窗体
    • WM_GETMINMAXINFO 设置了WS_THICKFRAME和WS_CAPTION样式,当窗体被用户最大化、最小化等操作时,系统发送此消息
    • WM_WINDOWPOSCHANGING 窗体位置、大小、z-order、显示状态即将发生改变时,系统发送此消息
      • 在这个时机,可以从消息中拿出WINDOWPOS,进行更改
    • WM_WINDOWPOSCHANGED 窗体位置、大小、z-order、显示状态已经发生改变时,系统发送此消息
      • 这个时机,调用DefWindowProc方法,将WM_WINDOWPOSCHANGED消息传递进去,系统接着会发送WM_SIZE和WM_MOVE消息

    窗体构造与销毁

    • 通过DestroyWindow 方法销毁
    • 销毁时,先发送WM_DESTROY消息给当前窗体,接着发送给子窗体。随后销毁当前窗体和子窗体
 
posted @ 2022-09-17 15:28  内心澎湃的水晶侠  阅读(407)  评论(0编辑  收藏  举报