I'm quite new to WPF so it would be better that I will describe the intent:
I have multiple User Controls inside the main window. Each User Control has its own buttons, that I would like to bind to command using keyboard shortcuts.
The MainWindow.xaml is something like this:
<Window ...>
<Window.DataContext>
<viewModel:MainViewModel/>
</Window.DataContext>
...
<ContentControl Grid.Column="1"
Margin="5"
Content="{Binding HomeToolbarView}"/>
...
</Window>
As you can See, I'm using some VM binding inside the Code-Behind. In app.xaml:
<Application ...>
<Application.Resources>
<ResourceDictionary>
...
<DataTemplate DataType="{x:Type viewModel:HomeToolbarViewModel}">
<view:HomeToolbar/>
</DataTemplate>
...
</ResourceDictionary>
</Application.Resources>
</Application>
Where I bind the HomeToolbar to its HomeToolbarViewModel and in MainViewModel:
public class MainViewModel : ObservableObject {
// This is bound to the XAML
private object homeToolbarView;
...
public HomeToolbarViewModel HomeToolbarVM { get; set; }
...
public object HomeToolbarView
{
get { return homeToolbarView; }
set
{
homeToolbarView = value;
OnPropertyChanged();
}
}
...
public MainViewModel() {
...
HomeToolbarVM = new HomeToolbarViewModel();
homeToolbarView = HomeToolbarVM;
...
}
}
Finally the HomeToolbar.xaml:
<UserControl xmlns:self="clr-namespace:ChordTuner.MVVM.ViewModels"
...>
<UserControl.CommandBindings>
<!-- Error: Compiler binds this to the DataContext of the view, i.e. HomeToolbar.xaml.cs, and not to the real DataContext, i.e. the HomeToolbarViewModel!
<CommandBinding Command="self:HomeToolbarCommands.NewCommand" Executed="{Binding NewCommand_Executed}" .../> />
</UserControl.CommandBindings>
<!-- Only to simplify -->
<StackPanel>
<Button Command="self:HomeToolbarCommands.NewCommand" />
<!-- No error, the command is correctly bound to the DataContext, i.e. the VM! -->
<Button Command={Binding ARelayCommandProp}"/>
</StackPanel>
</UserControl>
With HomeToolbarViewModel.cs:
namespace ChordTuner.MVVM.ViewModels {
public class HomeToolbarViewModel : ObservableObject {
...
private void NewCommand_Executed(object sender, ExecutedRoutedEventArgs e)
{
MessageBox.Show("New from XAML");
}
...
}
public static class HomeToolbarCommands
{
public static readonly RoutedUICommand NewCommand = new RoutedUICommand
(
"New1",
"New2",
typeof(HomeToolbarCommands),
new InputGestureCollection()
{
new KeyGesture(Key.N, ModifierKeys.Control)
}
);}
}
Visual Studio outputs the following error:
CS1061 'HomeToolbar' does not contain a definition for 'NewCommand_Executed' and no accessible extension method 'NewCommand_Executed' accepting a first argument of type 'HomeToolbar' could be found (are you missing a using directive or an assembly reference?) ChordTuner
It is strange to me that buttons using RelayCommand class are correctly bound to the the view model (as instructed in app.xaml), but the CommandBindings does not.
The goal of all of this is to assign global shortcuts to all User Control buttons. Using InputBindings imposes that User Control is focused. But I have multiple User Controls that need to respond to differente commands/shortcuts regardless focus.
UPDATE
This is the HomeToolbarView constructor
public HomeToolbar()
{
InitializeComponent();
for (int index = 0; index < MidiIn.NumberOfDevices; index++)
{
midiInComboBox.Items.Add(MidiIn.DeviceInfo(index).ProductName);
}
for (int index = 0; index < MidiOut.NumberOfDevices; index++)
{
midiOutComboBox.Items.Add(MidiOut.DeviceInfo(index).ProductName);
}
// This event is called when DataContext is changed. This happens because we binded the HomeToolbarViewModel inside the app.xaml.
DataContextChanged += new DependencyPropertyChangedEventHandler(HomeToolbar_DataContextChanged);
}
...
void HomeToolbar_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
viewModel = (HomeToolbarViewModel?)DataContext;
if (viewModel == null) return;
if (MidiOut.NumberOfDevices > 0)
{
viewModel.SelectedMidiOutIndex = 0;
}
if (MidiIn.NumberOfDevices > 0)
{
viewModel.SelectedMidiInIndex = 0;
}
}
Do someone explain what I'm missing?
To handle global input events, you must use
RoutedCommanddefinitions and then handle them at the visual root e.g., theMainWindow.Using routed commands allows you to decouple the different scopes (data contexts) so that each data context don't
ICommandproperty and logicICommand.Using a routed command in this scenario allows to execute the actual view model command from a single place amd within the correct data context.
If commands should have a smaller scope, then define the command bindings on an appropriate common parent of the controls that must share a command action. Every
UIElementcan defineCommandBindingobjects and therefore act as a routed command scope.MainWindow.cs
HomeToolbar.xaml
HomeToolbarViewModel.cs
This is an alternative version where the
MainWindowis allowed to handle the view model command anonymously, without an explicit reference to the view model i.e. without explicit knowledge of the actual view model type andICommandidentity (commands property name or member path).It's an improved example, but requires to increase the complexity a tiny bit (nothing is for free).
This decoupling is achieved by introducing a dependency property for each routed command. This property can then be bound to the view model's
ICommandimplementation (the same way aButton.Commandproperty is bound).This version successfully eliminates the requirement that the
MainWindowhas to explicitly reference its data context (although this ain't something bad in general, we just strive to decouple as much as possible. Therefore, avoiding this explicit dependency is favorable).MainWindow.cs
MainWindow.xaml
The rest, how the routed command is invoked, is exactly the same as in the previous example.