基于Qt 的QScrollArea自定义鼠标拖拽滑动式导航栏及解决拖动时抖动问题

  • A+
所属分类:Qt专栏 Qt经验集

思路

QScrollArea中可以添加控件,并且随着控件数量的增多,自动显示滚动条;通过拖拽滚动条来浏览被遮挡住的控件。但这里,我们希望通过鼠标拖拽滑动来代替拖拽滚动条,这样可以提高交互友好性。一方面,我们要重写界面的鼠标事件,来响应鼠标拖拽;另一方面,如果QScrollArea中存在控件QPushButton,那么当鼠标点击QPushButton进行拖拽时,QPushButton会拦截掉鼠标事件,不会向父控件传递,也就无法滑动QScrollArea,所以,我们还要写个事件过滤器,给QScrollArea中每个子控件安装该过滤器,防止鼠标事件被子控件拦截掉。

这里需要补充下知识点,Qt界面的事件传递机制是从子控件向父控件传递的。鼠标事件产生后,会一路转发给鼠标位置所在的控件,如果这个转发过程没有被拦截(如过滤器等),那么就会直接到控件的事件处理函数中;控件处理完毕后,如果该事件没有被消费完,则继续向父控件传递。QPushButton默认是消费了鼠标点击事件的,除非重写。这里与Android的事件分发机制不一样,Android是父控件到子控件,再从子控件到父控件的u型传递路线。

代码

基于Qt 的QScrollArea自定义鼠标拖拽滑动式导航栏及解决拖动时抖动问题 基于Qt 的QScrollArea自定义鼠标拖拽滑动式导航栏及解决拖动时抖动问题 基于Qt 的QScrollArea自定义鼠标拖拽滑动式导航栏及解决拖动时抖动问题 基于Qt 的QScrollArea自定义鼠标拖拽滑动式导航栏及解决拖动时抖动问题

这里写的是横向移动,如果是纵向,把move事件中改成纵向计算即可。

使用时,创建一个EDragSlideWgt,将所有按钮或者控件放到一个widget中,通过调用setContent方法,将widget放入到EDragSlideWgt中。另外,每个按钮或者控件都要将EDragSlideWgt对象安装为事件过滤器。

QScrollArea中的Widget如何随内容自适应大小

Designer拖拽QScrollArea时默认设置widgetResizable属性为true,因此通过Designer拖拽的QScrollArea中的Widget大小是随内容自适应的;但是用代码创建的QScrollArea默认widgetResizable属性为false,要调用setWidgetResizable来将该属性设置为true

拖动时抖动问题

基于Qt 的QScrollArea自定义鼠标拖拽滑动式导航栏及解决拖动时抖动问题

Qt帮助里面写的很清楚,要使用globalPos来避免拖动抖动的问题。

主要原因是:如果在moveEvent()中调用move()或者setGeometry()时会造成无限递归的情况。

我个人猜测是,在mouseMoveEvent事件中一旦使用了如move()方法,控件位置发生了变化,同时鼠标相对于控件的位置也自动发生了一次变化(第一次是用户鼠标移动产生信号,第二次则是move后控件位置改变发出信号),在处理一次鼠标移动事件中是产生了两次鼠标移动信号,后边不再产生信号则是因为鼠标的相对坐标未改变而没有触发下个信号。

原因找到了,解决方法就有了:屏蔽掉move后产生的信号,不对该信号进行处理,跳过即可。可怎么操作呢?经测试,使用move()方法时,如果控件移动的坐标与当前的位置一致(例如当前的geometry为(10,10,640,480),move(10,10)),是不会触发鼠标移动事件的,因此只要保证move()的传入的坐标与移动后传入的一致即可。而要做到这一点则需要用到globalPos() 。

这里边都说了,如果在鼠标事件中使用了move(),最好使用globalPos()来防止抖动的发生。原理应该还是鼠标的相对坐标问题,如果使用了globalPos(),鼠标的位置一直是相对于屏幕左上角的,move()之后还是不变的;而pos()是相对于控件左上角的,在move()之后会改变造成二次触发事件。

Qt大课堂-QtShare

发表评论

您必须登录才能发表评论!