Using QtQuick / QML Passively with C++ - qt

Veteran C++ and C#/WinForms programmer here, getting familiar with Qt/QML.
There's a lot of information on how to call into C++ from QML, and I've got that working just fine, but I'd rather treat QML passively and manipulate it from C++ when at all possible.
For example:
frmLogin::frmLogin()
{
// Load QML file
// Wire up controls to their own pointers
cmdOK = QtQuick_GetControlFromQML("cmdOK");
cmdQuit = QtQuick_GetControlFromQML("cmdQuit");
}
void frmLogin::Show()
{
MyQMLPointerToWindow->Show();
}
void frmLogin::DoSomethingFromCPP()
{
cmdOK->SetProperty("text", "I just changed the button text");
rectBox->SetProperty("visible", true); // rectBox Rectangle from QML now appears on the screen
frmMainMenu = new frmMainMenu(); // Create new main menu window
frmMainMenu->ShowDialog(); // modal display
}
Is there a way to do this? Is it highly discouraged? I'm trying to make a multi-form modal application. It's difficult to find straightforward answers on all of this because it seems like QtQuick has gone through several design iterations. Any advice is appreciated!

If you know the objectName of the item you're interested in, you can use QObject::findChild():
QQuickItem *okButton = findChild<QQuickItem*>("cmdOK");
If the button is declared as a property in QML:
Item {
id: item
property alias button: item.button
Button {
id: button
text: "OK"
}
}
Then you can access it as a property in C++:
QObject *button = property("button").value<QObject*>();
button->setProperty("text", "I just changed the button text");

Related

QT: MAC menu shared among multiple QWidgets only triggers MainWindow's functions

I have an application generally structured as the following:
class MultiEditor : public QWidget
{
...
MultiSplitter *mSplitter;
...
}
MultiEditor::MultiEditor( Main *main, QWidget * parent )
{
...
mActions[SplitHorizontally] = action = new QAction(tr("Split To Right"), this);
action->setShortcut( tr("Ctrl+P, 3", "Split To Right"));
connect(action, SIGNAL(triggered()), this, SLOT(splitHorizontally()));
settings->addAction( action, "editor-split-right", editorCategory);
...
}
void MultiEditor::splitHorizontally()
{
... do something on &mSplitter (above);
}
and a class MainWindow:
MainWindow::MainWindow(Main * main)
{
...
mEditors = new MultiEditor(main);
setCurrentMultiEditor(mEditors);
...
createActions();
mMenu = createMenus();
this->setMenuBar(mMenu);
...
}
and a class SubWindow that creates a widget:
SubWindow::SubWindow( QVariant * splitterData = 0, QWidget * parent = 0 )
{
...
sEditors = new MultiEditor(Main::instance(), this);
setCurrentMultiEditor(sEditors);
...
#ifndef Q_OS_MAC
QMenuBar *newMenu = main->createMenus();
newMenu->setSizePolicy(QSizePolicy ::Expanding , QSizePolicy ::Fixed );
windowLayout->addWidget(newMenu);
#endif
...
}
and the actual menu constructor:
QMenuBar * MainWindow::createMenus()
{
QMenuBar *menuBar;
QMenu *menu;
QMenu *submenu;
...
#ifdef Q_OS_MAC
menuBar = new QMenuBar(0);
#else
menuBar = new QMenuBar();
#endif
...
menu->addAction( currentMultiEditor()->action(MultiEditor::SplitHorizontally) );
...
return menuBar;
}
Let's suppose that I have the MainWindow and a open SubWindow.
Both create a MultiEditor with a Splitter inside.
All this works good on Ubuntu: every action is associated with the right Splitter. If I click on the SubWindow's Splitter, for example, and I trigger the action "splitHorizontally", the SubWindow's MultiEditor:splitHorizontally() is triggered, and the SubWindow's Splitter is affected.
This does not happen on Mac. Here, if I click on a SubWindow's Splitter, the mSplitter of the MainWindow is affected.
I believed that clicking on a mSplitter, I would focus that mSplitter, so the MAC's parentless menu would act on whatever mSplitter focused. But it happens that it get stucked with the mSplitter on the MainWindow.
Actually, could it be that I was thinking that the function currentMultiEditor() was dynamic (every time the action was triggered, a different MultiEditor was called), but in the truth the multiEditor is fixed when creating the menu (in MAC the menu is created only on the MainWindow, so the currentMultiEditor() would be MainWindow's mEditors, and that would still be anytime the action were triggered from anywhere)?
What is the best design for you?
Should I create new menus on MAC too? How?
If I understood your question correctly, this looks indeed like a focus issue and I'm not surprised Linux behaves differently than macOS.
Probably on macOS even if you open a window and click somewhere, the focus remains on the parent window, thus calling the unexpected QAction slot.
I think you have 2 options:
when a subwindow is open, make sure it gains the focus. Call the QWidget::setFocus() method on one of the visible widgets of the window
accept keystrokes only in one place and redirect them to the current context, which in other terms is your active window
Hope that helps

How to get selected QTextEdit using on-screen keyboard?

I have to make a GUI for a touch screen software. It's on the same window as the QTextEdit. I was thinking of something simple with a limited set of characters (I also have to make PIN Pads for other windows later).
The approach I'm thinking of is hard-coding the text modifications done by each button. The problem I'm facing getting the QTextEdit that actually has the focus (is selected by the user's cursor).
So I would like to know how I could find out if a certain QTextEdit currently has focus or not ?
Also if there are better ways to do this whole thing ?
Here is my new code, what's wrong with it ?
#include "settings2.h"
#include "ui_settings2.h"
Settings2::Settings2(QWidget *parent) :
QWidget(parent),
ui(new Ui::Settings2)
{
ui->setupUi(this);
}
Settings2::~Settings2()
{
delete ui;
}
void Settings2::on_q_btn_clicked()
{
QTextEdit *textedit = qobject_cast<QTextEdit*>(QApplication::focusWidget());
if(textedit){
textedit->setText("aze");}
}
The way you are trying to get the QTextEdit in focus is wrong. Moreover as soon as you click on a button on your on-screen keyboard, the focus will move to the key and will not stay on the QTextEdit.
I would suggest using a pointer to hold address of modified QTextEdit as soon as one comes to focus. Thus you will always know which was the last text edit in focus and keep appending the new text to that.
You will have to write your own class inheriting QTextEdit and implement the QTextEdit::focusInEvent where you will be pointing the above mentioned pointer to the this pointer.
Per #thuga's comment QApplication::focusWidget.
If you want to be sure the focused widget is a certain category of widget you can use qobject_cast, which will only return a non-null pointer if that cast is valid:
QLineEdit *lineedit = qobject_cast<QLineEdit*>(widget);
QTextEdit *textedit = qobject_cast<QTextEdit*>(widget);
...
if (lineedit) {
// do QLineEdit stuff with lineedit
...
}
if (textedit) {
// do QTextEdit stuff with textedit
...
}
...

How to make this effect in qt: adding a new lineedit when I click a button

Newbie here. I want to have a GUI effect in my dock widget that whenever I click "add more" button or link, a new lineEdit field appears in the bottom.
I saw many software has something like
point-1 (_____,_____)
point-2 (_____,_____)
+ Add More Points
And when you click "+ Add More Points", a new point-3 will show up and wait for the input.
The code I have now is something like this:
#include "perfectPanel.hpp"
perfectPanel::perfectPanel(QWidget *parent) : QWidget(parent)
{
setupUi(this);
readInfo();
connect
(
btn_accept,
SIGNAL(clicked()),
this,
SLOT(readInfo()),
Qt::UniqueConnection
);
}
// Destructor
perfectPanel::~perfectPanel()
{}
void perfectPanel::readInfo()
{
xObject_ = vtkDoubleArray::New();
yObject_ = vtkDoubleArray::New();
xObject_->InsertNextValue( lineEdit_xObject01X->text().toDouble() );
xObject_->InsertNextValue( lineEdit_xObject02X->text().toDouble() );
yObject_->InsertNextValue( lineEdit_yObject01Y->text().toDouble() );
yObject_->InsertNextValue( lineEdit_yObject02Y->text().toDouble() );
}
You'll need to add that + Add More Points button to the perfectPanel class. Let's say you've already done that with this declaration in your class' private data section:
QPushButton* m_AddPoint;
Now, connect the button's clicked() signal to some slot to add the point. From the example code, you seem to already know how to do this, so I won't go into the specifics. Let's say you've connected the button's click event to the addPoint function.
void perfectPanel::addPoint()
{
/* The "this" argument is needed to prevent memory leaks */
QLineEdit* Field = new QLineEdit(this);
/* Your perfectPanel class has some layout where the existing LineEdit rows
are. I'm assuming m_Layout is a pointer to that layout here. */
m_Layout->addWidget(Field);
Field->show();
}

Drag and drop widget outside source application widgets

I have a Qt Desktop aplication which has several top-level widgets. Subwidgets of top-level widgets can be moved between top-level widgets by using drag-and-drop mechanism.
The problem i have now is to drop a sub-widget outside any of existing top-level widgets and create a new top-level widget to contain this one. Lets call this separation.
Can this be done using drag-and-drop? I could not find a way where my dropEvent goes?
Can i want to handle the drop event in my application even if the drop place is not allowed? Maybe a mouse release or something?
I cannot change everything now but also a question for the future. Is docking/undocking a better way to do this?
Regards
Mihai
I found a way to do this. When drag moves outside of the application widgets QDrag object emits a targetChanged signal with 0 parameter.
So i inherited from QDrag and then emit a custom signal in destructor if the target() is null.
The only problem is that the cursor looks like interdiction of drop and this i could not fix because QDrag can only set cursor pixmap for valid actions like Move or Copy or Link
Update:
Here is the inherited class.
class TabDrag: public QDrag
{
Q_OBJECT
public:
explicit TabDrag(QWidget *dragSource);
~TabDrag();
signals:
void tearOff(); /// emit tearOff signal if the QDrag object is destroyed and target was null
};
TabDrag::TabDrag(QWidget *dragSource):QDrag(dragSource)
{
}
TabDrag::~TabDrag()
{
// check if we need to detach this tab
if(!target())
{
emit tearOff();
}
}
The tearOff signal should be connected to whatever you want to happen. In my case i pull out the widget from the tab and change parent to a new window.
Example of usage
void MyTabBar::mouseMoveEvent(QMouseEvent* event)
{
..................
TabDrag * drag = new TabDrag(this);
drag->setMimeData(mimeData);
drag->setPixmap(*m_tabPixmap.data());
drag->setHotSpot(QPoint(m_dragStartPos.x() - tabAtRect.x(), m_dragStartPos.y() - tabAtRect.y()));
drag->exec();
connect(drag, SIGNAL(tearOff()), this, SLOT(onTearOff()));
}

How to make QComboBox popup upwards?

my QComboBox-derived class lives in a QGraphicsScene at the bottom end of the (visible) screen - but it pops up downwards, thus out of view.
(How) is it possible to force the popup to open above the widget?
I've tried re-implementing showPopup like this:
void MyComboBox::showPopup()
{
QAbstractItemView *popupView = view();
popupView->move(0,-100);
//popupView->window->move(0,-100);
QComboBox::showPopup();
}
The result is, that the content seems to be shifted, but not the underlying popup object.
I think it might be possible to find a solution with styles as indicated in
this article, but I can't find any Styles control that might be helpful here. I am rather new to C++ as well as Qt, so I might be missing something obvious.
I'd appreciate any help on this matter!
Best regards,
Sebastian
With the information found here, I was able to get it done this way:
void SteuerQComboBox::showPopup() {
QComboBox::showPopup();
QWidget *popup = this->findChild<QFrame*>();
popup->move(popup->x(),popup->y()-this->height()-popup->height());
}
Note that it's crucially important to call the base classes "showPopup" first.
Thanks to everybody who was reading my question and thinking about it!
user1319422's solution isn't bad, but it has two problems.
If your platform has GUI animation, the listbox will animate opening downwards, then is moved above the text box.
If you disable combobox animation (or you don't have it), the call to QComboBox::showPopup() still makes the GUI element start to appear on the screen already. So, moving it there would cause it to flicker as it appears in the first place and moves to the next.
So, to address the first problem, I just switched off animation:
void MyComboBox::showPopup()
{
bool oldAnimationEffects = qApp->isEffectEnabled(Qt::UI_AnimateCombo);
qApp->setEffectEnabled(Qt::UI_AnimateCombo, false);
QComboBox::showPopup();
qApp->setEffectEnabled(Qt::UI_AnimateCombo, oldAnimationEffects);
}
Then, for the second problem, I moved the frame in the Show event:
bool MyComboBox::eventFilter(QObject *o, QEvent *e)
{
bool handled = false;
if (e->type() == QEvent::Show)
{
if (o == view())
{
QWidget *frame = findChild<QFrame*>();
//For some reason, the frame's geometry is GLOBAL, not relative to the QComboBox!
frame->move(frame->x(),
mapToGlobal(lineEdit()->geometry().topLeft()).y() - frame->height());
}
}
/*else if other filters here*/
if (!handled)
handled = QComboBox::eventFilter(o, e);
return handled;
}
if you want to force popup to open above only when it is out of view you can do this:
void SteuerQComboBox::showPopup() {
QComboBox::showPopup();
QWidget *popup = this->findChild<QFrame*>();
if((popup->y() + popup->height()) > this->window()->height())
popup->move(popup->x(),popup->y()-this->height()-popup->height());
}

Resources