QComboBox: Only show the icons when expanded - c++

Starting from a "normal" QCombobox
I'd like to get a QCombobox that only shows the icon when it's expanded, but not when it's collapsed.
I've found several answers to similar questions, but all of them show code for much more complex situations and I have not managed to distill the core of it.
There are two approaches I've seen: attaching a QListView or using a QItemDelegate (or both).
But I could not find any sample code that is straight to the point.
This is my starting point:
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
ui->iconsComboBox->addItem(QIcon(":/icons/1.png"), "red");
ui->iconsComboBox->addItem(QIcon(":/icons/2.png"), "green");
ui->iconsComboBox->addItem(QIcon(":/icons/3.png"), "pink");
auto quitAction = new QAction();
quitAction->setShortcuts(QKeySequence::Quit);
addAction(quitAction);
connect(quitAction, SIGNAL(triggered()), this, SLOT(close()));
}
The full working code at that stage is here: https://github.com/aoloe/cpp-qt-playground-qcombobox/tree/simple-qcombobox
How can I hide the icon when the QCombobox is closed?
I have accepted the two pull requests by eyllanesc:
A solution extending QComboBox
A solution using QProxyStyle
You can get the code and run it to see it in action.

One possible solution is to override the paintEvent method:
##ifndef COMBOBOX_H
#define COMBOBOX_H
#include &ltQComboBox>
#include &ltQStylePainter>
class ComboBox : public QComboBox
{
public:
using QComboBox::QComboBox;
protected:
void paintEvent(QPaintEvent *)
{
QStylePainter painter(this);
painter.setPen(palette().color(QPalette::Text));
// draw the combobox frame, focusrect and selected etc.
QStyleOptionComboBox opt;
initStyleOption(&opt);
opt.currentIcon = QIcon();
opt.iconSize = QSize();
painter.drawComplexControl(QStyle::CC_ComboBox, opt);
// draw the icon and text
painter.drawControl(QStyle::CE_ComboBoxLabel, opt);
}
};
#endif // COMBOBOX_H
and if you want to use it in the .ui then you must promote it.
Another possible solution is to use a QProxyStyle
#ifndef COMBOBOXPROXYSTYLE_H
#define COMBOBOXPROXYSTYLE_H
#include <QProxyStyle>
class ComboBoxProxyStyle : public QProxyStyle
{
public:
using QProxyStyle::QProxyStyle;
void drawControl(QStyle::ControlElement element, const QStyleOption *opt, QPainter *p, const QWidget *w) const
{
if(element == QStyle::CE_ComboBoxLabel){
if (const QStyleOptionComboBox *cb = qstyleoption_cast<const QStyleOptionComboBox *>(opt)) {
QStyleOptionComboBox cb_tmp(*cb);
cb_tmp.currentIcon = QIcon();
cb_tmp.iconSize = QSize();
QProxyStyle::drawControl(element, &cb_tmp, p, w);
return;
}
}
QProxyStyle::drawControl(element, opt, p, w);
}
};
#endif // COMBOBOXPROXYSTYLE_H
ui->iconsComboBox->setStyle(new ComboBoxProxyStyle(ui->iconsComboBox->style()));

Related

How to show selected text different from list text in QComboBox?

I have a QComboBox with a popup list (a QAbstractItemView) showing different items (QStandardItems). Now, I want the item in the list to show a different text than if the item is selected.
Background:
I am creating a word-processor like style chooser where one can choose, say, "1.1 Heading 2" from the list, indicating the numbering and the style name, but when an item is chosen the combobox should only show the style name, say "Heading 2".
I thought the following question was exactly about what I was asking for but apparently an answer was chosen that does not work (apparently even according to the person asking the question): Can a QComboBox display a different value than whats in it's list?
Solution
Since QComboBox uses a list view to display the values, probably the "Qt'iest" way to achieve the desired effect, is to use a custom delegate and modify the text within its paint method, using a hash map (QHash) to get the corresponding string.
Example
Here is a simple example I have prepared for you to demonstrate how the proposed solution could be implemented:
Delegate.h this is where the magic is happening
#include <QStyledItemDelegate>
#include <QApplication>
class Delegate : public QStyledItemDelegate
{
Q_OBJECT
public:
explicit Delegate(QObject *parent = nullptr) :
QStyledItemDelegate(parent) {}
void setHash(const QHash<int, QString> &hash) {
m_hash = hash;
}
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override {
if (!index.isValid())
return;
QStyleOptionViewItem opt = option;
initStyleOption(&opt, index);
opt.text = m_hash.value(index.row());
QApplication::style()->drawControl(QStyle::CE_ItemViewItem, &opt, painter);
}
private:
QHash<int, QString> m_hash;
};
MainWindow.h only for demo purposes
#include <QWidget>
#include <QBoxLayout>
#include <QComboBox>
#include <QStandardItemModel>
#include "Delegate.h"
class MainWindow : public QWidget
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr)
: QWidget(parent)
{
auto *l = new QVBoxLayout(this);
auto *cmbBox = new QComboBox(this);
auto *model = new QStandardItemModel(this);
auto *delegate = new Delegate(this);
QHash<int, QString> hash;
for (int n = 0; n < 5; n++) {
// For demo purposes I am using "it#" and "item #"
// Feel free to set those strings to whatever you need
model->appendRow(new QStandardItem(tr("it%1").arg(QString::number(n))));
hash.insert(n, tr("item %1").arg(QString::number(n)));
}
delegate->setHash(hash);
cmbBox->setModel(model);
cmbBox->setItemDelegate(delegate);
l->addWidget(cmbBox);
resize(600, 480);
}
};
Result
The example produces the following result:
The easiest way to do is to set the ComboBox as editable and then when the current item changes, you change the text to whatever you want. Example:
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
QStringList a = {"Red", "Green", "Blue"};
aModel.setStringList(a);
ui->comboBox->setModel(&aModel);
ui->comboBox->setEditable(true);
}
void MainWindow::on_comboBox_currentIndexChanged(const QString &arg1)
{
if (arg1 == "Green") {
ui->comboBox->setCurrentText("Green on");
} else if (arg1 == "Red") {
ui->comboBox->setCurrentText("Red on");
}
}
ui->comboBox->setCurrentText("Green on"); will only change the text when the item is selected, when you reopen the combobox, the text will be reverted back to original. This is somewhat similar to my answer here.
Another way to do this would be to inherit the QComboBox class and then reimplement the mousePressEvent to change the model whenever the mouse is pressed, and switch it back after releasing. This will probably be more difficult to get right or may not even work as I have not tried it myself

How to make invisible button on widget with background image?

I want to make a simple application with invisible button.
I set background image for my widget by UI property styleSheet and Resources -
border-image:url(:/image.jpg).
I always get something like this
and then I try to add button on it
I was trying with
ui->pushButton->setStyleSheet("QPushButton{background: transparent;}");
ui->pushButton->setStyleSheet("background-color: rgba(255, 255, 255, 0);");
and it works with buttons on default background, but not in my case.
Every button that I add takes default parent background image. I dont want to see any hints of a button, but when I click on an area to be able to perform some functionality.
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
ui->centralWidget->setStyleSheet("background-image:url(:image.jpg)");
ui->pushButton->setStyleSheet("QPushButton{border:none;}");
}
Code an above makes button flat, but it duplicate background image from parent widget anyway.
Have you any idea how to resolve it?
Cause
A common misconception is that when a stylesheet without a selector is applied to an element, then it is used only for that element. In fact all element's children are styled as well. Thus a selector should be used to achieve the expected result.
Solution
I would suggest you to change this line in your code
ui->centralWidget->setStyleSheet("background-image:url(:image.jpg)");
to
ui->centralWidget->setStyleSheet(".QWidget { background-image:url(:image.jpg) }");
Important: Note the dot before QWidget. It means style the QWidget, but exclude the subclasses. This is necessary because QPushButton is a subclass of QWidget and otherwise would be affected as well.
Then you can set the pushButton's backgroung color to transparent as you do with
ui->pushButton->setStyleSheet("QPushButton{background: transparent;}");
Example
Here is a simple example I have prepared for you in order to demonstrate the proposed solution (requires cat.png in the resource file under pix/images):
#include <QMainWindow>
#include <QWidget>
#include <QPushButton>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr) :
QMainWindow(parent) {
auto *widget = new QWidget(this);
auto *button = new QPushButton(widget);
widget->setStyleSheet(".QWidget {"
" background-image:url(':/pix/images/cat.png');"
" background-repeat: no-repeat;"
"}");
button->setStyleSheet(".QPushButton {"
" background-color: transparent"
"}");
button->move(100, 100);
button->resize(100, 100);
connect(button, &QPushButton::clicked, [](){
qDebug("clicked");
});
setCentralWidget(widget);
resize(600, 480);
}
};
Result
The given example produces a window with a background and a 100x100px invisible clickable area positioned at (100, 100):
I think it's better to answer here than in comments.
You just have to set the following stylesheet for your QPushButton to make it invisible:
QPushButton
{
border: none;
}
I've made the test and it worked well.
For the tests, I have set the wrapping widget's background-image property. I also did another test with the background-color property instead. It worked in both cases (whether the background is a plain color or a picture/photo).
I hope it helps.
EDIT:
I have written a widget that performs what you want. And I also provided a windows in order to make the below example minimal and complete so that you can reproduce it.
I have tested it and it worked well.
test.h:
#ifndef TEST_H
#define TEST_H
#include <QMainWindow>
#include <QPushButton>
class WidgetWithHiddenButton : public QWidget
{
Q_OBJECT
protected:
QPushButton * invisible_button;
public:
WidgetWithHiddenButton(QWidget * parent = nullptr);
QPushButton * getButton();
protected:
void paintEvent(QPaintEvent *) override;
};
class TestWindow final : public QMainWindow
{
Q_OBJECT
private:
WidgetWithHiddenButton * widget;
public:
TestWindow();
};
#endif // TEST_H
test.cpp:
#include "test.h"
#include <QApplication>
#include <QStyleOption>
#include <QPainter>
#include <QVBoxLayout>
WidgetWithHiddenButton::WidgetWithHiddenButton(QWidget * parent) : QWidget(parent)
{
// build your widget as you want.
invisible_button = new QPushButton("Here is a button", this);
QVBoxLayout * lay = new QVBoxLayout;
QHBoxLayout * inner_lay = new QHBoxLayout;
inner_lay->addStretch();
inner_lay->addWidget(invisible_button);
inner_lay->addStretch();
lay->addLayout(inner_lay);
this->setLayout(lay);
this->setStyleSheet("WidgetWithHiddenButton {background-image: url(path_to_image/image.jpg);}");
invisible_button->setStyleSheet("QPushButton {border: none;}");
}
QPushButton * WidgetWithHiddenButton::getButton()
{
return invisible_button;
}
void WidgetWithHiddenButton::paintEvent(QPaintEvent *)
{
QStyleOption opt;
opt.init(this);
QPainter p(this);
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
}
TestWindow::TestWindow()
{
resize(500, 300);
widget = new WidgetWithHiddenButton;
this->setCentralWidget(widget);
connect(widget->getButton(), &QPushButton::clicked, qApp, &QApplication::quit);
}
int main(int argc, char ** argv)
{
QApplication app(argc, argv);
TestWindow tw;
tw.show();
return app.exec();
}
Feel free to adapt it (especially by changing the class name because WidgetWithHiddenButton is very ugly :) ).
Notes:
I have written a text in the button in order to make it visible (for tests purposes) but you can remove it if you want the button completely invisible.
I connected the QPushButton::clicked() signal to the QApplication::quit() slot in order to perform an action when we click on the area of the button.
I redefined the paintEvent() method because it is needed when using Q_OBJECT macro alongside stylesheets over a custom QWidget as the documentation mentioned.
Feel free to modify the way I build the widget in the constructor (layouts, sizes, ...) to make it fit your requirements.

Centering a square qWidget in qt

I am trying to solve a graphics problem using the latest version of Qt. The image below shows what I managed to get so far and I will use it to explain the expected result.
I'm using a main vertical layout and within the biggest widget of the layout there is a horizontal layout with only one child: the square widget. The expected behavior would be of course to have the square widget centered horizontally and taking up the biggest space available. It is not required to use the same layout configuration, but the look of the interface should be the same.
The image above has been obtained by setting a QSizePolicy of minimumExpanding for both vertical and horizontal to the square widget and by forcing it to be square with the following code:
void SquareWidget::resizeEvent(QResizeEvent *event) {
//This is an override to the QWidget method
QSize s = size();
if (s.height()<s.width()) {
resize(s.height(), s.height());
} else {
resize(s.width(), s.width());
}
return;
}
While trying to solve this problem I went through the documentation some of the answers on this website and I couldn't find a clear answer about how to do two tasks.
First problem: how to make the widget square and keep its aspect ratio?
In this question
it is said that the method heightForWidth () doesn't work in newer versions of qt, and after a test it doesn't work for me either. The above override of resizeEvent, on the other hand, causes recursion because there are calls to resize() (and as far as I understand the layout should handle the resizing).
Second problem: how to center the square?
I tried using the layout alignment properties (center horizontally and vertically) but they cause the widget size to be immutable.
Maybe I am not understanding something about how Qt handles the widget placement. Any suggestion or clarification will be greatly appreciated.
You can do it with a QGridLayout.
Please see the attached code.
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
class MyWidget final : public QWidget
{
Q_OBJECT
protected:
virtual void resizeEvent(QResizeEvent * event) override;
};
class MainWindow final : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = 0);
~MainWindow() = default;
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include <QGridLayout>
#include <QLabel>
#include <QResizeEvent>
#include <QSpacerItem>
void MyWidget::resizeEvent(QResizeEvent * event)
{
event->accept();
const QSize current_size = size();
const int min = std::min(current_size.width(), current_size.height());
resize(min, min);
}
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
auto main_widget = new QWidget;
auto header = new QLabel("Hello World");
auto center_widget = new MyWidget;
auto footer = new QLabel("Good bye World");
auto spacer_left = new QSpacerItem(10, 10, QSizePolicy::Expanding);
auto spacer_right = new QSpacerItem(10, 10, QSizePolicy::Expanding);
auto grid_layout = new QGridLayout(main_widget);
auto center_palette = center_widget->palette();
center_palette.setColor(QPalette::Background, Qt::blue);
center_widget->setAutoFillBackground(true);
center_widget->setPalette(center_palette);
grid_layout->addWidget(header, 0, 1);
grid_layout->addItem(spacer_left, 1, 0);
grid_layout->addWidget(center_widget, 1, 1);
grid_layout->addItem(spacer_right, 1, 2);
grid_layout->addWidget(footer, 2, 1);
header->setAlignment(Qt::AlignCenter);
footer->setAlignment(Qt::AlignCenter);
setCentralWidget(main_widget);
}
Please see the result here
Rather than trying to get the widget to keep itself square and centred it might be simpler to reparent it and put the required logic in the parent widget type.
So, something like...
class keep_child_square_and_centred: public QWidget {
using super = QWidget;
public:
explicit keep_child_square_and_centred (QWidget *parent = nullptr)
: super(parent)
, m_widget(nullptr)
{}
virtual void set_widget (QWidget *widget)
{
if ((m_widget = widget))
m_widget->setParent(this);
}
virtual QSize sizeHint () const override
{
return(m_widget ? m_widget->sizeHint() : super::sizeHint());
}
protected:
virtual void resizeEvent (QResizeEvent *event) override
{
super::resizeEvent(event);
fixup();
}
private:
void fixup ()
{
if (m_widget) {
QRect r(QPoint(), QSize(height(), height()));
r.moveCenter(rect().center());
m_widget->setGeometry(r);
}
}
QWidget *m_widget;
};
Then use as...
keep_child_square_and_centred w;
SquareWidget sq;
w.set_widget(&sq);
You may still need to play around with a few settings if the parent is in a layout though.

How to get a Widget as a QListWidget with 2 columns (with some constraints)?

Looking for : To display log messages, with date-time (grey color) and message (red for errors, orange for warnings ... -> number of colors not etablished and have to be flexible). Sometimes we will need to open a link from the message, the act to open not etablished (context menu, simple click -> nevermind)
History : 1- I tried QTextBrowser with html text style -> after 500 lines, it begin to slow the app, after a lot, it crashes the app
2- I Tried QListWidget with only the message with color (not the date-time), works very well !
So now you understand that I need a second column, the date-time in grey color.
My Question is: what is the easiest, more efficient way to do it, by keeping the QListWidget style, that I like.
I heard about QTreeWidget and QTableWidget to do that, but what is the best and what properties whould I change for those widgets? (to sum up : 2 columns with 2 different text colors, the ability to open links, the QListWidget style, and scrolling bar always at bottom)
Windows, C++, Qt 5
MainWindow.h
#include <QMainWindow>
#include <QTreeWidget>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = 0);
~MainWindow();
public slots:
void contextMenuRequest(const QPoint& pos);
void openLink();
private:
QTreeWidget* treeWidget;
};
MainWindow.cpp
#include "mainwindow.h"
#include <QTreeWidgetItem>
#include <QStringList>
#include <QBrush>
#include <QAction>
#include <QMenu>
#include <QTime>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
treeWidget = new QTreeWidget(this);
treeWidget->setGeometry(0, 0, 500, 500);
treeWidget->setColumnCount(2);
treeWidget->setContextMenuPolicy(Qt::CustomContextMenu);
connect(treeWidget, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(contextMenuRequest(QPoint)));
QStringList headers;
headers << "Date" << "Message";
treeWidget->setHeaderLabels(headers);
QTreeWidgetItem* errorItem = new QTreeWidgetItem();
errorItem->setForeground(1, QBrush(QColor(255, 0, 0)));
errorItem->setText(0, QTime::currentTime().toString());
errorItem->setText(1, "error message");
QTreeWidgetItem* warningItem = new QTreeWidgetItem();
warningItem->setForeground(1, QBrush(QColor(255, 165, 0)));
warningItem->setText(0, QTime::currentTime().toString());
warningItem->setText(1, "warning message");
treeWidget->addTopLevelItem(errorItem);
treeWidget->addTopLevelItem(warningItem);
for (int i = 0; i < 500; ++i)
{
QTreeWidgetItem* item;
if (i % 2)
{
item = errorItem->clone();
treeWidget->addTopLevelItem(item);
}
else
{
item = warningItem->clone();
treeWidget->addTopLevelItem(item);
}
treeWidget->scrollToItem(item); //call after adding new item
}
resize(500, 500);
}
MainWindow::~MainWindow()
{
}
void MainWindow::contextMenuRequest(const QPoint& pos)
{
QAction* openAction = new QAction("Open link", this);
connect(openAction, SIGNAL(triggered()), this, SLOT(openLink()));
QMenu menu;
menu.addAction(openAction);
menu.exec(mapToGlobal(pos));
}
void MainWindow::openLink()
{
}

QSlider show min, max and current value

Is it possible to show minimum, maximum and current selected value of QSlider? Of course I can use labels to display this, but I think there must be such possibility in QSlider
You have two options..
1) as being mentioned in comments - sub - class
2) add as many QLabel's as you like with QSlider as a parent, install eventHandler() on QSlider to catch resize event to proper position them, and obviously handle scroll events, so you can update them... So labels will just float on top of QSlider
Here is my quick implementation of a fancy slider which subclass qslider to displays the current value just below the slider handle into a tooltip.
Header
#ifndef FANCYSLIDER_H
#define FANCYSLIDER_H
#include <QSlider>
class FancySlider : public QSlider
{
Q_OBJECT
public:
explicit FancySlider(QWidget *parent = 0);
explicit FancySlider(Qt::Orientation orientation, QWidget *parent = 0);
protected:
virtual void sliderChange(SliderChange change);
};
#endif // FANCYSLIDER_H
Cpp
#include "FancySlider.h"
#include <QStyleOptionSlider>
#include <QToolTip>
FancySlider::FancySlider(QWidget * parent)
: QSlider(parent)
{
}
FancySlider::FancySlider(Qt::Orientation orientation, QWidget * parent)
: QSlider(orientation, parent)
{
}
void FancySlider::sliderChange(QAbstractSlider::SliderChange change)
{
QSlider::sliderChange(change);
if (change == QAbstractSlider::SliderValueChange )
{
QStyleOptionSlider opt;
initStyleOption(&opt);
QRect sr = style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderHandle, this);
QPoint bottomRightCorner = sr.bottomLeft();
QToolTip::showText(mapToGlobal( QPoint( bottomRightCorner.x(), bottomRightCorner.y() ) ), QString::number(value()), this);
}
}
Here is my implementation, without subclassing. Instanciate a slider with a label at right, showing the current value of slider :
QWidget *tmpW1 = new QWidget(this);
QHBoxLayout *tmpH1 = new QHBoxLayout(this);
QLabel *tmpLabel = new QLabel("0");
QSlider *tmpSlider = new QSlider(Qt::Horizontal, this);
tmpSlider->setMinimum(0);
tmpSlider->setMaximum(10);
tmpSlider->setValue(5);
tmpH1->addWidget(tmpSlider);
tmpH1->addWidget(tmpLabel);
tmpW1->setLayout(tmpH1);
QObject::connect(tmpSlider, &QSlider::valueChanged, this, [=] () {
tmpLabel->setText(QString::number(tmpSlider->value()));
});