[gelöst] 2 Fragen zu Drag & Drop

Alles rund um die Programmierung mit Qt
Antworten
suamikim
Beiträge: 59
Registriert: 1. November 2009 15:58

[gelöst] 2 Fragen zu Drag & Drop

Beitrag von suamikim »

Hallo zusammen,

ich weiß, dass es hier schon genug Fragen zu DnD gibt und die Doku auch einiges ausspukt, doch leider bringt mich weder das eine noch das andere wirklich weiter, darum der neue Thread...

Also:

Ich hab ein QTreeWidget, welches ich mittlerweile "gesubclassed" (kann mir bitte irgendwer ein ordentlich Wort hierfür sagen?) habe und ein paar Methoden überschreibe (ua. dropEvent, ...).

Prinzipiell funktioniert das ganze mit der Standard-DnD-Funktionalität ganz gut, allerdings benötige ich 2 "Zusatzfunktionen".

1.)
Gewisse Elemente dürfen nur als Nodes fungieren (sie dürfen keine Parents werden, dh. sie dürfen keine Unterelemente bekommen).
Das hab ich gelöst, indem ich einfach bei der Item-Erstellung die Flags händisch setze und das ItemIsDropEnabled-Flag weglassen:

Code: Alles auswählen

item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled);
Funktioniert soweit auch wie es soll. Was ich allerdings noch bräuchte:
Diese Items dürfen kein "Root"-Item werden, dh. sie MÜSSEN ein Parent-Item besitzen (gehören immer in eine bestimmte Kategorie)!

Wie könnte ich das erreichen?
Wenn es einfacher wäre könnte ich mir auch vorstellen, dass gar kein Item per DnD auf Root-Ebene verschoben werden darf!

2.)
Nachdem Item(s) gedropped wurden, soll mein TreeWidget ein Signal schicken. Dieses Signal soll 2 Dinge beinhalten:
Gedroppte Items (kein Problem), Target-Item (also jenes Item, in welches verschoben wurde).

Wie komme ich an das Target-Item? Sofern ich die Doku richtig verstehe gibt es hierzu keine Hilfe außer die Mausposition beim Drop. Richtig?

Wenn ja, wie komme ich dann von der Mauspos an mein Tree-Item? Vorhanden wäre ja die ItemAt-Methode des TreeWidgets. Leider bringt mir das recht wenig. Warum, erkläre ich am besten mit 2 Beispielen:

Ich hab in meinem überschriebenen DropEvent mal eingebaut, dass der Text vom Item, dass ich über ItemAt erhalte, ausgegeben wird:

Code: Alles auswählen

void QEditDatabaseTreeWidget::dropEvent(QDropEvent *event)
{
  QTreeWidgetItem *pItem = this->itemAt(event->pos());
  if (pItem)
  {
    qDebug() << pItem->text(0);
  }
  else
    qDebug() << "Drop to no Item!";

  //...
  QTreeWidget::dropEvent(event);
}
Beispiel 1:

Bild

"Frage 5" wird zwischen "Unterkategorie 1" & "Unterkategorie 2" abgelegt.
Als "Target-Item" sollte mein Signal also "Hauptkategorie" schicken.

Der Source-Code oben gibt aber "Unterkategorie 1" aus (ist ja soweit auch richtig, da ich ja mit ItemAt(Mousepos) abfrage...).

Beispiel 2:

Bild
"Frage 5" wird quasi neben "Hauptkategorie" abgelegt -> "Frage 5" wird selbst zu einem "Root"-Item.
Genau das möchte ich eigentlich komplett verhindern (siehe Punkt 1.).

Das über ItemAt ermittelte Item ist natürlich "Hauptkategorie". Also auch wieder korrektes Verhalten, aber nicht das, was ich erreichen möchte...

Ich hoffe, dass sich jemand die Mühe antut und mir hier ein wenig weiterhilft!

danke, mfg

ps.: Bei der ganzen Geschichte geht es ausschließlich um das verschieben von Items innerhalb meines TreeWidgets. Also nur InternalMove!
Zuletzt geändert von suamikim am 1. März 2010 16:59, insgesamt 1-mal geändert.
Chris81T
Beiträge: 82
Registriert: 4. Mai 2008 00:06
Wohnort: Urbar

Beitrag von Chris81T »

Hallo,
Ich hab ein QTreeWidget, welches ich mittlerweile "gesubclassed" (kann mir bitte irgendwer ein ordentlich Wort hierfür sagen?)
--> Ich habe eine Klasse, die von QTreeWidget erbt / abgeleitet ist ;)

Zu deinem Thema:

Grundlegend ist es ja so, dass du beim Start eine gültige (im Normalfall) Struktur dem User vorgibst (fest vorgegeben, durch laden einer Datei, die die Informationen hat, oder wie auch immer).
Jetzt willst du dem Benutzer die Möglichkeit geben, dass er die Struktur manipulieren darf.

So würde ich es machen:
Ich würd mir die mousePressEvent() reimplementieren und darin, falls nach eigener Prüfung auf dass currentItem erlaubt ist, ein QDrag erzeugen, mit sinnvollen / wichtigen Daten befüllen und dieses starten.

Nun ist es ja eigentlich so, wenn man auf ein anderes Widget seinen Drop durchführen will, dass bei Eintritt in den Widgetbereich die dragEnterEvent ausgeführt wird. Darin sollte man dann die Prüfung einbauen, ob dieser Drag gestattet ist oder nicht. Da du aber dich im gleichen Widget (dein View) bewegst, wird diese nicht greifen. Ich hab grad mal nachgeschaut, dafür gibt's die dragMoveEvent Funktion, die dir dafür behilflich sein sollte, sprich:
Über das dragMoveEvent erhält du ja auch die aktuelle Position. Damit kannst du schön das darunterliegende Item erhalten, so wie du es auch schon gemacht hast. Und dann ist wieder deine! Prüfung gefragt. Du hast gesagt, das zB. dein Frage5 Item nicht Kind deines Hauptkategorie Item werden darf. Nun gut, dann prüfe doch einfach ab, ob das aktuelle Item das ROOT Item ist (gibt's mehrere Möglichkeiten für). Wenn ja, lehne den Drag ab, ansonsten akzeptiere diesen! So ist es ja dann auch Benutzerfreundlich. Denn der Benutzer bekommt dann das passende Mauscursor-symbol gezeigt. Wenn der gestattet ist, und der User die Maustaste loslässt, wird die dropEvent Funktion aufgerufen. Dann kannst du Manipulierung der Struktur durchführen, da sie von deiner vorherigen Prüfung gestattet ist.

Ich hoffe, dir hilft das weiter, bringt dich auf ein paar Ideen.

Viel Erfolg!
suamikim
Beiträge: 59
Registriert: 1. November 2009 15:58

Beitrag von suamikim »

Hallo Chris81T,

herzlichen dank gleich mal für deine ausführliche Antwort!
Chris81T hat geschrieben:--> Ich habe eine Klasse, die von QTreeWidget erbt / abgeleitet ist ;)
Aaaja, da war beim verfassen der Knoten im Hirn mal wieder zu groß, als dass ich aufs einfachste gekommen wäre ;)

Generell hilft mir dein Vorschlag leider nicht allzuviel weiter. Mein Hauptproblem ist ja, dass ich mittels der itemAt-Methode des TreeWidgets nicht herausfinde, ob der Drop auf oder neben einem Item platziert wurde (also ob mein gedroptes Item ein Unteritem des mittels itemAt ermitteltem Item wird oder ob es auf der selben Ebene wie dieses landet. Siehe mein 1. Beispielt mit Screenshot).

Mittlerweile hab ich aber eine Idee, wie ich das lösen kann:

Mittels itemAt bekomme ich wie bisher mein "Target-Item". Nun werde ich noch zusätzlich eine Abfrage einbauen, ob die Mausposition innerhalb des Item-Rect's vom "Target-Item" liegt.

Bisher konnte ich das noch nicht ausprobieren, allerdings hoffe ich, dass dies nicht der Fall ist, wenn mein drop neben und nicht direkt auf dem "Target-Item" stattfindet (siehe wieder 1. Beispiel).

Somit könnte ich dann mit Sicherheit sagen, ob mein drop neben oder auf dem "Target-Item" stattgefunden hat.

Ich werde das jedenfalls mal testen und hier bescheid geben, ob das ganze so funktioniert hat wie ich es mir vorgestellt habe...

danke, mfg
suamikim
Beiträge: 59
Registriert: 1. November 2009 15:58

Beitrag von suamikim »

Soda, nach ein wenig herumprobieren bin ich jetzt auf eine Lösung gekommen, die für mich passt und mit dem (wahrscheinlich) kleinstem nötigem Aufwand umgesetzt wurde ;)

Mein Problem wurde übrigens, wie oben bereits angedeutet gelöst, indem ich einfach die Mausposition und das Rect des Items, welches ich mittels itemAt bekomme, vergleiche:

Nun hab ich 1 Methode getDropTarget:

Code: Alles auswählen

QTreeWidgetItem * QEditDatabaseTreeWidget::getDropTarget(QPoint pos)
{
  QTreeWidgetItem *pItem = this->itemAt(pos);
  if (pItem)
  {
    // Prüfen, ob auf oder neben das Item gedropt wurde
    QRect rc = this->visualItemRect(pItem);
    QRect rcNew = QRect(rc.left(), rc.top() + 1, rc.width(), rc.height() - 2);
    if (rcNew.contains(pos, true))
    {
      // Es wird AUF das Item gedropt -> Target ist das Item selbst oder das Parent-Item (wenn das Item eine Frage ist)
      if (pItem->data(0, QTestMe::QuestionRole) != -1)
        pItem = pItem->parent(); // Es wurde auf eine Frage gedropt -> Target ist das Parent-Item
    }
    else
      pItem = pItem->parent(); // Es wird NEBEN das Item gedropt -> Target ist das Parent-Item
  }

  return pItem;
}
Überschrieben habe ich lediglich das dragMoveEvent und das dropEvent:

Code: Alles auswählen

void QEditDatabaseTreeWidget::dragMoveEvent(QDragMoveEvent *event)
{
  if (!getDropTarget(event->pos()))
  {
    // getDropTarget hat NULL zurückgeliefert -> Hier darf nicht gedropt werden
    event->ignore();
    return;
  }

  QTreeWidget::dragMoveEvent(event);
}

void QEditDatabaseTreeWidget::dropEvent(QDropEvent *event)
{
  QTreeWidgetItem *pItem = getDropTarget(event->pos());
  if (pItem)
    emit itemsDropped(this->selectedItems(), pItem);

  QTreeWidget::dropEvent(event);
}
}
Somit ist mein "Drop-Target" immer eine Kategorie und niemals eine Frage. Außerdem wird der Drop nicht erlaubt, wenn das Item als "Root" gedropt wurde (wird auch entsprechend visual dargestellt, wie von der DnD-Standard-Funktionalität gewohnt).

danke, mfg

mik

ps.: Verbesserungsvorschläge, Anregungen & Fragen sind natürlich wie immer herzlich willkommen!
Antworten