Forum: PC-Programmierung C# Routed Event erstellen


von Jan S. (jannemann)


Angehängte Dateien:

Lesenswert?

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:
1
using System;
2
using System.Collections.Generic;
3
using System.Linq;
4
using System.Text;
5
using System.Windows;
6
using System.Windows.Controls;
7
using System.Windows.Data;
8
using System.Windows.Documents;
9
using System.Windows.Input;
10
using System.Windows.Media;
11
using System.Windows.Media.Imaging;
12
using System.Windows.Navigation;
13
using System.Windows.Shapes;
14
15
namespace Test_ListChanged_Event
16
{
17
    /// <summary>
18
    /// Interaktionslogik für MainWindow.xaml
19
    /// </summary>
20
    /// 
21
22
    public delegate void ChangedEventHandler(object sender, EventArgs e);
23
24
    public partial class MainWindow : Window
25
    {
26
        public int i = 0;
27
        public MainWindow()
28
        {
29
            InitializeComponent();
30
31
            ListWithChangedEvent<int> list=new ListWithChangedEvent<int>();
32
            list.Changed +=new ChangedEventHandler(list_Changed);
33
            list.Add(1);
34
            list.Add(2);
35
            list.Add(3);
36
            list.Add(4);
37
            list.replace(2,10);
38
39
40
        }
41
42
        void list_Changed(object sender, EventArgs e)
43
        {
44
            i++;  //Test
45
        }
46
    }
47
   
48
   public class ListWithChangedEvent<T>: List<T> 
49
   {
50
      // An event that clients can use to be notified whenever the
51
      // elements of the list change.
52
      public event ChangedEventHandler Changed;
53
54
      // Invoke the Changed event; called whenever list changes
55
      protected virtual void OnChanged(EventArgs e) 
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
      public new void Add(T value) 
64
      {
65
         base.Add(value);
66
         OnChanged(EventArgs.Empty);
67
      }
68
69
      public new void Clear() 
70
      {
71
         base.Clear();
72
         OnChanged(EventArgs.Empty);
73
      }
74
      
75
      public void replace(int index, T item)
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)?

von Dever (Gast)


Lesenswert?

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. :)

von Jan S. (Gast)


Lesenswert?

Danke, die Klasse kannte ich nicht.
Werde es zu Hause ausprobieren und dann eine Rückmeldung geben.
Sieht aber sehr vielversprechend aus :)

von Jan S. (jannemann)


Lesenswert?

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:
1
<Window>
2
  <ObservabelCollection_1>
3
     <von_Grid_abgeleitete_Klasse_1>
4
        <ObservableCollection_2>
5
           <Daten>
6
        </ObservableCollection_2>
7
     </von_Grid_abgeleitete_Klasse_1>
8
      ...
9
     <von_Grid_abgeleitete_Klasse_n>
10
     </von_Grid_abgeleitete_Klasse_n>
11
  </ObservabelCollection_1>
12
</Window>

von Dever (Gast)


Lesenswert?

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;
10
        };
11
    }
12
    private void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
13
    {
14
        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. :)

von Jan S. (jannemann)


Lesenswert?

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

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.