I am trying to implement a keyClick with a shift modifier but it doesn't work. Below is a basic setup of what I am trying to do. The first test_case1 can perform what I am doing but I'd like the second test_case2 to work as well but with using the Qt.ShiftModifier.
../MyTextBox.qml
Page {
id: page1
objectName: "page1"
TextField {
id: lastNameField
objectName: "lastNameField"
text: qsTr("")
}
}
tst_page.qml
import "../"
Item {
width: 800
height: 600
MyTextBox {
id: page1
}
TestCase {
id: "txtBox"
when: windowShown
function test_case1 () {
//var qmlObj = findChild(page1, "lastNameField")
var qmlObj = page1.lastNameField
// Bring to focus
mouseClick(qmlObj, Qt.LeftButton, Qt.NoModifier)
// Keypress
keyPress("Y")
keyPress("e")
keyPress("s")
tryCompare(qmlObj, "text", "Yes") // pass
}
function test_case2 () {
var qmlObj = page1.lastNameField
// Bring to focus
mouseClick(qmlObj, Qt.LeftButton, Qt.NoModifier)
// Keypress
keyClick(QT.Key_Y, Qt.ShiftModifier)
keyClick(QT.Key_E)
keyClick(QT.Key_S)
tryCompare(qmlObj, "text", "Yes") // fail
}
}
}
test output
PASS : test_case1()
FAIL! : test_case2()
Actual (): yes
Expected (): Yes
Edit: Added a simple project to github for testing.
Related
I am creating a Form in SwiftUi with a section that is including a flexible number of instruction.
Next to the last instruction TextField, I am showing a "+"-Button that is extending the instructions array with a new member:
var body: some View {
NavigationView {
Form {
...
Section(header: Text("Instructions")) {
InstructionsSectionView(instructions: $recipeViewModel.recipe.instructions)
}
...
struct InstructionsSectionView: View {
#Binding var instructions: [String]
var body: some View {
ForEach(instructions.indices, id: \.self) { index in
HStack {
TextField("Instruction", text: $instructions[index])
if(index == instructions.count-1) {
addInstructionButton
}
}
}
}
var addInstructionButton: some View {
Button(action: {
instructions.append("")
}) {
Image(systemName: "plus.circle.fill")
}
}
}
Now the problem is, that the button click-area is not limited to the picture but to the whole last row. Precisely the part just around the textField, meaning if I click in it, I can edit the text, but if I click on the border somewhere, a new entry is added.
I assume that this is specific to Form {} (or also List{}), since it does not happen if I use a Button next to a text field in a "normal" set-up.
Is there something wrong with my code? Is this an expected behaviour?
I am not sure why border is getting tappable, but as a workaround I used plainButtonStyle and that seems to fix this issue, and keeps functionality intact .
struct TestView: View {
#State private var endAmount: CGFloat = 0
#State private var recipeViewModel = ["abc","Deef"]
var body: some View {
NavigationView {
Form {
Section(header: Text("Instructions")) {
InstructionsSectionView(instructions: $recipeViewModel)
}
}
}
}
}
struct InstructionsSectionView: View {
#Binding var instructions: [String]
var body: some View {
ForEach(instructions.indices, id: \.self) { index in
HStack {
TextField("Instruction", text: $instructions[index])
Spacer()
if(index == instructions.count-1) {
addInstructionButton
.buttonStyle(PlainButtonStyle())
.foregroundColor(.blue)
}
}
}
}
var addInstructionButton: some View {
Button(action: {
instructions.append("")
}) {
Image(systemName: "plus.circle.fill")
}
}
}
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!
I have:
MyItem.qml
import QtQuick 2.0
Item {
property string info1: "info"
property int info2: 1
}
How to add an item to an array qml during initialization?
It's work:
import QtQuick 2.0
Item {
property var arr: [{ info1: "test", info2: 1}, { info1: "info" }]
}
But, it's not work:
import QtQuick 2.0
Item {
property var arr: [MyItem { info1: "test", info2: 1}, MyItem { info1: "info" }]
}
If you use a newer QML version you can use the new list<Item>-type for properties. There you can easily add Items in the syntax you want - just like when you were using a property alias someProperty: someItem.somePropertyList (e.g. children) for example.
include QtQuick 2.10 // I think 2.9 is sufficient
ApplicationWindow {
...
property list<Item> myItemList: [
Item { Component.onCreated: console.log(parent) }, // parent will be null! Set it explicitly if needed.
Item {},
Item {},
...
]
}
http://doc.qt.io/qt-5/qml-list.html
Small side note from the documentation:
Note: The list type is not recommended as a type for custom properties. The var type should be used instead for this purpose as lists stored by the var type can be manipulated with greater flexibility from within QML.
IMHO, you can ignore that as long as you don't need the "greater flexibility"
In older versions this is not possible but you could hack your way around, if you have a type "ArrayObject.qml"
import QtQuick 2.0
QtObject {
id: root
default property QtObject store
readonly property var arr: (arr === undefined ? [] : arr) // This will give you a binding loop, but ensures that after filling the array, it is not reset to [].
onStoreChanged: {
if (store) {
console.log(store)
arr[arr.length] = store
console.log(arr, arr.length)
}
store = null
}
}
You can then use it:
ArrayObject {
id: tst
Item {
}
Item {
}
Item {
}
}
Of course, there might be other workarounds.
E.g:
Item {
property var myArray: [
itemId1, itemId2, itemId3, itemId4
]
Item { id: itemId1 }
...
}
You can try to use Dynamic QML Object Creation:
import QtQuick 2.0
Item {
property var arr: {
component = Qt.createComponent("MyItem.qml");
item1 = component.createObject(null, {"info1": "test", "info2": 1});
item2 = component.createObject(null, {"info1": "info"});
return [item1, item2];
}
}
See Qt.createComponent and createObject for more information.
I have a modal window in Angular 4 that works fine but if the user clicks on the background / parent page the modal is closed.
I have found some solutions that suggest using backdrop='static' and keyboard=false when opening the modal but our modal uses a local Dialog class with a BehaviorSubject object so is opened using the .next method. I've also tried setting these attributes using div config but to no avail.
Therefore I'm looking for another solution, maybe using CSS or another setting / attribute that can be directly applied to the parent page or modal HTML.
See below for some of the relevant code.
dialog.component.ts:
constructor(private location: PlatformLocation,
private _dialog: DialogService,
private router: Router) { }
open() {
this.showDialog = true;
const body = document.body;
body.classList.add('cell-modal-open');
}
close() {
this.dialog = undefined;
}
private handleDialog(d: Dialog) {
if (!d) {
this.close();
} else if (d.template) {
if (this.showDialog) {
this.close();
}
this.dialog = d;
this.open();
}
}
ngOnInit() {
this.subscription = this
._dialog
.getDialog()
.subscribe({
next: (d) => { this.handleDialog(d); console.log('subscribed dialog') },
error: (err) => this.handleDialogError(err)
});
this.initialiseRoutingEventListeners();
}
dialog.service.ts
private d: Dialog = { template: null, size: DialogSizeEnum.XLarge };
private dialogSubject = new BehaviorSubject<Dialog>({ template: null, size: DialogSizeEnum.XLarge });
constructor() { }
showDialog(template: TemplateRef<any>, size = DialogSizeEnum.XLarge, requiresAction = false) {
Object.assign(this.d, { template: template, size: size, requiresAction: requiresAction });
if (this.d !== null) {
this.dialogSubject.next(this.d);
}
}
getDialog(): BehaviorSubject<Dialog> {
return this.dialogSubject;
}
clear() {
this.dialogSubject.next(null);
}
Any suggested approaches are welcome!
Added flag to the close() method and adding condition to only set to undefined if true (i.e. from a valid location).
I'm using this example: QML Flipable Example.
I created rectangles with GridLayout. I just added my new state the example:
states: [
State {
name: 'back'
PropertyChanges { target: rotation; angle: 180 }
when: flipable.flipped
},
State {
name: 'remove'
PropertyChanges {
target: card
visible: false
}
}
]
I wanted when I click the rectangles, check rectangles, they open and if they are same or not. My algorithm for this job:
property int card1: -1
property int card2: -1
property int remaining: 20
function cardClicked(index) {
var card = repeater.itemAt(index); // Selected card
if (!card.flipped) { // If selected card is not opened
card.flipped = true; // Open card
if (card1 === -1) { // If card is first selected card
card1 = index; // Set first selected card
} else { // If selected card is not first card, I mean that is second card because first card is already selected
card2 = index; // Set second card
area.enabled = false; // Disabled GridLayout (area)
delayTimer.start(); // Start `Timer` QML component
}
} else { // If card is opened so, close that card, because that card is opened and player clicked its
card.flipped = false; // Close that card
card1 = -1; // first card is not selected
}
}
function validateCards() {
var state = ''; // Default state: Close cards
if (imageIndexes[card1] === imageIndexes[card2]) { // If first card and second card are equal, you found same cards :)
state = 'remove'; // You found cards so, remove cards
--remaining; // If this equals 0, you found all cards
}
// If cards are not same, close cards but if they are same remove them
repeater.itemAt(card1).state = state;
repeater.itemAt(card2).state = state;
card1 = -1; // first card is not selected
card2 = -1; // second card is not selected
area.enabled = true; // Enabled area (GridLayout)
if (remaining === 0) { // If you found all cards, game over
console.log('Game Over!');
}
}
I added MouseArea into Rectangles:
MouseArea {
anchors.fill: parent
onClicked: cardClicked(index) // This index belongs to my `Repeater`.
}
I put a Timer for animation to work correctly and check cards:
Timer {
id: delayTimer
interval: 1000
onTriggered: validateCards()
}
This example and animations are running nice but sometimes they aren't running correctly:
How can I solve this animation bug?
UPDATE!
You can find all source code on here.
I think you are complicating the application by using the timer since you do not know if the item finished changing its position, it would be appropriate to execute that task when it finishes turning the letter over.
Another error that I see in your code is that you are assigning a state = "".
I have modified in the following parts:
GameArea.qml
GridLayout {
[...]
property variant imageIndexes: GameUtils.generateCardIndexes(
imageCount, repeatCount)
property int lastIndex : -1
Repeater {
id: repeater
model: 40
Card {
id: card
backImageSource: 'qrc:/images/img_' + area.imageIndexes[index] + '.jpg'
onFinished: verify(index)
}
}
function verify(index){
if(lastIndex == -1){
lastIndex = index
return
}
area.enabled = false
var lastItem = repeater.itemAt(lastIndex)
var currentItem = repeater.itemAt(index)
if(lastItem.backImageSource === currentItem.backImageSource){
lastItem.state = "remove"
currentItem.state = "remove"
}
else{
lastItem.flipped = false
currentItem.flipped = false
}
if(repeater.model === 0){
console.log("Winning")
}
lastIndex = -1
area.enabled = true
}
}
Card.qml
Item {
[...]
property alias state: flipable.state
signal finished()
Flipable {
[...]
transitions: Transition {
NumberAnimation { target: rotation; property: 'angle'; duration: 400 }
onRunningChanged: {
if ((state == "back") && (!running))
finished()
}
}
MouseArea {
anchors.fill: parent
onClicked: card.flipped = true
}
}
}
The complete code can be found at the following link