Displaying a C++ class via QML in QtQuick2 - c++

I am working on a QtQuick 2 application (Qt version 6.2.3). I created one C++ class (let's call this class "Example") that contains the data that my application should deal with. This class can be instantiated several times, representing different datasets to be displayed.
class ExampleObject : public QObject {
Q_OBJECT
Q_PROPERTY(QString property1 MEMBER property1 CONSTANT)
...
public:
QString property1;
};
Q_DECLARE_METATYPE(ExampleObject*)
I want to be able to display instances of this class via QML, therefore I created a "Example" custom component with a property pointing to the Example C++ object containing the data I want to display.
ExampleComponent {
property var exampleCppObject // exampleCppObject is a pointer to an instance of ExampleObject
Label {
text: exampleCppObject.property1
}
}
To be able to change the Example instance used by the QML component, I created functions to "reinitialize" and "update" the component:
ExampleComponent {
property var exampleCppObject // exampleCppObject is a pointer to an instance of ExampleObject
property string textToDisplay
function update() {
textToDisplay=Qt.binding(() => exampleCppObject.property1);
}
function reinitialize() {
textToDisplay=""
}
Label {
text: textToDisplay
}
}
I call these functions after changing or deleting the Example object pointed by ExampleCppObject, and this works quite fine. But I feel like this isn't best practice, and it seems to me that I am doing things wrong.
What are better ways of connecting C++ to QML, in the situation I described?
Edit: my main.cpp essentially consists in:
MainModel mainModel;
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("mainModel", &mainModel);
engine.load(QStringLiteral("qrc:/main.qml"));
Where mainModel is an object which can create different instances of ExampleObject while the application is running.

You can optimize the binding textToDisplay such that you don't have to call the upate and reinitialize functions, which seems to be the question you are after:
property var exampleCppObject
property string textToDisplay : exampleCppObject ? exampleCppObject.property1 : ""
In case you need more complex logic in the future, you can also use braces:
property string textToDisplay: {
console.log("log me everytime the binding is reevalutated")
if(condition1)
return "invalid"
else if(condition2)
return exampleCppObject.property2
else
return exampleCppObject.property1
}
The best part of this, is that QQmlEngine actually reevaluates the binding for every property that is used in this binding (which has a notify signal), so if crafted correctly, you can largely leave the binding alone (meaning you don't need the update and reinitialize function)

Related

Unable to handle unregistered datatype - Qt

everyone!
I am developing a code with Qt and many of my components have a similar behavior regarding changing colors. Therefore, I have made a class called ComponentState which inherits from QObject.
class ComponentState : public QObject
{
Q_OBJECT
Q_PROPERTY(QColor ui_state_color READ getStateColor WRITE setStateColor NOTIFY onStateColorChanged)
}
One of my components that inherits from this class is called PneumaticLine.
class PneumaticLine : public ComponentState
{
Q_OBJECT
...
}
One of the views in my app (made in QML), has many of these PneumaticLine elements, so I decided to make a class called Controller to have a QList<PneumaticLine*> and export this using QQmlListProperty.
class Controller : public QObject
{
Q_OBJECT
Q_PROPERTY(QQmlListProperty<PneumaticLine> anti_ice_pneumatic_lines READ getAntiIcePneumaticLines)
...
QQmlListProperty<PneumaticLine> getAntiIcePneumaticLines() {
return QQmlListProperty<PneumaticLine> (this, &antiIcePneumaticLines);
}
private:
QList<PneumaticLine*> antiIcePneumaticLines;
}
Even though I declare the qml types in my main file, I still get the error:
QMetaProperty::read: Unable to handle unregistered datatype 'QQmlListProperty'<PneumaticLine'>' for property 'Controller::anti_ice_pneumatic_lines'
As a consequence of this error, I get the following in my qml file:
TypeError: Cannot read property '2' of undefined
I would like to access the List<PneumaticLine*> in my qml files. How can I do that?
OBS: I have declared the types PneumaticLine, ComponentState and Controller:
qmlRegisterType<ComponentState>("ECS", 1, 0, "ComponentState");
qmlRegisterType<PneumaticLine>("ECS", 1, 0, "PneumaticLine");
qmlRegisterType<Controller>("ECS", 1, 0, "Controller");
Controller controller;
engine.rootContext()->setContextProperty("Controller", &controller);
Thank you!
You need to include the newly created type in the meta object system:
Q_DECLARE_METATYPE(QQmlListProperty<PneumaticLine>)
You need to set function as Q_INVOKABLE for Q_PROPERTY can invoke that as:
Q_INVOKABLE QQmlListProperty<PneumaticLine> getAntiIcePneumaticLines();

Unreal GAS: Print out the current value of an attribute to UI when it changes

When the base value of an attribute changes, there is UAttributeSet::PostGameplayEffectExecute() available to access the (new) value and GameplayEffect with its context. I'm using this to print the changed value to UI (this is also done in ActionRPG).
Is there something similar available for the current value of an attribute? How to notify UI, when FGameplayAttributeData::CurrentValue is updated?
Though UAttributeSet::PreAttributeChange() is called on every value update, it doesn't provide any context so it is not possible to access the UI from there (events broadcasted by FAggregator also don't fit).
It is possible to use a GameplayCue instead, to set the value of FGameplayAttributeData::CurrentValue within the UI (the cue is triggered by the GameplayEffect who sets the current value). This is possible by deriving from a GameplayCueNotifyActor and use its events OnExecute and OnRemove. However, instantiating an actor just to update UI seems to be a waste of resources.
It is also possible to fetch the information using the UI itself (calling a function which accesses the attribute each tick or with a timer), but in comparison to event driven UI update, this is also wasteful.
The GameplayAbilitySystem has UAbilitySystemComponent::GetGameplayAttributeValueChangeDelegate() which returns a callback of type FOnGameplayAttributeValueChange that is triggered whenever an attribute is changed (base value or current value). This can be used to register a delegate/callback, which can be used to update the UI.
Minimal example
In MyCharacter.h
// Declare the type for the UI callback.
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FAttributeChange, float, AttributeValue);
UCLASS()
class MYPROJECT_API MyCharacter : public ACharacter, public IAbilitySystemInterface
{
// ...
// This callback can be used by the UI.
UPROPERTY(BlueprintAssignable, Category = "Attribute callbacks")
FAttributeChange OnManaChange;
// The callback to be registered within AbilitySystem.
void OnManaUpdated(const FOnAttributeChangeData& Data);
// Within here, the callback is registered.
void BeginPlay() override;
// ...
}
In MyCharacter.cpp
void MyCharacter::OnManaUpdated(const FOnAttributeChangeData& Data)
{
// Fire the callback. Data contains more than NewValue, in case it is needed.
OnManaChange.Broadcast(Data.NewValue);
}
void MyCharacter::BeginPlay()
{
Super::BeginPlay();
if (AbilitySystemComponent)
{
AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(MyAttributeSet::GetManaAttribute()).AddUObject(this, &MyCharacterBase::OnManaUpdated);
}
}
In MyAttributeSet.h
UCLASS()
class MYPROJECT_API MyAttributeSet : public UAttributeSet
{
// ...
UPROPERTY(BlueprintReadOnly, Category = "Mana", ReplicatedUsing=OnRep_Mana)
FGameplayAttributeData Mana;
// Add GetManaAttribute().
GAMEPLAYATTRIBUTE_PROPERTY_GETTER(URPGAttributeSet, Mana)
// ...
}
Example for updating the UI via the EventGraph of the character blueprint, which derived from MyCharacter. UpdatedManaInUI is the function which prints the value to the UI.
Here, UpdatedManaInUI retrieves the value by itself. You might want to use the AttributeValue of OnManaChange.

Passing different classes in connect() in Qt

I'm working on a image editing software which includes a few classes. But I need my code to be more generic. But I've got a big problem with my classes when it comes to connections.
QObject::connect(actionSmartContrast, SIGNAL(triggered(bool)), effectsWindow, SLOT(addSmartContrast()));
QObject::connect(actionSaturation, SIGNAL(triggered(bool)), effectsWindow, SLOT(addSaturation()));
I've got a Menu called "Effects", and when the user clicks the QAction actionSmartContrast, then the effect Smart Contrast is added to my effects window. The thing is, given that each effect has its own class, I have to create a function for each class as you can see in the code above. And this is very repetitive. I would like to avoid this problem by doing something like this:
QObject::connect(actionSmartContrast, SIGNAL(triggered(bool)), effectsWindow, SLOT(addEffect(new SmartContrast())));
QObject::connect(actionSaturation, SIGNAL(triggered(bool)), effectsWindow, SLOT(addEffect(new Saturation())));
Everything would be fine for the function addEffect() because it expects a pointer to an Effect object and both SmartContrast and Saturation inherit from Effect. The only problem is that it is impossible to pass variables in connect() like this. So I thought of subclassing QAction and creating a signal which would return the class I like everytime but again, how to tell my new Action class what class it should return? If I have a thousand effects, I won't subclass QAction a thousand times! I need to create a function which would take for example a pointer to a SmartContrast object and it will guess that it has to return a SmartContrast pointer everytime the Action is clicked. And that would still be possible to do it because of the inheritance from the class Effect. But I really can't figure out how to do that. Any help would be much appreciated. Thanks in advance!
Looks like QSignalMapper is exactly what you're looking for.
UPDATED:
Another way is to use lambda (if Qt version and c++ compiler allows):
QObject::connect(actionSmartContrast, &QAction::triggered, [effectsWindow](){ effectsWindow->addEffect(new SmartContrast()) });
There are several options.
If it is enough to have the base class pointer of the effects because you use e.g. virtual methods following solution should do:
You can create an intermedite class:
class Intermediate : public QObject
{
Q_OBJECT
public:
Intermediate(QObject* parent = 0) : QObject(parent){}
signals:
void triggerEffect(Effect*);
public slots:
void effectTriggered()
{
QAction* action = qobject_cast<QAction*>(QObject::sender());
if ( action ) {
std::map<QAction*,Effect*>::iterator it = m_mapping.find(action);
if ( it != m_mapping.end() )
{ emit triggerEffect( it->second ); }
}
}
public:
void registerActionEffectPair(QAction* action,Effect* effect)
{ m_mapping[action]=effect; }
private:
std::map<QAction*,Effect*> m_mapping;
};
To use your Effect base class as type for signals and slots, you have to register it as a metatype:
qRegisterMetaType<Effect*>();
Connect it:
QObject::connect(intermediateInstancePtr, SIGNAL(triggerEffect(Effect*),
effectsWindow, SLOT(addEffect(Effect*)));
And the connections of each action would look like:
intermediateInstancePtr->registerActionEffectPair( yourEffectAction, theEffectPtr );
QObject::connect(yourEffectAction, SIGNAL(triggered(bool)),
intermediateInstancePtr, SLOT(effectTriggered()));
Another one could be to use QObjects properties:
setProperty( "property", "value" )
Call this for each effect QAction and read the property in the slot "addEffect".
The property can be read by calling
QAction* action = qobject_cast<QAction*>(QObject::sender());
if ( action ){
QVariant val = action->property("property");
if ( val.isValid() )
{
//TODO
}
}
since Object::sender returns the sender which is responsible for the slot call.
Afterwards you can do a switch case or stuff like this to distinguish between the different effects.
I finally solved my problem! I subclassed QAction and added a signal to my new class which creates a new effect from the class I want depending on the property text(). Simple if blocks are enough. Thank you all for your answers!

Exchange values between C++ and QML

How do i send a value from main.cpp into Qml file within my qt quick project
transform: Rotation {
id: needleRotation
origin.x: 5; origin.y: 65
angle: -120 + VALUE*2
}
I need the value from Cpp frequently for a speedometer made with qt quick 2.0
I guess the property is produced by some object. In that case you can exploit Q_PROPERTY (see here).
Following what is shown in the link I provided you can rewrite your class as follows:
class DataProvider : public QObject
{
Q_OBJECT
Q_PROPERTY(qreal value READ value WRITE setValue NOTIFY valueChanged)
public:
void setValue(qreal newVal) { // <--- do your stuff to update the value
if (newVal != m_value) {
m_value = newVal;
emit valueChanged(); // <--- emit signal to notify QML!
}
}
qreal value() const {
return m_value;
}
signals:
void valueChanged(); // <--- actual signal used as notification in Q_PROPERTY
private:
qreal m_value; // <--- member value which stores the actual value
};
Here we defined a property value with the corresponding getter and setter (value and setValue resp.) The setter method emits the notification signal which is fundamental to notify QML when the value is changed.
Now, to expose the object to QML (and hence its property) just register it as a context property; just write in your main:
DataProvider data;
engine.rootContext()->setContextProperty("data", &data); // ALWAYS before setting the QML file...
Now the DataProvider instance data can be used through the name data inside QML. Simply rewrite your QML like this:
transform: Rotation {
id: needleRotation
origin.x: 5; origin.y: 65
angle: -120 + data.value * 2
}
Each time you call setValue() in your C++ code and a change occurs to the value, a notification is issued and the binding revaluated.
Q_PROPERTY is the answer.
For general inf on properties: http://qt-project.org/doc/qt-4.8/qml-extending.html.
Look for Q_PROPERTY in this article: http://qt-project.org/doc/qt-5/properties.html.
The second article is a must for C++/QML development (read the whole article).
And more recent and structured info: http://qt-project.org/doc/qt-5/qml-extending-tutorial-index.html
I read the second and that still works but it makes sense to revisit with new docs.
If you want to expose single value to QML directly from main() function, use QQmlContext::setContextProperty. Place this before engine.load(...) call:
engine.rootContext()->setContextProperty("VALUE", 10.0);
Note: You might want to adopt some sort of naming convention to distinguish context properties from local variables and properties. For example, I start all context property names with underscore like this: _value.

Model View Presenter and Repeater

I'm building an application using the Supervising Controller pattern (Model View Presenter) and I am facing a difficulty. In my page I have a repeater control that will display each item of a collection I am passing to it. The reapeater item contains 2 dropdown list that allow the user to select a particular value. When I click the next button, I want the controller to retrieve those values.
How can I do that I a clean way?
You can also make a 'widget' interface for the drop down. I'll give you an easy example of a some working code for a TextBox widget so you get the idea.
public interface ITextWidget
{
event EventHandler TextChanged;
string Text { get; set; }
}
public abstract class TextWidget<T> : ITextWidget
{
protected T _wrappedWidget { get; set; }
public event EventHandler TextChanged;
protected void InvokeTextChanged(object sender, EventArgs e)
{
var textChanged = TextChanged;
if (textChanged != null) textChanged(this, e);
}
public abstract string Text { get; set; }
}
Notice that so far everything is technology agnostic. Now here's an implementation for a Win Forms TextBox:
public class TextBoxWidget : TextWidget<TextBox>
{
public TextBoxWidget(TextBox textBox)
{
textBox.TextChanged += InvokeTextChanged;
_wrappedWidget = textBox;
}
public override string Text
{
get { return _wrappedWidget.Text; }
set { _wrappedWidget.Text = value; }
}
}
This gets instantiated in the Form itself, which back to MVP is also the IViewWhatever:
public partial class ProjectPickerForm : Form, IProjectPickerView
{
private IProjectPickerPresenter _presenter;
public void InitializePresenter(IProjectPickerPresenter presenter) {
_presenter = presenter;
_presenter.InitializeWidgets(
...
new TextBoxWidget(txtDescription));
}
...
}
And in the Presenter:
public class ProjectPickerPresenter : IProjectPickerPresenter
{
...
public void InitializeWidgets(ITextWidget descriptionFilter) {
Check.RequireNotNull<ITextWidget>(descriptionFilter, "descriptionFilter");
DescriptionFilter = descriptionFilter;
DescriptionFilter.Text = string.Empty;
DescriptionFilter.TextChanged += OnDescriptionTextChanged;
}
...
public void OnDescriptionTextChanged(object sender, EventArgs e) {
FilterService.DescriptionFilterValue = DescriptionFilter.Text;
}
It's looks worse than it is to setup because most of the work is fairly mechanical once you get the idea. The clean part is that the presenter can get (and set) whatever info it needs on the widget without knowing or caring what the actual implemented widget is. It also lends itself to reuse with other widgets (you wind up building a library of them) of the same type (Win Forms here) and in other UI technologies as needed (once you have the interface / base class the implementation in another technology is trivial). It also is easy to test with mock objects because you have the interface. And your UI is now wonderfully ignorant of just about everything but UI related tasks. The downside is the bunch of classes per widget and a little learning curve to get comfortable with it.
For your drop down, your might just need the SelectedIndexChanged type event, which you'd substitute for this examples TextChanged event.
When controller-view interation get's too complex I usually split them up into subcontrollers and subviews.
You can have the items in the repeater be user-controls that have their own views and controllers. Your main view can then have a list of subviews(usercontrols) that have their own controllers that are maintained by the main controller.
When the user clicks next your main controller can signal all the subcontrollers to refresh their items from their views.