I want to be able to stop a user from moving a QSplitter at runtime. Calling setEnabled(false) does this, but it also disables all child widgets - which isn't what I want. Is there a way to achieve this? Do I have to disable the splitter, and then manually re-enable all child widgets? That seems rather cumbersome, for something that must be a reasonably common practise.
Can anyone suggest anything?
Do this:
for (int i = 0; i < splitter->count(); i++)
{
QSplitterHandle *hndl = splitter->handle(i);
hndl->setEnabled(false);
}
Actually, I've never seen anyone ever disable a splitter: They are there so the user can layout the UI as she needs, so why would anyone want to disable this? Either you need a splitter or you can use one of the normal layouts (which the user can't resize).
If you still want to try, I think you should look at closestLegalPosition() or getRange(). If you just return the width of the widget, then resizing should stop working.
You have to do two things. Set the widgets (that shouldn't be resizeable) inside the splitter to FixedSize and change the cursor of the correspondent splitter handles to Qt::ArrowCursor. The handles start with zero (left and not used), so the first handle between two widgets is by index 1.
Here's a sample (put the code in main.cpp):
#include <QtGui>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QWidget window;
window.resize(800, 300);
window.setWindowTitle("Splitter Test");
window.show();
QSplitter *splitter = new QSplitter(&window);
QListView *listview = new QListView;
QTreeView *treeview = new QTreeView;
QTextEdit *textedit = new QTextEdit;
splitter->addWidget(listview);
splitter->addWidget(treeview);
splitter->addWidget(textedit);
splitter->setChildrenCollapsible(false);
splitter->show();
listview->show();
treeview->show();
textedit->show();
//make the lisview 'fix'
listview->setFixedSize(listview->width(), listview->height());
//change the cursor over the splitter handle between listview and
//treeview to ArrowCursor
splitter->handle(1)->setCursor(Qt::ArrowCursor);;
return app.exec();
}
Now the first splitter handle is disabled and the second works.
Related
I have a delete button(QPushButton) in the last column of each row of my table view. I am creating these push buttons and directly setting them in view. Since I have allocated memory dynamically I wish to free this memory but I haven't stored pointers of these buttons anywhere so I am trying to obtain the widget at the time of clean up and deleting them.
SDelegate* myDelegate;
myDelegate = new SDelegate();
STableModel* model = new STableModel(1, 7, this);
myWindow->tableView->setModel(model);
myWindow->tableView->setItemDelegate(myDelegate);
for(int i = 0; i < no_of_rows; ++i) {
QPushButton* deleteButton = new QPushButton();
myWindow->tableView->setIndexWidget(model->index(i, 6), deleteButton);
}
exec();
// Cleanup
for(int i = 0; i < no_of_rows; ++i) {
// code works fine on removing this particular section
QWidget* widget = myWindow->tableView->indexWidget(model->index(i, 6));
if (widget)
delete widget;
}
delete model;
delete myDelegate;
I am getting a crash in qt5cored.dll (Unhandled exception) and application is crashing in qcoreapplication.h at the following code:
#ifndef QT_NO_QOBJECT
inline bool QCoreApplication::sendEvent(QObject *receiver, QEvent *event)
{ if (event) event->spont = false; return self ? self->notifyInternal(receiver, event) : false; }
While debugging there is no issue in deleting these widgets but code crashes afterwards at some other point. I am using QTableView and custom class for model which has inherited QAbstractTableModel.
There's a Qt bug that manifests as follow: if there are any index widgets, and you invoke setModel(nullptr) on the view, it'll crash in an assertion on visualRow != -1 in qtableview.cpp:1625 (in Qt 5.6.0). Presumably this bug could be triggered when the model is being removed in some other fashion too.
But I can't reproduce it by merely destroying the model instance. So I doubt that it's relevant here unless you get the same assertion failure.
Given the style of your code, it's more likely that you have a memory bug elsewhere. If you think that the code above is crashing, you should have a self-contained test case that demonstrates the crash. Is your model or delegate to blame? Would it crash using no delegate? Would it crash using a stock model?
Your code excerpt seems to be fine, if mostly unnecessary. You could allocate the delegate and the model locally. The buttons are owned by the view: as soon as the need for the buttons goes away, such as when the model changes the row count or goes away, they will get appropriately deleted. So you don't have to delete them yourself, it's safe but completely unnecessary.
Here's an example that demonstrates that in all cases, the buttons will get disposed when the model gets destroyed or the view gets destroyed, whichever comes first. Tracking object lifetime is super simple in Qt: keep a set of objects, and remove them from the set using a functor attached to the object's destroyed signal. In Qt 4 you'd use a helper class with a slot.
// https://github.com/KubaO/stackoverflown/tree/master/questions/model-indexwidget-del-38796375
#include <QtWidgets>
int main(int argc, char ** argv) {
QApplication app{argc, argv};
QSet<QObject*> live;
{
QDialog dialog;
QVBoxLayout layout{&dialog};
QTableView view;
QPushButton clear{"Clear"};
layout.addWidget(&view);
layout.addWidget(&clear);
QScopedPointer<QStringListModel> model{new QStringListModel{&dialog}};
model->setStringList(QStringList{"a", "b", "c"});
view.setModel(model.data());
for (int i = 0; i < model->rowCount(); ++i) {
auto deleteButton = new QPushButton;
view.setIndexWidget(model->index(i), deleteButton);
live.insert(deleteButton);
QObject::connect(deleteButton, &QObject::destroyed, [&](QObject* obj) {
live.remove(obj); });
}
QObject::connect(&clear, &QPushButton::clicked, [&]{ model.reset(); });
dialog.exec();
Q_ASSERT(model || live.isEmpty());
}
Q_ASSERT(live.isEmpty());
}
Check out QObject::deleteLater() , it usually helps with issues around deleting QObjects / QWidgets.
In my GUI, I would like to add a QComboBox to a verticalLayout programmatically based on a a signal triggered by specific action. The following code works fine and the widget is added:
QComboBox* userOptions = new QComboBox();
ui->verticalLayout_13->addWidget(userOptions);
However, this way the widget is always added to the end of the layout.
My question is: how to position the added QComboBox to the verticalLayout in alignment to another widget in the same layout ? (i.e.: above the "Go" push button for example)
There doesn't seem to be a way to explicitly insert an item in a layout where you want it to be.
You have a few choices to achieve that the "hard" way:
use QLayout::takeAt(int index) to take all items after the index you want to insert at, insert your item, then insert back the taken items.
create a placeholder widget which you can use to reserve an index in the layout, then you don't insert the item in the layout, but in a layout nested inside the placeholder widget. Without an item, the placeholder widget takes no space, and expands to accommodate whatever is put into it.
implement your own QLayout subclass which supports inserting at a specific index. There are several functions you will have to implement.
EDIT: An omission on my end, as Kuba Ober noted, most of the concrete layout implementations support inserting at a specific index, for example QBoxLayout derived have insert methods which pass an index as a parameter.
First, iterate a layout to find the index of the reference item you're inserting relative to. Then use the concrete layout's specific widget insertion/addition functionality.
Since you presumably use a QBoxLayout, you'd use its insertWidget method to insert a widget.
// https://github.com/KubaO/stackoverflown/tree/master/questions/insert-widget-36746949
#include <QtWidgets>
namespace SO { enum InsertPosition { InsertBefore, InsertAfter }; }
bool insertWidget(QBoxLayout * layout, QWidget * reference, QWidget * widget,
SO::InsertPosition pos = SO::InsertBefore, int stretch = 0,
Qt::Alignment alignment = 0) {
int index = -1;
for (int i = 0; i < layout->count(); ++i)
if (layout->itemAt(i)->widget() == reference) {
index = i;
break;
}
if (index < 0) return false;
if (pos == SO::InsertAfter) index++;
layout->insertWidget(index, widget, stretch, alignment);
return true;
}
Similar functions can be easily devised for QFormLayout, QGridLayout and QStackedLayout.
And a test harness:
int main(int argc, char ** argv) {
QApplication app{argc, argv};
QWidget w;
QVBoxLayout l{&w};
QLabel first{"First"};
QLabel second{"Second"};
l.addWidget(&first);
l.addWidget(&second);
insertWidget(&l, &first, new QLabel{"Before First"}, SO::InsertBefore);
insertWidget(&l, &second, new QLabel{"After Second"}, SO::InsertAfter);
w.show();
return app.exec();
}
I currently have a QTabWidget and some layout.
Now I want all the tabs of my QtabWidget to occupy the width of the QtabWidget so I have something like this. The following method expands the two tabs in my Qtabwidget so they are the same size as the QtabWidget
void Contacts::AdjustTabs( int pos, int index )
{
std::string orig_sheet = ui.tabWidget->styleSheet().toStdString();
QString new_style = QString("QTabBar::tab { width: %1px; } ").arg((ui.tabWidget->size().width() /ui.tabWidget->count()));
std::string t = new_style.toStdString();
orig_sheet = orig_sheet + t;
ui.tabWidget->setStyleSheet(orig_sheet.c_str());
}
The above method works fine. Now the problem starts when the QTabwidget is set in a Qsplitter layout along with some other layout.At startup construction the above method does not seem to work for instance I have something like this
Someclass::SomeConstructor()
{
ui.setupUi(this);
.........
QObject::connect(ui.splitter,SIGNAL(splitterMoved(int,int)),this,SLOT(AdjustTabs(int,int)));
AdjustTabs(0,0);
}
Now when the form appears the horizontal size of the tabs remain unchanged (they do not expand - which is wrong). However when i move the splitter the tabs expand and everything is fine. My question is how can I make my tabs expand during form load ? Why arent they expanding ?
I think trying to explicitly adjust the tabs' sizes whenever a resize event occurs isn't the best approach. If things are set up properly, the appropriate size adjustments should naturally occur due to the default behavior of the QSplitter and QTabWidget's internal layout managers. Here is a very simple example of what I mean; note that there is no code in this program to explicitly handle resizing of anything, and yet all the tabs resize correctly as the splitter is moved and/or the window is resized:
#include <QApplication>
#include <QMainWindow>
#include <QPushButton>
#include <QSplitter>
#include <QTabWidget>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QMainWindow mw;
QSplitter * splitter = new QSplitter(&mw);
QTabWidget * tab = new QTabWidget;
tab->addTab(new QPushButton("Content of tab #1"), "tab #1");
tab->addTab(new QPushButton("Content of tab #2"), "tab #2");
tab->addTab(new QPushButton("Content of tab #3"), "tab #3");
splitter->addWidget(tab);
splitter->addWidget(new QPushButton("Some other content"));
mw.setCentralWidget(splitter);
mw.show();
return a.exec();
}
So the question becomes -- why aren't your tabs resizing themselves automatically? Perhaps there you have their sizePolicy property set to Fixed, rather than Preferred or Ignored?
So the non-QMdiArea version of my code,
MyWidget::MyWidget(QWidget* parent)
{
...
layout()->setSizeConstraint( QLayout::SetFixedSize );
}
MainWindow::MainWindow(...)
{
...
MyWidget* wgt = new MyWidget(NULL);
wgt->show();
}
works just fine and produces a widget that the user can't resize. But when the MainWindow code is replaced with
MainWindow::MainWindow(...)
{
...
MyWidget* wgt = new MyWidget(ui->mdiArea); //Or MyWidget(NULL), same result
ui->mdiArea->addSubWindow(wgt);
}
the window, now within the QMdiArea, is resizeable. It doesn't seem to be an issue of Qt::WindowFlags, they don't handle resize policy. Surely there is a way to do this? NB I cant use something like setFixedSize(ht, wd) since the size of the widget can change programmatically (subwidgets are added and removed). But the user should not be able to resize it.
Even though MyWidget is not resizeable, when you call:
ui->mdiArea->addSubWindow(wgt);
The widget is put in a QMdiSubWindow which is resizeable. All you have to do is get the window that's created and fix its size:
QMdiSubWindow* subWindow = ui->mdiArea->addSubWindow(wgt);
subWindow->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
This should work, but I haven't tried this code myself.
EDIT: well... apparently that doesn't fix the size. :(
The following worked for me:
MyWidget* wgt = new MyWidget(ui->mdiArea);
QMdiSubWindow* subWindow = ui->mdiArea->addSubWindow(wgt);
subWindow->setFixedSize(wgt->size());
wgt->show();
Please consider we have a menu which has text set "MyMenu" and I want to change the menu text by clicking a button in the same widget from "MyMenu" to "МойМеню". Could you bring a code snippet please for that operation?
Have a look at "Dynamic Translation", http://doc.qt.io/qt-5/internationalization.html
void MyWidget::changeEvent(QEvent *event)
{
if (e->type() == QEvent::LanguageChange)
{
titleLabel->setText(tr("Document Title"));
...
okPushButton->setText(tr("&OK"));
// You could also use : retranslateUi(QWidget*);
}
else
{
QWidget::changeEvent(event);
}
}
This will be helpful to you as well : http://doc.qt.io/qt-5/qcoreapplication.html#installTranslator
Basically, when you call : qApp->installTranslator(MyAppTranslator) it will create a QEvent::LanguageChange.
So, provide a simple QComboBox with English/Russian, and when the selected language changes, call qApp->installTranslator(MyAppTranslator);. Then make sure your buttons are properly set up in changeEvent, and that's it !
Hope it helps a bit !
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QTextCodec::setCodecForTr(QTextCodec::codecForName("UTF-8"));//this is the solution
.............
}
And in the code you can dynamically change the strings if you set them from the begining by using tr() function [tr("your text")].