Autor Beitrag
Schafschaf
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 63
Erhaltene Danke: 2

Windows 10
C# (VS 2015)
BeitragVerfasst: Do 08.10.15 11:20 
Hallo,

ich weiß mal wieder nicht weiter.
Ich habe ein Viewmodel, in das ich ein paar Datenbindungen habe.
Jetzt möchte ich ein paar Commands im Viewmodel feuern und dabei auf die gebundenen Objekte im ViewModel.
Hier mal ein Ausschnitt:
ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
    public class MenschenVM : INotifyPropertyChanged
    {
        public ObservableCollection<Mensch> Menschen { get; set; } //An ein Gridview gebunden

        public Add add { get; set; }
        
    }

    public class Add : ICommand
    {
        public event EventHandler CanExecuteChanged;

        public bool CanExecute(object parameter)
        {
            return true;
        }

        public void Execute(object parameter)
        {
            //Hier möchte ich, wenn der Button geklickt ist, einen Mensch der Collection hinzufügen.
        }
    }


Die Daten für den Mensch werden in einem Formular eingegeben und sollen dann eben mit dem Button ein Menschobjekt der ObservableCollection hinzugefügt werden.
Nur leider komme ich aus der "Add" Klasse nicht auf die ObservableCollection im Viewmodel.
Jetz könnte ich in MenschenVM noch die ICommand implentieren, aber nur einmal. Mehr Commands gehen dann nicht.
Hat jemand dazu eine Idee?
Freue mich über Antworten :)

LG Schafschaf
Palladin007
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 1282
Erhaltene Danke: 182

Windows 11 x64 Pro
C# (Visual Studio Preview)
BeitragVerfasst: Do 08.10.15 11:50 
Gib dem Command doch im Konstruktor die Daten mit, die es braucht?
Ist ja eine Klasse wie jede Andere auch :D

Oder, was auch gerne gemacht wird, streng genommen aber nicht ganz mit MVVM vereinbar ist, ist ein RelayCommand.
Also eine Klasse, die ICommand implementiert und für die Methoden eine Action<object> und eine Func<object, bool> im Konstruktor entgegen nimmt.
So kannst Du das im ViewModel "implementieren" und hast dann dort auch alle Daten, die es im ViewModel gibt - public und private



PS:
Hier gibt es eine Implementierung:
docs.telerik.com/dat...ase-and-relaycommand

Meine Eigene ist aber etwas komplexer, die nutze ich dann aber auch für jede weitere Command-Klasse:

ausblenden volle Höhe 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:
public class RelayCommand<TParameter> : ICommand
{
    private readonly Action<TParameter> _execute;
    private readonly Func<TParameter, bool> _canExecute;

    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }

    public RelayCommand(Action<TParameter> execute, Func<TParameter, bool> canExecute = null)
    {
        if (execute == null)
            throw new ArgumentNullException(nameof(execute));

        _execute = execute;
        _canExecute = canExecute ?? new Func<TParameter, bool>(_ => true);
    }
    protected RelayCommand()
    {
    }

    public virtual void Execute(TParameter parameter)
    {
        _execute(parameter);
    }
    public virtual bool CanExecute(TParameter parameter)
    {
        return _canExecute(parameter);
    }

    bool ICommand.CanExecute(object parameter)
    {
        return CanExecute((TParameter)parameter);
    }
    void ICommand.Execute(object parameter)
    {
        Execute((TParameter)parameter);
    }
}

public class RelayCommand : RelayCommand<object>
{
    public RelayCommand(Action execute, Func<bool> canExecute = null)
        : this(_ => execute(), canExecute == null ? null : new Func<objectbool>(_ => canExecute()))
    {
    }
    public RelayCommand(Action<object> execute, Func<objectbool> canExecute = null)
        : base(execute, canExecute)
    {
    }
    protected RelayCommand()
    {
    }
}


Die Nutzung sieht dann so aus:

ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
public class MyCommand : RelayCommand<int>
{
    public override bool CanExecute(int parameter)
    {
        // Do something and return bool
    }
    public override void Execute(int parameter)
    {
        // do something
    }
}


Du darfst dann natürlich nicht base.CanExecute und base.Execute aufrufen, das würde eine NullReferenceException geben.

Oder die nicht ganz MVVM-Konforme Methode:

ausblenden C#-Quelltext
1:
public ICommand MyCommand { get; }					

ausblenden C#-Quelltext
1:
MyCommand = new RelayCommand<int>(() => /* Do something */, () => /* Do something and return bool */);					

Für diesen Beitrag haben gedankt: Schafschaf
Ralf Jansen
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 4700
Erhaltene Danke: 991


VS2010 Pro, VS2012 Pro, VS2013 Pro, VS2015 Pro, Delphi 7 Pro
BeitragVerfasst: Do 08.10.15 12:00 
Macht man das in MVVM wirklich so oder ähnlich? Für mich fühlt sich das zumindest falsch an da man vermutlich auch einfach MenschenVM.Menschen.Add aufrufen könnte und ich erwarten würde das ein Add Logik auch dort zieht.
Palladin007
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 1282
Erhaltene Danke: 182

Windows 11 x64 Pro
C# (Visual Studio Preview)
BeitragVerfasst: Do 08.10.15 12:10 
Soweit ich weiß, sollten Commands alle eine eigene Klasse sein, so lässt es sich besser strukturieren.
In solchen Fällen ist das tatsächlich ein bisschen Overkill, eine Klasse für eine Zeile Code zu schreiben, daher gibt es ja das Konzept mit dem RelayCommand.

Für das Hinzufügen in eine Liste gibt es aber auch andere Möglichkeiten.
Das DataGrid unterstützt glaube ich auch das Hinzufügen und entfernen von Elementen aus der Datenquelle.
Wie das im Detail dann aber aus sieht, weiß ich nicht.
Schafschaf Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 63
Erhaltene Danke: 2

Windows 10
C# (VS 2015)
BeitragVerfasst: Fr 09.10.15 14:38 
Ich hab es jetzt ganz anders gemacht xD
Ich habe in der Klasse Add noch ein Event definiert, dass in der Excecute Methode gefired wird und in den EventArgs ein Mensch Objekt mitgibt.
Ist diese Lösung MVVM Konform?
Palladin007
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 1282
Erhaltene Danke: 182

Windows 11 x64 Pro
C# (Visual Studio Preview)
BeitragVerfasst: Fr 09.10.15 15:36 
Keine Ahnung :D
Ich finde es aber ziemlich unschön, das ist mehr Aufwand als nötig und auch nur um drei Ecken.

Warum verlangst Du im Konstruktor vom Command nicht einfach das ViewModel oder die ObservableCollection als Parameter?
Oder eben das RelayCommand, in dem Umfang finde ich das nicht unbedingt abwegig.
Schafschaf Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 63
Erhaltene Danke: 2

Windows 10
C# (VS 2015)
BeitragVerfasst: Mo 12.10.15 14:14 
Wenn ich mein Viewmodel oder meine ObservableCollection als Parameter im Konstruktor mitgebe und dann da was ändere passiert in der Collection in der Viewmodel Instanz nichts.
Ist ja eine neue Instanz wenn ichs als Parameter mitgebe und als ref lässt sich "this" und die Collection nicht parametrisieren :(
Das mit dem Event find ich eigentlich ganz praktisch, ich muss zwar für jedes Command ein Event definieren und umgekehrt aber dafür bringe ich alles in einer ViewModel Instanz unter.
Korrigier mich bitte wenn ich falsch liege.
Palladin007
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 1282
Erhaltene Danke: 182

Windows 11 x64 Pro
C# (Visual Studio Preview)
BeitragVerfasst: Mo 12.10.15 19:53 
Die ObservableCollection ist wie jeder class-Typ ein Referenz-Typ, das Ding wird immer als Referenz mit gegeben, wenn Du die Collection nicht irgendwo mit einer neuen Instanz überschreibst.
Ich tippe mal darauf, Du übergibst nicht die Collection, um die es dir geht, sondern Du erzuegst mit new eine neue Instanz.
Oder Du möchtest die Collection irgendwo leeren und überschreibst sie dabei mit einer neuen, leeren Instanz. Besser wäre die Clear-Methode.


Das System mit Events ist durchaus sinnvoll, aber auch nur, wenn es etwas gibt über das Informiert werden sollte.
In dem Fall ist es ein einfaches Command, das nur eine Collection bearbeiten soll.
Sinnvoll wäre es z.B., wenn Du einen komplexeren Ablauf hast, der in verschiedenen Abschnitten ausgeführt wird, während der Nutzer über jeden Abschnitt informiert werden soll.
Oder der Ablauf läuft asynchron im Hintergrund und die Beendigung muss dem Nutzer mitgeteilt werden.