I have several QListWidgets and would like to only allow a single row to be selected for all of these lists. So for example if I select a row in one of the lists any other selection in the other lists would be cleared. How can I achieve this?
Is there a built-in way to do this (similar to QButtonGroup for buttons)? If not, what approach would you recommend that I take when trying to implement this myself?
Grateful for help and with kind regards,
Tord
AFAIK, there is no ready built-in feature to provide a single selection in multiple list views.
Instead, it can be done by a respective signal handler for the QSelectionModel::selectionChanged signal which does this whenever the selection of one of the involved list views changes.
Thereby, you have to consider that clearing the selection will emit a selectionChanged signal as well. (Otherwise, you may end up in a recursive call of your signal handler until a stack overflow occurs.)
Unfortunately, I use Qt in C++. (My Python knowledge is rather limited.)
Thus, all I can provide for now is my "proof of concept" in C++:
#include <QtWidgets>
void singleSel(QListView *pLstView, const QList<QListView*> &pLstViews)
{
for (QListView *pLstViewI : pLstViews) {
if (pLstViewI == pLstView) continue; // skip sender
// the check is necessary to prevent recursions...
if (pLstView->selectionModel()->hasSelection()) {
// ...as this causes emission of selectionChanged() signal as well:
pLstViewI->selectionModel()->clearSelection();
}
}
}
int main(int argc, char **argv)
{
qDebug() << "Qt Version: " << QT_VERSION_STR;
QApplication app(argc, argv);
// build contents
QStandardItemModel tblModel(0, 1);
for (int i = 0; i < 10; ++i) {
tblModel.appendRow(
new QStandardItem(QString::fromUtf8("Entry %0").arg(i + 1)));
}
// build some GUI
QWidget win;
QHBoxLayout qHBox;
QListView lstView1;
lstView1.setModel(&tblModel);
qHBox.addWidget(&lstView1);
QListView lstView2;
lstView2.setModel(&tblModel);
qHBox.addWidget(&lstView2);
QListView lstView3;
lstView3.setModel(&tblModel);
qHBox.addWidget(&lstView3);
win.setLayout(&qHBox);
win.show();
// install signal handlers
QList<QListView*> pLstViews = { &lstView1, &lstView2, &lstView3 };
QObject::connect(lstView1.selectionModel(),
&QItemSelectionModel::selectionChanged,
[&lstView1, &pLstViews](const QItemSelection&, const QItemSelection &)
{
singleSel(&lstView1, pLstViews);
});
QObject::connect(lstView2.selectionModel(),
&QItemSelectionModel::selectionChanged,
[&lstView2, &pLstViews](const QItemSelection&, const QItemSelection &)
{
singleSel(&lstView2, pLstViews);
});
QObject::connect(lstView3.selectionModel(),
&QItemSelectionModel::selectionChanged,
[&lstView3, &pLstViews](const QItemSelection&, const QItemSelection &)
{
singleSel(&lstView3, pLstViews);
});
// exec. application
return app.exec();
}
I compiled and tested on Windows 10 (64 bit). This is how it looks:
Update:
I tried to port the C++ application to Python/PyQt5:
#!/usr/bin/python3
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QHBoxLayout, QListView
from PyQt5.QtGui import QStandardItemModel, QStandardItem
def singleSelect(lstView, lstViews):
for lstViewI in lstViews:
if lstViewI == lstView:
continue
# the check is necessary to prevent recursions...
if lstViewI.selectionModel().hasSelection():
# ...as this causes emission of selectionChanged() signal as well:
lstViewI.selectionModel().clearSelection()
if __name__ == '__main__':
app = QApplication(sys.argv)
# build contents
tblModel = QStandardItemModel(0, 1)
for i in range(0, 10):
tblModel.appendRow(QStandardItem("Entry %d" % (i + 1)))
# build GUI
win = QWidget()
hBox = QHBoxLayout()
lstView1 = QListView()
lstView1.setSelectionMode(QListView.SingleSelection)
lstView1.setModel(tblModel)
hBox.addWidget(lstView1)
lstView2 = QListView()
lstView2.setSelectionMode(QListView.SingleSelection)
lstView2.setModel(tblModel)
hBox.addWidget(lstView2)
lstView3 = QListView()
lstView3.setSelectionMode(QListView.SingleSelection)
lstView3.setModel(tblModel)
hBox.addWidget(lstView3)
win.setLayout(hBox)
win.show()
# install signal handlers
lstViews = [lstView1, lstView2, lstView3]
lstView1.selectionModel().selectionChanged.connect(lambda sel, unsel: singleSelect(lstView1, lstViews))
lstView2.selectionModel().selectionChanged.connect(lambda sel, unsel: singleSelect(lstView2, lstViews))
lstView3.selectionModel().selectionChanged.connect(lambda sel, unsel: singleSelect(lstView3, lstViews))
# exec. application
sys.exit(app.exec_())
From what I saw in the test, it behaves similar like the one written in C++.
The only exception is that I have to click twice on an entry when I change the list view.
As I already said: my experiences in PyQt are very limited. (Actually, I started today by making this sample.) Thus, I may have overseen something. Please, take it with a grain of salt...
Related
I have inherited a virtual keyboard system with an array of buttons, one for each key. The label for each button is a single QChar. When showing the "symbols" keyboard, the code uses an '&' QChar for a key label, but the key shows as blank. I'm sure Qt is processing the '&' as a shortcut key prefix. Similarly, the entered text is shown on another, longer, button label; this label, as well, handles '&' character as an accelerator. Entering "ABC&DEF" is shown as "ABCDEF" with the 'D' underlined.
I have tried building with QT_NO_SHORTCUT #defined, but that made no difference.
Does anyone know of an easy way to disable this special handling of '&'?
The answer is found in Qt doc. QAbstractButton::text:
If the text contains an ampersand character ('&'), a shortcut is automatically created for it. The character that follows the '&' will be used as the shortcut key. Any previous shortcut will be overwritten or cleared if no shortcut is defined by the text. See the QShortcut documentation for details. To display an actual ampersand, use '&&'.
(Emphasize by me.)
QPushButton is derived from QAbstractButton inheriting this behavior.
Sample testQPushButtonAmp.cc:
#include <QtWidgets>
int main(int argc, char **argv)
{
qDebug() << "Qt Version:" << QT_VERSION_STR;
QApplication app(argc, argv);
QPushButton qBtn("Text with &&");
qBtn.show();
return app.exec();
}
testQPushButtonAmp.pro:
SOURCES = testQPushButtonAmp.cc
QT = widgets
Compiled and tested on cygwin64 on Windows 10:
$ qmake-qt5 testQPushButtonAmp.pro
$ make
$ ./testQPushButtonAmp
Qt Version: 5.9.4
Concerning how to disable this default behavior:
I had a look at woboq.org QAbstractButton::setText().
void QAbstractButton::setText(const QString &text)
{
Q_D(QAbstractButton);
if (d->text == text)
return;
d->text = text;
#ifndef QT_NO_SHORTCUT
QKeySequence newMnemonic = QKeySequence::mnemonic(text);
setShortcut(newMnemonic);
#endif
d->sizeHint = QSize();
update();
updateGeometry();
#ifndef QT_NO_ACCESSIBILITY
QAccessibleEvent event(this, QAccessible::NameChanged);
QAccessible::updateAccessibility(&event);
#endif
}
So, QT_NO_SHORTCUT disables to retrieve the shortcut out of text but it has to be defined when Qt library is built from source. Actually, I'm afraid even with disabled shortcuts, the single & will still become invisible in output.
I digged deeper in woboq.org and found some promising candidates e.g.:
qt_set_sequence_auto_menmonic()
Specifies whether mnemonics for menu items, labels, etc., should be honored or not. On Windows and X11, this feature is on by default; on macOS, it is off. When this feature is off (that is, when b is false), QKeySequence::mnemonic() always returns an empty string.
Note: This function is not declared in any of Qt's header files. To use it in your application, declare the function prototype before calling it.
and a sample in QProxyStyle
#include "textedit.h"
#include <QApplication>
#include <QProxyStyle>
class MyProxyStyle : public QProxyStyle
{
public:
int styleHint(StyleHint hint, const QStyleOption *option = 0,
const QWidget *widget = 0, QStyleHintReturn *returnData = 0) const override
{
if (hint == QStyle::SH_UnderlineShortcut)
return 0;
return QProxyStyle::styleHint(hint, option, widget, returnData);
}
};
int main(int argc, char **argv)
{
Q_INIT_RESOURCE(textedit);
QApplication a(argc, argv);
a.setStyle(new MyProxyStyle);
TextEdit mw;
mw.resize(700, 800);
mw.show();
//...
}
which I tried in my sample.
Finally, nothing of them achieved the desired effect, i.e. only "&&" was rendered as & but "&" never.
__
I am using QListWidgetItem to add Items in my QListWidget.
In some situations, I want some rows of my QListWidget become non selectable. (I mean I want some QListWidgetItem to be non selectable)
Is ther any way to do this?
PS: I tried
listWidgetItem->setFlags(Qt::NoItemFlags)
listWidgetItem->setSelected(false);
but they don't disable the selection of items.
Edit:
QStringList _strListClients = _strClients.split(",",QString::KeepEmptyParts,Qt::CaseInsensitive);
for(int i = 0; i < _strListClients.count(); i++)//Add Client's Check Boxes
{
QListWidgetItem* _listWidgetItem = new QListWidgetItem(_strListClients[i], listWidgetClients);
listWidgetClients->addItem(_listWidgetItem);
if(_strListClients[i] == "Unknown"){
_listWidgetItem->setSelected(false);
_listWidgetItem->setTextColor(Qt::red);
_listWidgetItem->setFlags(_listWidgetItem->flags() & ~Qt::ItemIsSelectable);
}
}
Just remove the Qt::ItemIsSelectable flag from each item:
item->setFlags(item->flags() & ~Qt::ItemIsSelectable);
Or remove Qt::ItemIsEnabled if you want to remove all interaction with the item.
E.g.
#include <QtWidgets>
int main(int argc, char **argv)
{
QApplication app(argc, argv);
QListWidget widget;
for (int i = 0; i < 100; ++i) {
QListWidgetItem *item = new QListWidgetItem(QStringLiteral("Item %1").arg(i));
if (i % 2 == 0) // disable one every two
item->setFlags(item->flags() & ~Qt::ItemIsSelectable);
widget.addItem(item);
}
widget.show();
return app.exec();
}
You can try the following:
1) Override the clicked/selection event (sorry I don't remember the exact name.
Doing so you can have some sort of flag/bool value on the item, and if is set as not being selectable you just return.
2) Rather then override you just connect to the signal and perform the above check and if you don't want to select that item you un-select it afterwards.
A bit of a work around but I don't know if there is a built in way to do so.
Checking the documentation I did not see a disable method on the item itself.
If you go down the road of list view you should have more control on that, also on the display, so you might be able to displayed it greyed etc. A view is a bit more work though.
M.
I want to connect to windows in QT.
I know how to connect two widgets in same window.
using QObject::connect() in ui_a.h;
But now, I have two windows.
Six files.
For example:
ui_a.h(Window A)
a.h a.cpp (Widget in window A)
ui_b.h(Window B)
b.h b.cpp (Widget in window B)
How to connect two widget which are in different windows?
Thank you.
You have to find the scope of both window object and connect them there. Perhaps connecting them from main.cpp would make it easier. For example,
QApplication a(argc, argv);
A a;
B b;
QObject::connect(&a, SIGNAL(someSingal()), &b, SLOT(someSlot()));
QObject::connect(&b, SIGNAL(anotherSingal()), &a, SLOT(anotherSlot()));
a.show();
b.show();
return a.exec();
Do the two windows know about each other or are they completly independent? I mean is maybe Window A created by Window B or vice versa? But even if they are independent I assume you have a top parent-object wich creates the two windows right? If so, you should get access to the two widgets in this top parent object. There you can connect them:
QObject::connect(A->getWidgetInA(), SIGNAL(mySignal()), B->getWidgetInB(), SLOT(mySlot()));
getWidgetInA(), getWidgetInB() just return the pointer to your widgets in Window A and B.
For example in your class Window A: This is how getWidgetInA() would look like.
#include "a.h"
class WindowA: public QMainWindow
{
Q_OBJECT
public:
WindowA();
~WindowA();
a* getWidgetInA()
{
return widget_a;
}
private:
a *widget_a; //in WindowA.cpp you have then widget_a = new a;
}
I want to capture global mouse click event in X11 , now
I tried to install a x11event filter , but it just doesn't work globally.
class XApplication: public QApplication
{
public:
XApplication (int & argc, char **argv):
QApplication (argc , argv)
{
}
protected:
bool x11EventFilter (XEvent *e)
{
qDebug() << "X11 Event: " << e->type;
return QApplication::x11EventFilter(e);
}
};
UPDATE
I mean outside the window , the code above works when I click on the window.
You can query X11 info from Qt using the QX11Info class. See its documentation. Then you can use raw Xlib from it.
You can use XGrabPointer(). If you use it, other apps won't receive the pointer events while the pointer is grabbed. man XGrabPointer will help you.
The "normal" way of subscribing for events is to use XSelectInput() on a window, but the problem is that you'll have to call XSelectInput on every single existing window. See its man page...
I know the xxf86dga extension has some calls related to mouse, but I'm not sure what they do.
XQueryPointer() is another way to query the pointer state without stealing events from other windows.
The only other place I can think of is the XInput extension, but I'm not sure it will help you either.
See the xev source code for a good reference on handling X11 events: http://cgit.freedesktop.org/xorg/app/xev
Sample code using XGrabPointer:
#include <stdio.h>
#include <assert.h>
#include <X11/Xlib.h>
int main(void)
{
Display *d;
Window root;
d = XOpenDisplay(NULL);
assert(d);
root = DefaultRootWindow(d);
XGrabPointer(d, root, False, ButtonPressMask | ButtonReleaseMask |
PointerMotionMask, GrabModeAsync, GrabModeAsync, None,
None, CurrentTime);
XEvent ev;
while (1) {
XNextEvent(d, &ev);
switch (ev.type) {
case ButtonPress:
printf("Button press event!\n");
break;
case ButtonRelease:
printf("Button release event!\n");
break;
case MotionNotify:
printf("Motion notify event!\n");
break;
default:
printf("Unknown event...\n");
}
}
XCloseDisplay(d);
return 0;
}
Compiled using: gcc x11mouse.c -o x11mouse -lX11
I used Qt Creator to make a "keyboard" window with sixty QPushButtons and one QLineEdit. How can I make the buttons to add characters into QLineEdit text box? If I press a QPushButton with the label 'Q' on it, I want the program to add the Unicode character 'Q' on the text box.
One way to do this would be to just connect the 'clicked' signal from all the buttons to a slot, and then handle the adding of the character there.
For example, if the all keyboard buttons are inside a layout called 'buttonLayout', in your MainWindow constructor you can do this:
for (int i = 0; i < ui->buttonLayout->count(); ++i)
{
QWidget* widget = ui->buttonLayout->itemAt( i )->widget();
QPushButton* button = qobject_cast<QPushButton*>( widget );
if ( button )
{
connect( button, SIGNAL(clicked()), this, SLOT(keyboardButtonPressed()) );
}
}
Then in the slot implementation, you can use QObject::sender(), which returns the object that sent the signal:
void MainWindow::keyboardButtonPressed()
{
QPushButton* button = qobject_cast<QPushButton*>( sender() );
if ( button )
{
ui->lineEdit->insert( button->text() );
}
}
OPTION 1 - Multiple signals and slots
Connect all pushbuttons clicked() signal to a slot
// Let button01 be the A
connect(ui->button01, SIGNAL(clicked()), this, SLOT(buttonClicked()));
...
// Let button 60 be the Q
connect(ui->button60, SIGNAL(clicked()), this, SLOT(buttonClicked()));
In the buttonClicked() slot you have to figure out which button was clicked and append the corresponding letter to the line edit.
void buttonClicked()
{
QObject* callingButton = QObject::sender();
if (callingButton == button01)
ui->lineEdit->setText(ui->lineEdit->text()+ "A");
...
else if (callingButton == button60)
ui->lineEdit->setText(ui->lineEdit->text()+ "Q");
}
OPTION 2 - Subclass QPushButton
You could subclass QPushButton in order to avoid the multiple ifs. In your subclass just catch the mouse release event and emit a new signal which will contain the button's text
void KeyboardButton::mouseReleaseEvent(QMouseEvent* event)
{
emit clicked(buttonLetter); // Where button letter a variable of every item of your subclass
}
Similarly connect the clicked(QString) signal with a slot
connect(ui->button01, SIGNAL(clicked(QString)), this, SLOT(buttonClicked(QString)));
...
connect(ui->button60, SIGNAL(clicked(QString)), this, SLOT(buttonClicked(QString)));
void buttonClicked(QString t)
{
ui->lineEdit->setText(ui->lineEdit->text()+ t);
}
I have created an application with a similar issue, trying to convert the qpushbutton text to the qlineedit itself. The key is how you initialize the buttons and to use polymorphism in your function. To create an emit signal wont work for individual characters. The .digitValue will work if the case if for numerics (which the buttons would be of type int), but qt doesnt have a character value (I should say after 6hrs of reading qt doc and another 4 of trying different combinations it would not work), I think it has to do with how many bits it takes to store each variable type in an array. I even tried converting the button->text to QString to use with the emit function as a signal prototyped.
I do not know what your button layout is, but I will give you a synopsis of what I did. I first created a global, static const char array containing all the letters needed e.g.
static const char vowelarray[] = "AEIOU";
Then initialized the QPushButtons with in the MainWindow function, using iteration, setting a for loop's terminating condition equal to the size char array (in your case 60?). This all depends on your button layout though. I personally created a void function (setLocation) for the button->setGeometry of each button and iterated the setGeometry, and then passed the function to the MainWindow Function, at end of fucntion. The following code was used to initialize the buttons, connect signals to slots, and use polymorphism to connect to lineedit.
for (int i = 0; i < 26; i++){
characterButton[i] = new QPushButton(chararry[i], this); `
characterButton[i] -> setStyleSheet("QPushButton{background: grey; color: brown}");
connect(characterButton[i],SIGNAL(released(),this,SLOT(characterPushed()));
}
setLocation();
Then created a void function (e.g. void MainWindow::characterPuched()) where the following code was used:
void MainWindow::characterPushed(){
QPushButton *characterButton = (QPushButton*) sender();
if (characterButton )
{
lineEdit -> setText(letters.insert(letters.size(), characterButton -> text()));
}
lineEdit -> setText(letters);
}
of course letters was a global variable as well as:
QString letters = "";
and of course the QPushButtons and the function were prototype in the header file as a private variables and slots, e.g.
private:
QPushButton *characterButton[26];
the variable 'letters' was used to extract and input text to and from the line edit for further functions throughout the application.
Best Luck!!``