wxWidgets滚动窗口绘图总结
问题:从wxScrolledWindow派生一个类CXCanvas,作为绘图的画布。画布的尺寸可能非常大,远远超出屏幕的大小,绘制的内容可能非常多,全部绘制一遍非常耗时,当滚动条滚动时,覆盖对话框移动时,以及窗口尺寸变换时要让窗口更新竟可能的快,并且要减少闪烁。
分析:这个问题涉及到滚动窗口中的绘制,部分更新和减少闪烁。
1)滚动窗口绘制,和普通的DC绘制只有一个区别,就是要调用DoPrepareDC,这样还是按照画布的原点进行绘制就可以,不用考虑窗口滚动到哪儿了,或者直接使用OnDraw,默认的Paint事件处理器内部已经调用了DoPrepareDC,并调用OnDraw。
另外一个办法是用GetViewStart和GetScrollPixelsPerUnit(就是SetScrollRate设置的值)计算出滚动条的位置,然后用SetDeviceOrigin(-x,-y)手动设置原点位置(如果调用了DePrepareDC这一切都自动完成了)。
虚拟窗口的尺寸设置是SetVirtualSize,滚动条每次滚动的像素值设置用SetScrollRate。
2)消除闪烁。闪烁的根源是同一屏的画面不是一次呈现在眼前,让眼睛看到了整个绘制的过程从而感到闪烁。消除的办法是使用buffer,先在buffer上绘制,完成后一次性贴到窗口上。使用buffer能消除闪烁,但不能提高效率,因为往buffer上画和直接往窗口上画是一样慢的。
在wx中使用buffer很方便,最简单的是用wxBufferedPaintDC代替wxPaintDC,并且设置画布的SetBackgroundStyle(wxBG_STYLE_CUSTOM)。而且要自己处理EVT_ERASE_BACKGROUND事件,处理函数里面什么也不做就可以。
经过以上两步,可以实现在一个滚动窗口里面绘图,不需要考虑滚动条位置,且无闪烁。关键代码片段:
BEGIN_EVENT_TABLE(CXCanvas, wxScrolledWindow)
EVT_ERASE_BACKGROUND(CXCanvas::OnEraseBackground)
EVT_PAINT(CXCanvas::OnPaint)
END_EVENT_TABLE()
CXCanvas::CXCanvas(wxWindow* parent)
:wxScrolledWindow(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxALWAYS_SHOW_SB | wxHSCROLL | wxVSCROLL)
{
SetVirtualSize(4096,4096);//作为测试,在构造函数里设置了画布的尺寸
SetScrollRate(1,1);
SetBackgroundStyle(wxBG_STYLE_CUSTOM);
}
void CXCanvas::OnEraseBackground(wxEraseEvent& evt)
{
(void)evt;//什么也不做
}
void CXCanvas::OnPaint(wxPaintEvent& evt)
{
wxBufferedPaintDC dc(this);
DoPrepareDC(dc);//对于滚动窗口很重要,去掉后坐标就不对了
dc.SetBackground(*wxWHITE_BRUSH);
dc.Clear();
// 在Paint里面绘制整张画布的内容
Paint(dc);
}
具体使用时,绘制的代码放在Paint()里面,当Paint内容改变时,调用Refresh()刷新窗口,将画布整个重绘一遍。
需要注意的是,在重画事件中创建的 wxPaintDC设备上下文会自动将自己限制在需要重画的区域(即用GetUpdateRegio得到的区域),但这是针对窗口覆盖,resize,scroll这些系统能判断出来的情况的,如果你自己改变了画布的内容,必须自己调用Refresh或RefreshRect,因为wx并不知道要重绘哪些东西。另外Refresh只是让系统发送一个重绘事件,但不一定立刻重绘,可以调用Update立刻重绘。对于滚动条滚动的情况,在Windows平台上,wx使用了API ScrollWindow进行物理滚动,这样需要更新的区域就是新滚动进入的一块。所以滚动时不会更新整个客户区的。可以使用EnableScrolling(false,false)禁用物理滚动,这样当滚动条滚动时,Update region会包含整个客户区(当然一般没必要这样做,我的原则是自己知道要更新就refresh,否则让系统自己处理update region)。
3)滚动窗口内检测是否在客户区内
以上的做法对于普通的应用已经够了。考虑我们的情况,绘制整张画布一遍非常耗时。尽管wx在底层为我们处理了只针对更新区域绘制,以及滚动时采用物理滚动减少更新区域,但是将画布完全重绘一遍还是很耗时的(因为底层做像素是否要绘制的检测也很耗时),所以应该在绘制前就排除不在客户区里的物体,对于CXCanvas,由于他不知道Paint里面会画什么,所以无能为力,只有在使用CXCanvas时,在Paint里面得到客户区的尺寸已经滚动后的原点来计算出一个需要绘制的区域,然后用比较好的方法检测是否要绘制。
计算滚动窗口位置的方法是:
int vx, vy;
GetViewStart(&vx,&vy);
int px, py;
GetScrollPixelsPerUnit(&px,&py);
int x = vx*px;
int y = vy*py;
或者直接使用wxDCBase::GetDeviceOrigin,这个函数文档里没有
wxCoord x, y;
dc.GetDeviceOrigin(&x, &y);
x = -x;//因为原点位置肯定是<=0的
y = -y;
得到的x,y就是当前窗口区域的原点在整个虚拟画布上的位置。
得到客户区大小的方法:
int cl_width, cl_height;
GetClientSize(&cl_width, &cl_height);
这样虚拟画布上需要绘制的范围就是(x,y)-> (x+cl_width,y+cl_height),绘制时检测一下,或者根据这个范围计算出绘制的物体的范围。
进一步的优化,可以检测更新区域,但更新区域是个列表,检测时会遍历多遍,从而多次调用自己的paint,这样并不方便。可以在paint里面调用IsExposed来判断是否要绘制:
// 只有在需要的时候才重画以便提高效率
wxRect rectToDraw(i, j, tileSize, tileSize); //我要画一个tile,这是他在画布上的坐标
CalcScrolledPosition(rectToDraw.x, rectToDraw.y, & rectToDraw.x, & rectToDraw.y);
//因为窗口滚动了,而我们是在虚拟坐标上绘制,必须转成滚动后窗口的客户区坐标
if (!IsExposed(rectToDraw)) //检测tile所在的区域是否需要绘制,否则忽略
continue;
经过这个优化,能快不少,特别是滚动时和覆盖的窗口移动时感觉很明显。
到目前为止,对于大多数的应用,应该够用了,提高效率的关键还是检测方法,以及尽量使用RefreshRect来确定更新区域。比如画布上已经有很多东西了,往上面添加一个东西,只要绘制后RefreshRect这个物体的边框大小就可以了。否则整个客户区内的物体全画一遍可能还是很慢的。
4)画布buffer的解决方案
如果客户区内的东西还是很多怎么办? 可以建立一个虚拟画布大小的位图作为buffer。在OnPaint里面,只是将这个buffer贴到屏幕上,需要绘制时直接像这个buffer上绘制,这样只有第一次绘制整个画布时比较慢,画好后就能很快的绘制了(内容不改变时,只绘制一次),特别是滚动和覆盖窗口移动时速度比上面优化后的还快很多。这个方法的主要缺点是很费内存。如果使用客户区大小作为buffer的大小?那滚动的时候就比较麻烦了,因为buffer只有客户区那么大,滚动时必然要更新buffer的内容,如果简单的重画就和wx提供的BufferedDC没什么差别了,除非自己处理整体的像素移动,将保留的部分整体移动,并绘制新加入的部分。
分享到:
相关推荐
在wxWidgets中,有多种设备上下文类,例如wxWindowDC用于窗口绘图,wxClientDC用于绘制窗口客户区,wxPaintDC用于响应WM_PAINT消息时的绘制,wxMemoryDC则用于与位图交互,还有wxPrinterDC用于处理打印任务。...
窗口可以包含滚动条,具有特定的光标和鼠标指针样式,而窗口在屏幕上的位置和尺寸由坐标体系决定。开发者还可以自定义窗口的绘制过程,以及窗口的颜色和字体属性。 书中还提供了多个实例来辅助理解,包括一个简单的...
2. **窗口和控件**:wxWidgets 提供了丰富的窗口和控件类,如按钮、文本框、复选框、单选按钮、列表框、滚动条等,这些控件都具有与各自操作系统一致的外观和行为。 3. **事件系统**:wxWidgets 使用事件驱动模型,...
总结了wxWidgets的主要优势,并展望了其未来的发展方向。随着跨平台开发需求的增长,wxWidgets将继续成为一个重要的工具,为开发者提供强大而灵活的解决方案。通过不断的技术创新和社区支持,wxWidgets将更好地服务...
2. **窗口绘图**:使用GDI(Graphics Device Interface)或其他图形库(如Qt或wxWidgets)在窗口上绘制文本。在C++中,这通常涉及调用`TextOut`函数来在特定位置写入文本。 3. **滚动逻辑**:实现一个定时器或消息...
这是通过设置绘图的源矩形(对应于剪裁区域)和目标矩形(对应于窗口上的显示位置)来完成的。 6. 性能优化:对于大尺寸图像,直接处理整个位图可能会消耗大量资源。为提高性能,可以考虑预处理图像,将其分割成...
在MFC中,绘图通常涉及到CDC(Device Context)类,它是图形设备接口(GDI)的抽象,用于在窗口、打印机等设备上进行绘图操作。 绘图控件的核心在于其对数据的快速处理和渲染能力。对于波形图来说,这可能包括实时...
这可能需要用到像素级的绘图操作,或者利用图形库提供的路径绘制功能,如OpenGL、DirectX、Qt、wxWidgets或Java的AWT/Swing等。 实现这样的功能通常包括以下几个步骤: 1. **窗口初始化**:首先,需要设置一个窗口...
- `wx.ScrolledWindow`:可以滚动的窗口。 - `wx.Dialog`:对话框。 - `wx.TopLevelWindow`:顶层窗口的基类。 - `wx.Control`:控件的基础类,用于创建各种控件如按钮、文本框等。 #### 三、wxPython 2.5迁移...
在C++中,这通常通过重载窗口类的OnPaint事件处理函数来实现,使用GDI(Graphics Device Interface)或GDI+进行绘图。自绘可以用来创建独特的控件样式,如自定义按钮、菜单、滚动条等。开发者需要掌握图形绘制的基本...
在Microsoft Foundation Classes (MFC)库中,单文档界面(Single Document Interface, SDI)是一种常见的应用程序设计模式,它允许用户在一个窗口中查看和编辑单一的文档。在MFC中,实现SDI应用通常涉及创建一个继承...
5. Windows模块提供各种窗口类型,如面板、对话框和可滚动窗口。 wxPython API包含大量函数和组件,组件是构建GUI程序的基本构建块。在Windows平台上,这些组件被称为控件,如按钮、文本框和列表视图等。通过组合...
GUI是由窗口、按钮、菜单、滚动条等组成的交互式图形界面。开发者通常使用库或框架(如Windows API、Qt、wxWidgets、Java Swing或JavaFX)来创建GUI。在Python中,Tkinter是内置的GUI库,适用于快速搭建简单的绘图...
坐标变换可以通过调整绘图区域的逻辑坐标系来实现,这通常涉及到CDC类的SetWindowExtEx()和SetViewportExtEx()函数的使用,这两个函数可以分别设置设备坐标系和逻辑坐标系的大小。 此外,为了实现MATLAB那样的交互...
描述中提到的“滚动条”功能,通常是通过集成GUI库,如Qt或wxWidgets来实现的,但OpenCV自身并不直接支持滚动条。开发者可能使用了这些库与OpenCV结合,以便用户能够调整颜色。滚动条的值变化会触发一个事件,这个...
在C++ GUI编程中,这种特性可能通过事件处理、布局管理或者自定义绘图来实现,使得按钮在不同情境下可以有不同的显示效果,比如窗口大小变化时保持相对位置,或者在屏幕滚动时始终可见。 【标签】"c++" 指出这个...
5. **绘图基础**:虽然图形库提供了很多预定义的控件,但有时可能需要自定义绘图,如绘制图表或图形。这就需要用到基本的绘图命令,如设置颜色、线条样式、填充形状等。 6. **布局管理**:在设计屏幕界面时,合理地...
1. **图形用户界面设计**:PB画笔将涉及到窗口管理、控件布局、菜单设计等GUI基础元素。这包括使用各种控件如按钮、文本框、滚动条等,以及如何响应用户的点击、拖拽等交互事件。 2. **绘图函数和API**:源码中会...
在Visual C++中,界面特效通常涉及到Windows API、MFC(Microsoft Foundation Classes)库以及可能的Qt或WxWidgets等第三方库的使用。以下是一些可能涵盖的知识点: 1. **Windows API**: Windows API是开发Windows...
例如,使用OpenGL、DirectX或更简单的库如Qt、wxWidgets中的图形控件,创建可交互的地图视图。这些库提供了绘图函数,可以实现地图元素的绘制和更新。 4. **控件**:在Windows环境下,控件是用户界面的基本构建块。...