PDA

View Full Version : QScrollBar arrow keys



jstarr
1st October 2018, 19:50
I am displaying a scrolling page of line-oriented data. I want to control the display by specific lines and always know the index in my underlying data array of each line on the page, so I have my own scroll bar value-changed handling. When the value changes, I figure out what lines should be on the page and I update the display. For slider moves, it works fine. I want to arrange that the up/down arrows that come with the QScrollBar will move my index up one line (unless already at the top) or down one (unless already at the bottom). What I cannot find in the documentation is any way to capture a mouse click on the arrows. nextLine() and prevLine() don't do it. The value-changed signal does not let me distinguish arrow clicks. I need to know specifically when the user clicks on one of the arrows, just as with any control button I might create.

Can someone tell me how to do this?

Thanks.

d_stranz
2nd October 2018, 16:27
You want to change the single step and page step values. QAbstractSlider::setSingleStep() and QAbstractSlider::setPageStep().

It isn't necessary to capture mouse clicks. When the user clicks on one of the arrows (or presses the up arrow or down arrow key) or clicks above or below the slider (or presses up / down arrow with Shift, or Page Up / Down) , the valueChanged() signal will be emitted with the new value incremented or decremented by the single or page step size respectively.

You should consider simply using QListWidget or QListView to display your line-oriented data. It will probably do everything you want right out of the box without you doing anything more than supplying your data as a QStringList.

jstarr
2nd October 2018, 19:37
That doesn't work. I can't distinguish between a value change from an arrow key and an initial change caused by dragging the thumb. I don't want to totally reimplement just to switch to a QListView. As i indicated, I really want to keep complete control over the display. The idea of the system adjusting the display when scrolled based on a fixed page step will not work. I would really appreciate it if you or someone would answer my original question, or state that detecting a click on an arrow directly cannot be done.

d_stranz
3rd October 2018, 01:20
I would really appreciate it if you or someone would answer my original question

I tried to. What I don't understand is why you think you need to distinguish between changing the slider position by dragging the thumb versus changing the position by clicking on the arrows or in the space above or below the thumb, by using the thumbwheel, or by an arrow up / down, page up / down, home / end key press.

If you have done your math correctly and know how many lines fit on the display (which must be calculated to include font size and inter-line spacing), then you set the page step size equal to that value. You also set the scroll bar range knowing the full document range minus page size. If you haven't read the comments concerning these calculations in the QScrollBar documentation you should do so.

If you really want to get into it, you can implement a slot for QAbstractSlider::actionTriggered() and manually change the slider position.


The idea of the system adjusting the display when scrolled based on a fixed page step will not work.

The "fixed page step" is not a constant; it depends on the number of lines in your document and the number of lines that can be displayed on one "page". With a new document or a resize of the page, it must be recalculated.

This is why I suggested using QListView; it does all of these calculations for you based on the font and number of lines you load into the view and displays the correct part of the list in all cases. But if you insist that that "will not work", then you absolutely will have to reinvent the wheel and do all the calculations correctly including all the corner cases instead of using what the Qt developers have already done for you.

By the way, you do not have to convert your current implementation to anything. You simply wrap your existing document with a class derived from QAbstractItemModel. Each line in your document is a row in the model, and it has only one column. It is flat, so items have no parents, and the only data to be returned is the text for each line. You need to implement the rowCount(), columnCount(), index(), parent(), and data() methods for the model. You can do each of those in as few as one line of code. From the document side, all it needs is methods that return the number of lines and the text for each line by index. You almost certainly have those methods already if you are laying out the display manually.

You then pass a pointer to that wrapper model to the list view and your work is done. No calculations, no manual layout, nada, and you can go on to write the more important parts of your code.

jstarr
3rd October 2018, 18:03
Each "page" is a view into a list of thousands of text lines. At any time the selection of lines on display depends on various variable filters which decide to exclude or include lines until the page is full. The filters are applied during the display of each page. Thereby I avoid the needless overhead of maintaining a list of just the lines that are "eligible" for display at any moment (i.e., any set of filter conditions), at the expense of making it infeasible to step over a page without the ability to apply the filters. If, for example, the user clicks on the scroll-up-arrow, what I want is to start with the current top line (identified by an index into the total data array) and back up the index by one, filter to see if that line should be displayed, back up again if not, and so on, until I come to a line that should be displayed, or reach the very top of the array. Then I display a page of (filtered) items from there until the page is full, or the bottom of the array is reached.
I have chosen to implement this by creating a page-full of labels, the number varying by page size, and filling them with text as determined by the index and filters. Thereby I have a very small number of UI objects (say, 20) despite having perhaps 12000 data items. Of course there are other ways of doing this. But it is still a requirement that I be able to filter the display on the fly, and that, for example, I be able to specify that clicking to go up means backing up by exactly one filtered line.
I hope that explanation is helpful. Thank you for your time.

I can see that your suggestion could work as far as applying my filters is concerned. However if it is possible to capture the arrow clicks, I already have everything else that I need to complete the job much more easily.

d_stranz
3rd October 2018, 22:11
At any time the selection of lines on display depends on various variable filters which decide to exclude or include lines until the page is full.

A QSortFilterProxyModel sitting between the QAbstractItemModel and the QListView will handle that. The view will never ask the model for more lines than can be displayed so there is no worry about duplicating all of the strings contained in the document.

I appreciate that you have already put a lot of effort into your implementation; I'm just trying to point out that Qt already has many of the things in place that you have written yourself and that you are struggling to get working correctly.


However if it is possible to capture the arrow clicks

QScrollBar and QAbstractSlider are abstractions. There is no way to get to the internals to determine if a mouse click occurred on an arrow icon, the thumb, or anywhere else. You can install an event filter and trap mouse events, but without knowing the location and sizes of any of the sub-elements, you can't determine where the click occurred. The details of that are abstracted away from you, and instead you are given a set of signals that convert all of the user interactions (mouse, keyboard, and wheel) into logical locations of the thumb. Translating those logical locations into line numbers in your document means you must keep track of which filters are in effect, know how many resulting lines are eligible for display, and have set the scroll bar range accordingly.

jstarr
4th October 2018, 18:28
I thought I had posted another reply, but it seems to have gone into oblivion. I found that by using the actionTriggered signal, I could get exactly the behavior that I wanted. It is interesting to know that if I need to do something like this again, I could try the Model/View facility.

Thank you for your time.

d_stranz
5th October 2018, 17:22
I mentioned actionTriggered() a few posts back. Glad you could use it.

Once you wrap your head around it, the Model / View architecture is a pretty powerful concept. With a single internal data structure wrapped by one or more QAbstractItemModel implementations and layered with QAbstractProxyModel implementations, you can present the data in that model is all sorts of ways.

I write scientific data analysis software. In one app, I have a hierarchical data structure that represents the results of an analysis. From that internal model, I can display it as a tree and a table, drill down into specific layers of the hierarchy, or select out columns from the table to pass into x-y data plots. With the right signal and slot handling, any change to the internal model gets immediately transmitted to every part of the GUI.

I don't think about writing specialized classes and methods to pull data out of my data structures and display it any more; instead I think about how to structure models and proxies to display things in out of the box Qt views.