• 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消息给当前窗体,接着发送给子窗体。随后销毁当前窗体和子窗体
     
  • 相关阅读:
    【算法设计与分析基础】8、背包问题
    【算法设计与分析基础】8、穷举 旅行商问题
    【算法设计与分析基础】7、蛮力求平面中距离最近的两点
    【算法设计与分析基础】6、蛮力字符匹配
    【算法设计与分析基础】5、冒泡排序与选择排序
    【算法设计与分析基础】4、伪随机数
    【算法设计与分析基础】3、斐波拉契数列
    【算法设计与分析基础】2、矩阵相乘
    【算法设计与分析基础】1、埃拉托色尼筛选法
    【JAVA并发编程实战】12、使用condition实现多线程下的有界缓存先进先出队列
  • 原文地址:https://www.cnblogs.com/chenyingzuo/p/16702726.html
Copyright © 2020-2023  润新知