I need a button to execute two commands from two different ViewModels. I am using communitytoolkit.mvvm
Lets make an example:
INavigator:
public enum ViewType
{
MainView,
ItemView,
AddNewItemView
}
public interface INavigator
{
BaseVM CurrentViewModel { get; set; }
}
Navigator:
[INotifyPropertyChanged]
public partial class Navigator : INavigator
{
[ObservableProperty]
BaseVM currentViewModel = new DashboardVM();
[ICommand]
public void UpdateViewModel(object parameter)
{
if (parameter is ViewType)
{
ViewType viewType = (ViewType)parameter;
switch (viewType)
{
case ViewType.MainView:
CurrentViewModel = new MainViewModel();
break;
case ViewType.ItemView:
CurrentViewModel = new ItemViewModel();
break;
case ViewType.AddNewItemView:
CurrentViewModel = new AddNewItemViewModel();
break;
default:
break;
}
}
}
}
MainView:
xmlns:nav="clr-namespace:........."
<StackPanel DataContext="{Binding Navigator}">
<Button Command="{Binding UpdateViewModelCommand}"
CommandParameter="{x:Static nav:ViewType.MainView}"/>
<Button Command="{Binding UpdateViewModelCommand}"
CommandParameter="{x:Static nav:ViewType.ItemView}"/>
<Button Command="{Binding UpdateViewModelCommand}"
CommandParameter="{x:Static nav:ViewType.AddNewItemView}"/>
</StackPanel>
<ContentControl DataContext="{Binding Navigator}"
Content="{Binding CurrentViewModel}"/>
AddNewItemView:
<.....>
<Button Command="{Binding AddNewItemCommand}"/>
<.....>
AddNewItemViewModel:
[INotifyPropertyChanged]
public partial class ItemViewModel
{
[ICommand]
public void AddNewItem()
{
//Add new Item
}
}
MainViewModel:
[INotifyPropertyChanged]
public partial class MainViewModel
{
public INavigator Navigator { get; set; } = new Navigator();
}
I want to Bind:
Command="{Binding UpdateViewModelCommand}" CommandParameter="{x:Static nav:ViewType.ItemView}"
To the Button in the AddItemView, basically I want that Button to execute the "AddNewItemCommand" and then "UpdateViewModelCommand" to go back to ItemView.
Maybe my approach is wrong, or is there an easier way to navigate?
CodePudding user response:
You must improve your view model class design.
Your navigation model is controlled by the View Model. This means the general navigation logic takes place in the View Model. The View only triggers the navigation.
Therefore, the navigation back to a particular view after the current view's View Model class has completed its operation, must be executed by the View Model too.
Moving the navigation logic to a dedicated type, like your created INavigator
, to encapsulate the logic and to enable sharing, is the first step.
Next step is to share the same instance of INavigator
between all View Model classes that participate in the navigation logic.
You can further improve the design by adding a INavigator.NavigateToPrevious
method to the INavigator
.
This way the view model class must not know any particular ViewType
if its only goal is to navigate back to the previous view model.
To implement such a feature, INavigator
would have to maintain a navigation history e.g., by using a Stack<ViewType>
as backing store, to navigate backwards.
To do so, simply push the current ViewType
of the CurrentViewModel
to the Stack<ViewType>
and pop it later in your NavigateToPrevious
method:
INavigator.cs
interface INavigator
{
// The set() of this property must be private to add robustness
BaseVM CurrentViewModel { get; }
NavigateToViewModelCommand { get; }
void NavigateTo(ViewType nextViewType);
bool NavigateToPrevious();
}
Navigator.cs
Note how the example uses a Dictionary
that returns a view model factory to avoid the implementation of a type switch.
Also note how using a Stack<ViewType>
enables a navigation history.
class Navigator : INavigator
{
[ObservableProperty]
BaseVM currentViewModel = new DashboardVM();
private Dictionary<ViewType, Func<INavigator, BaseVM>> ViewModelFactoryMap { get; }
private Stack<ViewType> ViewHistory { get; }
private ViewType CurrentViewType { get; set; }
public Navigator()
{
this.ViewHistory = new Stack<ViewType>();
this.ViewModelFactoryMap = new Dictionary<ViewType, Func<INavigator, BaseVM>>
{
{ ViewType.ItemView, navigator => new ItemViewModel(navigator) },
{ ViewType.MainView, navigator => new MainViewModel(navigator) },
{ ViewType.AddNewItemView, navigator => new AddNewItemViewModel(navigator) },
};
// Load initial view
NavigateTo(ViewType.MainView);
}
public void NavigateTo(ViewType nextViewType)
{
if (this.ViewModelFactoryMap.TryGetValue(nextViewType, out Func<INavigator, BaseVM> viewModelFactory))
{
// Maintain a navigation history
this.ViewHistory.Push(this.CurrentViewType);
this.CurrentViewModel = viewModelFactory.Invoke(this);
this.CurrentViewType = nextViewType;
}
}
public bool NavigateToPrevious()
{
if (this.ViewHistory.TryPop(out Viewtype previousViewType))
{
NavigateTo(previousViewType);
return true;
}
return false;
}
[ICommand]
public void NavigateToViewModelCommand(object commandParameter)
{
if (commandParameter is ViewType viewType)
{
NavigateTo(viewtype);
}
}
}
AddNewItemViewModel.cs
Because AddNewItemViewModel
must know how to navigate, its constructor must accept a shared INavigator
instance. Follow this pattern for all View Model classes that must know how to navigate.
Since INavigator
exposes a INavigator.NavigateToPrevious
method, View Model classes can navigate to the previous view anonymously:
class AddNewItemViewModel : BaseVM
{
public ICommand AddNewItemCommand => new DelegateCommand(ExecuteAddNewItemCommand);
private INavigator Navigator { get; }
public AddNewItemViewModel(INavigator navigator)
{
this.Navigator = navigator;
}
private void ExecuteAddNewItemCommand(object commandParameter)
{
// TODO::Execute command
this.Navigator.NavigateToPrevious();
}
}
CodePudding user response:
Microsoft.Xaml.Behaviors.Wpf allows you to invoke multiple Commands by one event.
<Button>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:InvokeCommandAction Command="{Binding AddNewItemCommand, ElementName=...}"/>
<i:InvokeCommandAction Command="{Binding UpdateViewModelCommand, ElementName=...}"
CommandParameter="{x:Static nav:ViewType.ItemView}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>