Hallo,
ich habe eine von List<T> abgeleitete Klasse, für die ich gerne ein
BubblingEvent erstellen möchte. Leider bin ich nicht sehr fit in WPF und
bekomme das Event leider nur in der herkömmlichen Form hin.
Was muss ich tun, um daraus ein Routed Event zu machen?
Alles was ich im Internet gefunden habe bezieht sich auf Klassen die von
UIElement erben und auf Funktionen dieser Klasse aufbauen.
Ich hoffe ihr habt eine Anregung für mich.
Gruß Jan
Anbei mein Beispiel Code:
// An event that clients can use to be notified whenever the
51
// elements of the list change.
52
publiceventChangedEventHandlerChanged;
53
54
// Invoke the Changed event; called whenever list changes
55
protectedvirtualvoidOnChanged(EventArgse)
56
{
57
if(Changed!=null)
58
Changed(this,e);
59
}
60
61
// Override some of the methods that can change the list;
62
// invoke event after each
63
publicnewvoidAdd(Tvalue)
64
{
65
base.Add(value);
66
OnChanged(EventArgs.Empty);
67
}
68
69
publicnewvoidClear()
70
{
71
base.Clear();
72
OnChanged(EventArgs.Empty);
73
}
74
75
publicvoidreplace(intindex,Titem)
76
{
77
base.RemoveAt(index);
78
base.Insert(index,item);
79
OnChanged(EventArgs.Empty);
80
}
81
}
82
}
Edit: Zum Aufbau meiner Anwendung:
Ich habe eine benutzerdefinierte Klasse, die von Grid abgeleitet ist.
Diese enthält eine List, dessen Inhalt sich manchmal ändert.
Ich habe mehrere von den Benutzerdefinierten Klassen und möchte in
meiner Hauptanwendung gerne mit den Daten der Listen arbeiten.
Ich muss also meine Hautanwendung (Window) informieren, wenn sich in den
Listen was geändert hat. Oder gibt es einen anderen Ansatz (außer
pollen)?
RoutedEvents unterstuetzen Tunneling/Bubbeling, was im UI Sinn macht.
Wie soll das in einem Model-Objekt funktionieren?
Falls du die Notifizierung im UI brauchst (ich glaube, du meinst eher
das), kannst du IPropertyNotifyChanged bzw. ICollectionChanged
implementieren (oder so aehnlich). Oder du erfindest das Rad nicht neu
und verwendest gleich die ObservableCollection(T) oder gar die
ReadonlyObservableCollection(T). Dann klappts auch mit dem Binding. :)
Danke für den Tipp mit der ObservableCollection. Die Klasse hat mir
einiges an Arbeit abgenommen. Und Sorry, dass ich nicht früher
geschrieben habe.
Dafür komme ich auch gleich mit der nächsten Frage:
Das CollectionChanged Event aus der ObservableCollection Klasse kann ich
doch garnicht bubbeln lassen, oder? (Wenn ich richtig gesucht habe ist
es kein RoutedEvent)
Momentan bewirkt eine Änderung der Daten (siehe unten) ein Auslösen des
ObservableCollection_2.CollectionChanged Events. Das ist Gut so. Ich
möchte aber zusätzlich auf Ebene der ObservableCollection_1 darüber
informiert werden. Da sich die Datenänderungen bis hierhin auswirken.
Wie kann ich dort (ObsColl_1) ein Event abfeuern?
Im Eventhandler von ObservableCollection_2 ein
ObservableCollection_1.OnCollectionChanged() aufrufen geht nicht, weil
ObservableCollection_1 in dieser Klasse garnicht bekannt ist.
Mit AddHandler() komme ich auch nicht weiter, da ObservableColection ja
nicht von UIElement abgeleitet ist.
Mit welchem Ansatz komme ich hier weiter?
Gruß Jan
Hier ist nochmal meine vereinfachte Struktur:
RoutedEvents gehören zum Visual- bzw. LogicalTree. Eventuell könnte man
da was machen, wenn man ein CustomControl selbst implementiert und die
Route für das eigene Event bestimmt. Damit kenne ich mich nicht aus und
vermutlich wäre die Lösung alles andere als optimal.
Wenn du UIElements in einer ObservableCollection(T) hast, läuft mit
Sicherheit was falsch. View und ViewModel sollten getrennt werden
(natürlich auch das Model - wo die Grenzen liegen, hängt von der
Anwendung ab). Dann hat man im ViewModel die View-repräsentierenden
Objekte, die INotifyPropertyChanged bzw. INotifyCollectionChanged
implementieren und die man per DataBinding an entsprechende in XAML
definierte View-Elemente bindet. Dabei helfen DataTemplates in
ItemsControls, die bestimmen, wie Items einer Collection dargestellt
werden.
Bleibt aber das Problem, dass man nicht mitbekommt, wenn sich ein Item
in der Collection ändert (wenn man das Problem im ViewModel lösen
möchte). Als Alternative bliebe die alte BindingList(T), deren
ListChanged-Event auch ausgelöst wird, wenn ein Item das
INotifyPropertyChanged.PropertyChanged feuert. Leider sind in den
EventArgs nur der alte und neue Index des geänderten Items hinterlegt,
was bei einem Remove problematisch ist - was soll man mit dem Index für
ein Item, das nicht mehr da ist? Ausserdem gibt es die BindingList(T)
nicht in Silverlight.
Wenn es wirklich notwendig ist, auf Änderungen der Items-Objekte zu
reagieren, würde ich wohl die ObservableCollection(T) in etwa wie folgt
erweitern:
1
public class EnhancedCollection<T> : ObservableCollection<T> where T : INotifyPropertyChanged
2
{
3
public event EventHandler<ItemChangedEventArgs<T>> ItemChanged;
4
public EnhancedCollection()
5
{
6
CollectionChanged += (s, e) =>
7
{
8
if (e.OldItems != null) foreach (INotifyPropertyChanged oldItem in e.OldItems) oldItem.PropertyChanged -= item_PropertyChanged;
9
if (e.NewItems != null) foreach (INotifyPropertyChanged newItem in e.NewItems) newItem.PropertyChanged += item_PropertyChanged;
if (ItemChanged != null) ItemChanged(this, new ItemChangedEventArgs<T>((T)sender, e.PropertyName));
15
}
16
}
17
18
public class ItemChangedEventArgs<T> : EventArgs
19
{
20
public T Item { get; private set; }
21
public string PropertyName { get; private set; }
22
23
public ItemChangedEventArgs(T item, string propertyName)
24
{
25
Item = item;
26
PropertyName = propertyName;
27
}
28
}
So kann man in der Klasse, die ein Objekt der EnhancedCollection(T)
referenziert auf die CollectionChanged- und ItemChanged-Events reagieren
und eventuell auch noch "weiter hinauf" (Im Objektmodell hat man anders
als in den WPF-Trees nicht zwingend eine Baustruktur!) triggern. In den
ItemChangedEventArgs hat man dann die Item-Referenz und den Namen des
Propertys, das sich geändert hat.
Wenn man das Ganze nur einmal braucht, könnte man sich die Ableitung
auch sparen und die Logik in die Klasse bauen, die ein Objekt der
ObservableCollection(T) referenziert.
Ein anderer Ansatz wäre, dem Datenobjekt im Konstruktor das Objekt
mitzugeben, das letztlich auf die Änderungen reagieren soll. Dann
bräuchte man die Collections gar nicht anfassen. :)
Hallo Denver,
vielen Dank für deine Antwort. So wie du es schreibst, scheint es
wirklich aufwendig zu sein, das Event zu implementieren.
Die Idee die Funktion in die Klasse selbst zu verschieben hatte ich auch
schon. Ein Teil der arbeit konnte ich so auch schon wegschaffen.
Die ObservableCollection KLasse zu erweitern hatte ich auch schon vor.
Aber, ist aber eigentlich kein schöner weg.
Ende vom Lied: Nachden ich das Eventproblem letzte Woche erstmla auf die
lange Bank geschoben habe. Habe ich habe übers Wochenende zufällig eine
andere Lösung gefunden, die meiner Meinugn nach recht elegant ist und
ohne Events auskommt:
Ich hole mir in meiner von Grid abgeleiteten Klasse solange die
Elternelemente, bis ich auf das Window gestoßen bin. Dann führe ich von
meiner abgeleiteten Klasse aus eine Updatefunktion im Window aus.
1
MainWindow temp = new MainWindow(); //Parent Typ
2
Point point = new Point();
3
UIElement elem = this.InputHitTest(point) as UIElement;
4
bool found = false;
5
while (elem != null && !found)
6
{
7
//object item = (object)elem;
8
found = object.ReferenceEquals(elem.GetType(), temp.GetType());
9
if (found)
10
{
11
try
12
{
13
MainWindow MW = elem as MainWindow;
14
MW.update_gruppen(this);
15
}
16
catch
17
{
18
Debug.WriteLine("Funktion Update_gruppen nicht gefunden");
19
}
20
}
21
else
22
{
23
elem = VisualTreeHelper.GetParent(elem) as UIElement; //elem=getParent(elem);
24
}
25
}
Dazu habe ich jetzt meine abgeleiteten Klassen sowohl in einer Liste als
auch in einem visuellen Pfad liegen.
Ich musste zwar ein paar Sachen umbauen. habe dafür jetzt aber einen Weg
gefunden den ich verstehe und der mir einleuchet.
Ich habe mit mitte letzter Woche schon einmal deinen Vorschlag
angeschaut. Die Funktion habe ich so schon ein paar mal gesehen und
werde sie mit für zukünftige Sachen mal merken.
Dever schrieb:> Datenobjekt im Konstruktor das Objekt> mitzugeben, das letztlich auf die Änderungen reagieren soll
Die Idee habe ich auch ausprobiert. An ein/zwei Stellen war sie aber
nciht ganz optimal, da ich dann wieder ein Flag gesetzt habe, auf dass
ich in meinem Hauptfenster reagiert habe.
Auf jeden Fall vielen Dank für deine Mühe und die Zeit die du für mich
investiert hast.
Viele Grüße
Jan