PDA

View Full Version : Qt design jQuery like custom dropdown menu



Cupidvogel
2nd May 2015, 18:21
I am trying to design a custom rich dropdown, like the one you can find here: https://jsfiddle.net/53vLfjfu/.

I figured I will design the dropdown widget first, as the remaining task of showing and hiding on click will be trivial. So here is what I came up with:



class DropdownOption: public QPushButton
{

public:
DropdownOption(std::string aText, QWidget *aParent);

};

DropdownOption::DropdownOption(std::string aText, QWidget *aParent):
QPushButton(QString::fromStdString(aText),aParent)
{
this->setFixedHeight(30);
this->setCursor(Qt::PointingHandCursor);
this->setCheckable(false);
this->setStyleSheet("background: rgb(74,89,98); color: black; border-radius: 0px; text-align: left; padding-left: 5px; border-bottom: 1px solid black;");
}



//parent is the widget with all the options which will roll down when the dropdown widget showing the current value is clicked
QFrame *parent = new QFrame(this);
int width = 120;
parent->setParent(fParent);
//this width will be flexible, here I am setting to 160 for checking purpose
parent->setGeometry(40,40,width,160);
parent->setStyleSheet("border-radius: 5px; background:red;");

QFrame *actualDropDown = new QFrame(parent);
actualDropDown->setFixedWidth(width-30);
actualDropDown->setStyleSheet("background: blue;");

QVBoxLayout *layout = new QVBoxLayout();
actualDropDown->setLayout(layout);
for (int i = 0; i < fValues.size(); i++)
{
DropdownOption *button = new DropdownOption(fValues[i],actualDropDown);
layout->addWidget(button);
}

QScrollArea *scroll = new QScrollArea(this);
scroll->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
scroll->setWidget(actualDropDown);
//20 px less than parent, to ensure that the options don't clip parent's border radius when scrolling up or down past the ends
scroll->setMaximumHeight(140);
scroll->move(0,10);
scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOf f);


Couple of call outs, unlike in the HTML dropdown, I cannot 'snuggle' the options at the top and bottom of the parent widget, because I don't want them to have border-radius (which will show when they are highlighted after being clicked or hovered upon), and if the children don't have border-radius, the border radius of the parent (in this case, the widget which holds the options) gets clipped off when the children scroll through the ends. That's why I have set the widget at a height of 10 px from top and bottom.

Secondly, I have set the width of actualDropDown to be 30 px less than parent, because if I set to the same width, the scrollbar moves out of viewport.
Here's how it looks like currently:

11155

As you can see, I am unable to move the scrollbar inside the blue colored widget (in the example link, the options cover the parent widget breath wise, and the scroll bar appears over them, not outside them). Not to mention, QFrame does not let me access its scroll bar (which say QTextEdit does through the verticalScrollBar()->setStylesheet() method), thus I am not able to style it like the one in the example (which is actually how scroll bar appears in Mac by default).

How do I make it work, making it look just like the example dropdown? (I have set the backgrounds to blue, red, grey for understanding purpose, I will later reset them to match the ones in the link.)

anda_skoa
3rd May 2015, 09:26
You could draw the widget yourself and have full control over every pixel.

Btw, QFrame doesn't have scrollbars.

Cheers,
_

Cupidvogel
3rd May 2015, 09:34
You mean implement paintEvent? I have done that for basic stuff like inserting HTML in QPushButtons, but I don't know how to use that to achieve my aim here. Some pseudocode will help..

anda_skoa
3rd May 2015, 10:42
Yes, by implementing paintEvent().

All widgets that come with Qt are drawn this way, usually by delegating parts of the process to the current QStyle (to achieve different appearances on different platforms).

Hmm, you might also be able to approach your goal with a proxy style.

Or using a QListView.

Cheers,
_

Cupidvogel
3rd May 2015, 11:18
The problem with QListView is that it is very difficult, if at all possible, to style the individual QListWidgetItems. Same as QTreeWidgetItem. Neither have a setStyleSheet method to alter styles, or modify the geometry for layout purpose.

anda_skoa
4th May 2015, 07:41
I was not thinking about QListWidget, but about QListView and using a custom item delegate.

Mostly because it already deals with a scrolling list of items. A fully custom widget would have to implement that, so QListView sounded like something to investigate as a starting point.

Cheers,
_

Cupidvogel
9th May 2015, 21:32
In the meantime, I wanted to simulate the blur/focus events like in the JsFiddle link I provided, that whenever the dropdown option widget is showing, and I click anywhere else (somewhere in the main widget, some widget int he main widget, or outside the main window on desktop or somewhere else), it should hide. How do I capture the click event outside a QFrame?

anda_skoa
10th May 2015, 08:43
You could install an event filter on the parent, the parent window or the application.

Cheers,
_

Cupidvogel
10th May 2015, 08:54
Didn't understand. Install event filter for what? I already installed an event filter on the option widget, to detect hover event on the options and change the color appropriately when hovered in/out. What do I need to do in addition for the hide on clicking outside thingy?

Cupidvogel
10th May 2015, 11:15
Never mind. I solved it by implementing focusOutEvent().

I have one more problem (I swear it is the last!). I want to emulate exact OS provided dropdown behavior. So in normal dropdowns, whenever you press some character key, it takes you to the first option which starts with that character. Press 2 or 3 keys in rapid succession, and the dropdown will collect all three and take you to the first option which starts with those three characters.

How do I implement it here? I can set a keydown handler and get the first character. But how do I get the remaining ones in queue?

anda_skoa
10th May 2015, 11:55
I would do it like this:

when you receive a key event, store the key and start a timer
if there is another key, restart the timer.
if the timer emits timeout() consider all received keys your final input.

Cheers,
_

Cupidvogel
10th May 2015, 12:11
Okay. Suppose I receive a 'k' press. I start the timer for 5 seconds. After 2 seconds, I receive a 'a' press. Shouldn't I immediately fire an event with 'ka' instead of waiting for a timeout?

anda_skoa
10th May 2015, 22:47
That will obviously depend on the behavior you have as a goal.

Cheers,
_

Cupidvogel
10th May 2015, 23:00
Yes, I want it just like the OS behaves, if you press one character it will jump to that option. If you press two (I don't want to go beyond two) in rapid succession, it will jump to the first option with those as the first two, if not there, to the one whose first one matches the first character pressed (the second pressed character will be thus ignored). I am able to visualize it, just not able to bring it down to pseudocode. :)

d_stranz
10th May 2015, 23:34
If you keep your list contents as a QStringList, you can use QRegExp version of QStringList::indexOf() to find the first occurrence of the substring in the list of strings. You have state information already - when the first character is pressed, you find its location in the list, go to it, and remember it. On the second keypress within the timeout period, you search for that two-key combination, and if you don't find it, you don't move the list position.

I presume your list will be sorted, because otherwise it doesn't make a lot of sense to do this kind of lookup on an unsorted list unless it is a short list and you can get to an item uniquely in two letters. If the list is sorted, then you don't have to search the entire list over again when the second character comes in - the position of the 2-character combination, if present, will always be >= the position of the first 1-character entry. If not sorted, then you probably have to start at the beginning each time.