Can I bind a RoutedCommand to a Command in WPF? - c#

I have a ViewModel of the form:
class VM : DependencyObject
{
// This class exposes a command to data binding to delete things from the model
public static DependencyProperty DeleteProperty = DependencyProperty.Register(/*...*/);
public static ICommand Delete {
get {
return (ICommand)this.GetValue(DeleteProperty);
}
set {
this.SetValue(DeleteProperty, value);
}
}
}
For a ListBoxItem I would like to data bind execution of ApplicationCommands.Delete to this ICommand exposed by this ViewModel. That way, when someone presses the menu item that raises ApplicationCommands.Delete, this particular delete will be chosen if focus is currently on this thing:
<!-- Elsewhere... -->
<MenuItem Text="Delete" Command="ApplicationCommands.Delete" />
<!-- Some data template with DataContext == the above DependencyObject -->
<Grid>
<Grid.CommandBindings>
<!--
I want ApplicationCommands.Delete to forward to the command {Binding Delete}
-->
<CommandBinding Command="ApplicationCommands.Delete"
???
/>
</Grid.CommandBindings>
</Grid>
... but CommandBinding can only bind to event handlers; not other commands, so I can't use MVVM-style data binding to attach it. Is there some MVVM mechanism I can use here or am I forced into adding code-behind for the CommandBinding?

You can use this tutorial to bind ApplicationCommands.
1.Add command binding collection property in your view model:
public CommandBindingCollection CommandBindings { get; }
public YourViewModel()
{
//Create a command binding for the delete command
var deleteBinding = new CommandBinding(ApplicationCommands.Delete, DeleteExecuted, DeleteCanExecute);
//Register the binding to the class
CommandManager.RegisterClassCommandBinding(typeof(YourViewModel), DeleteBinding);
//Adds the binding to the CommandBindingCollection
CommandBindings.Add(deleteBinding);
}
Create attached property as mentioned in the tutorial.
Then bind attached property in your UI.

Related

Pass parameter via shell when navigating in UWP application using MVVM

Have started learning UWP development by creating a Windows Template Studio application with Horizontal Navigation project type, MVVM Light design pattern and two blank pages (MainPage and StatsPage).
In the MainPage I have a list of items and I want to show the selected item's statistics in the StatsPage when the user hits the relevant NavigationViewItem. In my wpf apps, I would set up a RelayCommand with command parameter on a button in the Main view, and in the viewmodel, call the command's method with the param/arg to open the Stats view with the correct info. In the created Template Studio app, the NavigationViewItem in the ShellPage calls OnItemInvoked() in its VM which doesn't know about the selection in the MainPage view.
<winui:NavigationView.MenuItems>
<winui:NavigationViewItem x:Uid="Shell_Main" helpers:NavHelper.NavigateTo="LG_Ess.ViewModels.MainViewModel" />
<winui:NavigationViewItem x:Uid="Shell_Stats" helpers:NavHelper.NavigateTo="LG_Ess.ViewModels.StatsViewModel" />
</winui:NavigationView.MenuItems>
<i:Interaction.Behaviors>
<ic:EventTriggerBehavior EventName="ItemInvoked">
<ic:InvokeCommandAction Command="{x:Bind ViewModel.ItemInvokedCommand}" />
</ic:EventTriggerBehavior>
</i:Interaction.Behaviors>
How do I pass the MainPage selected item as a param to StatsPage via the ShellPage's NavigationViewItem? I could probably work it out by adding a button to my MainPage and hiding the Shell navigation, but I'd prefer to do it in the Template Studio style.
Further investigations show that the ShellViewModel seems to be the default DataContext for all pages created by the Template Studio. In my pages I have set the Data Context to the autogenerated View Models by adding:
<Page.DataContext>
<local:{PageName}ViewModel/>
</Page.DataContext>
So, it seems I can either have a single ViewModel and treat the pages as UserControls, or map the DataContext for each page to its own ViewModel, and do some ViewModelLocator dancing to access properties from one VM in another.
So, it seems I can either have a single ViewModel and treat the pages as UserControls, or map the DataContext for each page to its own ViewModel, and do some ViewModelLocator dancing to access properties from one VM in another.
Correct. You could define a 'SelectedStats' relevant property in your MainViewModel and make your ListView's SelectedItem bind to this property and set Mode=TwoWay. Then, on your 'StatsPage', you could use EventTriggerBehavior to bind a 'LoadedCommand' in StatsViewModel like the 'ShellPage'. In StatsViewModel, you could get the selected item by calling ViewModelLocator.Current.MainViewModel.SelectedStats.
See my simple code sample:
<!--MainPage.xaml-->
<ListView ItemsSource="{x:Bind ViewModel.list}" SelectedItem="{x:Bind ViewModel.SelectedStats,Mode=TwoWay}">
</ListView>
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
list = new ObservableCollection<string>();
list.Add("string1");
list.Add("string2");
list.Add("string3");
}
public ObservableCollection<string> list { get; set; }
private object _SelectedStats;
public object SelectedStats
{
get { return _SelectedStats; }
set
{
if (_SelectedStats != value)
{
_SelectedStats = value;
RaisePropertyChanged("SelectedStats");
}
}
}
}
<!--StatsPage.xaml-->
<i:Interaction.Behaviors>
<ic:EventTriggerBehavior EventName="Loaded">
<ic:InvokeCommandAction Command="{x:Bind ViewModel.LoadedCommand}" />
</ic:EventTriggerBehavior>
</i:Interaction.Behaviors>
public class StatsViewModel : ViewModelBase
{
public StatsViewModel()
{
}
private ICommand _LoadedCommand;
public ICommand LoadedCommand => _LoadedCommand ?? (_LoadedCommand = new RelayCommand(LoaedAsync));
private async void LoaedAsync()
{
var selectedObject = ViewModelLocator.Current.MainViewModel.SelectedStats;
//TODO:...
await Task.CompletedTask;
}
}

WPF UserControl property binding

I'm trying to create a WPF UserControl which contains 2 buttons. I use this UserControl in a Window and apply a Window.Resource value to set the background of one button inside the user control.
Currently I have:
window.xaml
<Window.Resources>
<SolidColorBrush Color="Brown" x:Key="theBG"></SolidColorBrush>
</Window.Resources>
<theControl:TheControl
x:Name="TheControl"
buttonBG="{Binding Source={StaticResource theBG}}" />
usercontrol.xaml.cs
public SolidColorBrush buttonBG
{
get { return base.GetValue(buttonBGProperty) as SolidColorBrush; }
set { base.SetValue(buttonBGProperty, value); }
}
public static readonly DependencyProperty buttonBGProperty = DependencyProperty.Register("buttonBG", typeof(SolidColorBrush), typeof(DataPanel), null);
usercontrol.xaml
<Button ... Background="{Binding buttonBG}">
I was expecting this to work but the background is not the one I set in the window resource.
What am I doing wrong?
Background="{Binding buttonBG}"
Implies either that you changed the DataContext of the UserControl, which you should never do. Or that the binding is just wrong.
Use
Background="{Binding buttonBG, ElementName=control}"
Naming your UserControl root element control. RelativeSource works as well.
Try placing it in a separate model or even a viewmodel that has INotifyPropertyChanged. When you add view code in the cs for an xaml file, you need to bind with relativesource self and its hacky and goes against MVVM. I would create a seperate ViewModel with a Brush that has NotifyPropertyChanged baked into it. This will tell the UI to change everything its bound to on value change.
In the Window, bind your viewmodel to datacontext. In the viewmodel you can put:
private Brush _bgColor;
public Brush BgColor
{
get{return _bgColor;
}
set
{
_bgColor = value;
OnPropertyChanged("BgColor");
}
Create an ICommand, and bind your button to it like this in the viewmodel:
ICommand ChangeBgColor {get;set;
And in the XAML for the Button:
Command="{Binding Path=DataContext.ChangeBgColor,RelativeSource={RelativeSorce Mode=FindAncestor,AncestorType={x:Type UserControl}}"
This will fire the ICommand, bound to the viewmodel that is the datacontex of the window that you are working with.
And in that code for the ICommand change out your colors, you could do it like this:
private void OnChangeBgColor(object param){
var bc = new BrushConverter();
BgColor = (Brush)bc.ConvertFrom("#fff");
}
With the MVVM pattern, you want to get away from putting unnecessary code in the xaml.cs files and start putting them into viewmodels and models.

UserControl DataContext Binding

I have three projects in my solution:
My main WPF Application which contains a MainWindow + MainViewModel
UserControl Library with a UserControl (ConfigEditorView)
UIProcess class with the ViewModel for the UserControl (ConfigEditorViewModel)
In my MainWindow I want to use the UserControl with the ViewModel of UIProcess.
First I set the UserControl in my MainWindow:
<TabItem Header="Editor">
<Grid>
<cel:ConfigEditorView DataContext="{Binding ConfEditModel, NotifyOnSourceUpdated=True, NotifyOnTargetUpdated=True, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</TabItem>
I don't know which of these properties I need here, so I put all together but it still doesn't work.
Then I've set this in my MainViewModel:
public ConfigEditorViewModel ConfEditModel { get; set; }
With simple method that is bound to a Button:
private void doSomething()
{
ConfEditModel = new ConfigEditorViewModel("Hello World");
}
My ConfigEditorViewModel looks basically like this:
public class ConfigEditorViewModel : ViewModelBase
{
private string _Description;
public string Description
{
get
{
return _Description;
}
set
{
_Description = value;
base.RaisePropertyChanged();
}
}
public ConfigEditorViewModel(string t)
{
Description = t;
}
}
The description is bound to a TextBox in my UserControl.
<TextBox Grid.Row="1" Grid.Column="1" Margin="0,0,0,10" Text="{Binding Description}"/>
When I start the application and click the Button the TextBox should contain "Hello World" but it's empty.
What I've done wrong?
i gave you a general answer:
within a "real(a usercontrol you wanna use with different viewmodels with different property names)" usercontrol you bind just to your own DependencyProperties and you do that with ElementName or RelativeSource binding and you should never set the DataContext within a UserControl.
<UserControl x:Name="myRealUC" x:class="MyUserControl">
<TextBox Text="{Binding ElementName=myRealUC, Path=MyOwnDPIDeclaredInMyUc, Path=TwoWay}"/>
<UserControl>
if you do that you can easily use this Usercontrol in any view like:
<myControls:MyUserControl MyOwnDPIDeclaredInMyUc="{Binding MyPropertyInMyViewmodel}"/>
and for completeness: the Dependency Property
public readonly static DependencyProperty MyOwnDPIDeclaredInMyUcProperty = DependencyProperty.Register(
"MyOwnDPIDeclaredInMyUc", typeof(string), typeof(MyUserControl), new PropertyMetadata(""));
public bool MyOwnDPIDeclaredInMyUc
{
get { return (string)GetValue(MyOwnDPIDeclaredInMyUcProperty); }
set { SetValue(MyOwnDPIDeclaredInMyUcProperty, value); }
}
Your view models (and, optionally, models) need to implement INotifyPropertyChanged.
Binding's aren't magic. There is no inbuilt mechanism that allows for code to be notified when a plain old property's value changes. You'd have to poll it in order to check to see if a change happened, which would be very bad, performance-wise.
So bindings will look at the objects they are bound against and see if they implement INotifyPropertyChanged and, if so, will subscribe to the PropertyChanged event. That way, when you change a property and fire the event, the binding is notified and updates the UI.
Be warned, you must implement the interface and use it correctly. This example says it's for 2010, but it works fine.

WPF :: Custom Control's Dependency Properties Aren't Working

I'm making a Ribbon control for a WYSIWYG HTML editor. The ribbon has the typical Bold, Italic, Underline, FontFamily, etc. controls that you'd expect to see. I'll focus on the Bold functionality for this example.
I want the Ribbon to be reuseable, so I've added a Dependency Property (DP) and associated property wrapper to the control's code behind (standard boilerplate stuff):
public partial class EditorRibbon: UserControl
{
public static readonly DependencyProperty IsBoldProperty =
DependencyProperty.Register(
"IsBold",
typeof (bool),
typeof (EditorRibbon),
new PropertyMetadata(default(bool)));
public bool IsBold
{
get { return (bool) GetValue(IsBoldProperty); }
set { SetValue(IsBoldProperty, value); }
}
}
... and in the XAML I have my RibbonToggleButton, and I've bound the IsChecked property to the dependency property:
<UserControl x:Class="My.EditorRibbon">
<r:RibbonToggleButton Command="ToggleBold"
ToolTip="{Binding RelativeSource={RelativeSource Self}, Path=Command.Text}"
SmallImageSource="{StaticResource ToggleBoldIcon}"
IsChecked="{Binding IsBold}" />
</UserControl>
In my Editor window, I've bound the IsBold property of the EditorRibbon to a conventional property on the window's ViewModel:
<Window x:class="My.MainWindow>
<My.EditorRibbon IsBold="{Binding SelectionIsBold}"/>
</Window>
Here is the SelectionIsBold property:
public bool SelectionIsBold
{
get { return _selection.IsBold(); }
}
... and I raise the NotifyPropertyChanged() event (in the MainWindow's ViewModel) whenever the selection in the RichTextBox changes:
public class MainWindowViewModel : BaseViewModel
{
public MainWindowViewModel(MainWindow window)
{
rtb.SelectionChanged += rtb_OnSelectionChanged;
}
private void rtb_OnSelectionChanged(object sender, RoutedEventArgs routedEventArgs)
{
NotifyPropertyChanged(()=>SelectionIsBold);
}
}
To my mind, this should be enough to change the IsChecked state of the RibbonToggleButton whenever the selection changes... but it doesn't. Despite changing the selection, and despite the NotifyPropertyChanged() firing as expected, a breakpoint on the SelectionIsBold property (yes, I've deselected VS's "Step Over Property" setting) is never hit. Somewhere, the request to refresh the value isn't propagating correctly.
Do I need to trigger NotifyPropertyChanged() on the IsBold property after the value is set in the setter?
Change the IsBold binding to the following
<UserControl x:Class="My.EditorRibbon" x:Name="EditorRibbonInstance">
<r:RibbonToggleButton Command="ToggleBold"
ToolTip="{Binding RelativeSource={RelativeSource Self}, Path=Command.Text}"
SmallImageSource="{StaticResource ToggleBoldIcon}"
IsChecked="{Binding IsBold, ElementName=EditorRibbonInstance, Mode=TwoWay}" />
</UserControl>
With that you are sure that the binding is going to the property of the control and not to the datacontext of the control
You have to fire notifypropertychanged in ViewModel. Try somethings like this in ViewModel:
protected void FirePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
->> FirePropertyChanged("SelectionIsBold")
The reason is: now, your data context is ViewModel, all of binding to ViewModel must be triggered by ViewModel's properties
first of all, I never saw the injection of the Window to the ViewModel before... are you using some Kind of DI for the injection?
I think it is not a good idea to use the selection changed Event on viewmodel... This is not mvvm from my Point of view...
Are you updating the _selection somewhere? Might be that you always checking the same selection?!
You are not properly binding the command property of your button.
Should reflect something like this:
Command="{Binding ToggleBold}"

link command of KeyBinding in mainwindow's view model to an user control's view model commandbindings

I have a mainwindow inside which there is a usercontrol which contains a listview.
User control also has a button which copies all the contents of listview to clipboard.
This is how the copy functionality has been implemented.
below is a part of xaml of usercontrol -
<Button Command="Copy"
CommandTarget="{Binding ElementName=testCodeView}"
CommandParameter="Copy"
</Button>
<ListView x:Name="testCodeView"
ItemsSource="{Binding Products}" BorderThickness="0" Grid.Row="1"
ItemTemplate="{StaticResource testViewTemplate}"
ItemContainerStyle="{StaticResource testCodesListItem}"
infra:AttachedProperties.CommandBindings ="{Binding CommandBindings}">
</ListView>
The AttachedProperties class holds the Dependency property "CommandBindings" - below is the code -
public class AttachedProperties
{
public static DependencyProperty CommandBindingsProperty =
DependencyProperty.RegisterAttached("CommandBindings", typeof(CommandBindingCollection), typeof(AttachedProperties),
new PropertyMetadata(null, OnCommandBindingsChanged));
public static void SetCommandBindings(UIElement element, CommandBindingCollection value)
{
if (element != null)
element.SetValue(CommandBindingsProperty, value);
}
public static CommandBindingCollection GetCommandBindings(UIElement element)
{
return (element != null ? (CommandBindingCollection)element.GetValue (CommandBindingsProperty) : null);
}
}
Below is the usercontrol viewmodel's code related to copying the items of listview.
public class UserControlViewModel : INotifyPropertyChanged
{
public CommandBindingCollection CommandBindings
{
get
{
if (commandBindings_ == null)
{
commandBindings_ = new CommandBindingCollection();
}
return commandBindings_;
}
}
public UserControlViewModel
{
CommandBinding copyBinding = new CommandBinding(ApplicationCommands.Copy,
this.CtrlCCopyCmdExecuted, this.CtrlCCopyCmdCanExecute);
// Register binding to class
CommandManager.RegisterClassCommandBinding(typeof(UserControlViewModel), copyBinding);
this.CommandBindings.Add(copyBinding);
}
private void CtrlCCopyCmdExecuted(object sender, ExecutedRoutedEventArgs e)
{
copyToclipboard_.CtrlCCopyCmdExecuted(sender, e);
}
}
The sender object in CtrlCCopyCmdExecuted function is the listview in usercontrol which is futher used in
copying its contents.
The copy all functionality works fine with the button on user control.
I have to create a key binding for copy functionality in mainwindow. I have created other keybindings in mainwindow which works fine as the commands are defined in MainWindowViewModel, but since the commandbindings of the copy all command are in the usercontrol's view model, I am facing problems in linking command of keybinding in mainwindowviewmodel with the commandbinding in usercontrolviewmodel.
Can Someone help me out with this.
Thanks in advance.
If your parent view model has access to the child view model then you have two main options. Both options involve creating an ICommand property to Bind to in the parent view model:
<KeyBinding Gesture="CTRL+C" Command="{Binding Copy, Mode=OneWay}" />
The first option involves creating a BaseViewModel class that has an abstract ICommand property in it... any class that extends the BaseViewModel class has to implement this command... then you could have the child view model property of this type and simply pass the value along:
public override ICommand Copy
{
get { return new ActionCommand(action => ViewModel.Copy.Execute(null),
canExecute => ViewModel.Copy != null); }
}
I doubt that you want that command on every view model though, so let's investigate option two. The basic idea is that if you have access to the child view model, you can 'pass' the command along to the child:
public ICommand Copy
{
get { return new ActionCommand(action => childViewModel.Copy.Execute(action),
canExecute => childViewModel.Copy.Execute(canExecute)); }
}
ActionCommand is a custom class that is based on the common RelayCommand.
UPDATE >>>
Ok, so I've never used CommandBindings before because it's so much easier using my ActionCommand, or RelayCommand, but I've just investigated it on MSDN and two ideas spring to mind.
First, if you're using the ApplicationCommands.Copy command object in the child UserControl, then surely you can just use that same object in the KeyBinding in the parent view. ApplicationCommands is a static class and Copy is a static property, so wherever you call it, it's the same object:
<KeyBinding Gesture="CTRL+C" Command="ApplicationCommands.Copy" />
Failing that, you could create an ICommand property in your parent view model and either create your own Copy command, or just return the ApplicationCommands.Copy command. Then you could Bind to it from the parent view and the child view... or, you could even add it into the child view CommandBinding object from the parent view model.

Categories