先上效果图——
一、项目构建思路
1. 创建程序入口:“MyApp”类
- 继承
wxApp
类,创建MyApp
类,并重写OnInit()
函数。 - 在
OnInit()
函数中创建程序主窗口MyFrame
。
2. 创建主窗口
- 继承
wxFrame
创建MyFrame
类。
3. 给主窗口添加面板
- 继承
wxPanel
创建MyPanel
类。 - 在主窗口中创建一个面板(
MyPanel
),该面板作为主窗口的唯一控件(如此一来这个面板将会默认占满整个主窗口的客户区域)。
4. 给面板添加内容
- 给面板添加一个布局。
- 在面板中添加滚动窗口
wxScrolledWindow
,并且把滚动窗口放到布局中。 - 使用定时器定时创建或销毁静态文本控件(
wxStaticText
)。
二、代码分解(省略大部分注释)
1. 入口
程序入口类 MyApp
。它继承自 wxApp
,并且重写了 OnInit()
方法。在 OnInit()
中,创建了一个 MyFrame
对象,将其显示出来,最后返回true表示程序初始化成功。
class MyApp : public wxApp
{
public:
virtual bool OnInit()
{
MyFrame *frame = new MyFrame();
frame->Show();
return true;
}
};
wxIMPLEMENT_APP(MyApp);
2. 创建主窗口
主窗口类 MyFrame
。它继承自 wxFrame
,在其构造函数中,调用了基类 wxFrame
的构造函数来创建一个窗口,并在这个窗口中新建了一个 MyPanel
面板。
class MyFrame : public wxFrame
{
public:
MyFrame()
: wxFrame(NULL, wxID_ANY, "Scrollable Panel Example", wxDefaultPosition, wxSize(400, 300))
{
new MyPanel(this);
}
};
3. 给主窗口添加面板
首先,看看如何创建面板并添加到主窗口中。
创建了名为 MyPanel
的面板类,它继承自 wxPanel
。MyPanel
类在构造函数中,首先创建了一个名为 sizer
的垂直布局器(wxBoxSizer
)。
wxBoxSizer *sizer = new wxBoxSizer(wxVERTICAL);
然后创建了一个名为 m_scrolledWindow
的滚动窗口(wxScrolledWindow
),并将其添加到 sizer
中。这样滚动窗口就能占据面板的全部空间。
m_scrolledWindow = new wxScrolledWindow(this);
sizer->Add(m_scrolledWindow, 1, wxEXPAND | wxALL, 5);
接着,创建了一个名为 m_innerSizer
的垂直布局器,并将其设置为滚动窗口的布局器,以及设置滚动窗口的滚动速率。
m_innerSizer = new wxBoxSizer(wxVERTICAL);
m_scrolledWindow->SetSizer(m_innerSizer);
m_scrolledWindow->SetScrollRate(0, 20);
最后,将 sizer
设置为面板的布局器。
SetSizer(sizer);
4. 给面板添加内容
在这部分代码中,设置了一个定时器用于定时添加和移除控件,并绑定了定时器事件的处理函数。
m_count = 0;
m_timer = new wxTimer(this, wxID_ANY);
Bind(wxEVT_TIMER, &MyPanel::OnTimer, this);
m_timer->Start(500);
定义了一个函数对象 funRefreshScrollWindow
,用于刷新滚动窗口。在这个函数中,首先获取 m_innerSizer
的最小高度并设置为滚动窗口的虚拟高度,然后根据滚动窗口的高度和面板的高度来判断是否需要启用滚动条,如果需要则将滚动窗口滚动到最底部,最后刷新滚动窗口的显示。
auto funRefreshScrollWindow = [this](bool increase) {
// 调整滚动窗口的滚动范围(重点)
int totalHeight = m_innerSizer->GetMinSize().y;
m_scrolledWindow->SetVirtualSize(-1, totalHeight);
// 检查是否需要启用滚动条
wxSize panelSize = GetClientSize();
if (totalHeight > panelSize.y)
m_scrolledWindow->EnableScrolling(increase, increase);
// 手动滚动到最底部
int scrollPos = totalHeight - panelSize.y;
m_scrolledWindow->Scroll(0, scrollPos);
// 刷新滚动窗口
m_scrolledWindow->Refresh();
};
定时器事件的处理函数为 OnTimer
。在这个函数中,根据 m_count
的值来增加或移除 wxStaticText
控件,每当增加或移除一个控件时就调用 funRefreshScrollWindow
来刷新滚动窗口的显示。当 m_count
超过 20 时,停止定时器。
if (m_count <= 10) {
wxString text = wxString::Format("Item %d", m_count);
wxStaticText *item = new wxStaticText(m_scrolledWindow, wxID_ANY, text);
m_innerSizer->Add(item, 0, wxALL, 5);
funRefreshScrollWindow(true);
}
else if (m_count <= 20) {
wxWindow *lastItem = m_innerSizer->GetItem(m_innerSizer->GetItemCount() - 1)->GetWindow();
m_innerSizer->Detach(lastItem);
lastItem->Destroy();
funRefreshScrollWindow(false);
}
else {
m_timer->Stop();
}
m_count++;
三、重点代码分析
wxBoxSizer *sizer = new wxBoxSizer(wxVERTICAL);
:创建一个垂直方向的盒型布局器。布局器是wxWidgets
中的一种工具,用于管理控件的位置和大小。sizer->Add(m_scrolledWindow, 1, wxEXPAND | wxALL, 5);
:将滚动窗口m_scrolledWindow
添加到布局器sizer
中。wxEXPAND | wxALL
表示滚动窗口应扩展以填充其在布局器中的空间,并在所有方向上都应添加边距,边距的大小为5个像素。m_scrolledWindow->SetSizer(m_innerSizer);
:将内部的布局器m_innerSizer
设置为滚动窗口m_scrolledWindow
的布局器。这将使滚动窗口内的控件按照m_innerSizer
的布局来排列。m_timer = new wxTimer(this, wxID_ANY); 和 m_timer->Start(500);
:创建并启动一个定时器,定时器每 500毫秒 触发一次。定时器用于定时添加和删除静态文本控件。Bind(wxEVT_TIMER, &MyPanel::OnTimer, this);
:将定时器事件绑定到MyPanel::OnTimer
函数。这意味着每次定时器触发时,都会调用MyPanel::OnTimer
函数。int totalHeight = m_innerSizer->GetMinSize().y;
:获取内部布局器m_innerSizer
的最小高度。这个高度值代表了内部布局器(及其内部的所有控件)的最小合适高度。m_scrolledWindow->SetVirtualSize(-1, totalHeight);
:设置滚动窗口m_scrolledWindow
的虚拟大小。虚拟大小是指滚动窗口内部的大小,可能大于滚动窗口本身的显示大小。如果虚拟大小大于显示大小,那么滚动窗口就会显示出滚动条。wxStaticText *item = new wxStaticText(m_scrolledWindow, wxID_ANY, text);
:创建一个静态文本控件,并将其添加到滚动窗口中。静态文本控件用于显示一些不可编辑的文本信息。m_timer->Stop();
:停止定时器。这个调用会停止定时器的触发,之后定时器不再触发,除非再次调用Start()
方法启动定时器。
四、完整代码(带注释)
#include <wx/wx.h>
class MyPanel : public wxPanel
{
public:
MyPanel(wxWindow *parent)
: wxPanel(parent)
{
wxBoxSizer *sizer = new wxBoxSizer(wxVERTICAL);
// 创建滚动窗口并添加到面板中
m_scrolledWindow = new wxScrolledWindow(this);
sizer->Add(m_scrolledWindow, 1, wxEXPAND | wxALL, 5);
m_innerSizer = new wxBoxSizer(wxVERTICAL);
m_scrolledWindow->SetSizer(m_innerSizer);
SetSizer(sizer);
// 设置滚动速率
m_scrolledWindow->SetScrollRate(0, 20);
// 初始化计数器和定时器
m_count = 0;
m_timer = new wxTimer(this, wxID_ANY);
Bind(wxEVT_TIMER, &MyPanel::OnTimer, this);
// 启动定时器,每500毫秒添加一个控件
m_timer->Start(500);
}
private:
void OnTimer(wxTimerEvent &event)
{
// 刷新滚动窗口
auto funRefreshScrollWindow = [this](bool increase) {
// 调整滚动窗口的滚动范围(重点)
int totalHeight = m_innerSizer->GetMinSize().y;
m_scrolledWindow->SetVirtualSize(-1, totalHeight);
// 检查是否需要启用滚动条
wxSize panelSize = GetClientSize();
if (totalHeight > panelSize.y)
m_scrolledWindow->EnableScrolling(increase, increase);
// 手动滚动到最底部
int scrollPos = totalHeight - panelSize.y;
m_scrolledWindow->Scroll(0, scrollPos);
// 刷新滚动窗口
m_scrolledWindow->Refresh();
};
if (m_count <= 10) { // 每次定时器触发时,增加一个控件,并刷新滚动窗口
wxString text = wxString::Format("Item %d", m_count);
wxStaticText *item = new wxStaticText(m_scrolledWindow, wxID_ANY, text);
m_innerSizer->Add(item, 0, wxALL, 5);
funRefreshScrollWindow(true);
}
else if (m_count <= 20) { // 每次定时器触发时,逐渐移除一个控件,并刷新滚动窗口
wxWindow *lastItem = m_innerSizer->GetItem(m_innerSizer->GetItemCount() - 1)->GetWindow();
m_innerSizer->Detach(lastItem);
lastItem->Destroy();
funRefreshScrollWindow(false);
}
else {
// 停用定时器
m_timer->Stop();
}
m_count++;
}
private:
wxScrolledWindow *m_scrolledWindow;
wxBoxSizer *m_innerSizer;
int m_count;
wxTimer *m_timer;
};
class MyFrame : public wxFrame
{
public:
MyFrame()
: wxFrame(NULL, wxID_ANY, "Scrollable Panel Example", wxDefaultPosition, wxSize(400, 300))
{
new MyPanel(this);
}
};
class MyApp : public wxApp
{
public:
virtual bool OnInit()
{
MyFrame *frame = new MyFrame();
frame->Show();
return true;
}
};
wxIMPLEMENT_APP(MyApp);
五、要点分析
- 面板和滚动窗口的布局管理:面板(
wxPanel
)和滚动窗口(wxScrolledWindow
)都使用了wxBoxSizer
进行布局管理。在wxWidgets
中,布局管理是一个重要的概念,它决定了窗口中各个控件的位置和大小。理解和熟悉各种布局器(wxSizer
的子类)的工作方式是很重要的。 - 虚拟大小和滚动条:在滚动窗口(
wxScrolledWindow
)中,需要设置虚拟大小(SetVirtualSize
)来启用滚动条。虚拟大小是滚动窗口内部的大小,如果这个大小大于滚动窗口本身的显示大小,滚动窗口就会显示出滚动条。那有没有什么办法可以始终禁用滚动条呢?有,只需向这样调用函数——m_scrolledWindow->ShowScrollbars(wxSHOW_SB_NEVER, wxSHOW_SB_NEVER)
,你要的效果就可以实现了。 - 定时器的使用:定时器(
wxTimer
)在这个程序中用来定时添加和删除控件。使用定时器时,需要注意的是要绑定定时器事件(wxEVT_TIMER
)到相应的处理函数,以便在定时器触发时能执行相应的操作。 - wxWidgets事件处理系统:在wxWidgets中,事件处理是通过绑定事件到处理函数来实现的。在这个程序中,定时器事件(
wxEVT_TIMER
)被绑定到了MyPanel::OnTimer
函数(正如第3点所言)。理解wxWidgets
的事件处理系统是掌握wxWidgets
的关键。 - 程序的入口:在
wxWidgets
程序中,通常需要定义一个继承自wxApp
的类,并在其OnInit
方法中创建主窗口。然后使用wxIMPLEMENT_APP
宏来指定这个类作为程序的入口。在理解这个程序时,需要先找到这个程序的入口,也就是wxApp
的子类。
【wxWidgets实战】窗口自适应滚动 至此完毕,欢迎大家指正!在此祝愿大家工作顺利,日日高升~~