Dynamically create context menu in QT associated to a toolbutton - qt

In my application I have a QToolButton related to the presence of an USB Pen Drive. When the Pen drive is inserted I would like to show the QToolButton and create a context menu associated to the content of the pen drive.I have a different menu dynamically created to be assigned to the Button.
My code works well for the first time, but when I create a new menu it doesn't appear.
In this last version of code, when I show the button for the second time I get the the previous menu (Dismount is the only item present) and when i click on the item it doesn't do anything.
EDIT: If I use the QAction instead of the QWidgetAction the code works fine. So it seems something related to the QWidgetAction of QLabel used inside of it.
The following is a simplified version of my code:
/* member variables */
QMenu *m_pqmConMenUSB;
QLabel m_MenuItem;
/* costructor */
ui->tbDriveUSB->setContextMenuPolicy(Qt::CustomContextMenu);
m_pqmConMenUSB = NULL;
QObject::connect(ui->tbDriveUSB, SIGNAL(customContextMenuRequested(const QPoint&)),this, SLOT(showUSBCM(const QPoint&)));
m_MenuItem.setStyleSheet("QLabel { background-color : black; color : white; }");
m_MenuItem.setText("Dismount");
QFont fonte = m_MenuItem.font();
fonte.setPixelSize(16);
m_MenuItem.setFont(fonte);
QPalette ChePalette = m_MenuItem.palette();
m_MenuItem.setMinimumSize(0,32);
ChePalette.setColor(m_MenuItem.backgroundRole(), Qt::black);
ChePalette.setColor(m_MenuItem.foregroundRole(), Qt::white);
m_MenuItem.setPalette(ChePalette);
/*member functions*/
void MainWindow::showUSBCM(const QPoint& pos)
{
// copied from an example
if (pos != QPoint(0,0)) {
// Execute context menu
if (m_pqmConMenUSB!=NULL) m_pqmConMenUSB->exec(pos);
}
}
void MainWindow::OnUSBMounted()
{
/* this static boolean is used to simulate a change in the menu content */
static bool tryToChange = false;
ui->tbDriveUSB->show();
m_pqmConMenUSB = new QMenu(this);
QWidgetAction *menuItemW = new QWidgetAction(this);
menuItemW->setDefaultWidget(&m_MenuItem);
menuItemW->setText("Dismount");
connect(menuItemW,SIGNAL(triggered()), this, SLOT(DoDismount()));
m_pqmConMenUSB->addAction(menuItemW);
if (tryToChange)
{
menuItemW = new QWidgetAction(this);
menuItemW->setDefaultWidget(&m_MenuItem);
menuItemW->setText("Update");
connect(menuItemW,SIGNAL(triggered()), this, SLOT(Update()));
m_pqmConMenUSB->addAction(menuItemW);
}
tryToChange = !tryToChange;
ui->tbDriveUSB->setMenu(m_pqmConMenUSB);
}
void MainWindow::OnUSBDismounted()
{
ui->tbDriveUSB->hide();
/* the first version of the code tries to destroy the menu with the following code, but it doesn't work
/*ui->tbDriveUSB->setMenu(NULL);
QAction *pAction;
foreach (pAction, m_pqmConMenUSB->actions())
pAction->disconnect(this);
delete(m_pqmConMenUSB);
m_pqmConMenUSB = NULL;*/
}

A useful pattern to dynamically populate a menu associated with a QToolButton is to first create the menu and attach it to the button, but do not populate it. Then connect a slot to the QMenu::aboutToShow() signal. In the slot implementation, clear the contents of the menu and populate it as needed for the current state of the application.

As I mentioned yesterday, the problem was related to QLabels. In my code I used two member variables of QLabel type. The QLabels weren't pointers.
When I delete the action, the QLabels weren't able to show them again. I suppose it was related to the removeAction(d->menuAction); function which destroys QWidget associated to the QWidgetAction. That function was called when the ui->tbDriveUSB->setMenu(NULL); was called.
I choose to use the QLabel just for stylesheet and size, but it's possibile to set that properties in the menu. This is enough for me.
I think that, making a new QLabel when the QWidgetAction is created and delete it when the QWidgetAction is deleted, could make works the previous code. I haven't tested it.
In order to complete the answer, the following is my current code that works well
/* member variable */
QMenu *m_pqmConMenUSB;
/* constructor */
ui->tbDriveUSB->setContextMenuPolicy(Qt::CustomContextMenu);
QObject::connect(ui->tbDriveUSB, SIGNAL(customContextMenuRequested(const QPoint&)),this, SLOT(showUSBCM(const QPoint&)));
m_pqmConMenUSB = new QMenu(this);
QFont fonte = m_pqmConMenUSB->font();
fonte.setPixelSize(16);
m_pqmConMenUSB->setFont(fonte);
m_pqmConMenUSB->setStyleSheet("QMenu { background-color : black; color : white; }");
m_pqmConMenUSB->setMinimumSize(0,32);
/*member functions*/
void MainWindow::showUSBCM(const QPoint& pos)
{
if (pos != QPoint(0,0))
{
// Execute context menu
if (m_pqmConMenUSB!=NULL) m_pqmConMenUSB->exec(pos);
}
}
void MainWindow::OnUSBMounted()
{
static bool tryToChange = true;
ui->tbDriveUSB->show();
QAction *menuItem = new QAction("Dismount",this);
connect(menuItem,SIGNAL(triggered()), this, SLOT(DoDismount()));
m_pqmConMenUSB->addAction(menuItem);
if (tryToChange)
{
QAction *menuItem2 = new QAction("upDate",this);
connect(menuItem2,SIGNAL(triggered()), this, SLOT(Update()));
m_pqmConMenUSB->addAction(menuItem2);
}
tryToChange = !tryToChange;
ui->tbDriveUSB->setMenu(m_pqmConMenUSB);
}
void MainWindow::OnUSBDismounted()
{
printf("SEI UNO SMONTATO\n\r");
ui->tbDriveUSB->setMenu(NULL);
QAction *pAction;
foreach (pAction, m_pqmConMenUSB->actions())
{
pAction->disconnect(this); // receiver
delete pAction;
}
ui->tbDriveUSB->hide();
m_pqmConMenUSB->clear();
}

Related

Qt5 custom context menu ignoring style sheet

I have a two editor classes, say BaseEditor and AdvancedEditor. BaseEditor inherits from QPlaintTextEdit and its standard context menu follows my style sheet properly.
My AdvancedEditor now inherits from BaseEditor and reimplements the method void showContextMenu(const QPoint &point) to generate a custom context menu. In it I basically do the following:
void AdvancedEditor::showContextMenu(const QPoint &point)
{
QMenu* pStandardMenu = createStandardContextMenu();
QMenu* pMenu = new QMenu();
[add various stuff to pMenu]
connect(pSignalMapper, SIGNAL(mapped(const QString&)), this, SLOT(onContextMenuSelected(const QString&)));
pMenu->addSeparator();
pMenu->addActions(pStandardMenu->actions());
pMenu->exec(mapToGlobal(point));
delete pMenu;
}
This menu however is rendered in the default OS design despite me having the following part in my QSS style sheet (which is properly used for all other menus):
QMenu {
background-color: white;
border: 1px solid #4495D1;
padding: 1px;
}
I tried adding a custom paintEvent() as described here without any luck: http://qt-project.org/forums/viewthread/25664/#117575. Do I need another PE_* type?
Set pMenu's parent widget to the one which has your stylesheet.

Hide QLineEdit blinking cursor

I am working on QT v5.2
I need to hide the blinking cursor (caret) of QLineEdit permanently.
But at the same time, I want the QLineEdit to be editable (so readOnly and/or setting editable false is not an option for me).
I am already changing the Background color of the QLineEdit when it is in focus, so I will know which QLineEdit widget is getting edited.
For my requirement, cursor (the blinking text cursor) display should not be there.
I have tried styleSheets, but I can't get the cursor hidden ( {color:transparent; text-shadow:0px 0px 0px black;} )
Can someone please let me know how can I achieve this?
There is no standard way to do that, but you can use setReadOnly method which hides the cursor. When you call this method it disables processing of keys so you'll need to force it.
Inherit from QLineEdit and reimplement keyPressEvent.
LineEdit::LineEdit(QWidget* parent)
: QLineEdit(parent)
{
setReadOnly(true);
}
void LineEdit::keyPressEvent(QKeyEvent* e)
{
setReadOnly(false);
__super::keyPressEvent(e);
setReadOnly(true);
}
As a workaround you can create a single line QTextEdit and set the width of the cursor to zero by setCursorWidth.
For a single line QTextEdit you should subclass QTextEdit and do the following:
Disable word wrap.
Disable the scroll bars (AlwaysOff).
setTabChangesFocus(true).
Set the sizePolicy to (QSizePolicy::Expanding, QSizePolicy::Fixed)
Reimplement keyPressEvent() to ignore the event when Enter/Return is hit
Reimplement sizeHint to return size depending on the font.
The implementation is :
#include <QTextEdit>
#include <QKeyEvent>
#include <QStyleOption>
#include <QApplication>
class TextEdit : public QTextEdit
{
public:
TextEdit()
{
setTabChangesFocus(true);
setWordWrapMode(QTextOption::NoWrap);
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
setFixedHeight(sizeHint().height());
}
void keyPressEvent(QKeyEvent *event)
{
if (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter)
event->ignore();
else
QTextEdit::keyPressEvent(event);
}
QSize sizeHint() const
{
QFontMetrics fm(font());
int h = qMax(fm.height(), 14) + 4;
int w = fm.width(QLatin1Char('x')) * 17 + 4;
QStyleOptionFrameV2 opt;
opt.initFrom(this);
return (style()->sizeFromContents(QStyle::CT_LineEdit, &opt, QSize(w, h).
expandedTo(QApplication::globalStrut()), this));
}
};
Now you can create an instance of TextEdit and set the cursor width to zero :
textEdit->setCursorWidth(0);
Most straight forward thing I found was stolen from this github repo:
https://github.com/igogo/qt5noblink/blob/master/qt5noblink.cpp
Basically you just want to disable the internal "blink timer" Qt thinks is somehow good UX (hint blinking cursors never were good UX and never will be - maybe try color or highlighting there eh design peeps).
So the code is pretty simple:
from PyQt5 import QtGui
app = QtGui.QApplication.instance()
app.setCursorFlashTime(0)
voilà.
Solution in python:
# somelibraries
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.layout = QVBoxLayout()
self.setFocus() # this is what you need!!!
container = QWidget()
container.setLayout(self.layout)
# Set the central widget of the Window.
self.setCentralWidget(container)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
I ran into the same problem but setReadOnly is not a viable option because it alters the UI behavior in other places too.
Somewhere in a Qt-forum I found the following solution that actually solves the problem exactly where it occurs without having impact on other parts.
In the first step you need to derive from QProxyStyle and overwrite the pixelMetric member function:
class CustomLineEditProxyStyle : public QProxyStyle
{
public:
virtual int pixelMetric(PixelMetric metric, const QStyleOption* option = 0, const QWidget* widget = 0) const
{
if (metric == QStyle::PM_TextCursorWidth)
return 0;
return QProxyStyle::pixelMetric(metric, option, widget);
}
};
The custom function simply handles QStyle::PM_TextCursorWidth and forwards otherwise.
In your custom LineEdit class constructor you can then use the new Style like this:
m_pCustomLineEditStyle = new CustomLineEditProxyStyle();
setStyle(m_pCustomLineEditStyle);
And don't forget to delete it in the destructor since the ownership of the style is not transferred (see documentation). You can, of course, hand the style form outside to your LineEdit instance if you wish.
Don't get complicated:
In QtDesigner ,
1.Go the the lineEdit 's property tab
2.Change focusPolicy to ClickFocus
That's it...

gif image in QLabel

I want to add a gif animated image in QLabel that add into the QGraphicsScene.
My code is here:
QLabel *lbl = new QLabel;
QMovie *mv = new QMovie(":/Images/sun.gif");
mv->start();
lbl->setWindowFlags(Qt::FramelessWindowHint);
lbl->setMask((new QPixmap(":/Images/sun.gif"))->mask()); // for create transparent for QLabel image
lbl->setMovie(mv);
lbl->setGeometry(10,10,10,10);
scene.addWidget(lbl);
but when I run that it will transparent with first frame of that gif and when the gif is running the photo will not show completely and it will run with transparented area in the first frame.
How can I solve that?
Thanks
The problem is that QLabel has window background by default. You're trying to remove it by do it incorrectly:
FramelessWindowHint doesn't make sense here, since it's only used for top level widgets, and a widget added to scene is technically hidden and doesn't have system window frame. This line should be removed.
setMask does exactly what you describe it does. Since QPixmap is not animated, its mask is the alpha mask of the first frame of animation. And you permanently apply this mask to the label. It's not surpising that it works, but obviously it's not what you want. This line should also be removed.
setGeometry line is incorrect. It prevents picture from being visible for me. Label has good size by default and there is no need for setGeometry. If you want to scale or move the item on the scene, you can do it after addWidget as for any other QGraphicsItem. E.g. addWidget(lbl)->setPos(10, 10).
The magic bullet you need is WA_NoSystemBackground. It disables background painting for QLabel completely. So, the full code would be:
QLabel *lbl = new QLabel;
QMovie *mv = new QMovie("c:/tmp/sun.gif");
mv->start();
lbl->setAttribute(Qt::WA_NoSystemBackground);
lbl->setMovie(mv);
scene.addWidget(lbl);
It works fine for me. However I consider it over-complicated. You should not use proxy widgets in scene unless necessary. You can easily add a movie using QMovie and QGraphicsPixmapItem and switching pixmaps as movie frames change. I wrote a convenient class for this:
Header:
class GraphicsMovieItem : public QObject, public QGraphicsPixmapItem {
Q_OBJECT
public:
GraphicsMovieItem(QGraphicsItem* parent = 0);
void setMovie(QMovie* movie);
private:
QMovie* m_movie;
private slots:
void frameChanged();
};
Source:
GraphicsMovieItem::GraphicsMovieItem(QGraphicsItem *parent)
: QGraphicsPixmapItem(parent), m_movie(0) {
}
void GraphicsMovieItem::setMovie(QMovie *movie) {
if (m_movie) {
disconnect(m_movie, SIGNAL(frameChanged(int)), this, SLOT(frameChanged()));
}
m_movie = movie;
if (m_movie) {
connect(m_movie, SIGNAL(frameChanged(int)), this, SLOT(frameChanged()));
}
frameChanged();
}
void GraphicsMovieItem::frameChanged() {
if (!m_movie) { return; }
setPixmap(m_movie->currentPixmap());
}
Usage:
QMovie *mv = new QMovie("c:/tmp/sun.gif");
GraphicsMovieItem* item = new GraphicsMovieItem();
item->setMovie(mv);
scene.addItem(item);

QTabWidget how to hide pane only?

I have added a QToolButton as corner widget in QTabWidget which is checkable. I want to hide all tabs (panes only) when the tool button is unchecked. I tried to connect button's signal clicked(bool) with all tab's setVisible(bool) slot not working but. I also connected tabwidget's setvisible to the signal but complete widget became invisible(it was a silly trial). Is there any way to make only pane invisible and tab bar will not disappear ?
Edit: Code (ui have a tabwidget and two tabs namely tab and tab_2)
ui->setupUi(this);
QToolButton * b = new QToolButton;
b->setCheckable(true);
b->setChecked(true);
b->setAutoRaise(true);
b->setText("Hide Tabs");
ui->tabWidget->setCornerWidget(b);
connect(b,SIGNAL(clicked()),ui->tab,SLOT(hide()));
connect(b,SIGNAL(clicked()),ui->tab_2,SLOT(hide()));
Use qFindChild to find the QTabBar within the QTabWidget:
QTabBar *tabBar = qFindChild<QTabBar *>(ui->tabWidget);
tabBar->hide();
For Qt5:
QTabBar *tabBar = ui->tabWidget->findChild<QTabBar *>();
tabBar->hide();
so I understand it like this, you want to hide the TabBar and let the tab visible. Or at least that's what I get from your question
Well if that the case all you have to do it's this:
connect(ui->pushButton,SIGNAL(clicked()),ui->tabWidget->tabBar(),SLOT(hide()));
I hope this was helpful, even do the questions in a little old, I though it may help new viewers.
Here is my take on this. I've created a class that inherits QTabWidget. What I do is; set the "maximum vertical size of QTabWidget" to its tabBars height to hide the panels.
It is a hacky solution and I had to add some extra lines to deal with quirks.
file: hidabletabwidget.h
#ifndef HIDABLETABWIDGET_H
#define HIDABLETABWIDGET_H
#include <QTabWidget>
#include <QAction>
class HidableTabWidget : public QTabWidget
{
Q_OBJECT
public:
explicit HidableTabWidget(QWidget *parent = 0);
QAction hideAction;
private slots:
void onHideAction(bool checked);
void onTabBarClicked();
};
#endif // HIDABLETABWIDGET_H
file: hidablewidget.cpp
#include "hidabletabwidget.h"
#include <QTabBar>
#include <QToolButton>
HidableTabWidget::HidableTabWidget(QWidget *parent) :
QTabWidget(parent),
hideAction("▾", this)
{
hideAction.setCheckable(true);
hideAction.setToolTip("Hide Panels");
QToolButton* hideButton = new QToolButton();
hideButton->setDefaultAction(&hideAction);
hideButton->setAutoRaise(true);
this->setCornerWidget(hideButton);
connect(&hideAction, SIGNAL(toggled(bool)), this, SLOT(onHideAction(bool)));
connect(this, SIGNAL(tabBarClicked(int)), this, SLOT(onTabBarClicked()));
}
void HidableTabWidget::onHideAction(bool checked)
{
if (checked)
{
this->setMaximumHeight(this->tabBar()->height());
this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
}
else
{
this->setMaximumHeight(QWIDGETSIZE_MAX); // by default widgets can expand to a maximum sized defined by this macro
this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
}
}
void HidableTabWidget::onTabBarClicked()
{
hideAction.setChecked(false);
}
To use this, you can simply "promote" your QTabWidget to "HidableTabWidget" using qt designer.
And here is how it looks on my system:
You usually want to remove the Tab from the QTabWidget:
void QTabWidget::removeTab ( int index )
The Tab removed will not be deleted and can be reinserted!
So you would connect your QToolButton b to a slot which simply removes the Tabs like this:
connect( b, SIGNAL(clicked()), this, SLOT(hideTabs() );
..
void Foobar::hideTabs( void )
{
for( int i = 0; i < ui->tabWidget->count(); ++i )
ui->tabWidget->removeTab(i);
}
I can not comment due to my low "reputation" so far. If I could I'd just add a comment to Anatoli's answer: the goal is to hide "page area", not "tab bar". So if we imply they always use QStackedWidget for that then the answer should be more like:
auto * tab_pane = qFindChild<QStackedWidget *>(ui->tabWidget);
tab_pane->hide();
or for Qt5:
auto * tab_pane = ui->tabWidget->findChild<QStackedWidget *>();
tab_pane->hide();

Qt menu without shadow?

I copied the question description below from other asked but not answered question, because this is the exactly the same one I wanna ask.
I have a QMenu with a translucent background and rounded edges (border-radius). Unfortunately, Windows 7 draws a drop shadow for this menu, which does not fit to the rounded edges. Its the shadow that would be drawn for normal rectangular menus.
Is there either - a way to completely disable drawing drop shadows for QMenu or - a way to make the shadow fit to the rounded edges ?
Here is a minimalistic example where it occurs:
QPushButton b("press me");
QMenu m;
m.addAction("hello"); m.addAction("world");
m.setWindowFlags(m.windowFlags() | Qt::FramelessWindowHint);
m.setAttribute(Qt::WA_TranslucentBackground);
m.setStyleSheet("background:rgba(255,0,0,50%); border-radius:5px;");
b.setMenu(&m);
b.show();
Right now I have to turn off the menu shadow in Windows Control panel manually to get rid of that shadow.
Actually what I want to achieve is a menu like Qt's pie menu or a menu like this one:
http://upload.wikimedia.org/wikipedia/commons/8/85/Blender_2.36_Screenshot.jpg
I tried the popup widget, but it gets the shadow artifact described above.
Could anyone help this out?
On Windows Vista and higher, I wanted a menu with normal window shadow. So I had to do two things:
Remove CS_DROPSHADOW from the menu HWND's WNDCLASS that Qt is adding deep down in the core.
Add shadow using DWM APIs.
The trick is to capture QEvent::WinIdChange to get the HWND handle to the menu window, and then to use GetClassLong / SetClassLong to remove CS_DROPSHADOW flag. I'm doing this only once (by using a static bool), as theWNDCLASS is always the same for all menus. This might lead into a problem if part of your app wants to show the menu shadows and other does not.
I have subclassed the QMenu and I'm always using my overriden class when creating menus
Menu * my_menu = new Menu(tr("&File"));
mainMenu->addMenu(my_menu);
Here's the whole code, enjoy:
menu.h
#ifndef MENU_H
#define MENU_H
#include <QMenu>
class Menu : public QMenu
{
Q_OBJECT
public:
explicit Menu(QWidget *parent = 0);
explicit Menu(const QString & title);
protected:
virtual bool event(QEvent *event);
signals:
public slots:
};
#endif // MENU_H
menu.cpp
#include "menu.h"
#pragma comment( lib, "dwmapi.lib" )
#include "dwmapi.h"
Menu::Menu(QWidget *parent) :
QMenu(parent)
{
}
Menu::Menu(const QString &title) :
QMenu(title)
{
}
bool Menu::event(QEvent *event)
{
static bool class_amended = false;
if (event->type() == QEvent::WinIdChange)
{
HWND hwnd = reinterpret_cast<HWND>(winId());
if (class_amended == false)
{
class_amended = true;
DWORD class_style = ::GetClassLong(hwnd, GCL_STYLE);
class_style &= ~CS_DROPSHADOW;
::SetClassLong(hwnd, GCL_STYLE, class_style);
}
DWMNCRENDERINGPOLICY val = DWMNCRP_ENABLED;
::DwmSetWindowAttribute(hwnd, DWMWA_NCRENDERING_POLICY, &val, sizeof(DWMNCRENDERINGPOLICY));
// This will turn OFF the shadow
// MARGINS m = {0};
// This will turn ON the shadow
MARGINS m = {-1};
HRESULT hr = ::DwmExtendFrameIntoClientArea(hwnd, &m);
if( SUCCEEDED(hr) )
{
//do more things
}
}
return QWidget::event(event);
}
I just remove the Qt::popup flag to get rid of the shadow.
And I have to add close codes to any other background UI. These have been more extra work, but I got what I want :)

Resources