I have the following QML code:
ListView {
model: ListModel {
ListElement {
eleText: "Me"
eleFirst: true
eleSecond: true
}
ListElement {
eleText: "My GF"
eleFirst: false
eleSecond: false
}
ListElement {
eleText: "Ben Dover"
eleFirst: true
eleSecond: false
}
}
delegate: Row {
visible: true
Label: eleText
}
}
I intend to have a complex logic to control the visibility of each row on my ListView. I mean, to set the visible: field, I like to have this complex logic:
if( someObject.getStatus() == "first" ) {
if (eleFirst) {
return true;
} else {
return false;
} else if ( someObject.getStatus() == "second" ) {
if (eleSecond) {
return true;
} else {
return false;
}
} else {
// Should not get here
// throw errors
}
How can I use such a complex logic to control the visibility of my ListView rows?
I have to use Qt 5.9.4
If you want a complex model, subclass the QAbstractListModel. There you can define your own role for visibility and whatever logic you need. Alternatively shorten your logic to a ternary operator.
Related
How do I toggle the presence of a button to be hidden or not?
We have the non-conditional .hidden() property; but I need the conditional version.
Note: we do have the .disabled(bool) property available, but not the .hidden(bool).
struct ContentView: View {
var body: some View {
ZStack {
Color("SkyBlue")
VStack {
Button("Detect") {
self.imageDetectionVM.detect(self.selectedImage)
}
.padding()
.background(Color.orange)
.foreggroundColor(Color.white)
.cornerRadius(10)
.hidden() // ...I want this to be toggled.
}
}
}
}
I hope hidden modifier gets argument later, but since then, Set the alpha instead:
#State var shouldHide = false
var body: some View {
Button("Button") { self.shouldHide = true }
.opacity(shouldHide ? 0 : 1)
}
For me it worked perfectly to set the frame's height to zero when you do not want to see it. When you want to have the calculated size, just set it to nil:
SomeView
.frame(height: isVisible ? nil : 0)
If you want to disable it in addition to hiding it, you could set .disabled with the toggled boolean.
SomeView
.frame(height: isVisible ? nil : 0)
.disabled(!isVisible)
You can utilize SwiftUI's new two-way bindings and add an if-statement as:
struct ContentView: View {
#State var shouldHide = false
var body: some View {
ZStack {
Color("SkyBlue")
VStack {
if !self.$shouldHide.wrappedValue {
Button("Detect") {
self.imageDetectionVM.detect(self.selectedImage)
}
.padding()
.background(Color.orange)
.foregroundColor(Color.white)
.cornerRadius(10)
}
}
}
}
}
The benefit of doing this over setting the opacity to 0 is that it will remove the weird spacing/padding from your UI caused from the button still being in the view, just not visible (if the button is between other view components, that is).
all the answers here works specifically for a button to be hidden conditionally.
What i think might help is making a modifier itself conditionally e.g:
.hidden for button/view, or maybe .italic for text, etc..
Using extensions.
For text to be conditionally italic it is easy since .italic modifier returns Text:
extension Text {
func italicConditionally(isItalic: Bool) -> Text {
isItalic ? self.italic() : self
}
}
then applying conditional italic like this:
#State private var toggle = false
Text("My Text")
.italicConditionally(isItalic: toggle)
However for Button it is tricky, since the .hidden modifier returns "some view":
extension View {
func hiddenConditionally(isHidden: Bool) -> some View {
isHidden ? AnyView(self.hidden()) : AnyView(self)
}
}
then applying conditional hidden like this:
#State private var toggle = false
Button("myButton", action: myAction)
.hiddenConditionally(isHidden: toggle)
You can easily hide a view in SwiftUI using a conditional statement.
struct TestView: View{
#State private var isVisible = false
var body: some View{
if !isVisible {
HStack{
Button(action: {
isVisible.toggle()
// after click you'r view will be hidden
}){
Text("any view")
}
}
}
}
}
It isn't always going to be a pretty solution, but in some cases, adding it conditionally may also work:
if shouldShowMyButton {
Button(action: {
self.imageDetectionVM.detect(self.selectedImage)
}) {
Text("Button")
}
}
There will be an issue of the empty space in the case when it isn't being shown, which may be more or less of an issue depending on the specific layout. That might be addressed by adding an else statement that alternatively adds an equivalently sized blank space.
#State private var isHidden = true
VStack / HStack
if isHidden {
Button {
if !loadVideo(),
let urlStr = drill?.videoURL as? String,
let url = URL(string: urlStr) {
player = VideoPlayerView(player: AVPlayer(), videoUrl: url)
playVideo.toggle()
}
} label: {
Image(playVideo ? "ic_close_blue" : "ic_video_attached")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 50)
}
.buttonStyle(BorderlessButtonStyle())
}
.onAppear {
if shouldShowButton {
isHidden = false
} else {
isVideoButtonHidden = true
}
}
I have a ListView, where the currently displayed modelData changes as a button cycles through several department options. If one of these departments has no data, my delegate continues showing the previous list data until it reaches a new section of modelData with data.
What I want to do, is when the model is 'empty' (being undefined, which may happen when the key it's looking for is yet to be created in my Firebase Database, or no items are currently visible), text/image is shown instead; i.e, "Move along now, nothing to see here".
My model is drawn from JSON, an example is below. and my calendarUserItems is the root node of multiple children within my Firebase Database, the aim of my AppButton.groupCycle was too add a further direction to each child node, filtering the data by this to view and edit within the page.
A sample of my code is:
Page {
id: adminPage
property var departments: [1,2,3,4]
property int currGroupIndex: 0
AppButton {
id: groupCycle
text: "Viewing: " + departments[currGroupIndex]
onClicked: {
if (currGroupIndex == departments.length - 1)
currGroupIndex = 0;
else
currGroupIndex++;
}
}
ListView {
model: Object.keys(dataModel.calendarUserItems[departments[currGroupIndex]])
delegate: modelData.visible ? currentGroupList : emptyHol
Component {
id: emptyHol
AppText {
text: "nothing to see here move along now!"
}
}
Component {
id: currentGroupList
SimpleRow {
id: container
readonly property var calendarUserItem: dataModel.calendarUserItems[departments[currGroupIndex]][modelData] || {}
visible: container.calendarUserItem.status === "pending" ? true : false
// only pending items visible
// remaining code for simple row
}
}
}
}
an example of JSON within my dataModel.calendarUserItems is:
"groupName": [
{ "department1":
{ "1555111624727" : {
"creationDate" : 1555111624727,
"date" : "2019-03-15T12:00:00.000",
"name" : "Edward Lawrence",
"status": "pending"
},
//several of these entries within department1
},
},
{ "department2":
{ "1555111624727" : {
"creationDate" : 1555111624456,
"date" : "2019-05-1T12:00:00.000",
"name" : "Katie P",
"status": 1
},
//several of these entries within department2
},
}
//departments 3 & 4 as the same
]
If departments 2 and 3 have modelData, yet 1 and 4 do not, I want the text to display instead, and the ListView emptied, instead of showing the previous modelData.
I have tried playing with the image/text visibility but the issue lays more with clearing the modelData and I'm unsure where to begin?
Any help is greatly appreciated!
I have achieved the display by using the following as my delegate:
delegate: {
if (!(departments[currGroupIndex] in dataModel.calendarUserItems) ) {
return emptyHol;
}
var subgroups = Object.keys(dataModel.calendarUserItems[departments[currGroupIndex]]);
for (var i in subgroups) {
var subgroup = dataModel.calendarUserItems[departments[currGroupIndex]][subgroups[i]];
modelArr.push(subgroup);
}
var modelObect = modelArr.find( function(obj) { return obj.status === "pending"; } );
if (modelObect === undefined) {
return emptyHol;
}
return currentGroupList;
}
Then when my AppButton.groupCycle is pressed, I have added modelArr = [] to clear the array on each press, this works as intended.
Thanks!
Is there a way to hide the language selection key from the virtual keyboard without use a custom layout?
I was able to hide the language key with a workaround:
property var keyboardLayout: inputPanel.keyboard.layout
function findChildByProperty(parent, propertyName, propertyValue, compareCb) {
var obj = null
if (parent === null)
return null
var children = parent.children
for (var i = 0; i < children.length; i++) {
obj = children[i]
if (obj.hasOwnProperty(propertyName)) {
if (compareCb !== null) {
if (compareCb(obj[propertyName], propertyValue))
break
} else if (obj[propertyName] === propertyValue) {
break
}
}
obj = findChildByProperty(obj, propertyName, propertyValue, compareCb)
if (obj)
break
}
return obj
}
onKeyboardLayoutChanged: {
if(keyboardLayout!=""){
var ChangeLanguageKey= findChildByProperty(inputPanel.keyboard, "objectName", "changeLanguageKey", null)
if(ChangeLanguageKey){
ChangeLanguageKey.visible=false
}
}
}
InputPanel {
id: inputPanel
z: 99
y: parent.height
anchors.left: parent.left
anchors.right: parent.right
states: State {
name: "visible"
when: inputPanel.active
PropertyChanges {
target: inputPanel
y: parent.height - inputPanel.height
}
}
transitions: Transition {
from: ""
to: "visible"
reversible: true
ParallelAnimation {
NumberAnimation {
properties: "y"
duration: 400
easing.type: Easing.InOutBack
}
}
}
CustomComponents.AutoScroller {
id:autoscroller
panelY: inputPanel.y
}
}
This only works in version 5.9 where the objectname property is defined with "changeLanguageKey", for previous versions set the property in the source code and recompile.
No, not without using a custom layout.
You can always modify the layouts that come with the keyboard though.
I was able to hide the hideKeyboard key with this trick. I basically tried to get the reference of the emoji key and thereby was able to disable the next key which is hideKeyboard key.
function disableKey(parent, objectText)
{
var obj = null
if (parent === null)
return null
var children = parent.children
for (var i = 0; i < children.length; i++) {
obj = children[i]
if (obj.text === objectText && obj.toString().substring(0, 7) === "BaseKey") {
console.log("Disabling symbols. " + obj.text)
obj.enabled = false
}
else if(obj.displayText === "HWR"){
console.log("Disabling Handwriting mode button." + obj.displayText + " " + objectText)
obj.visible = false
}
else if(obj.text === ":-)" && obj.toString().substring(0, 7) === "BaseKey"){
console.log("Disabling hidekeyboard key." + obj.text)
children[i+1].visible = false
}
obj = disableKey(obj, objectText)
if (obj)
break
}
return obj
}
I have a treeview from which I drag items to a drop area by creating draggable items dynamically. Everything works except I cannot pass mimedata
function createItem(parentItem)
{
if (itemComponent.status === Component.Ready && draggedItem == null)
{
draggedItem = itemComponent.createObject(
parentItem,
{
"x": positionInParent.x,
"y": positionInParent.y,
"baseColor":Qt.lighter("red", 1.5),
"copyable":false,
visible: true
}
);
draggedItem.Drag.mimeData = { "text/plain": "Hello!" };
draggedItem.Drag.supportedActions = Qt.CopyAction;
draggedItem.Drag.dragType = Drag.Automatic;
draggedItem.Drag.start();
}
else if (itemComponent.status === Component.Error) {
draggedItem = null;
console.log("error creating component");
console.log(itemComponent.errorString());
}
}
when I try to get the mime data from drop event (drop.text field) it is empty.
DropArea
{
height:parent.height
width:parent.width
onDropped: {
drop.acceptProposedAction()
var txt = drop.text; // Empty!!!
console.log("dropped into dest")
}
onEntered: {
console.log("entered dest")
}
}
Resolved it myself! Just to add
draggedItem.Drag.active = true;
I've been learning QtQuick for about a week and I'm facing a weird behaviour on what I'm trying to achieve. I would like to make a vertical ListView with a Keyboard navigation so that when I press UP or DOWN, the items move up or down and if an item goes in or out of the "viewport", its opacity property will change smoothly to 0 or 1.
Here is my current QML code:
import QtQuick 2.4
Rectangle {
width:200
height:400
ListView {
property int activePosition:1
property int itemDisplayed:3
width:parent.width-50
height:parent.height-50
anchors.centerIn:parent
model:10
snapMode:ListView.SnapToItem
focus:true
cacheBuffer:2000
Component.onCompleted: {
console.log(count+' != '+contentItem.children.length+' ???')
}
Keys.onPressed: {
var i = 0
console.log('pos='+activePosition)
console.log(count+' != '+contentItem.children.length+' ???')
if (event.key === Qt.Key_Up) {
if (activePosition == 1 && currentIndex > 0) {
i = currentIndex+itemDisplayed-1
if (i < contentItem.children.length - 2/* why -2 instead of -1 ??? */) {
console.log('out='+i)
contentItem.children[i].state = 'out'
}
}
activePosition = activePosition > 1 ? activePosition - 1 : activePosition
}
if (event.key === Qt.Key_Down) {
if (activePosition == itemDisplayed && currentIndex < contentItem.children.length - 2) {
i = currentIndex-itemDisplayed+1
if (i >= 0) {
console.log('out='+i)
contentItem.children[i].state = 'out'
}
}
activePosition = activePosition < itemDisplayed ? activePosition + 1 : activePosition
}
}
delegate: Rectangle {
id:rect
state:index < ListView.view.itemDisplayed ? 'in' : 'out'
opacity:1.0
width:ListView.view.width
height:ListView.view.height/ListView.view.itemDisplayed
border.color:'white'
border.width:1
color:activeFocus ? 'red': 'gray'
onActiveFocusChanged: {
if (activeFocus) {
state = 'in'
console.log('in='+index)
}
}
states: [
State { name:'in'; PropertyChanges { target:rect; opacity:1.0 } },
State { name:'out'; PropertyChanges { target:rect; opacity:0.0 } }
]
transitions: [
Transition {
to:'in'
NumberAnimation { property:'opacity'; duration:250 }
},
Transition {
to:'out'
NumberAnimation { property:'opacity'; duration:250 }
}
]
Text {
text:index
anchors.centerIn:parent
}
}
}
}
First question : model=10, why model.count is not equal to contentItem.children.length? onCompleted gives 5 vs 11 and during navigation 10 vs 11
Second question: If I press UP or DOWN, it works fine until I reach index=4, why?
As I'm a beginner on QtQuick so maybe it's not the right approach. I tried to use the visible property but every item has visible = true even if they are outside. I tried also indexAt() with no success.
Any help would be great :-)
Now I know better about the ListView behavior. My previous code can be fixed by removing the Keys.onPressed event which is no longer useful, and by using the itemAt() method directly into the onActiveFocusChanged handler.