I'm showing a list of Orders, (each Order is a UserControl), in more than one Page. Every time the user left-click the control, main window should load & show the order in a new Page using an ICommand.
x:Name of MainWindow is: mainWindow
MainViewModel
This is de view model associated to the MainWindow.
public class MainViewModel : BaseViewModel
{
public INavigator Navigator { get; set; }
public ICommand OpenOrderCommand { get; set; }
public MainViewModel(INavigator navigator) : base()
{
Navigator = navigator;
OpenOrderCommand = new RelayCommand(OpenOrder);
}
private void OpenOrder(object obj)
{
// Loads the order from db
}
}
I've added an InputBindings tag in the UserControl to detect when the user left-click it.
UserControl
<Grid.InputBindings>
<MouseBinding MouseAction="LeftClick"
Command="{Binding DataContext.OpenOrderCommand,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type Window}}}"
CommandParameter="{Binding OrderId}"/>
</Grid.InputBindings>
But OpenOrderCommand is never fired, and I get a XAML binding failure with this text:
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Window', AncestorLevel='1''. BindingExpression:Path=DataContext.OpenOrderCommand; DataItem=null; target element is 'MouseBinding' (HashCode=20815867); target property is 'Command' (type 'ICommand')
How can I execute a ICommand of MainWindow from a UserControl?
UPDATE
It works fine if I use current Page instead of MainWindow.
<Grid.InputBindings>
<MouseBinding MouseAction="LeftClick"
Command="{Binding DataContext.OpenOrderCommand,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type local:HomePage}}}"
CommandParameter="{Binding OrderId}"/>
</Grid.InputBindings>
In the error message it says it can't find the binding source. Try to bind it like this:
<Grid.InputBindings>
<MouseBinding MouseAction="LeftClick"
Command="{Binding OpenOrderCommand}"
CommandParameter="{Binding OrderId}"/>
</Grid.InputBindings>
and to set the DataContext like this:
public MainWindow()
{
InitializeComponent();
DataContext = new MainViewModel();
}
In my test code the Grid only raised the event when I had explecitly set a value for the Background-Property. Transparent was also a value which worked.
Related
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?
I have app, where some controls can 'publish' certain keybinding which in turn appears in context help, for example a button publishes F5 a keybinding:
<Button Name="PublishKeybinding" Content="Publish my keybindings!">
<Button.InputBindings>
<KeyBinding Command="{Binding TestCommand}" Gesture="F5" />
</Button.InputBindings>
</Button>
And there is context help showing all published Commands as clickable list:
<ListView Name="PublicKeybindingsList" ItemsSource="{Binding}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Gesture}">
<TextBlock.InputBindings>
<MouseBinding Command="{Binding Command}" Gesture="LeftClick"/>
</TextBlock.InputBindings>
</TextBlock>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
both(published and context) instances of the command work and execute correctly, however there is a binding error logged when PublicKeybindingsList item's binding is being evaluated:
System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=TestCommand; DataItem='TestVM' (HashCode=47530006); target element is 'KeyBinding' (HashCode=53182860); target property is 'Command' (type 'ICommand')
How can I 'fix' the binding error?
For completeness codebehind used to reproduce the problem:
public class SomeCommand : ICommand
{
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter) => true;
public void Execute(object parameter)
{
MessageBox.Show("Success!");
}
}
public class TestVM
{
public ICommand TestCommand { get; } = new SomeCommand();
}
public partial class MainWindow : Window
{
public ObservableCollection<KeyBinding> AllCommands { get; } = new ObservableCollection<KeyBinding>();
public MainWindow()
{
InitializeComponent();
PublishKeybinding.DataContext = new TestVM();
PublicKeybindingsList.DataContext = AllCommands;
foreach (var cmd in PublishKeybinding.InputBindings.OfType<KeyBinding>())
{
AllCommands.Add(cmd);
}
}
}
Note that the code sample is "Minimal, Reproducible Example" and displayed values and/or keybindings does not make much sense.
I have a content control:
<ContentControl Content="{Binding PageViewModel}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="ContentChanged">
<i:InvokeCommandAction Command="{Binding MyCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ContentControl>
I need to invoke MyCommand every time PageViewModel changes.
However the command is not getting triggered.
Is this possible to do?
You should handle this in the setter of the PageViewModel property:
private object pageViewModel;
public object PageViewModel
{
get { return pageViewModel; }
set
{
pageViewModel = value;
OnPropertyChanged();
MyCommand.Execute(null);
}
}
A ContentControl has no ContentChanged event. It has a protected OnContentChanged method that you could override if you create a custom ControlControl class though:
WPF: How to created a routed event for content changed?
You can create a custom event that you raise in this method.
But the MVVM way of doing this would be to invoke the command in the view model when the PageViewModel property is set.
I am trying to show a ContextMenu in a TreeView. Some entries must be available whether an item was selected or not, but all commands are disabled until I populate the TreeView with at least one item:
<TreeView Name="myTreeView" Width="200px">
<TreeView.ContextMenu>
<ContextMenu>
<MenuItem Command="New" IsEnabled="True" />
</ContextMenu>
</TreeView.ContextMenu>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate>
<TextBlock Text="{Binding Path=Title}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
However, the menu item is still disabled:
The very same command is enabled in the File menu in the menu bar and there is no CanExecute attribute.
How can I enable the context menu entry even if no item exists?
The issue is that the DataContext of the ContextMenu (i.e. where it's looking to bind the New command) is the tree-view node, not the tree view itself. Great if you've got commands related to the node - editing, moving, changing settings.
Not so good for the few that are pan-node like adding and deleting.
As it's looking in the node's DataContext (and no nodes exit) it can't find the command (and it doesn't make sense for it to be there anyway, as the object that manages the TreeView should be creating new items, not the items themselves).
The solution is to bind to a New command that's not in the DataContext of the item, but the TreeView. There's the frustration of dealing with data-binding with ContextMenu... as it's not in the same visual tree as the rest of the window it's often frustrating to deal with.
A solution is to reference the PlacementTarget of the context menu like this:
<TreeView Name="myTreeView" Width="200px">
<TreeView.ContextMenu>
<ContextMenu>
<MenuItem Header="Edit (This command exists in the Node's ViewModel)" Command="{Binding Edit}"/>
<MenuItem Header="New (This command exists in the Window's ViewModel)" Command="{Binding PlacementTarget.DataContext.New, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}}"/>
</ContextMenu>
</TreeView.ContextMenu>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate>
<TextBlock Text="{Binding Path=Title}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Further questions
An example of adding a command as a static resource (Change Window to UserControl if you're in a view that's a UserControl):
<Window.Resources>
<local:MyCommand x:Key="MyCommand"/>
</Window.Resources>
Then referenced with:
<MenuItem Header="MyCommand" Command="{StaticResource MyCommand}"/>
Binding to your commands in the ViewModel (i.e. DataContext) is done like in the first example. In the same way you bind the Title, you can bind to any property, such as an ICommand.
So for a view:
<MenuItem Header="New" Command="{Binding New}"/>
The View Model has a property NewCommand named New:
public NewCommand New { get; private set; }
People often use this because they have a generic ICommand that takes a delegate so they can configure all the actions that relate to that ViewModel. For example:
public class MyCommand : ICommand
{
public event EventHandler CanExecuteChanged;
public Action<object> Action { get; set; }
public MyCommand(Action<object> action)
{
Action = Action;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
Action(parameter);
}
}
Then in the ViewModel, instead of having loads of ICommand classes all implemented, we can just re-use this and get it to do different things:
public MyCommand New { get; private set; }
public MyCommand Delete { get; private set; }
public MyCommand ClearAll { get; private set; }
public MyViewModelConstructor()
{
New = new MyCommand((parameter) =>
{
//Add new object
});
Delete = new MyCommand((parameter) =>
{
//Delete object
});
ClearAll = new MyCommand((parameter) =>
{
//Clear all objects
});
}
I have a View with a button as follows:
<Button Grid.Column="2" Grid.Row="1" Content="Test" Margin="10,4"
Command="{Binding DataContext.CmdTestButtonClicked}"
CommandParameter="{Binding}" />
In the view's code-behind, I set the DataContext to the ViewModel:
public GlobalSettings()
{
InitializeComponent();
...
DataContext = Helpers.IoCHelper.GlobalSettingsVM;
...
}
My ViewModel derives from a base class which exposes the ICommand:
public class GlobalSettingsVM : CollectionViewModel<GlobalSettings> { ... }
public abstract class CollectionViewModel<TModel> : IInstallModuleViewModel, INotifyPropertyChanged,
INotifyDataErrorInfo where TModel : Model, new()
{
...
public ICommand CmdTestButtonClicked
{
get
{
return _testButtonClicked ??
(_testButtonClicked = new RelayCommand(TestButtonClicked));
}
}
protected virtual void TestButtonClicked(object o)
{
// I never get here
}
}
I don't have any other issues using this pattern throughout my application, however all my other implementations have the Button within a ListView, so there I have to use RelativeSource={RelativeSource AncestorType={x:Type ListView}}.
Why would this command never fire? Do I need to set a RelativeSource here as well?
This
Command="{Binding DataContext.CmdTestButtonClicked}"
Implies that the Command will look for a property called DataContext in the object to which the button is bound.
If the DataContext of the button is a GlobalSettingsVM this should work:
Command="{Binding CmdTestButtonClicked}"
You could also use the MVVM Light toolkit wich is very convenient and helping on these situations.
You would get Something like this :
<Button Grid.Column="2" Grid.Row="1" Content="Test" Margin="10,4"
<i:Interaction.Triggers>
<i:EventTrigger EventName="OnClick" >
<Command:EventToCommand Command="{Binding DataContext.CmdTestButtonClicked}" CommandParameter="{Binding}" PassEventArgsToCommand="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>
In my case, I was listening to PreviewMouseLeftButtonDown under constructor of xaml.cs class which was stopping command event callback to the view model.
this.PreviewMouseLeftButtonDown += (s, e) => DragMove();
Instead of above line, in xaml file for window added MouseLeftButtonDown="Window_MouseLeftButtonDown" click handler and handled window drag within it, as below
private void Window_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
DragMove();
}