I've got some XAML like the following:
<UserControl x:Class="Foo">
<UserControl.Resources>
<ContextMenu x:Key="ContextMenu1">
<MenuItem Header="Command 1a"/>
<MenuItem Header="Command 1b"/>
</ContextMenu>
<ContextMenu x:Key="ContextMenu2">
<MenuItem Header="Command 2a"/>
<MenuItem Header="Command 2b"/>
</ContextMenu>
</UserControl.Resources>
<DockPanel>
<TreeView>
<TreeView.Resources>
<DataTemplate DataType="{x:Type Type1}">
<StackPanel ContextMenu="{StaticResource ContextMenu1"}/>
</DataTemplate>
<DataTemplate DataType="{x:Type Type2}">
<StackPanel ContextMenu="{StaticResource ContextMenu2"}/>
</DataTemplate>
</TreeView.Resources>
</TreeView>
</DockPanel>
</UserControl>
and a code behind similar to the following:
public class Type1 {
public void OnCommand1a() {}
public void OnCommand1b() {}
}
public class Type2 {
public void OnCommand2a() {}
public void OnCommand2b() {}
}
What do I need to do so that clicking the respective items on the menus calls the corresponding function?
If I add:
Command="{Binding Path=OnCommand1a}" CommandTarget="{Binding Path=PlacementTarget}"
etc then at runtime I get errors about how OnCommand1a is not a property. Some searching suggests this has something to do with a RoutedUIEvent, but I don't really understand what that's about.
If I use
Click="OnCommand1a"
then it looks for OnCommand1a() on the UserControl instead of on the type that is bound to the DataTemplate.
What is the standard way of dealing this?
First of all you need a class that extends ICommand.
You can use this:
public class DelegateCommand : ICommand
{
private readonly Action<object> executeMethod = null;
private readonly Func<object, bool> canExecuteMethod = null;
public event EventHandler CanExecuteChanged
{
add { return; }
remove { return; }
}
public DelegateCommand(Action<object> executeMethod, Func<object, bool> canExecuteMethod)
{
this.executeMethod = executeMethod;
this.canExecuteMethod = canExecuteMethod;
}
public bool CanExecute(object parameter)
{
if (canExecuteMethod == null) return true;
return this.canExecuteMethod(parameter);
}
public void Execute(object parameter)
{
if (executeMethod == null) return;
this.executeMethod(parameter);
}
}
Then, in your class Type1 you have to declare this:
public DelegateCommand OnCommand1a {get; private set;}
and set it in your Type1 constructor in this way:
OnCommand1a = new DelegateCommand(c => Cmd1a(), null);
where Cmd1a is:
private void Cmd1a()
{
//your code here
}
Finally, in your xaml:
Command="{Binding Path=OnCommand1a}"
Related
I have a property List<Filter> Filters which is the ItemSource of an ItemsControl. What I am trying to accomplish is to show at the beginning only the filters which have the property IsShown = true. Then, when I push the button, to show the rest of the filters. Is it possible to be done using XAML? If no, which approach should I use?
The content of the Filter class is:
public List<string> Options { get; set; } = new List<string>();
public bool IsShown { get; set; }
public string Title { get; set; }
public string ValueSelected { get; set; }
public Filter(List<string> Options, string Title, string ValueSelected, bool IsShown)
{
this.Options = Options;
this.Title = Title;
this.ValueSelected = ValueSelected;
this.IsShown = IsShown;
}
In MainContext I have defined the List and a button:
public ObservableCollection<Filter> Filters { get; set; } = new ObservableCollection<Filter>();
public ICommand DoShowHide
In MainWindow.XAML at this point I have the following:
<ItemsControl ItemsSource="{Binding Filters}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical" Visibility="{Binding Path=IsShown, Converter={StaticResource BoolToVisConverter} }" Name="MyStackPanel">
<TextBlock Text="{Binding Title}"/>
<ComboBox ItemsSource="{Binding Path=Options}"
SelectedValue="{Binding Path=ValueSelected}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button Content="Show/Hide" Command="{Binding DoShowHide}"/>
with the mentioning that I have defined the converter
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVisConverter" />
</Window.Resources>
****I have tried to set all the filters' IsShown property to true at the push of the button. No need to mention that it did not work...
private void ShowHide(object obj)
{
MessageBox.Show("message");
foreach(Filter filter in Filters)
{
if(filter.IsShown == false)
{
filter.IsShown = true;
NotifyPropertyChanged("Filters");
}
}
}
Thank you for taking the time to read my question :)
Your Filter class must implement INotifyPropertyChanged. Otherwise property changes inside this class are not propagated to the binding system.
Raising the ProperyChanged event on the Filters property, as you did, is useless.
Note: you can use XOR operation to toggle boolean values (show/hide).
Shortened Filter class:
class Filter : INotifyPropertyChanged
{
private bool isDisplayed
public bool IsDisplayed
{
get => this.isDisplayed;
set
{
if (value != this.isDisplayed)
{
this.isDisplayed = value;
OnPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
The shortened DataTemplate for the Filter item:
<DataTemplate DataType="{x:Type Filter}">
<StackPanel Visibility="{Binding IsDisplayed, Converter={StaticResource BoolToVisibilityConverter}}">
</StackPanel>
</DataTemplate>
The modified ICommand execution handler:
private void ShowHide(object obj)
{
// Toggle all Filter.IsDisplayed
foreach (Filter filter in Filters)
{
filter.IsDisplayed ^= true;
}
}
you can make it using converter where you return the a visibility object
I have my MainWindow which has an ItemsControl for my EngineersUserControl (Engineers_UC) along with other controls. My Engineers_UC consists of a few TextBoxes for which I want to add MouseBinding with the aim of being able to left click on a TextBox and another method in my ViewModel to be executed. I have read that the issue might be that the elements of ItemsControl are not focusable but I haven't found a solution. Any ideas ?
MainWindow:
<Grid>
<StackPanel>
<UserControl:Ribbon_UC Loaded="Ribbon_UC_Loaded" Margin="0,0,0,70"/>
<UserControl:Calendar_UC/>
<ItemsControl ItemsSource="{Binding Engineer}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<UserControl:Engineers_UC />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>
Engineers_UC:
<TextBox Name="EngineerName" IsReadOnly="True" Style="{StaticResource StyleTrigger} Text ="{Binding FULLNAME}">
<TextBox.InputBindings>
<MouseBinding Command="{Binding EngineerCommand}" CommandParameter="{Binding ElementName=EngineerName}" MouseAction="{Binding EngineerCommand.MouseGesture}"/>
</TextBox.InputBindings>
</TextBox>
EngineerCommand:
void RelayCommands()
{
EngineerCommand = new SimpleDelegateCommand(x => EngineerFunction(x))
{
MouseGesture = MouseAction.LeftClick
};
}
void EngineerFunction (object _engineername)
{
EngineerNameClicked = (_engineername as TextBox).Text;
}
public class SimpleDelegateCommand : ICommand
{
public Key GestureKey { get; set; }
public ModifierKeys GestureModifier { get; set; }
public MouseAction MouseGesture { get; set; }
Action<object> _executeDelegate;
public SimpleDelegateCommand(Action<object> executeDelegate)
{
_executeDelegate = executeDelegate;
}
public void Execute(object parameter)
{
_executeDelegate(parameter);
}
public bool CanExecute(object parameter) { return true; }
public event EventHandler CanExecuteChanged;
}
If the EngineerCommand command is defined in the same view model class as the collection to which you bind the ItemsSource property of the ItemsControl to (Engineer), you should use a RelativeSource for your binding(s) in the ItemTemplate:
<MouseBinding Command="{Binding DataContext.EngineerCommand, RelativeSource={RelativeSource AncestorType=ItemsControl}}" ... />
I have a ContextMenu that suppose to set value on its parent TextBox.
The textbox cannot have a name (by requirement), so I am setting it as CommandTarget
<TextBox Text="{Binding TextBoxOne, UpdateSourceTrigger=LostFocus}">
<TextBox.ContextMenu>
<ContextMenu>
<MenuItem Header="Set to 35"
Command="{Binding SetAmountCommand}"
CommandParameter="35"
CommandTarget="{Binding Text, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TextBox}}}" />
<MenuItem Header="Set to 50"
Command="{Binding SetAmountCommand}"
CommandParameter="50"
CommandTarget="{Binding Text, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TextBox}}}" />
</ContextMenu>
</TextBox.ContextMenu>
How to access the TextBox.Text from inside the Command ?
ViewModel
public class MainVm : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public string TextBoxOne { get; set; } = "One";
private ICommand _setAmountCommand;
public ICommand SetAmountCommand
{
get
{
return _setAmountCommand ?? (_setAmountCommand = new CommandParameterHandler((o) =>
{
object param = o;
double amount = (double)o;
//MyParentTextBox.Text = amount; //What to put here ? (Cannot be TextBoxOne = amount, need to route from View)
}, true));
}
}
}
Generic CommandParameterHandler
public class CommandParameterHandler : ICommand
{
private Action<object> _action;
private bool _canExecute;
public CommandParameterHandler(Action<object> action, bool canExecute)
{
_action = action;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExecute;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
_action(parameter);
}
}
You can only pass one CommandParameter to the command. If you want to pass is something in addition to the actual value, you could create a custom composite type that carries more than one value:
public class CompositeParameter : Freezable
{
protected override Freezable CreateInstanceCore()
{
return this;
}
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(nameof(Value),
typeof(string), typeof(CompositeParameter));
public string Value
{
get { return (string)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ControlProperty = DependencyProperty.Register(nameof(Control),
typeof(FrameworkElement), typeof(CompositeParameter));
public FrameworkElement Control
{
get { return (FrameworkElement)GetValue(ControlProperty); }
set { SetValue(ControlProperty, value); }
}
}
View Model:
public ICommand SetAmountCommand
{
get
{
return _setAmountCommand ?? (_setAmountCommand = new CommandParameterHandler((o) =>
{
CompositeParameter param = o as CompositeParameter;
if (param != null)
{
double amount = Convert.ToDouble(param.Value);
//...
TextBox textBox = param.Control as TextBox;
if (textBox != null)
textBox.Text = param.Value;
}
}, true));
}
}
View:
<TextBox Text="{Binding TextBoxOne, UpdateSourceTrigger=LostFocus}">
<TextBox.ContextMenu>
<ContextMenu>
<ContextMenu.Resources>
<local:CompositeParameter x:Key="paramA"
Value="35"
Control="{Binding PlacementTarget, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
<local:CompositeParameter x:Key="paramB"
Value="50"
Control="{Binding PlacementTarget, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
</ContextMenu.Resources>
<MenuItem Header="Set to 35"
Command="{Binding SetAmountCommand}"
CommandParameter="{StaticResource paramA}" />
<MenuItem Header="Set to 50"
Command="{Binding SetAmountCommand}"
CommandParameter="{StaticResource paramB}" />
</ContextMenu>
</TextBox.ContextMenu>
</TextBox>
After 2 days searching for answer, I came across this RoutedCommand tutorial. Yes, you can access CommandTarget from Command, but it has to be a static RoutedCommand. This approach fits the need as SetAmountCommand is shared by multiple MenuItem.
XAML
<Window x:Class="WpfCommandTargetDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfCommandTargetDemo">
<Window.CommandBindings>
<CommandBinding CanExecute="SetAmountCommand_CanExecute"
Command="{x:Static local:CustomRoutedCommand.SetAmountCommand}"
Executed="SetAmountCommand_Executed" />
</Window.CommandBindings>
<StackPanel>
<TextBox Text="{Binding TextBoxOne, UpdateSourceTrigger=LostFocus}">
<TextBox.ContextMenu>
<ContextMenu>
<MenuItem Header="Set to 35"
Command="{x:Static local:CustomRoutedCommand.SetAmountCommand}"
CommandParameter="35"
CommandTarget="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=ContextMenu}, Path=PlacementTarget}" />
<MenuItem Header="Set to 50"
Command="{x:Static local:CustomRoutedCommand.SetAmountCommand}"
CommandParameter="50"
CommandTarget="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=ContextMenu}, Path=PlacementTarget}" />
</ContextMenu>
</TextBox.ContextMenu>
</TextBox>
</StackPanel>
</Window>
CodeBehind
public partial class MainWindow : Window
{
private readonly MainVm _mainVm;
public MainWindow()
{
InitializeComponent();
_mainVm = new MainVm();
DataContext = _mainVm;
}
void SetAmountCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
void SetAmountCommand_Executed(object sender, ExecutedRoutedEventArgs e)
{
object param = e.Parameter; //CommandParameter
TextBox textbox = e.OriginalSource as TextBox; //CommandTarget
if (textbox != null)
{
textbox.Text = param.ToString();
}
}
}
RoutedCommand has to be static, because it is statically bound to XAML element.
public static class CustomRoutedCommand
{
public static readonly RoutedCommand SetAmountCommand = new RoutedCommand();
}
For completeness, I cannot have the Command on my ViewModel. SetAmountCommand property is removed.
public class MainVm : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public string TextBoxOne { get; set; } = "One";
}
I'm currently in the process of mastering the C# WPF MVVM pattern and have stumbled upon a pretty big hurdle...
What I am trying to do fire off a LoginCommand that when successfully executed will allow me to change the parent window's viewmodel. The only issue is I can't quite think of a way to change the parent window's viewmodel without breaking the MVVM design pattern because I can't access the parent window's ContentControl that sets its path to the active UserControlViewModel in the window.
Here's the scenario:
In our App.xaml we have two DataTemplates:
<DataTemplate DataType="{x:Type ViewModels:LoginViewModel}">
<Views:LoginView />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModels:LoggedInViewModel}">
<Views:LoggedView />
</DataTemplate>
In our MainWindow we have:
<ContentControl Content="{Binding ViewModel}" />
The MainWindow code behind will set the ViewModel = LoginViewModel
In our LoginViewModel we have:
<Button Command="{Binding LoginCommand}" CommandParameter="{Binding ElementName=pwPasswordBoxControlInXaml}" />
Now for the money... the LoginCommand:
public void Execute(object parameter)
{
// Do some validation
// Async login task stuff
// ...
// Logged in... change the MainWindow's ViewModel to the LoggedInViewModel
}
How can I make the Execute method change the window's viewmodel without breaking the MVVM pattern?
Things I've tried thus far:
Making the MainWindow have a static Instance singleton that I can access and then change the ViewModel property from the command.
Attempting to implement some form of routed command listener in the MainWindow and then have commands fire off routed command events to be handled by the parent window.
I've done a quick demo to show one way of doing it. I've kept it as simple as possible to give the general idea. There are lots of different ways of accomplishing the same thing (e.g. you could hold a reference to MainWindowViewModel inside LoginViewModel, handle everything there then call a method on MainWindowViewModel to trigger the workspace change, or you could use Events/Messages, etc).
Definitely have a read of Navigation with MVVM though. That's a really good introduction that I found helpful when I was getting started with it.
The key thing to take away from this is to have an outer MainWindowViewModel or ApplicationViewModel which handles the navigation, holds references to workspaces, etc. Then the choice of how you interact with this is up to you.
In the code below, I've left out the clutter from defining Window, UserControl, etc. to keep it shorter.
Window:
<DockPanel>
<ContentControl Content="{Binding CurrentWorkspace}"/>
</DockPanel>
MainWindowViewModel (this should be set as the DataContext for the Window):
public class MainWindowViewModel : ObservableObject
{
LoginViewModel loginViewModel = new LoginViewModel();
LoggedInViewModel loggedInViewModel = new LoggedInViewModel();
public MainWindowViewModel()
{
CurrentWorkspace = loginViewModel;
LoginCommand = new RelayCommand((p) => DoLogin());
}
private WorkspaceViewModel currentWorkspace;
public WorkspaceViewModel CurrentWorkspace
{
get { return currentWorkspace; }
set
{
if (currentWorkspace != value)
{
currentWorkspace = value;
OnPropertyChanged();
}
}
}
public ICommand LoginCommand { get; set; }
public void DoLogin()
{
bool isValidated = loginViewModel.Validate();
if (isValidated)
{
CurrentWorkspace = loggedInViewModel;
}
}
}
LoginView:
In this example I'm binding a Button on the LoginView to the LoginCommand on the Window DataContext (i.e. MainWindowViewModel).
<StackPanel Orientation="Vertical">
<TextBox Text="{Binding UserName}"/>
<Button Content="Login" Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.LoginCommand}"/>
</StackPanel>
LoginViewModel:
public class LoginViewModel : WorkspaceViewModel
{
private string userName;
public string UserName
{
get { return userName; }
set
{
if (userName != value)
{
userName = value;
OnPropertyChanged();
}
}
}
public bool Validate()
{
if (UserName == "bob")
{
return true;
}
else
{
return false;
}
}
}
LoggedInView:
<StackPanel Orientation="Vertical">
<TextBox Text="{Binding RestrictedData}"/>
</StackPanel>
LoggedInViewModel:
public class LoggedInViewModel : WorkspaceViewModel
{
private string restrictedData = "Some restricted data";
public string RestrictedData
{
get { return restrictedData; }
set
{
if (restrictedData != value)
{
restrictedData = value;
OnPropertyChanged();
}
}
}
}
WorkspaceViewModel:
public abstract class WorkspaceViewModel : ObservableObject
{
}
Then some other classes you probably already have implemented (or alternatives).
ObservableObject:
public abstract class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this,
new PropertyChangedEventArgs(propertyName));
}
}
RelayCommand:
public class RelayCommand : ICommand
{
private readonly Action<object> execute;
private readonly Predicate<object> canExecute;
public RelayCommand(Action<object> execute)
: this(execute, null)
{ }
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
{
throw new ArgumentNullException("execute");
}
this.execute = execute;
this.canExecute = canExecute;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
[DebuggerStepThrough]
public bool CanExecute(object parameter)
{
return canExecute == null ? true : canExecute(parameter);
}
public void Execute(object parameter)
{
execute(parameter);
}
}
App.Xaml:
<DataTemplate DataType="{x:Type ViewModels:LoginViewModel}">
<Views:LoginView />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModels:LoggedInViewModel}">
<Views:LoggedInView />
</DataTemplate>
<ContentControl Content="{Binding ViewModel}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type vm:LoginViewModelClass}">
<!-- some LoginView -->
</DataTemplate>
<DataTemplate DataType="{x:Type vm:LoggedInViewModelClass}">
<!-- some LoggedInView -->
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
i'm new in WPF and MVVM. I read many articles about WPF commands, but i have still problem with sending value from property text of textbox to ViewModel.
I'm using entity framework code first.
I want to show text from textbox in MessageBox, but when I click to button with command, linked property of viewmodel is null.
Please can you help me?
View- DetailIncidentWindow.xaml
xmlns:wm="clr-namespace:Admin.ViewModels"
<StackPanel>
<StackPanel.DataContext>
<wm:CommentViewModel/>
</StackPanel.DataContext>
<TextBlock Text="Text komentáře:" Style="{StaticResource TextBlockLabel}" Margin="0,10,0,0"/>
<TextBox Name="TextBox_textKomentar" Width="auto" Height="100" TextWrapping="Wrap" Text="{Binding TextKomentar, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<TextBlock Text="{Binding TextKomentar, UpdateSourceTrigger=PropertyChanged}"/>
</StackPanel>
Ribbon button- DetailIncidentWindow.xaml
<Custom:RibbonGroup.DataContext>
<wm:CommentViewModel/>
</Custom:RibbonGroup.DataContext>
<Custom:RibbonButton
LargeImageSource="..\Shared\img\save_diskete.png"
Label="Show text"
Command="{Binding ButtonCommand}">
</Custom:RibbonButton>
ViewModel- KomentarViewModel.cs
namespace Admin.ViewModels
{
class CommentViewModel:BaseViewModel
{
#region Data
private string textKomentar;
public string TextKomentar
{
get
{
return textKomentar;
}
set
{
textKomentar = value;
OnPropertyChanged("TextKomentar");
}
}
private ICommand m_ButtonCommand;
public ICommand ButtonCommand
{
get
{
return m_ButtonCommand;
}
set
{
m_ButtonCommand = value;
OnPropertyChanged("ButtonCommand");
}
}
#endregion
#region Constructor
public CommentViewModel()
{
ButtonCommand = new RelayCommand(new Action<object>(ShowMessage));
}
#endregion
#region Methods
public void ShowMessage(object obj)
{
MessageBox.Show(TextKomentar);
}
#endregion
}
}
Command- RelayCommand.cs
class RelayCommand : ICommand
{
private Action<object> _action;
public RelayCommand(Action<object> action)
{
_action = action;
}
#region ICommand Members
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
if (parameter != null)
{
_action(parameter);
}
else
{
_action("Hello World");
}
}
#endregion
}
You should not create multiple instances of your view model, like you do in
<StackPanel.DataContext>
<wm:CommentViewModel/>
</StackPanel.DataContext>
and
<Custom:RibbonGroup.DataContext>
<wm:CommentViewModel/>
</Custom:RibbonGroup.DataContext>
The value of the DataContext property is inherited by child elements, so you could just set it at the top level, e.g. the Window:
<Window ...>
<Window.DataContext>
<wm:CommentViewModel/>
</Window.DataContext>
...
</Window>