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;
Related
I'm not so familiar with SwfitUI.
I found this helper to achieve this tag view:
But making it selectable is pain & for everything I did, I got many errors...
How can I make this thing work!?
Here is my complete class:
import SwiftUI
struct TagViewItem: Hashable {
var title: String
var isSelected: Bool
static func == (lhs: TagViewItem, rhs: TagViewItem) -> Bool {
return lhs.isSelected == rhs.isSelected
}
func hash(into hasher: inout Hasher) {
hasher.combine(title)
hasher.combine(isSelected)
}
}
struct TagView: View {
#State var tags: [TagViewItem]
#State private var totalHeight = CGFloat.zero // << variant for ScrollView/List // = CGFloat.infinity // << variant for VStack
var body: some View {
VStack {
GeometryReader { geometry in
self.generateContent(in: geometry)
}
}
.frame(height: totalHeight)// << variant for ScrollView/List
//.frame(maxHeight: totalHeight) // << variant for VStack
}
private func generateContent(in g: GeometryProxy) -> some View {
var width = CGFloat.zero
var height = CGFloat.zero
return ZStack(alignment: .topLeading) {
ForEach(tags.indices) { index in
item(for: tags[index].title, isSelected: &tags[index].isSelected)
.padding([.horizontal, .vertical], 4)
.alignmentGuide(.leading, computeValue: { d in
if (abs(width - d.width) > g.size.width) {
width = 0
height -= d.height
}
let result = width
if tag == self.tags.last! {
width = 0 //last item
} else {
width -= d.width
}
return result
})
.alignmentGuide(.top, computeValue: {d in
let result = height
if tag == self.tags.last! {
height = 0 // last item
}
return result
})
}
}.background(viewHeightReader($totalHeight))
}
private func item(for text: String, isSelected: inout Bool) -> some View {
Text(text)
.foregroundColor(isSelected ? Colors.primaryBarBackground : Colors.textColor)
.padding()
.lineLimit(1)
.background(isSelected ? Colors.primaryBlue : Colors.primaryBarBackground)
.frame(height: 36)
.cornerRadius(18)
.overlay(Capsule().stroke(Colors.primaryBlue, lineWidth: 4))
.onTapGesture {
isSelected.toggle()
}
}
private func viewHeightReader(_ binding: Binding<CGFloat>) -> some View {
return GeometryReader { geometry -> Color in
let rect = geometry.frame(in: .local)
DispatchQueue.main.async {
binding.wrappedValue = rect.size.height
}
return .clear
}
}
}
Finally, after wrestling with it a lot, it is the working version:
struct TagView: View {
#State var tags: [TagViewItem]
#State private var totalHeight = CGFloat.zero // << variant for ScrollView/List // = CGFloat.infinity // << variant for VStack
var body: some View {
VStack {
GeometryReader { geometry in
self.generateContent(in: geometry)
}
}
.frame(height: totalHeight)// << variant for ScrollView/List
//.frame(maxHeight: totalHeight) // << variant for VStack
}
private func generateContent(in g: GeometryProxy) -> some View {
var width = CGFloat.zero
var height = CGFloat.zero
return ZStack(alignment: .topLeading) {
ForEach(tags.indices) { index in
item(for: tags[index].title, isSelected: tags[index].isSelected)
.padding([.horizontal, .vertical], 4)
.alignmentGuide(.leading, computeValue: { d in
if (abs(width - d.width) > g.size.width) {
width = 0
height -= d.height
}
let result = width
if tags[index].title == self.tags.last!.title {
width = 0 //last item
} else {
width -= d.width
}
return result
})
.alignmentGuide(.top, computeValue: {d in
let result = height
if tags[index].title == self.tags.last!.title {
height = 0 // last item
}
return result
}).onTapGesture {
tags[index].isSelected.toggle()
}
}
}.background(viewHeightReader($totalHeight))
}
private func item(for text: String, isSelected: Bool) -> some View {
Text(text)
.foregroundColor(isSelected ? Colors.primaryBarBackground : Colors.textColor)
.padding()
.lineLimit(1)
.background(isSelected ? Colors.primaryBlue : Colors.primaryBarBackground)
.frame(height: 36)
.cornerRadius(18)
.overlay(Capsule().stroke(Colors.primaryBlue, lineWidth: 1))
}
private func viewHeightReader(_ binding: Binding<CGFloat>) -> some View {
return GeometryReader { geometry -> Color in
let rect = geometry.frame(in: .local)
DispatchQueue.main.async {
binding.wrappedValue = rect.size.height
}
return .clear
}
}
}
Usage:
TagView(tags: [TagViewItem(title: "ff", isSelected: false), TagViewItem(title: "yyhuuuh", isSelected: false), TagViewItem(title: "kjhgdtfyughuihu", isSelected: true), TagViewItem(title: "nbyvyvuyv", isSelected: false)])
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.
I have this working Plunker Example, you can drag the mouse and select the cards.
My problem is that the code has alot of bugs and I want to do
something similar to this Demo
Here something that I want to fix (img1 and img2 - the cards in img #2 were clicked before), another thing is that if you drag the mouse fast sometimes the cards will not be select.
Here is my component code
export class App {
private dragStart:number = 0;
private dragOver:number = 0;
public users:Array<{id?: number; name: string; admin: boolean;}> = [
{ name: 'Alexis Wursten', admin: false },
{ name: 'Janco Boscan', admin: true },
{ name: 'Noemi Iturralde', admin: false },
];
public added: string[] = [];
x1 = 0; y1 = 0; x2 = 0; y2 = 0;
#ViewChild('selector') selector: ElementRef;
constructor(private renderer: Renderer2) {
}
isRectangeVisible = false;
isMouseDown = false;
#HostListener('mousedown', ['$event'])
onMouseDown(ev) {
this.dragStart = ev.clientY;
this.isMouseDown = true;
}
#HostListener('document:mouseup', ['$event'])
onMouseUp(ev) {
this.dragStart = 0;
this.dragOver = 0;
this.renderer.setStyle(this.selector.nativeElement, 'display', 'none');
this.isRectangeVisible = false;
this.isMouseDown = false;
}
#HostListener('document:mousemove', ['$event'])
onMouseMove(ev) {
if(!this.isRectangeVisible && this.isMouseDown){
this.renderer.setStyle(this.selector.nativeElement, 'display', 'block');
this.x1 = ev.clientX;
this.y1 = ev.clientY;
this.isRectangeVisible = true;
}
this.x2 = ev.clientX;
this.y2 = ev.clientY;
this.reCalc();
}
reCalc() {
const x3 = Math.min(this.x1, this.x2);
const x4 = Math.max(this.x1, this.x2);
const y3 = Math.min(this.y1, this.y2);
const y4 = Math.max(this.y1, this.y2);
this.renderer.setStyle(this.selector.nativeElement, 'left', x3 + 'px');
this.renderer.setStyle(this.selector.nativeElement, 'top', y3 + 'px');
this.renderer.setStyle(this.selector.nativeElement, 'width', x4 - x3 + 'px');
this.renderer.setStyle(this.selector.nativeElement, 'height', y4 - y3 + 'px');
}
onSelecUser(item) {
if(this.added.indexOf(item.name)===-1) { // or compare by id
this.added = this.added.concat([item.name]);
}
else {
this.added = this.added.filter((x) => item.name!==x); // or compare by id
}
item.selected = !item.selected ? true : false;
}
onMouseOver(ev, item) {
if(ev.which!==1) {
return false;
}
ev.preventDefault();
if(ev.type==='mouseenter' && !item.selected) {
this.dragOver = ev.clientY - this.dragStart > 0 ? 1:-1;
this.onSelecUser(item);
return false;
}
if(ev.type==='mouseleave') {
if(this.dragOver===1 && ev.clientY < ev.target.offsetTop && item.selected) {
console.log('desel...', item);
this.onSelecUser(item);
return false;
}
if(this.dragOver===-1 && ev.clientY > ev.target.offsetTop && item.selected) {
console.log('desel...', item);
this.onSelecUser(item);
return false;
}
}
}
}
Thanks for read.
UPDATE #1:
https://plnkr.co/edit/d9aTb0E0OKFfTSIAM0MY?p=preview
Added option to select/unselect a user by a click.
For that, no reset code needed.
#HostListener('mousedown', ['$event'])
onMouseDown(ev) {
this.dragStart = ev.clientY;
this.isMouseDown = true;
}
Only div's click handler changed.
(click)="onSelecPersona(user, !user.selected)"
INITIAL ANSWER:
Here is modified code: https://plnkr.co/edit/QryFWtLQwNuGkrtzDehm?p=preview
It solves few issues:
(1) HTML selection: the "user-select" CSS should be on the "row" no on the "card" because the selection starts at the "row" boundaries
.row {
user-select: none;
-moz-user-select: none;
}
.card-content {
padding: 0;
}
(2) Handling of selected divs: initial implementation relies on mouse events on the user's div. That does not handle the case when "selector" rectangle never crosses "user" div's boundaries (i.e. goes around but still within selection boundaries).
My implementation calculates an overlap of a "selector" and "user" divs to determine if a user selected.
<div class="card"
#ucard
[attr.id]="user.name"
[class.selected]="user.selected"
*ngFor="let user of users"
(click)="onSelecPersona(user, !user.selected)"
>
import {Component, NgModule, HostListener, Renderer2, ElementRef, ViewChild, ViewChildren } from '#angular/core'
...
#ViewChildren('ucard') components: QueryList<ElementRef>;
...
// return true if two HTML elements overlap
overlap(e1:ElementRef, e2:ElementRef){
var rect1 = e1.getBoundingClientRect();
var rect2 = e2.getBoundingClientRect();
return !(
rect1.top > rect2.bottom ||
rect1.right < rect2.left ||
rect1.bottom < rect2.top ||
rect1.left > rect2.right
);
}
// updates user selection based on the current "selector"
markSelected(){
this.components.forEach(it=> {
var overlaps: boolean = this.overlap(this.selector.nativeElement, it.nativeElement);
this.onSelecPersona(this.users.find(u=> u.name == it.nativeElement.id), overlaps);
});
}
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 try to handle a set of actions for multiple items selection on a ListView. The context: a RSS reader. An article can be read/unread, marked/unmarked, published/unpublished. Currently, my code adds every possible action when the multi-selection is started by the user: mark as read, keep unread, … So mark as read is available even if every selected article is already marked as read.
I'm trying to hide irrelevant actions. There's an example in the documentation to switch between multiple selection handlers, with predefined action lists. I would need to create an handler for every possibility, so 8 handlers. That's clearly not the solution.
I tried to call MultiSelectionHandler::removeAllActions() every time my selection changes and MultiSelectionHandler::addAction() to add back needed actions. This is the relevant part of my current code:
ListView {
dataModel: _articleModel
id: listView
multiSelectAction: MultiSelectActionItem {
}
multiSelectHandler {
status: qsTr("None selected")
actions: []
attachedObjects: [
ActionItem {
id: actionMultiRead
title: qsTr("Mark as read")
imageSource: "asset:///images/mark_as_read.png"
onTriggered: {
var selectionList = listView.selectionList();
listView.clearSelection();
for (var i = 0; i < selectionList.length; ++i)
_articleModel.data(selectionList[i]).unread = false;
}
},
ActionItem {
id: actionMultiUnread
title: qsTr("Keep unread")
imageSource: "asset:///images/keep_unread.png"
onTriggered: {
var selectionList = listView.selectionList();
listView.clearSelection();
for (var i = 0; i < selectionList.length; ++ i)
_articleModel.data(selectionList[i]).unread = true;
}
},
ActionItem {
id: actionMultiPublish
title: qsTr("Publish")
imageSource: "asset:///images/publish.png"
onTriggered: {
var selectionList = listView.selectionList();
listView.clearSelection();
for (var i = 0; i < selectionList.length; ++ i)
_articleModel.data(selectionList[i]).published = true;
}
},
ActionItem {
id: actionMultiUnpublish
title: qsTr("Unpublish")
imageSource: "asset:///images/unpublish.png"
onTriggered: {
var selectionList = listView.selectionList();
listView.clearSelection();
for (var i = 0; i < selectionList.length; ++ i)
_articleModel.data(selectionList[i]).published = false;
}
},
ActionItem {
id: actionMultiStar
title: qsTr("Star")
imageSource: "asset:///images/star.png"
onTriggered: {
var selectionList = listView.selectionList();
listView.clearSelection();
for (var i = 0; i < selectionList.length; ++ i)
_articleModel.data(selectionList[i]).marked = true;
}
},
ActionItem {
id: actionMultiUnstar
title: qsTr("Unstar")
imageSource: "asset:///images/unstar.png"
onTriggered: {
var selectionList = listView.selectionList();
listView.clearSelection();
for (var i = 0; i < selectionList.length; ++ i)
_articleModel.data(selectionList[i]).marked = false;
}
}
]
}
onSelectionChanged: {
if (selectionList().length > 1) {
multiSelectHandler.status = qsTr("%1 items selected").arg(selectionList().length);
} else if (selectionList().length == 1) {
multiSelectHandler.status = qsTr("1 item selected");
} else {
multiSelectHandler.status = qsTr("None selected");
}
// Update available actions
multiSelectHandler.removeAllActions();
for (var i = 0; i < selectionList().length; ++ i) {
var elt = _articleModel.data(selectionList()[i]);
if (elt.marked) {
multiSelectHandler.addAction(actionMultiUnstar);
console.log("Adding unstar");
} else {
multiSelectHandler.addAction(actionMultiStar);
console.log("Adding star");
}
if (elt.published) {
multiSelectHandler.addAction(actionMultiUnpublish);
console.log("Adding unpublish");
} else {
multiSelectHandler.addAction(actionMultiPublish);
console.log("Adding publish");
}
if (elt.unread) {
multiSelectHandler.addAction(actionMultiRead);
console.log("Adding read");
} else {
multiSelectHandler.addAction(actionMultiUnread);
console.log("Adding unread");
}
}
}
}
While it could greatly be optimized and works fine until the selection changes, it doesn't work anymore after a change: MultiSelectionHandler::removeAllActions() delete the actions, they can't be added back.
Is there any way to achieve this without declaring 8 multi selection handlers?
the question is quite old, and probably you have already solved, but for any other one who stumbled up on this question my solution is to use MultiSelectionHandler::removeAction() instead of removeAllActions().
removeAllActions automatically delete the object, thus one should recreated the actions every time.
for (var i = 0; i < multiSelectAction.actionCount(); ++ i)
multiSelectAction.removeAction(multiSelectAction.actionAt(i));
Cheers.