Du bist nicht angemeldet.

Stilllegung des Forums
Das Forum wurde am 05.06.2023 nach über 20 Jahren stillgelegt (weitere Informationen und ein kleiner Rückblick).
Registrierungen, Anmeldungen und Postings sind nicht mehr möglich. Öffentliche Inhalte sind weiterhin zugänglich.
Das Team von spieleprogrammierer.de bedankt sich bei der Community für die vielen schönen Jahre.
Wenn du eine deutschsprachige Spieleentwickler-Community suchst, schau doch mal im Discord und auf ZFX vorbei!

Werbeanzeige

CeDoMain

Alter Hase

  • »CeDoMain« ist der Autor dieses Themas

Beiträge: 587

Wohnort: Ilmenau

Beruf: Student für Mechatronik

  • Private Nachricht senden

1

21.08.2015, 16:43

[C# WPF] ListBoxen mit DragDrop-Funktion verschachteln oder "EventArgs.Handled = true bricht die Eventroutine nicht ab"

Hallo Leute,

ich habe mir in WPF eine DragDropListBox programmiert, die auf einer ListBox aufbaut (Basisklasse) und zusätzlich vollständig DragDrop fähig ist.

Kurze Erlärung der Funktionsweise: Der Dragvorgang wird von den ListBoxItems gestartet, indem zunächst der Mausklick (ListBoxItem_PreviewMouseLeftButtonDown) gespeichert wird. Wird nun die Maus eine bestimmte Distanz bewegt, wird der Dragvorgang gestartet. Das wird in ListBoxItem_MouseMove geprüft. Ich habe 4 virtuelle Methoden, um die Drag- bzw. Dropbehandlung, später in davon abgeleiteten Klassen, ändern zu können.

Das ganze funktioniert echt prima, zügig und ohne Fehler - mit zwei Einschräkungen, die ich beheben möchte: Wenn ich zwei Listboxen ineinander platziere und ein Item der vordersten "Draggen" will, dann führt die hintere ListBox auch einen Drag aus. Außerdem kann ich keine Items in den Listboxen platzieren, die einen scrollbaren Container beinhalten, denn wenn ich davon die Scrollbar verschiebe wird auch ein Drag ausgelöst.

Meine Idee, das Problem der Verschachtelung zu lösen, war e.Handled = true zu setzen, damit das Bubble-Routing des RoutedEvents abgebrochen wird. Das funktioniert aber nicht. Außerdem ist mir aufgefallen, dass DragDrop.DoDragDrop(...) erst returnt, wenn ein Drop bzw. Abbruch stattgefunden hat. Solange müsste aber das MouseMove-Routing angehalten sein. Ist es aber nicht, weil die hintere ListBox ja das Event auch gemeldet bekommt.

Ich konnte leider nichts genaueres dazu finden, wie die Events aufgerufen werden, bzw. wie man verlässlich die Events stoppen kann. :( Ich hoffe mal, einer von euch kan mir helfen!

Wer sich den Code durchlesen möchte oder eine DragDropListBox in WPF haben möchte kann sich unten bedienen. ;)

C#-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
using System;
using System.Collections;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace ACALC
{
    public partial class DragDropListBox : ListBox
    {
        // Eigenschaften
        public static readonly DependencyProperty CanModifyProperty = 
            DependencyProperty.Register("CanModify", typeof(bool), typeof(DragDropListBox), null);
        public bool CanModify
        {
            get
            {
                return (bool)GetValue(CanModifyProperty);
            }
            set
            {
                SetValue(CanModifyProperty, value);
            }
        }
        public string AllowedDropKey { get; set; }
        public DragDropEffects AllowedDragDropEffects { get; set; }

        private Point MouseLeftDownPosition;
        private bool MouseLeftDownOccured;
        protected object DraggedObject { get; private set; }
        protected int DraggedIndex { get; private set; }

        // Konstruktor
        public DragDropListBox()
        {
            InitializeComponent();
            AllowDrop = true;
            AllowedDropKey = "AllAllowed";
            AllowedDragDropEffects = DragDropEffects.None;
            CanModify = true;
            MouseLeftDownPosition = new Point();
            MouseLeftDownOccured = false;
            DraggedObject = null;
            DraggedIndex = -1;
        }

        // Methoden
        protected IList GetItems()
        {
            if (ItemsSource != null)
            {
                return ItemsSource as IList;
            }
            else
            {
                return Items;
            }
        }
        protected virtual void RemoveItem(ListBoxItem Item)
        {
            // Ausgewähltes Item löschen und versuchen nächstes Item auszuwählen
            int i = GetItems().IndexOf(Item.Content);
            GetItems().RemoveAt(i);
            if (i >= Items.Count) i = Items.Count - 1;
            if (i >= 0) (ItemContainerGenerator.ContainerFromIndex(i) as ListBoxItem).Focus();
        }
        protected virtual bool DoDrag(object DraggedObject)
        {
            // Item aus Liste entfernen
            GetItems().Remove(DraggedObject);

            // Dragvorgang beginnen
            switch (DragDrop.DoDragDrop(this, new DataObject(AllowedDropKey, DraggedObject), DragDropEffects.Move))
            {
                case DragDropEffects.None:
                    // Item hinzufügen
                    GetItems().Insert(DraggedIndex, DraggedObject);
                    return false;

                case DragDropEffects.Move:
                    return true;

                case DragDropEffects.Copy:
                case DragDropEffects.All:
                case DragDropEffects.Link:
                case DragDropEffects.Scroll:
                default:
                    throw new NotImplementedException();
            }
        }
        protected virtual DragDropEffects DoDropOnBox(object DraggedObject)
        {
            // Ist die Maus über keinem Item wird am Ende hinzugefügt
            GetItems().Add(DraggedObject);
            
            // Drop erfolgreich
            return DragDropEffects.Move;
        }
        protected virtual DragDropEffects DoDropOnItem(ListBoxItem Item, object DraggedObject)
        {
            // Ist die Maus über einem Item wird dort eingefügt
            GetItems().Insert(Items.IndexOf(Item.Content), DraggedObject);
            
            // Drop erfolgreich
            return DragDropEffects.Move;
        }
        private void ListBoxItem_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            // Nur wenn Veränderungen erlaubt sind
            if (!CanModify) return;

            // Mausposition speichern
            MouseLeftDownPosition = e.GetPosition(sender as IInputElement);

            // Mausklick merken
            MouseLeftDownOccured = true;
        }
        private void ListBoxItem_MouseMove(object sender, MouseEventArgs e)
        {
            // Nur wenn Veränderungen erlaubt sind
            if (!CanModify) return;

            // Ereignis nicht weiterleiten >>> Versuch, die Ereignisbehandlung hier zu unterbrechen funktioniert nicht - kann deshalb auch weggelassen werden <<<
            e.Handled = true;

            // ist die linke Maustaste gedrückt und um die systemdefinierte Entfernung bewegt worden?
            if (e.LeftButton != MouseButtonState.Pressed || !MouseLeftDownOccured) return;
            Point Current = e.GetPosition(sender as IInputElement);
            if (Math.Abs(Current.X - MouseLeftDownPosition.X) >= SystemParameters.MinimumHorizontalDragDistance ||
                Math.Abs(Current.Y - MouseLeftDownPosition.Y) >= SystemParameters.MinimumVerticalDragDistance)
            {
                // Mausklick wurde beachtet
                MouseLeftDownOccured = false;

                // Item speichern
                DraggedObject = (sender as ListBoxItem).Content;
                DraggedIndex = GetItems().IndexOf(DraggedObject);

                // Dragvorgang mit Item kann beginnen
                if (DoDrag(DraggedObject)) e.Handled = true;
            }
        }
        private void ListBox_QueryContinueDrag(object sender, QueryContinueDragEventArgs e)
        {
            // Nur wenn Veränderungen erlaubt sind
            if (!CanModify) return;

            if (e.EscapePressed)
            {
                // Drag abbrechen
                e.Action = DragAction.Cancel;
                e.Handled = true;
            }
        }
        private void ListBox_Drop(object sender, DragEventArgs e)
        {
            // Nur wenn Veränderungen erlaubt sind
            if (!CanModify) return;

            // Gibt es ein Drop-Objekt mit dem benötigten Key?
            object d = e.Data.GetData(AllowedDropKey);
            if (d != null)
            {
                if(sender is ListBoxItem)
                {
                    e.Effects = DoDropOnItem(sender as ListBoxItem, d);
                }
                else
                {
                    e.Effects = DoDropOnBox(d);
                }
                
                if(e.Effects != DragDropEffects.None)
                {
                    e.Handled = true;
                }
            }
        }
        private void ListBoxItem_KeyDown(object sender, KeyEventArgs e)
        {
            // Nur wenn Veränderungen erlaubt sind
            if (!CanModify) return;

            if (e.Key == Key.Delete && e.IsDown)
            {
                RemoveItem(sender as ListBoxItem);
            }
        }
    }
}
Mit freundlichem Gruß
CeDo
Discord: #6996 | Skype: cedomain

Lass solche persönlichen Angriffe lieber bleiben, meine sind härter.

Sylence

Community-Fossil

Beiträge: 1 663

Beruf: Softwareentwickler

  • Private Nachricht senden

2

21.08.2015, 20:32

Das ist zwar keine Hilfe beim eigentlichen Problem, aber wenn ich in WPF Drag'n'Drop brauche, verwende ich eigentlich immer gong-wpf-dragdrop

CeDoMain

Alter Hase

  • »CeDoMain« ist der Autor dieses Themas

Beiträge: 587

Wohnort: Ilmenau

Beruf: Student für Mechatronik

  • Private Nachricht senden

3

30.08.2015, 22:44

Danke Sylence, für den Link! Ich schaue mir das die Woche mal an, weil ich die letzte Segeln war. :thumbsup:

Trotzdem ist das eigentliche Problem nicht gelöst, wie du richtig bemerkst! Kann mir irgendwer anders helfen? Vielleicht auch mit einem Link bezüglich dem Eventsystem und dem blockieren der Eventkaskade? Wie würdet ihr zwei Buttons ineinander lösen? So dass nur der, der direkt unter der Maus ist reagiert.

Ich hoffe mal, dass ich noch ne brauchbare Antwort bekomme... ;)
Mit freundlichem Gruß
CeDo
Discord: #6996 | Skype: cedomain

Lass solche persönlichen Angriffe lieber bleiben, meine sind härter.

BlueCobold

Community-Fossil

Beiträge: 10 738

Beruf: Teamleiter Mobile Applikationen & Senior Software Engineer

  • Private Nachricht senden

4

31.08.2015, 06:43

Mal ganz anders gefragt: Wieso willst Du mehrere Listboxen verschachteln? Das klingt für mich nach einem doch eher ungewöhnlichen Konzept.
Teamleiter von Rickety Racquet (ehemals das "Foren-Projekt") und von Marble Theory

Willkommen auf SPPRO, auch dir wird man zu Unity oder zur Unreal-Engine raten, ganz bestimmt.[/Sarkasmus]

5

31.08.2015, 13:05

@CeDoMain: Vielleicht hilft dir das? https://social.msdn.microsoft.com/Forums…xtbox?forum=wpf
Hier wird originalsource geprüft und ggf. dann einfach abgebrochen.

@BlueCobold: Man kann das in WPF durchaus machen, wenn man z.B. für eine Collection von Objekten komplexere Controls anzeigen will. Hier werden dann die Vorteile der Listbox verwendet, wenn beispielsweise zur Collection noch Objekte hinzugefügt werden sollen.

In einigen Beispielen wird für solche Zwecke eher ein ItemsControl oder eine ListView verwendet, wenn man nicht unbedingt Selection-Events benötigt bzw. diese nicht selbst implementieren will. ListBox wird in WPF letztendlich von ItemsControl abgeleitet.

CeDoMain

Alter Hase

  • »CeDoMain« ist der Autor dieses Themas

Beiträge: 587

Wohnort: Ilmenau

Beruf: Student für Mechatronik

  • Private Nachricht senden

6

31.08.2015, 13:39

Ich bin immernoch an meinem Lichtsteuerprogramm zugange... Da bin ich jetzt am FrontEnd des Effekt-Editors (BackEnd ist fertig).

BackEnd:
Ein Effekt kann mehrere Ein- und Ausgangsslots oder -listen besitzen: Ein Slot hat immer einen Typ (RGB, Dimmer, Pan/Tilt, Strobe) und kann nur ein Element dieses Typs besitzen. Eine Liste hat immer einen Typ (wie oben) und kann eine beliebige Anzahl Elemente dieses Typs besitzen. Ein Element wären zum Beispiel die drei Farb(RGB)-Kanäle eines LED-Strahlers oder die beiden Bewegungs(Pan/Tilt)-Kanäle eines Movingheads. Außerdem lassen sich die Effektaus- auch mit Effekteingängen verknüpfen, indem ein Link-Element mit dem entsprechenden Typ in die Slots oder Listen eingefügt wird. So kann ein Effekt einen anderen steuern.

FrontEnd:
Alle Effekte sollen in einer DragDropListBox angezeigt werden, damit ich sie umsortieren kann. Nun sollen die Effektslots und -listen des Eingangs in einer DDListBox auf dem jeweiligen Effekt angezeigt werden und das gleiche auch für die Ausgänge. Die Slots und Listen werden dann auch als DDListBox realisiert, wobei die Slots eine Drop-Logik bekommen, die nur ein Element gleichzeitig zulässt. Also im zweifelsfall ersetzt.

Ich hoffe, das war nicht zu konkret, sodass auch Nicht-Lichttechniker verstehen, was ich bezwecken möchte... :)
Mit freundlichem Gruß
CeDo
Discord: #6996 | Skype: cedomain

Lass solche persönlichen Angriffe lieber bleiben, meine sind härter.

CeDoMain

Alter Hase

  • »CeDoMain« ist der Autor dieses Themas

Beiträge: 587

Wohnort: Ilmenau

Beruf: Student für Mechatronik

  • Private Nachricht senden

7

31.08.2015, 14:15

@CeDoMain: Vielleicht hilft dir das? https://social.msdn.microsoft.com/Forums…xtbox?forum=wpf
Hier wird originalsource geprüft und ggf. dann einfach abgebrochen.
Diese Idee ist mir auch schon gekommen, aber ich weis nicht wie ich das realisieren soll, wenn auch die ListBoxItems Events werfen können. Die Idee war

C#-Quelltext

1
if (sender != e.OriginalSource) return;
an den Anfang jeder Eventroutine zu setzen, aber OriginalSource ist nicht zwingen das oberste Element, was diesen Ereignishandler behandeln kann. Ich habe zum Beispiel in den ListBoxItems eine Border - bewege ich die Maus darüber, dann ist OriginalSource die Border und nicht das ListBoxItem. Sender ist aber letzteres.

Gibt es eine andere Möglichkeit, in so einer Situation an das ListBoxItem zu kommen, ohne sender zu benutzen?
Mit freundlichem Gruß
CeDo
Discord: #6996 | Skype: cedomain

Lass solche persönlichen Angriffe lieber bleiben, meine sind härter.

Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von »CeDoMain« (31.08.2015, 14:22)


8

31.08.2015, 15:40

Ich denke, es könnte daran liegen, dass e.Handled nicht funktioniert:
http://stackoverflow.com/questions/62968…rue-not-working

CeDoMain

Alter Hase

  • »CeDoMain« ist der Autor dieses Themas

Beiträge: 587

Wohnort: Ilmenau

Beruf: Student für Mechatronik

  • Private Nachricht senden

9

31.08.2015, 16:02

Dann sollte es aber doch auch nicht weitergeleitet werden!? Außerdem gilt das anscheinend nicht für MouseMove.
Mit freundlichem Gruß
CeDo
Discord: #6996 | Skype: cedomain

Lass solche persönlichen Angriffe lieber bleiben, meine sind härter.

CeDoMain

Alter Hase

  • »CeDoMain« ist der Autor dieses Themas

Beiträge: 587

Wohnort: Ilmenau

Beruf: Student für Mechatronik

  • Private Nachricht senden

10

31.08.2015, 20:18

Ich ziehe meine Aussage, dass e.Handled = true nicht funktioniert zurück! Ich habe eben die neue Haltepunktfunktion (während der Laufzeit was in der Ausgabe ausgeben) von VSC2015 ausprobiert und mir die sender der MouseMove-Ereignisse in der Ausgabe anzeigen lassen. Dabei fällt auf, dass bei einem normalen MouseMove nur das Event des obersten ListBoxItem ausgelöst wird, weil dort direkt e.Handled = true gesetzt wird.

Nach kurzem Überlegen habe ich glaube ich auch schon den Grund herausgefunden, warum bei einem Drag auch das MouseMove-Ereignis des hinteren ListBoxItem ausgelöst wird: Wenn das MouseMove-Ereignis des oberen ListBoxItem einen Drag-Vorgang auslöst, dann wird für dieses Element das MouseMove-Ereignis gesperrt - für alle anderen Elemente anscheinend aber nicht. Denn nachdem das obere ListViewItem einen Drag durch DragDrop.DoDragDrop(...) ausgelöst hat, wird direkt (ohne die Maus zu bewegen) das MouseMove-Ereignis des hinteren ListViewItem aufgerufen - das löst auch einen Drag aus. Warum ist das so?

EDIT: Neue Erkenntnisse: Wird ein DragDrop Vorgang gestartet, dann werden KEINE MouseMove-Ereignisse mehr ausgelöst. Es muss allein daran liegen, dass DoDragDrop solange nicht zurückkehrt, bis der Drag beendet ist. Kann man die Methode asyncron aufrufen bzw. dafür sorgen dass MouseMove nicht auf die Beendigung warten muss? Es liegt wahrscheinlich daran, dass die RoutedEventRoutine nach einer bestimmten Zeit einfach den Event weiterroutet, obwohl der vorherige Handler (der e.Handled = true setzt) noch nicht beendet wurde. Kommt mir zwar komisch vor, aber würde das Verhalten erklären.
Mit freundlichem Gruß
CeDo
Discord: #6996 | Skype: cedomain

Lass solche persönlichen Angriffe lieber bleiben, meine sind härter.

Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von »CeDoMain« (31.08.2015, 21:06)


Werbeanzeige