当前位置: 首页 > news >正文

wxWidgets滚动窗口绘图总结

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没什么差别了,除非自己处理整体的像素移动,将保留的部分整体移动,并绘制新加入的部分。

相关文章:

  • mysql count when_在mysql中使用COUNT 或者SUM函数计算记录总数
  • Oracle创建主键时处理重复数据的程序
  • mysql engine用哪个_mysql各个engine之间的区别
  • 为什么要转mysql_资深程序员剖白:我为何要从MySql转向图形数据库
  • 近代自然科学为啥未诞生在中国----中国文化的欠缺
  • 一个可以独立运行的java应用程序_在Ubuntu上将Java应用程序作为服务运行
  • Commons-net FTPClient completePendingCommand()经常使程序死掉的原因分析以及解决方式
  • mysql数据的导出与导入_浅析MySQL数据的导出与导入知识点
  • Nebula3渲染层: Graphics
  • go分析和kegg分析_一些GO及KEGG分析的知识
  • iPhone对OpenGL ES的支持程度!
  • XACT与X3DAudio整合的问题
  • mysql的分离搭建_MySQL 读写分离环境搭建
  • Thanksgiving!——2008博文视点的光荣归于支持我们的读者、专家们
  • ai文字变成路径_新手必备!AI常用快捷键和一些小技巧
  • axios 和 cookie 的那些事
  • Javascript编码规范
  • jQuery(一)
  • JS基础篇--通过JS生成由字母与数字组合的随机字符串
  • mysql_config not found
  • Python利用正则抓取网页内容保存到本地
  • ReactNative开发常用的三方模块
  • Redux系列x:源码分析
  • spring学习第二天
  • thinkphp5.1 easywechat4 微信第三方开放平台
  • vue2.0一起在懵逼的海洋里越陷越深(四)
  • vue-router 实现分析
  • 技术:超级实用的电脑小技巧
  • 如何将自己的网站分享到QQ空间,微信,微博等等
  • 我与Jetbrains的这些年
  • 小程序开发之路(一)
  • ​​​​​​​​​​​​​​Γ函数
  • ​总结MySQL 的一些知识点:MySQL 选择数据库​
  • #每天一道面试题# 什么是MySQL的回表查询
  • #我与Java虚拟机的故事#连载04:一本让自己没面子的书
  • $(document).ready(function(){}), $().ready(function(){})和$(function(){})三者区别
  • (14)目标检测_SSD训练代码基于pytorch搭建代码
  • (离散数学)逻辑连接词
  • (理论篇)httpmoudle和httphandler一览
  • (译)计算距离、方位和更多经纬度之间的点
  • (原創) 如何使用ISO C++讀寫BMP圖檔? (C/C++) (Image Processing)
  • (转)Oracle 9i 数据库设计指引全集(1)
  • (转)setTimeout 和 setInterval 的区别
  • (转)创业家杂志:UCWEB天使第一步
  • .bat批处理(六):替换字符串中匹配的子串
  • .NET Core跨平台微服务学习资源
  • .net 调用php,php 调用.net com组件 --
  • .NET 设计模式—适配器模式(Adapter Pattern)
  • .net 微服务 服务保护 自动重试 Polly
  • .NET实现之(自动更新)
  • .vue文件怎么使用_我在项目中是这样配置Vue的
  • /etc/fstab 只读无法修改的解决办法
  • @ModelAttribute 注解
  • @property @synthesize @dynamic 及相关属性作用探究
  • @vue/cli脚手架