How to change a property binding during an element creation in QML? - c++

Suppose I have a custom CheckBox:
//MyCheckBox.qml
CheckBox {
required property QtObject proxy
checked: proxy.value
Binding {
target: proxy
property: "value"
value: checked
}
}
So the checked status of the MyCheckBox bound to the value property of my object (proxy) and vise-versa, i.e. I have a two-way binding.
I am using my custom checkbox as follows:
//My window
Item {
...
MyCheckBox {
id: ordinaryCheck
proxy: ...
}
...
}
Everything works as expected. But what if I need to invert the logic for some instance of MyCheckBox: when proxy.value is true the checkbox is unchecked, and when proxy.value is false the checkbox is checked ? But this, ofc, doesn't work as I have a binding loop here if I try to do this:
Item {
...
MyCheckBox {
id: invertedCheck
proxy: ...
checked: !proxy.value
Binding {
target: proxy.value
property: "value"
value: !checked
}
}
The Qt bindings are also not an option:
//MyCheckBox.qml
CheckBox {
required property QtObject proxy
checked: proxy.value
Component.onCompleted {
property.value = Qt.binding(function() { return checked });
}
}
I have the same binding loop error in this case.
So what is my option to reach the goal, how to alternate the binding at the moment of instantiation ?
Update 1
Here is my PropertyProxy::setValue member function:
void setValue( const QVariant& v )
{
if( v != value() )
{
//do some stuff
emit valueChanged();
}
}

If you're concerned about a potential binding loop, it is a potential candidate to rewrite it as an imperative implementation, e.g.
CheckBox {
required property QtObject proxy
onCheckedChanged: {
if (proxy && proxy.value !== checked) {
proxy.value = checked;
}
}
property bool proxyValue: proxy && proxy.value ? true : false
onProxyValueChanged: {
if (checked !== proxyValue) {
checked = proxyValue;
}
}
}

Your first version might also technically be a binding loop but is not detected as such.
You want to bind your checkbox value to your proxy value, and when the user toggles the checkbox you want to modify the proxy value.
To avoid binding loops, use specific interaction signals for each component, not generic onChanged signal. The former will fire only when the user interact with the UI, the latter will fire every time, even when the changes come from a backend change, leading to a potential binding loop.
In your case you want the toggled signal:
//MyCheckBox.qml
CheckBox {
required property QtObject proxy
checked: proxy.value
onToggled: proxy.value = checked
}
Note that this only works with QQC2 since they are not breaking the checked: proxy.value binding when the user toggles the checkbox, but still change the checked property value, hoping for an eventual resolution.

Related

Unexpected SwiftUI #State behavior

The setup:
My app has a View ToolBar (all shortened):
struct ToolBar: View {
#State private var ownerShare: CKShare?
#State private var show_ownerModifyShare = false
// …
The toolbar has some buttons that are created by functions. One of them is
func sharingButton(imageName: String, enabled: Bool) -> SharingButton {
return SharingButton(systemImageName: imageName, enabled: enabled) {
Task {
do {
(_, participantShare, ownerShare) = try await dataSource.getSharingInfo()
// …
if ownerShare != nil { show_ownerModifyShare = true }
} catch (let error) {
//...
}
}
}
}
This is the body:
var body: some View {
HStack {
// …
sharingButton(imageName: "square.and.arrow.up", enabled: currentMode == .displayingItems)
.fullScreenCover(isPresented: $show_ownerModifyShare) {
// A CKShare record exists in the iCloud private database. Present a controller that allows to modify it.
CloudSharingView(container: CKContainer(identifier: kICloudContainerID), shareRecord: ownerShare!, dataSource: dataSource)
}
}
}
}
The problem:
When the sharingButton is tapped, ownerShare is set in Task {…}, since the iCloud database is shared as an owner.
Accordingly, show_ownerModifyShare = true is executed, and thus the body of struct ToolBar is newly rendered.
However, CloudSharingView(container: CKContainer(identifier: kICloudContainerID), shareRecord: ownerShare!, dataSource: dataSource) crashes, because ownerShare is nil, although it is only set true after ownerShare has been set != nil.
My question:
What could be the reason, and how to correct the code?
EDIT: (due to the comment of jnpdx):
I replaced .fullScreenCover(isPresented: by
.fullScreenCover(item: $ownerShare) { _ in
CloudSharingView(container: CKContainer(identifier: kICloudContainerID), shareRecord: ownerShare!, dataSource: dataSource)
}
but the code still crashes with Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value when ownerShare! is used.
You should use the item form of fullScreenCover, which allows you to send a dynamically-changed parameter to the inner closure:
.fullScreenCover(item: $ownerShare) { share in
CloudSharingView(container: CKContainer(identifier: kICloudContainerID), shareRecord: share, dataSource: dataSource)
}
This is a common issue with sheet and the related functions in SwiftUI, which calculate their closures when first rendered and not at presentation time. See related:
SwiftUI: Switch .sheet on enum, does not work

qml component connects with signal

I have a component/delegate declared in qml and I would like to connect one o its actions to a signal in another qml or C++ class. So, the Component looks like this:
Component
{
id: mainButtonDelegate
Button {
id: buttonOperation
text: qsTr(buttonText)
buttonEnabled: false
onIsEnableChanged:
{
buttonEnabled = cppRegisteredClass.isButtonEnabled(text)
}
}
}
Can I create connection for the component (so for every button), like the following expressed in Qt?
connect(loginForm, SIGNAL(loginChanged()), this, SLOT(onIsEnableChanged()))
My model is a ListModel expressed in a qml file.
Edit
If this is not the correct approach could somebody suggest me another way to do sth like this?
You can use the Connections-object.
You expression would be translated to:
Connections {
target: loginForm
onLoginChanged: whatIsThis.onIsEnabledChanged()
}
Alternatively you can use the JS connect syntax:
http://doc.qt.io/qt-5/qtqml-syntax-signals.html#connecting-signals-to-methods-and-signals
You expression would translate to:
loginForm.loginChanged.connect(this.onIsEnabledChanged)
I am posting how I have done what I wanted, just if anyone else need this.
I have added a function in the model and used set property.
function action()
{
for(var i= 0; i<count; ++i)
{
if(backend.disable())
{
setProperty(i, "enable", false)
setProperty(i, "opac", 0.3)
}
else
{
setProperty(i, "enable", true)
setProperty(i, "opac", 1)
}
}
}
In the component (inside an Item parent), I have added:
LoginForm{
id: loginForm
onLoginIdentityChanged: {
mymodel.action()
}

QML can't hide Rectangle

I have a Window containing what I called a Page - a Rectangle the size of the main window:
LoginWindow {
id: loginWindow
}
SelectionWindow {
id: selectionWindow
}
ServiceWindow {
id: serviceWindow
}
ConfirmWindow {
id: confirmWindow
}
IssueWindow {
id: issueWindow
}
Each *Window here is an element inheriting from Page.
Now I can jump between different Page elements setting their visibility. Only one Page can be visible at a time. The last Page is IssueWindow and from there I want to switch back to LoginWindow and logout the user.
From C++ I emit a signal issueFinished and in MainWindow I have this code:
onIssueFinished: {
// This line won't hide the Page
issueWindow.visible = false;
loginHandle.logout();
}
As noted in the comment, the Page element just won't hide, while other elements will change their visibility. I am using GammaRay to see the changes and everything works, just hiding the issueWindow doesn't. Also when printing the issueWindow.visible to console, it's false.
Use opacity instead of visibility, and bind one method on opacity change which handle the enabled and disabled state like.
onIssueFinished: {
// This line won't hide the Page
issueWindow.opacity = 0.0;
loginHandle.logout();
}
IssueWindow {
id: issueWindow
onOpacityChanged: {
enabled = ( opacity === 1.0)
}
}
You need to implement opacity change in all the windows.
And set the opacity for all other windows.
Or you can use stack view or swipe view.
Found out the issue was actually incorrect order of switching visibility and firing signals and responding to them.
To make sure only one Page is visible, you might use an index.
You set the visibility to:
visible: (selected == thisPageIdentifier)
With this you make sure, only the page with the corresponding pageIdentifyer is visible, and you don't have to throw around multiple signals to show and hide.
When a new page shall become visible, just set the selected property to the corresponding identifier (int or string or what ever) and the other pages turn invisible.
This should increase scalability as you do not have new signals to handle in all the other pages, if you decide to add a new page once. You only need to make sure, the identifier is unique.
Your example code adjusted to this:
Item {
property int selection: 0
LoginWindow {
id: loginWindow
visible: (selection === 0)
}
SelectionWindow {
id: selectionWindow
visible: (selection === 1)
}
ServiceWindow {
id: serviceWindow
visible: (selection === 2)
}
ConfirmWindow {
id: confirmWindow
visible: (selection === 3)
}
IssueWindow {
id: issueWindow
visible: (selection === 4)
}
}

Ionic2 SearchBar confusion, onInput called after firing onClear, onCancel

I have been following the tutorial at ionic2 SearchBar to work on the filter functionality.
The question is, I am not able to figure out how to get onCancel and onClear to work.
Steps:
1) Enter some keywords in SearchBar. It calls the onInput event and I get the value from searchItem.target.value unlike in tutorial which just uses searchItem.value
2) Now i try to click on clear "x" or Cancel button and it calls the onClear/onCancel event which is immediately followed by onInput event. And during this call, it does not find the searchItem.target.value and results in undefined due to which it breaks the functionality.
Can anyone provide more in depth details on how this works?
i fixed it in tutorial sample for ionic2 by stopping onClear event propagation
import {Component} from '#angular/core';
#Component({
selector: 'my-search',
template: '<ion-toolbar primary><ion-searchbar (input)="onInput($event)" (ionClear)="onClear($event)"></ion-searchbar></ion-toolbar><ion-content><ion-list><ion-item *ngFor="let item of items">{{ item }}</ion-item></ion-list></ion-content>'
})
export class HomePage {
items = [];
constructor() {
this.initializeItems();
}
initializeItems() {
this.items = [
'Angular 1.x',
'Angular 2',
'ReactJS',
'EmberJS',
'Meteor',
'Typescript',
'Dart',
'CoffeeScript'
];
}
onClear(ev)
{
this.initializeItems();
ev.stopPropagation();
}
onInput(ev) {
// Reset items back to all of the items
this.initializeItems();
// set val to the value of the searchbar
var val = ev.target.value;
// if the value is an empty string don't filter the items
if (val && val.trim() != '') {
this.items = this.items.filter((item) => {
return (item.toLowerCase().indexOf(val.toLowerCase()) > -1);
})
}
}
}

How do I access the parent item from a custom field in the Sitecore Content Editor?

Normally, when I want to access the current Item in Sitecore, I go through Sitecore.Context.Item. This works well when creating a tool which will be used by the end user, but not for a tool which is consumed by the Sitecore admins. If I want something to show up as a custom field in the Content Editor itself, the Context.Item is a reference to the Content Editor and not to the node which is selected in the editor.
For the most part I can get around this by using the ItemID property, but if I have event dispatchers on the field, those will no longer have access to the ItemID. Eg:
protected override void OnPreRender(EventArgs e)
{
if (IsEvent)
{
// ItemID is defined here!
Button live = FindControl(GetID(LiveButton)) as Button;
if (live != null)
{
live.ServerProperties["Click"] = string.Format("{0}.LiveClicked", ID);
}
}
}
public void LiveClicked()
{
// ItemID is blank here!
DoSomething();
}
How do I gain access to ItemID in my listener (like LiveClicked above)?
The way I solved it was through something called ServerProperties and I called the equivalent to this function in every listener.
private void SyncID()
{
var live = FindControl(GetID(LiveButton)) as Button;
if (live != null)
{
if(string.IsNullOrEmpty(ItemID))
{
ItemID = live.ServerProperties["owner_id"].ToString();
}
live.ServerProperties["owner_id"] = ItemID;
}
}