WPF: Calling ICommand from Ancestor in Code - c#

In my MVVM Application i have the MainWindow which displays my child view in a ContentControl Element.
The MainWindow DataContext houses an ICommand which allows the child views to change the view:
public ICommand ChangePageCommand { get; }
private void ChangePage(object parameter)
{
Type paramType = parameter as Type;
if (paramType == typeof(Main))
{
ViewModel = new Main();
}
}
The ICommand works great when im Calling it from the childs wpf like this:
<Button
Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType {x:Type Window}}, Mode=OneWay}"
CommandParameter="{x:Type ViewModel:Main}">
Connect to Process
</Button>
But now i want to call the ICommand from the ViewModel of the childview, because after clicking the button i need to execute some code and dependent on the result i want to ether change the window or display an error message.
I have tried a lot of searching on how to do this, but I could not find anything about calling an ICommand of an Ancerstor from code.
Maybe I'm looking at wrong and there is a different way to achieve what I want?

Related

View inside other View (WPF/MVVM)

I have this UserControl called ControlButtonsView
<Grid>
<Button Style="{StaticResource MinimizeButton}" Command="{Binding MinimizeAppCommand}" Height="40" Width="120" VerticalAlignment="Top" HorizontalAlignment="Right"/>
<Button Content="X" Style="{StaticResource ExitButton}" Command="{Binding ExitAppCommand}" Height="40" Width="60" VerticalAlignment="Top" HorizontalAlignment="Right"/>
</Grid>
and ControlButtonsViewModel
class ControlButtonsViewModel
{
private MainWindow _mainWindow;
public ICommand MinimizeAppCommand { get; set; }
public ICommand ExitAppCommand { get; set; }
public ControlButtonsViewModel(MainWindow mainWindow)
{
_mainWindow = mainWindow;
MinimizeAppCommand = new BaseICommand(MinimizeApp);
ExitAppCommand = new BaseICommand(ExitApp);
}
public void MinimizeApp(object obj)
{
_mainWindow.WindowState = System.Windows.WindowState.Minimized;
}
public void ExitApp(object obj)
{
_mainWindow.Close();
}
}
In my MainWindow.xaml.cs
this.DataContext = new AppManagerViewModel();
AppManagerViewModel controls the switching between Views
What I want is to be able to use this ControlButtonsView with its ControlButtonsViewModel in multiple other Views, this view is a UserControl with a minimize and a maximize buttons and I want to use them in multiple Views, in LogInView, MenuView etc.
If there is an easier way to do this please tell me) Thank you.
Window logic does not belong to the view model. View model does not care about UI. You must always implement the view model pretending like there is no UI, only a model.
Therefore having a reference of MainWindow in you view model will lead to a tight coupling of the application to the view/UI.
The goal of MVVM is to remove this tight coupling. Obviously, due to the tight coupling you have introduced, you are currently not implementing the MVVM pattern (you are implementing it wrong).
For example, you won't be able to test the view model without creating a view.
Injecting the view as constructor dependency makes it even worse.
Because the commands execute UI logic (close, minimize), they have to be moved to a control - to the view component from a MVVM point of view.
To make those commands available throughout your view or globally relative to the actual visual tree, you should implement those commands as routed commands e.g. on your MainWindow, which you want to control via commanding.
Since routed commands are static, they can be referenced by every other control. Because they are routed, they can be used everywhere in the same visual tree that the command target (the MainWindow) belongs to.
Internally the command, once executed, will raise a routed event which will traverse the visual tree until it finds a handler.
Commanding Overview
In your case, MainWindow will register the Execute and CanExecute handler to close or minimize itself.
The following example implements only the logic to close the Window.
You can follow the pattern to provide additional logic e.g. to maximize the Window:
MainWindow.xaml.cs
partial class MainWindow : Window
{
public static readonly RoutedUICommand CloseWindowRoutedCommand = new RoutedUICommand(
"Closes the application.",
nameof(MainWindow.CloseWindowRoutedCommand),
typeof(MainWindow));
public MainWindow()
{
InitializeComponent();
this.CommandBindings.Add(
new CommandBinding(MainWindow.CloseWindowRoutedCommand,
ExecuteCloseWindow,
CanExecuteCloseWindow));
}
private void CanExecuteCloseWindow(object sender, CanExecuteRoutedEventArgs e) => e.CanExecute = true;
private void ExecuteCloseWindow(object sender, ExecutedRoutedEventArgs e) => Close();
}
ControlButtonsView.xaml
<Grid>
<-- ICommand traverse visual tree until handler(s) is found -->
<Button Content="X" Command="{x:static MainWindow.CloseWindowRoutedCommand}" />
</Grid>
In AppManagerViewModel, add a property of ControlButtonsViewModel.
public ControlButtonsViewModel ControlButtonsViewModel {get; set;}
In the constructor of AppManagerViewModel, add
ControlButtonsViewModel = new ControlButtonsViewModel();
In Xaml of AppManagerView,
<ControlButtonsView DataContext="{Binding ControlButtonsViewModel}" ... />

Mvvm-Light User Control RelayCommand TemplateBinding

[UWP - Windows 10]
I'm new to MVVM-Light and so I got some starter issues. I created a custom Usercontrol which is called TileToolbar and is containing this xaml:
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<RadioButton Style="{StaticResource NavRadioButtonStyle}" Tag="" Foreground="Green"></RadioButton>
<RadioButton Style="{StaticResource NavRadioButtonStyle}" Tag="" Foreground="Green"></RadioButton>
<RadioButton Style="{StaticResource NavRadioButtonStyle}" Tag="" Foreground="Green"></RadioButton>
</StackPanel>
Now I want to add a RelayCommand for each RadioButton and I want each Page which is containing the custom usercontrol to be able to bind a custom RelayCommand.
My first Approach was to set the Command Property in xaml and to implement the method in the viewmodel (e.g. MainViewModel) which actually worked - shorten xaml:<RadioButton Command="{Binding Command}"></RadioButton>
Because I wanted to set the Propery in the Page using the customcontrol like this <TileToolbar PinCommand={Binding Command}></TileToolbar> I created a dependency property of type RelayCommand but the TemplateBinding didn't work.
So my question:
How would I create a property like PinCommand of type RelayCommand in the UserControl so I can later bind to it in xaml for example on the Mainpage?
So my question: How would I create a property like PinCommand of type RelayCommand in the UserControl so I can later bind to it in xaml for example on the Mainpage?
You can register a PinCommand in the type of RelayCommand in your UserControl's code behind for example like this:
public static DependencyProperty PinCommandProperty = DependencyProperty.Register("PinCommand", typeof(RelayCommand), typeof(TileToolbar), new PropertyMetadata(null));
public RelayCommand PinCommand
{
get
{
return (RelayCommand)GetValue(PinCommandProperty);
}
set
{
SetValue(PinCommandProperty, value);
}
}
Now you can use this TileToolbar in your MainPage for example like this:
<Controls:TileToolbar Grid.Row="1" VerticalAlignment="Bottom" PinCommand="{Binding pinCommand, Mode=OneWay}" />
Code in view model is like this:
private RelayCommand _pinCommand;
public RelayCommand pinCommand
{
get
{
if (_pinCommand == null)
{
_pinCommand = new RelayCommand(() =>
{
//TODO:
},
() => true);
}
return _pinCommand;
}
}
And for the work of connecting the Command of RadioButton to the PinCommand of TileToolBar, you can in your user control for example code like this:
<RadioButton Tag="" Foreground="Green" Command="{x:Bind PinCommand, Mode=OneWay}"></RadioButton>

ICommand Dependency Property

I have an UserControl with a button inside. This button needs to add some items to a Grid that's inside said UC. I'm aware I can do this with a Click event.
The issue here is I am using MVVM and altering data outside their corresponding ViewModel would break the format (So to say).
Is there a way to create an ICommand Dependency Property so I can bind said DP to the button and have the functionality of adding the item to the Grid in my ViewModel? (I already have the List in both my UC and my ViewModel and they are working as expected)
Thank you.
Found a way to solve it in the way I was trying to. Leaving the answer here so people may use it:
1) In your User Control's code-behind, create a Dependency Property. I choose ICommand, since in my ViewModel I set it as a DelegateCommmand:
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register(
"Command",
typeof(ICommand),
typeof(UserControl));
public ICommand Command
{
get
{
return (ICommand)GetValue(CommandProperty);
}
set
{
SetValue(CommandProperty, value);
}
}
2) In your UserControl's XAML code, bind this Dependency Property (In this case, a button):
<Grid DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}">
<Button Command="{Binding Command}" />
</Grid>
3) Next, on your ViewModel, declare a Command property and configure accordingly:
public ICommand ViewModelCommand { get; set; }
public ViewModelConstructor()
{
ViewModelCommand = new DelegateCommand(ViewModelCommandExecute);
}
private void ViewModelCommandExecute()
{
// Do something
}
4) Finally, on your View where the UserControl is nested, we declare the binding:
<UserControls:UserControl Command={Binding ViewModelCommand}/>
This way, the binding will take place and you can bind Commands from the buttons of any User Control to your ViewModels without breaking MVVM.
The basic way is to create an Object (ie MyCommand) which implements ICommand, and nest it inside your ViewModel. Inside MyCommand you have no access to your ViewModel. You can workaround it (ie pass a reference to the ViewModel in MyCommand constructor) but at the end it's too much code (for simple stuff like this). I think almost nobody really do this.
Most use a DelegateCommand which resolve (most of) the above issues.
Last but not least, just use event handlers.
If you code them simply like this:
void Grid_MouseMove(object sender, MouseEventArgs e)
{ viewModel.SaveMousePosition(e.GetPosition()); }
you are not breaking any MVVM rule.
And you can't handle the above event with Commands.
There is no Command for MouseMove (there is none for most events), and you can't pass event parameters in a Command.
You can handle every event using Interaction.Triggers like this
But you still miss the capability to handle event parameters (and add ugly XAML).
To me, until WPF will support databinding in event handlers, like
Grid MouseMove="{Binding SaveMousePosition(e)}"
code behind is still the most effective way to handle events.
I faced similar problem and this question/answers helped me the most; so I will post my solution here in case somebody else will google it later. Made with mvvm light.
I had a custom winforms control as a Model and a WPF control as a View. So, xaml of View (I have an usercontrol for my View, no app.xaml):
<UserControl.Resources>
<ResourceDictionary>
<viewModel:ViewModelLocator x:Key="Locator" />
</ResourceDictionary>
</UserControl.Resources>
<UserControl.DataContext>
<Binding Path = "Main" Source="{StaticResource Locator}"></Binding>
</UserControl.DataContext>
<Grid>
<Button Command="{Binding Zoom, ElementName=Wrapper}"></Button>
<viewModel:ProfileWrapper x:Name="Wrapper" >
</viewModel:ProfileWrapper>
</Grid>
Click of a Button is routed to a RelayCommand Zoom in ProfileWrapper (which is where my Model implemented)
Then the xaml of ProfileWrapper is straghtforward:
<Grid>
<WindowsFormsHost>
<local:ManualControl x:Name="abc" ></local:ManualControl>
</WindowsFormsHost>
</Grid>
And the codebehind of ProfileWrapper :
public partial class ProfileWrapper : UserControl
{
public ProfileWrapper()
{
InitializeComponent();
test = abc;
Command = new RelayCommand(() => test.bZoomIn());
}
public ManualControl test;
public RelayCommand Zoom { get; set; }
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register(
"Zoom",
typeof(ICommand),
typeof(ProfileWrapper));
public ICommand Command
{
get
{
return (ICommand)GetValue(CommandProperty);
}
set
{
SetValue(CommandProperty, value);
}
}
}
My MainViewModel class is empty and all fuctionality goes to ProfileWrapper class, which might be bad, but at least it works.

Is there a way to programatically bind to InputBinding Command property?

I know it's a generic title, but my question is specific. I think it will boil down to a question of practice. So, I have the following code:
public partial class MainWindow : Window
{
InitializeComponent();
MyViewModel viewModel = new MyViewModel();
this.myGrid.DataContext = viewModel;
}
public class MyViewModel
{
public ICommand SomeCommandProperty { get { return this.someCommandProperty; }}
}
public class ComponentCollection : Panel
{
public ComponentCollection()
{
for (int i = 0; i < n; i++)
{
this.Children.Add(new Component());
}
}
}
public class Component : UIElement
{
public Component()
{
this.InputBindings.Add(new MouseBinding(SomeCommandProperty, new MouseGesture(MouseAction.LeftClick)));
}
}
I could easily aggregate the ViewModel that owns SomeCommandProperty into the Component class, but I'm currently waiving that option assuming there is another way.
Component is a child of ComponentCollection which is child of a Grid which DataContext is MyViewModel. ComponentCollection as the name suggests contains a collection of Components.
<Grid Name="myGrid">
<someNamespace:ComponentCollection x:Name="componentCollection"/>
</Grid>
It's the same scenario as the XAML below, but with TextBlock. I guess I'm trying to replicate what's being done in the XAML below programatically. Again, Component's top most ancestor's DataContext is set to ViewModel.
<Grid Name="myGrid">
<TextBlock Text="SomeText">
<TextBlock.InputBindings>
<MouseBinding Command="{Binding SomeCommandProperty}" MouseAction="LeftClick" />
</TextBlock.InputBindings>
</TextBlock>
</Grid>
Update 1
Basically, I have a custom control which inherit from a Panel which children are a collection of Component. It's not a hack, like I've mentioned, I could directly have access to SomeCommandProperty If I aggregate the ViewModel into Component. Doing so, however, feels icky. That is, having direct access to ViewModel from a Model.
I guess the question I'm asking is. Given the situation that Component's parent UIElement's DataContext is set to MyViewModel, is it possible to access SomeCommandProperty without Component owning a reference to the MyViewModel that owns SomeCommandProperty? Programatically, that is.
Using ItemsControl doesn't change the fact that I still need to bind SomeCommandProperty to each Items.
Update 2
See code above.
Update 3
Apparently, there isn't a mechanism I know of that will set the binding on the Command property of an InputBinding.
For example if my Component class were to Inherit from ButtonBase instead of UIElement, I would have the Command property to which I could easily set the binding programatically using FrameWorkElement's SetBinding. Unfortunately, I can't do this with InputBinding's Command property.
public class Component : ButtonBase
{
public Component()
{
System.Windows.Data.Binding binding = new System.Windows.Data.Binding
{
RelativeSource = new System.Windows.Data.RelativeSource(System.Windows.Data.RelativeSourceMode.FindAncestor, typeof(ComponentCollection), 1 ),
Path = new PropertyPath("DataContext.SomeCommandProperty")
};
// I can do this.
this.SetBinding(this.CommandProperty, binding);
// But I want to do something like below. Note: It's a pseudo code.
MouseBinding mouseBinding = new MouseBinding();
mouseBinding.SetBinding(mouseBinding.CommandProperty, binding);
this.InputBindings.Add(mouseBinding);
}
}
Update 4
BindingOperations.SetBinding can be used on Objects that don't have direct access to SetBinding.
Solution
MouseBinding mouseBinding = new MouseBinding();
BindingOperations.SetBinding(mouseBinding, MouseBinding.CommandProperty, binding);
this.InputBindings.Add(mouseBinding);
Use an ItemsControl for this. Don't try to hack something together yourself when there is a built-in class that already does this.
You can also access the ViewModel from a parent UI element in the Visual Tree by using a RelativeSource binding:
<ItemsControl>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="SomeText">
<TextBlock.InputBindings>
<!-- See how I'm using RelativeSource to get a hold of the DataContext of the parent ItemsControl -->
<MouseBinding Command="{Binding DataContext.SomeCommandProperty,
RelativeSource={RelativeSource AncestorType=ItemsControl}}"
MouseAction="LeftClick" />
</TextBlock.InputBindings>
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

How can I invoke a command on the ViewModel on SelectionChanged of a ListView?

In a MVVM/WPF environment, I want to invoke a command (ComputeCommand) on the ViewModel when the SelectionChanged event of a ListView is raised. How can this be done, either in XAML or in C#?
Here is my command class. I have tried MainViewModel.Instance.MyCommand.Execute(); in the codebehind, but it's doesn't accept that.
public class ComputeCommand : ICommand
{
public ComputeCommand(Action updateReport)
{
_executeMethod = updateReport;
}
Action _executeMethod;
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
_executeMethod.Invoke();
}
}
I really recommend the use of a Mvvm framework like MVVM Light, so you can do something like this:
XAML:
xmlns:MvvmLight_Command="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras"
xmlns:Custom="clr-namespace:System.Windows.Interactivity; assembly=System.Windows.Interactivity"
<ListBox>
...
<Custom:Interaction.Triggers>
<Custom:EventTrigger EventName="SelectionChanged ">
<MvvmLight_Command:EventToCommand PassEventArgsToCommand="False" Command="{Binding Path=ComputeCommand}"/>
</Custom:EventTrigger>
</Custom:Interaction.Triggers>
</Listbox>
ViewModel:
public RelayCommand ComputeCommand{ get; private set; }
This is IMO an elegant way to keep your events wiring clean and tidy.
To answwer your question - you are missing a parameter. THis call should work:
MainViewModel.Instance.MyCommand.Execute(null);
However, you dont need an ICommand for that, this interface serves different purpose.
What you need is to either handle SelectionChanged on view side
var vm = DataContext as YourViewModelType;
if (vm != null)
{
vm.Compute(); //some public method, declared in your viewmodel
}
or to handle it on viewmodel side by binding to IsSelected property of item container
In general: To invoke a command when an event of a control is raised, you can use EventTriggers.
<ListView>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged" >
<i:InvokeCommandAction Command="{Binding CommandToBindTo}" CommandParameter="{Binding CommandParameterToBindTo}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ListView>
To do this, you need to reference System.Windows.Interactivity.dll in your XAML:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
That being said, you should use a MVMM framework, for example MVVM to simplify the implementation of commands in general. It is not maintainable in the long run to have a single class for every command you need. A framework like MVVMLight or PRISM provides DelegateCommands which allow you to create new commands from delegates (methods on your ViewModel) directly.
First you have to define a binding for Command, so the function that have to be
executed on the invokation of the command.
This can be done in XAML, like:
<CommandBinding Command="name_of_the_namespace:ComputeCommand" Executed="ComputeCommandHandler" />
After, you can, for example in some class initialize a command, like:
public class AppCommands {
public static readonly ICommand ComputeCommand =
new RoutedCommand("ComputeCommand", typeof(AppCommands));
}
and after can use it like:
AppCommands.ComputeCommand.Execute(sender);
When you're dealing with WPF, so MVVM pattern, you need to write the much more code the usual, but benefit from flexibility of it.

Categories

Resources