Laden...

CommandTarget bei MVVM und Commands in mehreren ViewModels

Erstellt von Master15 vor 3 Jahren Letzter Beitrag vor 3 Jahren 872 Views
M
Master15 Themenstarter:in
78 Beiträge seit 2007
vor 3 Jahren
CommandTarget bei MVVM und Commands in mehreren ViewModels

Hallo zusammen,

ich habe vor über 10 Jahren eine private WPF-Anwendung entwickelt, in der ich eine Art MVC-Pattern nutze, wobei ich damals schon (Data)Bindings eingesetzt habe.

Mein Core-Projekt (Model-Schicht mit Anwendungslogik, Datenhaltungsschicht) habe ich mittlerweile unter .NET Core 3.1 laufen.
Die WPF-Oberfläche wollte ich aus diversen Gründen mal neu machen und MVVM eine Chance geben.

Ich habe früher auf RoutedCommand gesetzt und mit InputBindings bzw. CommandBindings gearbeitet.

Beim MVVM Pattern nutzt man ja z.B. DelegateCommand/RelayCommand und legt die jeweiligen Commands im ViewModel an.
So sind z.B. im MVVM-Beispiel hier aus dem Forum folgende Zeilen im MainViewModel enthalten:

public ICommand AddNewEmployeeCommand { get; set; }
public ICommand RemoveEmployeeCommand { get; set; }

Angenommen man bräuchte im EmployeeViewModell aber auch noch Commands, hier mal als primitives Beispiel einfach ein HelloCommand:

public ICommand HelloCommand { get; set; }

public EmployeeViewModel()
{
	HelloCommand = new RelayCommand(HelloCommand_Execute);
}

private void HelloCommand_Execute(object obj)
{
	System.Diagnostics.Debug.WriteLine($"HelloCommand_Execute: Hello {FullName}");
}

In MVVM-Beispiel würde ich das aus dem MainWindow gerne per Button auslösen:

<Button Content="Hallo Mitarbeiter" 
		Command="{Binding Path=SelectedEmployee.HelloCommand}" 
		Margin="5,0,0,0"
		Background="LightYellow" />

Prinzipiell funktioniert das schon, wenn ein Mitarbeiter ausgewählt ist.
Wenn jedoch kein Mitarbeiter ausgewählt ist, wird der Button nicht ausgegraut, was nicht sonderlich schön ist.

Sicherlich wird der ein oder andere jetzt fragen, warum ich den HelloCommand nicht einfach im MainViewModel anlege (also alle Commands im MainViewModel).
Es gibt bei Anwendungen durchaus Commands wie Cut/Copy/Paste, die man in mehreren ViewModels braucht, aber unterschiedliche Umsetzungen benötigen.
Hätte man z.B. ein DocumentViewModel (sei mal dahingestellt, ob das für Texte, Zeichnungen ausgelegt ist) und ein SolutionExplorerViewModel, dann möchte man z.B. bei Copy bei einem aktiven Dokument eine Linie kopieren bzw. im aktiven SolutionExplorer eine Datei kopieren. Also je nach Fokus ist das Ziel des Commands ein anderes. Aber selbst wenn gar kein Dokument ausgewählt ist bzw. der SolutionExplorer nicht den Fokus hat, sollten die MenuItems, (Ribbon)Buttons, ... ausgegraut werden.

Man kann zwar ein CommandTarget setzen, aber ich sehe keine Änderung:

<Button Content="Hallo Mitarbeiter" 
		Command="{Binding Path=SelectedEmployee.HelloCommand}"
		CommandTarget="{Binding Path=SelectedEmployee}"
		Margin="5,0,0,0" />

Wie setzt man das richtig um, wenn man nicht nur im MainViewModell Commands hat?

309 Beiträge seit 2020
vor 3 Jahren
<Button Content="Hallo Mitarbeiter"   
  	Command="{Binding Path=SelectedEmployee.HelloCommand}"   
  	Margin="5,0,0,0"  
  	Background="LightYellow" />  

Prinzipiell funktioniert das schon, wenn ein Mitarbeiter ausgewählt ist.
Wenn jedoch kein Mitarbeiter ausgewählt ist, wird der Button nicht ausgegraut, was nicht sonderlich schön ist.

Woher soll der Button das auch wissen? Dafür kannst du es ja an die **IsEnabled **Eigenschaft binden.

3.170 Beiträge seit 2006
vor 3 Jahren

Hallo,

bei Commands auf Buttons ist für das enablen/disablen normalerweise die CanExecute-Methode und das CanExecuteChanged-Event zuständig. Wenn Du die richtig implementierst, solte Dein Button vom Command die Information automatisch erhalten.

Edit: Und ich sollte richtig lesen - kein ausgewählter Employee, kein Command, kein CanExecute... dann wie JimStark geschrieben hat, über IsEnabled.

Gruß, MarsStein

Non quia difficilia sunt, non audemus, sed quia non audemus, difficilia sunt! - Seneca

M
Master15 Themenstarter:in
78 Beiträge seit 2007
vor 3 Jahren

OK, danke für die Info.

Ich hatte angenommen, dass das Binding eines Commands intelligenter sei und prüft, ob das Objekt bzw. Command evt. null ist und dass dann generell als CanExecute=false angenommen wird.
Da bleibt dann vermutlich doch nur der Weg über IsEnabled und einem NullToBooleanConverter. Hatte gehofft das zu vermeiden.

Damit erübrigt sich dann auch meine Frage mit dem Fokus. Da muss ich mir im MainViewModel selbst merken, welches Dokument/Panel gerade aktiv ist und entsprechend das Command daran binden und ebenfalls mit IsEnabled arbeiten, falls gerade gar nichts aktiv ist.

5.657 Beiträge seit 2006
vor 3 Jahren

Ich hatte angenommen, dass das Binding eines Commands intelligenter sei

Ich glaube, du hast den Artikel nicht richtig gelesen. Wenn du in deinem ersten Beispiel ein CommandParameter verwendest, und diesen in CanExecute abfragst, dann funktioniert es wie gewünscht.

Weeks of programming can save you hours of planning

M
Master15 Themenstarter:in
78 Beiträge seit 2007
vor 3 Jahren

Wenn du in deinem ersten Beispiel ein CommandParameter verwendest, und diesen in CanExecute abfragst, dann funktioniert es wie gewünscht.

Unter "3. Commands" wird eine CommandParameter-Eigenschaft erwähnt und in einem Beispiel mit einem ItemsControl veranschaulicht.

Im Beispielprojekt (MVVMTestProject.zip) gibt es eine ListView für die Mitarbeiter.
Da kann ich natürlich einen Button einfügen und auch das HelloCommand aufrufen:

<ListView ItemsSource="{Binding Employees}" SelectedItem="{Binding SelectedEmployee}">
	<ListView.ItemTemplate>
		<DataTemplate DataType="{x:Type myApp:EmployeeViewModel}">
			<StackPanel Orientation="Horizontal">
				<TextBlock>
					<Run Text="{Binding FirstName}" />
					<Run Text="{Binding LastName}" />
					<Run Text=" - " />
					<Run Text="{Binding Team.Name}" />
				</TextBlock>
				<Button Content="Hallo" Command="{Binding Path=HelloCommand}" />
			</StackPanel>
		</DataTemplate>
	</ListView.ItemTemplate>
</ListView>

Wofür ich die CommandParameter-Eigenschaft brauche, ist mir nicht klar.
Der Button ruft das HelloCommand auch so korrekt auf und wird auch ausgegraut, wenn CanExecute false entspricht.

Mir geht es z.B. um Buttons, die nicht in der ListView stecken, aber dennoch Commands eines "Sub-ViewModels" aufrufen.

<!-- Show Buttons databound to the ViewModel's Commands -->
<StackPanel Margin="0,5,0,0" Orientation="Horizontal">
	<Button Content="Neuen Mitarbeiter hinzufügen" 
			Command="{Binding AddNewEmployeeCommand}" 
			Style="{StaticResource GreenButton}" />
	<Button Content="Ausgewählten Mitarbeiter löschen" 
			Command="{Binding RemoveEmployeeCommand}" 
			CommandParameter="{Binding SelectedEmployee}" 
			Style="{StaticResource RedButton}"
			Margin="5,0,0,0" />
	<Button Content="Hallo Mitarbeiter" 
			Command="{Binding Path=SelectedEmployee.HelloCommand}"
			Margin="5,0,0,0" />
</StackPanel>

Ist SelectedEmployee gleich null, dann wird der Button "Hallo Mitarbeiter" nicht ausgegraut.
Wie JimStark und MarsStein schreiben, bleibt einen scheinbar nicht anderes übrig, in dem Fall zusätzlich über IsEnabled zu gehen.

<Button Content="Hallo Mitarbeiter" 
		Command="{Binding Path=SelectedEmployee.HelloCommand}"
		IsEnabled="{Binding Path=SelectedEmployee, Converter={StaticResource NullToBooleanConverter}}"
		Margin="5,0,0,0" />

Alternativ kann man natürlich auch im MainViewModel ein IsEmployeeSelected-Property (return SelectedEmployee != null) einbauen, was im Beispielprojekt schon enthalten ist:

<Button Content="Hallo Mitarbeiter" 
		Command="{Binding Path=SelectedEmployee.HelloCommand}"
		IsEnabled="{Binding Path=IsEmployeeSelected}"
		Margin="5,0,0,0" />

Deinen Satz, dass es mit einem CommandParameter wie gewünscht funktioniert, ist mir nicht klar.
Kannst du das bitte etwas genauer erklären, wie das mit einem CommandParameter funktioniert, aber ohne IsEnabled?

5.657 Beiträge seit 2006
vor 3 Jahren

Es ergibt keinen Sinn, das Command in einem ViewModel zu definieren, das nicht existiert, solange es keine Auswahl gibt. Definiere das Command in deinem ViewModel des Fensters/des Controls und übergib den Employee als CommandParameter:

Command="{Binding HelloCommand}" CommandParmeter="{Binding SelectedEmployee}"

Dann kannst du das Command so zuweisen:

HelloCommand = new RelayCommand(param => SayHello((EmployeeViewModel)param), param => param != null);

Weeks of programming can save you hours of planning

M
Master15 Themenstarter:in
78 Beiträge seit 2007
vor 3 Jahren

Aber damit ziehst du doch das HelloCommand aus dem EmployeeViewModel raus und definierst es im MainViewModel.
Letztlich würde das dazu führen, dass man das für jedes Command so machen müsste und dann per C#-Code das weiterreicht.
Das ergibt für mich keinen Sinn oder ich verstehe dich nicht richtig.

5.657 Beiträge seit 2006
vor 3 Jahren

Hab dir ja erklärt, warum ich das so machen würde.
Du kannst es auch anders machen, dann hat dir JimStark ja schon geschrieben, wie es geht.

Weeks of programming can save you hours of planning