Weird behavior when switching view between C++ and QML - c++

I'm currently working on a project using Qt 5.0.2 on an embedded linux (ARM Cortex A9).
The main UI interface is developped in QML but I need to be able to hide this view to show a QWebView directly in C++.
I coded a simple view controller in c++ who hide()/show() the QML view and the many instances of QWebView.
The hiding/showing method work fine but when i show back the QML view, it's very instable. QML object are visible (or not visible :p) when they should not and the focus are buggy too. Object are draw in the wrong position too.
I try several methods :
-Initialize the focus/visible property of the differents objects everytime I show the QML view.
-use .setSource() everytime before showing the view
-try to update() the differents object thank to rootObject() before showing the view.
Did anyone have a tips to make the QML view functionnal again after a switch to a c++ view ?
thank.

there is probably a better way but,
you could probably do something like this (I have not tested this):
note: if the slot implementation is wrong (bad math) it will result in infinite recursion.
//this code could probably be in the constructor
real widthOverHeightRatio = 2;//set this value to what you want, or what it is when user first presses shift depending on the use case.
QObject::connect(this, SIGNAL(widthChange()), this, SLOT(onWidthChanged()));
QObject::connect(this, SIGNAL(heightChanged()), this, SLOT(onHeightChanged()));
//don't forget to define these slots in the header
//implemented slots
void MyClass::onWidthChanged()
{
if(width/height!=widthOverHeightRatio){
height = width/widthOverHeightRatio;
}
}
void MyClass::onHeightChanged()
{
if(width/height!=widthOverHeightRatio){
width = height*widthOverHeightRatio;
}
}

Related

QML: C++ classes with "bring your own component"

I'm trying to develop a Qt C++ application, with a QML frontend, but I hit a roadblock.
This is what I have so far:
A Factory class that outputs a choice of objects. These objects, that I'm going to call "controllers", control different pieces of hardware.
The Factory would be exposed to the QML layer with setContextProperty.
The controller would be chosen basically with a combo box controlling the factory.
Now, for the tricky bit. I want that the "controllers" behave in a "bring your own component" way. This means that they would have a method returning the respective QML file for their controller. That shouldn't be to hard to do, it's basically biding a Loader to a method of the Factory/Manager saying the file with the component to load into a placeholder.
But the problem is: how can this newly created component and this newly created controller know and talk to each other? This is something I did before with QWidgets, just having pointers between the classes. Quite trivial.
I tried an architecture like this before for QWidgets, but seems to not be ideal for QML.
I made this drawing of what I would ultimately like to happen:
This architecture allows for a very trivial plugin system (at least in the QWidgets world) and I would very much like to keep that. Not a massive singleton and account for every possible action...
I'd appreciate ideas!
I think this is actually very easy, if you return a QQuickItem from the C++ side. If you do so you can create it with a specific context, in which you can set your "specific hardware controller" as a property
QQmlComponent *qml_controller = new QQmlComponent(qengine, "some_file.qml");
QQmlContext *context = new QQmlContext(); //should probably give a pointer to owning object
context->setContextProperty("controller", pointer_to_hw_cont);
return qml_controller->create(context);
The Loader setSource method have additional parameter you could pass to provide initial value for some property. Something like this:
ComboBox {
model: controlerFactory.specificHWListModel
onCurrentTextChanged: {
var specificHWControler = controlerFactory.getObjectFor( currentText );
loader1.setSource(
specificHWControler.qml_file,
{ "controler": specificHWControler }
);
}
}
Loader {
id: loader1
}
The specificHWListModel cold be QStringList or some custom QAbstractListModel.
And getObjectForcould be just a invokable function.
Q_INVOKABLE QObject* getObjectFor(QString hwName);
The object returned from Q_INVOKABLE function will be managed by QQmlEngine by default if you don't set by the QQmlEngine::setObjectOwnership. Remember to register your SpecificHWControler class to QQmlEngine.
The qml_file SpecificView.ui.qml, should have property controler, and could be edited with Designer:
import SpecificHWControlerModule 1.0
Item {
property SpecificHWControler controler
}
https://doc.qt.io/qtcreator/quick-connections-backend.html

JUCE - Making a New Window

Coming from making single-page applications with the visual WYSISWYG editor in JUCE, I'm having a bit of trouble figuring out how to invoke new windows (outside of the main GUI window). I made a test application that just has a small minimal main GUI that I created with the visual editor. It has a button "Make New Window." My goal is to be able to click that button and have a new window pop up and that this new window is a JUCE "GUI component," (AKA, the graphical / visual GUI editor file). Now, I actually have sort of achieved this, however, its throwing errors and assertions, so it would be great to get a very simple, step-by-step tutorial.
I studied the main.cpp file that the Projucer automatically created in order to get a feel for how they are creating a window. Here's what I did.
1) In my project, I added a new GUI Component (which becomes a class) and called it "InvokedWindow."
2) In my main GUI component class header, I added a new scoped pointer of type InvokedWindow: ScopedPointer<InvokedWindow> invokedWindow;
3) I created a new button in the main GUI editor called "Make New Window" and added this to the handler code:
newWindowPtr = new InvokedWindow; so that any time the button is hit, a new object of type InvokedWindow is created.
4) In the InvokedWindow class, in the constructor, on top of the automatically generated code, I added:
setUsingNativeTitleBar (true);
setCentrePosition(400, 400);
setVisible (true);
setResizable(false, false);
Which I sort of got from the main file of the JUCE application.
I also added a slider to this new window just to add functionality to it.
5) I added an overloaded function to let me close the window:
void InvokedWindow::closeButtonPressed()
{
delete this;
}
So, now when I run the app and click the make new window button, a new window does pop up, but I get an assertion:
/* Agh! You shouldn't add components directly to a ResizableWindow - this class
manages its child components automatically, and if you add your own it'll cause
trouble. Instead, use setContentComponent() to give it a component which
will be automatically resized and kept in the right place - then you can add
subcomponents to the content comp. See the notes for the ResizableWindow class
for more info.
If you really know what you're doing and want to avoid this assertion, just call
Component::addAndMakeVisible directly.
*/
Also, I'm able to close the window once and hit the button in the main GUI to create another instance of a newWindow, but closing it a second time leads to an error:
template <typename ObjectType>
struct ContainerDeletePolicy
{
static void destroy (ObjectType* object)
{
// If the line below triggers a compiler error, it means that you are using
// an incomplete type for ObjectType (for example, a type that is declared
// but not defined). This is a problem because then the following delete is
// undefined behaviour. The purpose of the sizeof is to capture this situation.
// If this was caused by a ScopedPointer to a forward-declared type, move the
// implementation of all methods trying to use the ScopedPointer (e.g. the destructor
// of the class owning it) into cpp files where they can see to the definition
// of ObjectType. This should fix the error.
ignoreUnused (sizeof (ObjectType));
delete object;
}
};
This is all a bit over my head. I was figuring it wouldn't be too bad to be able to create a new window, via a button. A new window that I could edit with the graphical GUI editor, but I'm not able to fully figure it out all on my own, through I did try. Could anyone post a step-by-step guide to doing this the correct way? I did post this at the JUCE forums, but due to my lack of GUI programming, I was unable to understand the solutions posted (my own fault), so I'm hoping to get a very simple guide to this. It would be very much appreciated. Thank you.
I figured it out. I needed to create:
A new GUI component (Remember, this is the visual editor in JUCE)
A class (I called it BasicWindow, based on the JUCE demo code) that acts as a shell to run this new window and holds the GUI component.
A JUCE SafePointer that makes a new object of type BasicWindow whenever the button is clicked and sets the attributes to that window.
Here is my code:
Referring to line 3) Inside the handler section of the button to create the new window:
basicWindow = new BasicWindow("Information", Colours::grey, DocumentWindow::allButtons);
basicWindow->setUsingNativeTitleBar(true);
basicWindow->setContentOwned(new InformationComponent(), true);// InformationComponent is my GUI editor component (the visual editor of JUCE)
basicWindow->centreWithSize(basicWindow->getWidth(), basicWindow->getHeight());
basicWindow->setVisible(true);
Referring to line 2) A .cpp file that defines what the BasicWindow is:
#include "../JuceLibraryCode/JuceHeader.h"
class BasicWindow : public DocumentWindow
{
private:
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BasicWindow)
public:
BasicWindow (const String& name, Colour backgroundColour, int buttonsNeeded)
: DocumentWindow (name, backgroundColour, buttonsNeeded)
{
}
void closeButtonPressed() override
{
delete this;
}
};
And referring to line 1) Make the GUI editor component, which this is easy to do. You just right add a new file in the JUCE file manager. "Add New GUI Component," then visually add all your elements and handler code.
My biggest issue was that I was using a JUCE ScopedPointer, so after deleting the object, the pointer pointing to it wasn't being set back to NULL. The SafePointer does this. If any more explanation is needed, I'm happy to help, as this was terrible for someone with not much GUI development "under his belt."

ListView not initially showing data

My QML ListView doesn't show my data until I perturb it with the mouse (e.g. just drag it up and down.) After this the view shows the model without issue until it empties, and then I once again need to perturb it to get it working again. Is there way to kick this ListView into working?
I'm using Qt 5.8 on Linux 14.04. My model is a subclass of QAbstractListModel. I build it by following the AbstractItemModel Example. The main difference is that my list model is a property of an entity, rather than being set with setContextProperty in main.cpp.
There are a few similar issues here on SO about the ListViews not updating, but none seem to only have an issue at the start. Most of them relate to the OP calling dataChanged manually instead of beforeInsertRows() & endInsertRows() - both methods I'm calling (see below.)
My ListView is in an item loaded with a SceneLoader.
I posted all the relevant code here, because I'm a little suspicious of how I use the Layouts on my ListView (maybe that's causing it? Maybe my hierarchy is broken? I haven't been able to prove that though.)
In short though,
ListView:
ListView {
anchors.fill: parent
model: sceneGraph.blobs
delegate: delegate
}
BlobModel.cpp:
auto BlobModel::addBlob(const BlobPointDataPtr& data) -> void
{
// ...
// Each blob has a uuid
const auto idx = Contains(uuid);
if (-1 != idx)
{
blobs_[idx]->Update(data);
Q_EMIT dataChanged(createIndex(idx, 0), createIndex(idx, 0));
}
else
{
beginInsertRows(QModelIndex(), rowCount(), rowCount());
blobs_ << new Blob{data, id_count_}; id_count_++;
endInsertRows(); // responsible for the QQmlChangeSet
Q_EMIT dataChanged(createIndex(rowCount(), 0), createIndex(rowCount(), 0));
}
}
Also, on my terminal, I receive the message:
QObject::connect: Cannot queue arguments of type 'QQmlChangeSet'
(Make sure 'QQmlChangeSet' is registered using qRegisterMetaType().)
This seems to be emitted by endInsertRows(), but I'm not sure why. In the past the solution has been to register the missing type, e.g. qRegisterMetaType<QQmlChangeSet*>("QQmlChangeSet"); but this seems not to be a public type with Qt, and because everything mostly works without it, I'm not sure missing that is the exact issue.
The problem, as pointed out in the comments, was that I was modifying my model outside of the main thread.
My code was set up so that another thread would trigger additions to my model by directly calling addData. The reason my minimal example wasn't able to replicate this was because in it I used a QTimer to simulate the other thread, however QTimer also runs on the main thread.
The solution was to change my direct call to addData(data) to emitted a signal to do the addition, thus moving the actual work back to the main thread.

BB Cascades, Passing data from C to Page1.qml and also to Page2.qml

I know this could be a really small thing i am missing here, but I have spent some good amout our hours trying to figure this out. I am from an Objective-C background and what I am trying to do is this:
I have a main.qml which is a navigationPane and it has 2 other external pages added to it as attached object. The two pages have grid list views. Now, There is a MyApp.cpp file that loads a Json file and populates the result in the main.qml file. I only display the relevalt items on this page at first. When the user taps on any item, I want to take them to page2.qml which has a grid list view as I mentioned above and populate it with dataModel passed from main.qml (which has all the data from MyApp.cpp). This has give me no joy at all! I need help. What can I do to make this work? Please I need help on this one...
I'm not sure if this is best practice or not, but the easiest method I found was to use the Qt global object.
Basically assign either your data or an object to the Qt object and then you can access it from any other page.
E.g.
In main.qml in onCreationCompleted or whatever function you receive your data in:
function getData(data) {
Qt.myVariable = data;
}
Then in your other page(s) you can access it. E.g. in Page2.qml:
Page {
onCreationComplete: {
myLabel.text = Qt.myVariable;
}
}
As I mentioned, this works for objects as well, so you can assign a navigation pane, a sheet, a page, etc. So things like "Qt.myNavPane.push(page)" becomes possible.
I encountered issues where I needed to use Qt for various purposes. Signals and slots are better practice I think, but are not always practical.

Showing two windows in Qt4

My friend and I have each created parts of a GUI using Qt 4. They both work independently and I am trying to integrate his form with the my main window. As of now this is the code I am using to try and load his form:
//connect buttons and such
connect(exitbtn, SIGNAL(triggered()),this,SLOT(terminated()));
connect(add, SIGNAL(triggered()),this,SLOT(add_rec()));
void MainWindowImpl::add_rec()
{
//form quits as soon as it loads...?
DialogImpl dia;//name of his form
dia.show();
}
I have included his header file. The program compiles but when I hit the trigger his form loads up for maybe half a second and then closes. Does anyone know what I am doing wrong?
You have almost get it right. This is because the RAII of C++. If you allocate the Dialog on stack, it would be destructed as soon as the function return.
Assuming MainWindowImpl inherits publically from QWidget, you're looking for this:
void MainWindowImpl::add_rec()
{
// passing "this" to the constructor makes sure dialog will be cleaned up.
// Note that DialogImpl will need a constructor that takes a
// QObject* parent parameter.
DialogImpl* dialog = new DialogImpl(this);
dialog->show();
}
Look at the Qt documentation for examples of how the constructors should look.
Apparently QT4 only allows one instance of an object at a time, however pointers are another matter. Change both the main.cpp and what ever your main window to look something like this:
DialogImpl *dia=new DialogImpl;
dia->show();