wxWidgets源码分析(7) - 窗口尺寸

目录

  • 窗口尺寸

    • 概述
    • 窗口Size消息的处理
      • 用户调整Size消息的处理
    • 调整窗口大小
      • 程序调整窗口大小
      • wxScrolledWindow设置窗口大小
    • 获取TextCtrl控件最合适大小

窗口尺寸

概述

类型 说明
Size 当前窗口实际大小,通过wxWindow::SetSize()获取;
Client Size 客户区大小;
Best Size 最合适的大小,用户可以实现wxWindow::DoGetBestSize()方法,自定义返回最合适的大小;
Best Client Size 最合适的客户区大小,用户可以实现DoGetBestClientSize方法实现自定义;
Minimal Size 用户可以通过wxWindow::SetMinSize()方法设置窗口的最小大小,下限值;
Maximum Size 用户可以通过wxWindow::SetMaxSize()方法设置窗口的最大大小,上限值;
Initial Size 初始大小,用户使用构造函数创建对象时传递的大小,一般用户会传递wxDefaultSize,此时控件会使用Best Size作为控件大小;
Virtual Size 本控件可以显示的虚拟控件大小,如果此大小比实际窗口大小大,则可以通过滚动方式来显示;

窗口Size消息的处理

消息的派发过程可以参考wxWindow消息处理过程文档,这里重点讨论WM_SIZE消息的处理。

从前面的文档知道,wxWidgets注册窗口时同时指定了窗口处理函数wxWndProc,当收到消息后系统会调用此函数来处理消息。

先以wxDialog窗口类为入口点,分析SIZE消息处理流程:

wxWndProc
    -> wxDialog::MSWWindowProc
    -> wxWindow::MSWWindowProc
    -> wxWindowMSW::MSWHandleMessage
    -> wxWindowMSW::HandleSize
    -> wxWindowBase::HandleWindowEvent
    -> wxEvtHandler::SafelyProcessEvent

所有都使用窗口的默认处理,走到了EventHandler类的SafelyProcessEvent函数 ,接下来的流程与正常消息处理流程相同,查找动态/静态消息注册表,找到就处理。

对于wxDialog类来说,它继承自wxTopLevelWindow,再上一层为wxTopLevelWindowBase类,我们可以看到在wxTopLevelWindowBase类的消息注册表中定义了SIZE消息的处理方法:

BEGIN_EVENT_TABLE(wxTopLevelWindowBase, wxWindow)
    EVT_CLOSE(wxTopLevelWindowBase::OnCloseWindow)
    EVT_SIZE(wxTopLevelWindowBase::OnSize)
END_EVENT_TABLE()

wxTopLevelWindowBase::OnSize函数定义如下,它调用了DoLayout进行实际操作:

如果当前窗口中绑定了Sizer对象,则会走第一个分支,调用Layout继续现有处理,如果没有绑定,那么此时需要找到子窗口(此时必须有唯一的子窗口),将子窗口设置为自己的ClientSize大小。

// wxTopLevelWindowBase
void OnSize(wxSizeEvent& WXUNUSED(event)) { DoLayout(); }

void wxTopLevelWindowBase::DoLayout()
{
    // if we‘re using constraints or sizers - do use them
    if ( GetAutoLayout() )
    {
        Layout();
    }
    else
    {
        // 检查必须只有一个子窗口...如果有多个则不处理
        // do we have any children at all?
        if ( child && child->IsShown() )
        {
            // exactly one child - set it‘s size to fill the whole frame
            int clientW, clientH;
            DoGetClientSize(&clientW, &clientH);
            child->SetSize(0, 0, clientW, clientH);
        }
    }
}

一般情况下我们需要Sizer类进行窗口控件布局,所以都走第一种场景,也就是调用wxWindowBase::Layout继续处理:

bool wxWindowBase::Layout()
{
    if ( GetSizer() )
    {
        int w = 0, h = 0;
        GetVirtualSize(&w, &h);
        GetSizer()->SetDimension( 0, 0, w, h );
    }
    ...
}

调用Sizer类的SetDimension方法,进而调用wxSizer::Layout方法

void wxSizer::Layout()
{
    // (re)calculates minimums needed for each item and other preparations
    // for layout
    CalcMin();

    // Applies the layout and repositions/resizes the items
    wxWindow::ChildrenRepositioningGuard repositionGuard(m_containingWindow);

    RecalcSizes();
}

注意看到,这里调用了两个方法,CalcMinRecalcSizes

先看下CalcMin方法,每种Sizer的处理方式不同,会有各自的方法,比如wxBoxSizer::CalcMin方法,它首先找到所有的item,调用item的CalcMin方法,这个方法只不过最终获取到了最小的窗口值而已,并没有进行窗口调整动作:

  1. 如果item也是一个Sizer,那么继续调用sizer的GetMinSize
  2. 如果item直接绑定的窗口,那么则调用窗口的GetEffectiveMinSize方法,进而继续调用wxWindowBase::GetMinSize
wxSize wxSizerItem::CalcMin()
{
    if (IsSizer())
    {
        m_minSize = m_sizer->GetMinSize();

        // if we have to preserve aspect ratio _AND_ this is
        // the first-time calculation, consider ret to be initial size
        if ( (m_flag & wxSHAPED) && wxIsNullDouble(m_ratio) )
            SetRatio(m_minSize);
    }
    else if ( IsWindow() )
    {
        // Since the size of the window may change during runtime, we
        // should use the current minimal/best size.
        m_minSize = m_window->GetEffectiveMinSize();
    }

    return GetMinSizeWithBorder();
}

继续看wxBoxSizer::RecalcSizes函数,这个函数首先根据变化方向进行计算窗口,安排每个item的位置和大小,并调用item的SetDimension方法:

  1. item根据自身的属性,比如对齐属性,expand属性,设置自己绑定的窗口的大小;
  2. 调用绑定窗口的大小,绑定的窗口可能是Sizer,也有可能是window,对于window则调用window的wxWindow::SetSize方法。
wxBoxSizer::RecalcSizes
    -> wxSizerItem::SetDimension

void wxSizerItem::SetDimension( const wxPoint& pos_, const wxSize& size_ )
{
    wxPoint pos = pos_;
    wxSize size = size_;
    if (m_flag & wxSHAPED)
        ...
    switch ( m_kind )
    {
        case Item_Window:
        {
            // Use wxSIZE_FORCE_EVENT here since a sizer item might
            // have changed alignment or some other property which would
            // not change the size of the window. In such a case, no
            // wxSizeEvent would normally be generated and thus the
            // control wouldn‘t get laid out correctly here.
            m_window->SetSize(pos.x, pos.y, size.x, size.y,
                              wxSIZE_ALLOW_MINUS_ONE|wxSIZE_FORCE_EVENT );
            break;
        }
        case Item_Sizer:
            m_sizer->SetDimension(pos, size);
            break;
    }
}

wxWindow继承自wxWindowBase,调用关系如下,随后在函数内构造一个wxSizeEvent,然后调用消息处理函数,处理流程与通常的消息处理流程相同。

注意这个窗口是Dialog内部的子窗口,也就是控件的窗口,此时调用的消息处理函数是子窗口的处理函数。

注意:

  1. 对于首次设置,也就是用户没有调整过窗口的大小,此时需要设置的位置和大小与当前Window中保存的大小是相同的,所以直接构建一个SizeEvent消息,然后调用HandleWindowEvent就可以了;
  2. 对于用户调整窗口大小的厂家,此时的位置和消息与Windows中保存的不同,所以需要走另外的分支;
wxWindow::SetSize
    -> wxWindowMSW::DoSetSize

void wxWindowMSW::DoSetSize(int x, int y, int width, int height, int sizeFlags)
{
    if ( x == currentX && y == currentY &&
         width == currentW && height == currentH &&
            !(sizeFlags & wxSIZE_FORCE) )
    {
        if (sizeFlags & wxSIZE_FORCE_EVENT)
        {
            wxSizeEvent event( wxSize(width,height), GetId() );
            event.SetEventObject( this );
            HandleWindowEvent( event );
        }
        return;
    }
}

如果我们的控件定义了处理函数,此时就会调用控件的OnSize函数。

用户调整Size消息的处理

上文得知,用户调整窗口的大小时,的wxWindowMSW::DoSetSize方法的处理方式是不同的,调用的是DoMoveWindow方法进行窗口大小位置进行设置,最终实现是调用Win32的::DeferWindowPos函数,该函数为指定的窗口更新指定的多窗口位置结构,然后函数返回该更新结构的句柄,实际上,这个函数会通过Windows消息发送给指定的子窗口。

void wxWindowMSW::DoSetSize(int x, int y, int width, int height, int sizeFlags)
{
    if ( x == currentX && y == currentY &&
         width == currentW && height == currentH &&
            !(sizeFlags & wxSIZE_FORCE) )
    {
        if (sizeFlags & wxSIZE_FORCE_EVENT)
        {
            wxSizeEvent event( wxSize(width,height), GetId() );
            event.SetEventObject( this );
            HandleWindowEvent( event );
        }
        return;
    }

    DoMoveWindow(x, y, width, height);
}

void wxWindowMSW::DoMoveWindow(int x, int y, int width, int height)
{
    ...
    DoMoveSibling(m_hWnd, x, y, width, height);
    ...
}

wxWindowMSW::DoMoveSibling(WXHWND hwnd, int x, int y, int width, int height)
{
    hdwp = ::DeferWindowPos(hdwp, (HWND)hwnd, NULL, x, y, width, height,
                                SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOACTIVATE);
}

调整窗口大小

程序调整窗口大小

Sizer中每次回计算窗口的最小大小,然后应用,所以,如果用户调用SetSize接口设置空间窗口的大小,实际上是不生效的。

解决办法就是调用SetMinSize设置窗口的最小窗口大小。

还可以调用SetInitialSize设置窗口大小,此函数会调用SetMinSize函数,然后再计算最合适的大小,所以我们建议调用SetInitialSize来进行设置:

可以在窗口类的构造函数后,加入到父窗口的Sizer前调用,如下,这样父窗口的Sizer进行设置窗口大小时,会直接使用用户设置的Size:

tcPortion0 = new wxTextCtrl(this, ID_TEXTCTRL1, _("Text"));
// 设置窗口大小
tcPortion0->SetInitialSize();
BoxSizer1->Add(tcPortion0, 0, wxALL|wxEXPAND, 5);

还有另外一种场景,就是软件使用过程中动态设置窗口大小,此时就要使用到父窗口的Layout方法了,如果调用了子窗口的SetInitialSize函数,但是没有调用父窗口的Layout方法,结果就是子窗口的大小调整了,但是父窗口的布局却没有变化,显示会乱掉。

调用了父窗口的Layout后,父窗口的Sizer会重新计算控件的大小,重新调整布局。

tcPortion0->SetInitialSize();
Layout();

wxScrolledWindow设置窗口大小

wxScrolledWindow提供了滚动条功能,当窗口的可视区域大于当前窗口大小时,一般的窗口将无法显示完整,在wxScrolledWindow中就可以通过滚动条滚动来显示所有区域。

用户所需要操作的就是调用类中的SetVirtualSize来设置虚拟窗口大小,然后调用SetScrollRate设置滚动粒度。

    SetVirtualSize(GetSizer()->GetSize());
    SetScrollRate(1,1);

还有一种比较简单的用法,直接调用GetBestVirtualSize来获取最佳的虚拟大小。

    SetVirtualSize(GetBestVirtualSize());
    SetScrollRate(1,1);

获取TextCtrl控件最合适大小

有些情况下,我们希望能够根据当前Text框的内容设置高度,此时我们可以使用下面的方法来设置窗口大小:

SetInitialSize(wxSize(GetSize().x, PositionToCoords(GetLastPosition()).y));

要求:

  1. 文本必须以‘\n‘结尾;
  2. 在通过PositionToCoords计算高度时,必须保证此时TextCtrl框的宽度为最终真实宽度;

原文地址:https://www.cnblogs.com/psbec/p/9784642.html

时间: 2024-10-28 10:14:24

wxWidgets源码分析(7) - 窗口尺寸的相关文章

wxWidgets源码分析(5) - 窗口管理

窗口管理 所有的窗口均继承自wxTopLevelWindows: WXDLLIMPEXP_DATA_CORE(wxWindowList) wxTopLevelWindows; wxTopLevelWindows Frame窗口创建过程 在使用Frame窗口的时候,我们一般从wxFrame继承,创建时通过调用Create方法进行创建: debugWXFrame::debugWXFrame(wxWindow* parent,wxWindowID id) { ... Create(parent, id

wxWidgets源码分析(6) - 窗口关闭过程

目录 窗口关闭过程 调用流程 关闭文档 删除视图 删除文档对象 关闭Frame App清理 多文档窗口的关闭 窗口的正式删除 窗口关闭过程总结 如何手工删除view 窗口关闭过程 调用流程 用户点击窗口的关闭按钮后,Win32系统会向当前的Frame对象发送WM_CLOSE消息,此时会进入到Frame的wxFrame::MSWWindowProc函数进行处理: WXLRESULT wxFrame::MSWWindowProc(WXUINT message, WXWPARAM wParam, WX

wxWidgets源码分析(8) - MVC架构

目录 MVC架构 wxDocManager文档管理器 框架菜单命令的执行过程 SDI消息传递总结 更新视图 MVC架构 wxDocManager文档管理器 wxWidgets使用wxDocManager类来管理MVC中的文档和视图的对应关系,使用方法: 创建一个wxDocManager对象,然后向此对象中增加文档模板wxDocTemplate对象,文档模板对象中说明了文档类型和该文档对应的文档类.视图类: 将此wxDocManager对象传递给wxDocParentFrame类(SDI),这样框

wxWidgets源码分析(9) - wxString

目录 wxString wxString的中文字符支持 wxString与通用字符串的转换 字符集转换 wxString wxString的中文字符支持 中文字符的编码格式如下: 汉字 GBK 区位码 UTF-8 UTF-16 中 D6 D0 54 48 E4 B8 AD 4E 2D 文 CE C4 46 36 E6 96 87 65 87 不同操作系统的默认内码 Windows系统(默认GBK):41 42 d6 d0 ce c4 Linux系统(默认UTF-8):41 42 e4 b8 ad

wxWidgets源码分析(2) - App主循环

目录 APP主循环 MainLoop 消息循环对象的创建 消息循环 消息派发 总结 APP主循环 MainLoop 前面的wxApp的启动代码可以看到,执行完成wxApp::OnInit()函数后,接着就执行wxApp::OnRun()函数进入App的主循环,wxApp继承自wxAppBase,所以实际调用的是wxApp::OnRun(),过程如下: wxAppBase::OnRun() -> wxAppConsole::OnRun() -> wxAppConsoleBase::MainLoo

wxWidgets源码分析(4) - 消息处理过程

目录 消息处理过程 消息如何到达wxWidgets Win32消息与wxWidgets消息的转换 菜单消息处理 消息处理链(基于wxEvtHandler) 消息处理链(基于wxWindow) 总结 消息处理过程 消息如何到达wxWidgets Windows程序有其自身运行的一套规律,::SendMessage是MS提供的windows消息发送接口,用户调用这个接口后会进入到MS系统库程序,此接口指定了目标HWND和消息参数,Windows系统内部会查找指定HWND,然后通过gapfnScSen

wxWidgets源码分析(1) - App启动过程

目录 APP启动过程 wxApp入口定义 wxApp实例化准备 wxApp的实例化 wxApp运行 总结 APP启动过程 本文主要介绍wxWidgets应用程序的启动过程,从app.cpp入手. wxApp入口定义 wxApp通过IMPLEMENT_APP宏注册App类,这个宏同时定义了入口,实现在wx/app.h文件中. // wx/app.h 文件中定义 #define IMPLEMENT_APP(app) wxIMPLEMENT_APP(app); // 可以忽略 wxIMPLEMENT_

wxWidgets源码分析(3) - 消息映射表

目录 消息映射表 静态消息映射表 静态消息映射表处理过程 动态消息映射表 动态消息映射表处理过程 消息映射表 消息是GUI程序的核心,所有的操作行为均通过消息传递. 静态消息映射表 使用静态EventTable将事件号和处理代码绑定起来,用法示例: // 声明 class debugWXFrame: public wxFrame { DECLARE_EVENT_TABLE() }; // 实现 BEGIN_EVENT_TABLE(debugWXFrame,wxFrame) EVT_MENU(ID

Android应用Activity、Dialog、PopWindow窗口显示机制及源码分析

[工匠若水 http://blog.csdn.net/yanbober 转载烦请注明出处,尊重劳动成果] 1 背景 之所以写这一篇博客的原因是因为之前有写过一篇<Android应用setContentView与LayoutInflater加载解析机制源码分析>,然后有人在文章下面评论和微博私信中问我关于Android应用Dialog.PopWindow.Toast加载显示机制是咋回事,所以我就写一篇文章来分析分析吧(本文以Android5.1.1 (API 22)源码为基础分析),以便大家在应