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";
}
Related
The problem is that the binding of ViewModel properties to the control`s properties does not work correctly. I checked the properties and their values change, but the visibility of the controls does not change. Any idea what is involved in this? Or am I missing something?
ViewModel:
class MainViewModel
{
public LoginViewModel LoginViewModel { get; set; }
Notifier notifier = new Notifier();
public MainViewModel()
{
LoginViewModel = new LoginViewModel();
}
private Visibility mdiPanelVisibility=Visibility.Visible;
public Visibility MDIPanelVisibility
{
get
{
return mdiPanelVisibility;
}
set
{
mdiPanelVisibility = value;
NotifyPropertyChanged("MDIPanelVisibility");
}
}
private RelayCommand showMDIPanelCommand;
public RelayCommand ShowMDIPanelCommand
{
get
{
return showMDIPanelCommand ??
(showMDIPanelCommand = new RelayCommand(obj =>
{
MDIPanelVisibility = Visibility.Visible;
}));
}
}
private RelayCommand hideMDIPanelCommand;
public RelayCommand HideMDIPanelCommand
{
get
{
return hideMDIPanelCommand ??
(hideMDIPanelCommand = new RelayCommand(obj =>
{
MDIPanelVisibility = Visibility.Hidden;
}));
}
}
private event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
and View:
<Border Visibility="{Binding MDIPanelVisibility}">
<Border.InputBindings>
<MouseBinding MouseAction="LeftClick" Command="{Binding HideMDIPanelCommand}"/>
</Border.InputBindings>
</Border>
<ContentPresenter Width="Auto" Grid.RowSpan="2" Panel.ZIndex="1" VerticalAlignment="Center" Visibility="{Binding MDIPanelVisibility}">
<ContentPresenter.Content>
<local:MDIView/>
</ContentPresenter.Content>
</ContentPresenter>
<Button Content="Личный кабинет" FontSize="13" Command="{Binding ShowMDIPanelCommand}">
<Button.Style>
<Style TargetType="Button" BasedOn="{StaticResource aLogButton}"/>
</Button.Style>
</Button>
The MainViewModel class needs to inherit from INotifyPropertyChanged, which your class does not, in order for the binding framework to behave as expected when the view's DataContext is set to the MainViewModel instance.
Update class definition
public class MainViewModel: INotifyPropertyChanged {
//...
}
I am trying to get MVVM (pretty new to it) and RadRibbonView to work together. Problem is, I was working with a standard WPF application with an MVVM model.
In the XAML file I previously had.
<Menu>
<MenuItem Header="{x:Static properties:Resources.FileMenu}">
<MenuItem Header="{x:Static properties:Resources.FileMenuNewWorkflow}" Command="{Binding Path=NewWorkflowCommand}" InputGestureText="Ctrl+N"/>
<MenuItem Header="{x:Static properties:Resources.FileMenuNewService}" Command="{Binding Path=NewServiceCommand}" InputGestureText="Shift+Ctrl+N"/>
<MenuItem Header="{x:Static properties:Resources.FileMenuOpen}" Command="{Binding Path=OpenWorkflowCommand}" InputGestureText="Ctrl+O"/>
<Separator/>
<MenuItem Header="{x:Static properties:Resources.FileMenuSave}" Command="{Binding Path=SaveWorkflowCommand}" InputGestureText="Ctrl+S"/>
<MenuItem Header="{x:Static properties:Resources.FileMenuSaveAs}" Command="{Binding Path=SaveAsWorkflowCommand}"/>
<MenuItem Header="{x:Static properties:Resources.FileMenuSaveAll}" Command="{Binding Path=SaveAllWorkflowsCommand}" InputGestureText="Shift+Ctrl+S"/>
<Separator/>
<MenuItem Header="{x:Static properties:Resources.FileMenuAddReference}" Command="{Binding Path=AddReferenceCommand}"/>
<Separator/>
<MenuItem Header="{x:Static properties:Resources.FileMenuClose}" Command="{Binding Path=CloseWorkflowCommand}"/>
<MenuItem Header="{x:Static properties:Resources.FileMenuCloseAll}" Command="{Binding Path=CloseAllWorkflowsCommand}"/>
<Separator/>
</Menu>
RelayCommand.cs
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; }
}
public bool CanExecute(object parameter)
{
return this.canExecute == null ? true : this.canExecute(parameter);
}
public void Execute(object parameter)
{
this.execute(parameter);
}
}
As you can see the Command id was well defined. I wanted to add Ribbon to this and selected RibbonView from telerik WPF UI. Picked the Paint with MVVM model.
Converted the Menu to Ribbon
<telerik:RadRibbonView Grid.Row="0" x:Name="ribbonView" ApplicationName="MyApp" ItemsSource="{Binding Tabs}"
ApplicationButtonContent="File" Title="{x:Static properties:Resources.RibbonViewTitle}" ItemTemplate="{StaticResource TabTemplate}"
SelectedItem="{Binding SelectedTab, Mode=TwoWay}"
MinimizeButtonVisibility="Visible" HelpButtonVisibility="Visible">
In the MainWindowModelView The following is defined. SplitButtonViewModel inherits from ButtonViewModel.
private GroupViewModel GetFilesGroup()
{
GroupViewModel fileItems = new GroupViewModel();
fileItems.Text = "File";
SplitButtonViewModel newFile = new SplitButtonViewModel();
newFile.Text = "New";
newFile.Size = ButtonSize.Large;
newFile.LargeImage = GetPath("MVVM/new.png");
fileItems.Buttons.Add(newFile);
SplitButtonViewModel openFile = new SplitButtonViewModel();
openFile.Text = "Open";
openFile.Size = ButtonSize.Large;
openFile.LargeImage = GetPath("MVVM/open.png");
fileItems.Buttons.Add(openFile);
ButtonGroupViewModel buttonsGroup = new ButtonGroupViewModel();
buttonsGroup.Buttons.Add(GetButton("save", "Save"));
buttonsGroup.Buttons.Add(GetButton("SaveAll", "Save All"));
buttonsGroup.Buttons.Add(GetButton("SaveAs", "Save As"));
fileItems.Buttons.Add(buttonsGroup);
return fileItems;
}
My current ButtonViewModel from the telerik MVVM Ribbon example is
public class ButtonViewModel : ViewModelBase
{
private String text;
private ButtonSize size;
private string smallImage;
private string largeImage;
//perhaps work something with this...
private RelayCommand command;
/// <summary>
/// Gets or sets Text.
/// </summary>
public String Text
{
get
{
return this.text;
}
set
{
if (this.text != value)
{
this.text = value;
this.OnPropertyChanged("Text");
}
}
}
public ButtonSize Size
{
get
{
return size;
}
set
{
size = value;
}
}
public string SmallImage
{
get
{
return smallImage;
}
set
{
smallImage = value;
}
}
public string LargeImage
{
get
{
return largeImage;
}
set
{
largeImage = value;
}
}
}
So all the creation of the Groups is in the ModelView. The problem is I am not sure how to get the Command working. I have a RelayCommand class which takes care of the commands.
From the following link: http://docs.telerik.com/devtools/wpf/controls/radribbonview/how-to/howto-use-commands-with-radribbonview-buttons
Is it possible to just call the class RelayCommand (basically same as the example from the link), so I do not have to change much and just invoke the command from the above function? Something like openFile.Execute() or something.
Most of the questions were how to connect XAML to Commands. All the menu items are now in C# and wanted a command definition there.
Any help is appreciated.
I'm doing my first app using MVVM. I have in "View" declared Datagrid. Code XAML below:
<DataGridTemplateColumn Header="delete">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button
Command="{Binding RelativeSource={RelativeSource AncestorType={x:Type> UserControl},Mode=FindAncestor}, Path=DataContext.ClickCommand}"> Content="X" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>>
</DataGrid>
In my ViewModel class I can run function that I want after click button "delete" by part of code:
public ICommand ClickCommand => _clickCommand ?? (_clickCommand = new CommandHandler(Delete, _canExecute));
public void Delete()
{
// DataTable.Rows.RemoveAt();
}
I have problem because I can't get index of selectet row. Source of data in datagrid is dataTable.
Do you have any ideas how to do this?
I've tried something with passing parameter with command of button but I coudn't make it works.
Xmal code
<Button Command="{Binding Path=DataContext.ViewCommand,RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}" CommandParameter="{Binding Id}" Content="X" Background="Chocolate"/>
Codebehind code
public RelayCommand DeleteCommand
{
get
{
return new RelayCommand(p => Delete(p));
}
}
public void Delete(string id)
{
// DataTable.Rows.RemoveAt();
}
This is example you can pass whatever you want in that cmd parameter.
Relay cmd
public class RelayCommand : ICommand
{
private Action<object> action;
private Func<bool> canFuncPerform;
public event EventHandler CanExecuteChanged;
public RelayCommand(Action<object> executeMethod)
{
action = executeMethod;
}
public RelayCommand(Action<object> executeMethod, Func<bool> canExecuteMethod)
{
action = executeMethod;
canFuncPerform = canExecuteMethod;
}
public void RaiseCanExecuteChanged()
{
CanExecuteChanged(this, EventArgs.Empty);
}
public bool CanExecute(object parameter)
{
if (canFuncPerform != null)
{
return canFuncPerform();
}
if (action != null)
{
return true;
}
return false;
}
public void Execute(object parameter)
{
if (action != null)
{
action(parameter);
}
}
}
You shouldn't rely on the selected item. Instead pass the current row item as CommandParameter:
<DataGridTemplateColumn Header="delete">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button
Command="{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl},Mode=FindAncestor}, Path=DataContext.ClickCommand}"
CommandParameter="{Binding}"
Content="X" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
Then of course, use an ICommand implementation that is not discarding the command parameter and use it to identify the row to be deleted.
I've spent the last days reading and trying to apply the Navigation pattern from this page: https://rachel53461.wordpress.com/2011/12/18/navigation-with-mvvm-2/
Now, after I got my project to work I'm really confused about how the binding works here. At first I have to clarify that I don't want a Navigation pane which is always visible like in the given example. I just want to use my MainView for navigation and each "SubView" should be able to go back to it's "parent" only.
Here's what I've got:
Project: APP
Class: App.xaml.cs
protected override void OnStartup(StartupEventArgs e) {
base.OnStartup(e);
UI.View.Main.MainView app = new UI.View.Main.MainView();
UI.View.Main.MainViewModel viewModel = new UI.View.Main.MainViewModel(some dependencies);
app.DataContext = viewModel;
app.Show();
}
ViewModel Base Class
public abstract class BaseViewModel : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
private string _name;
public string Name {
get {
return _name;
}
set {
if (Name != value) {
_name = value;
OnPropertyChanged("Name");
}
}
}
private BaseViewModel _homePage;
public BaseViewModel HomePage {
get {
return _homePage;
}
set {
if (HomePage != value) {
_homePage = value;
OnPropertyChanged("HomePage");
}
}
}
public void OnPropertyChanged(string propertyName) {
PropertyChangedEventHandler temp = PropertyChanged;
if (temp != null) {
temp(this, new PropertyChangedEventArgs(propertyName));
}
}
}
MainViewModel
namespace SGDB.UI.View.Main {
public class MainViewModel : BaseViewModel {
private BaseViewModel _currentPageViewModel;
public BaseViewModel CurrentPageViewModel {
get {
return _currentPageViewModel;
}
set {
if (CurrentPageViewModel != value) {
_currentPageViewModel = value;
OnPropertyChanged("CurrentPageViewModel");
}
}
}
public List<BaseViewModel> PageViewModels { get; private set; }
public RelayCommand ChangePageCommand {
get {
return new RelayCommand(p => ChangeViewModel((BaseViewModel)p), p => p is BaseViewModel);
}
}
//Some Dependencies
public List<BaseViewModel> ViewPages { get; private set; }
public MainViewModel(some dependencies) {
HomePage = new HomeViewModel() { Name = "TEST" };
//assign dependencies
var uavm = new UserAdministration.UserAdministrationViewModel(_userUnitOfWork, _personUnitOfWork) {
Name = Resources.Language.Sys.UserAdministartionTitle
};
PageViewModels = new List<BaseViewModel>();
PageViewModels.Add(uavm);
ChangeViewModel(HomePage);
}
public void ChangeViewModel(BaseViewModel viewModel) {
CurrentPageViewModel = viewModel;
}
}
}
MainView
<Window x:Class="SGDB.UI.View.Main.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SGDB.UI.View.Main"
xmlns:ua="clr-namespace:SGDB.UI.View.UserAdministration"
xmlns:home="clr-namespace:SGDB.UI.View.Home"
mc:Ignorable="d"
Title="MainView" Height="400" Width="800">
<Window.Resources>
<DataTemplate DataType="{x:Type home:HomeViewModel}">
<home:Home/>
</DataTemplate>
<DataTemplate DataType="{x:Type ua:UserAdministrationViewModel}">
<ua:UserAdministration/>
</DataTemplate>
</Window.Resources>
<ContentControl Content="{Binding CurrentPageViewModel}"/>
HomeViewModel
public class HomeViewModel : BaseViewModel {
public RelayCommand TestCommand {
get {
return new RelayCommand((x) => MessageBox.Show(x.ToString()), (x) => true);
}
}
}
HomeView
<UserControl x:Class="SGDB.UI.View.Home.Home"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SGDB.UI.View.Home"
xmlns:controls="clr-namespace:SGDB.UI.Controls"
xmlns:resx="clr-namespace:SGDB.UI.Resources.Language"
mc:Ignorable="d"
d:DesignHeight="400" d:DesignWidth="800">
<Grid>
<Grid.Resources>
<Style TargetType="controls:ModernButton">
<Setter Property="Margin" Value="1"/>
<Setter Property="FontFamily" Value="Bosch Office Sans"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Size" Value="155"/>
</Style>
</Grid.Resources>
<Grid.Background>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<LinearGradientBrush.GradientStops>
<GradientStop Color="#26688B" Offset="1"/>
<GradientStop Color="#11354C" Offset="0"/>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Grid.Background>
<Grid.RowDefinitions>
<RowDefinition Height="60"/>
<RowDefinition Height="3*"/>
<RowDefinition Height="2*"/>
<RowDefinition Height="25"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="150"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Row="0" Grid.Column="0" HorizontalAlignment="Center">
<StackPanel.Resources>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="White"/>
<Setter Property="FontFamily" Value="Bosch Office Sans"/>
</Style>
</StackPanel.Resources>
<TextBlock Text="{x:Static resx:Sys.ApplicationTitle}" FontSize="20" FontWeight="Bold" Margin="5"/>
<TextBlock Text="{x:Static resx:Sys.ApplicationSubTitle}" FontSize="12" FontWeight="Light"/>
</StackPanel>
<WrapPanel Grid.Row="1"
Grid.Column="0"
FlowDirection="LeftToRight"
HorizontalAlignment="Left"
Width="367">
<ItemsControl ItemsSource="{Binding DataContext.PageViewModels, RelativeSource={RelativeSource AncestorType={x:Type Window}}}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<controls:ModernButton Background="Dark"
Text="{Binding Name}"
Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
CommandParameter="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button Content="{Binding Name}" Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
CommandParameter="{Binding HomePage}"/>
// This Button is always disabled although HomePage is of Type HomeViewModel which is based on BaseViewModel.
</WrapPanel>
</Grid>
My questions are:
Why does the HomeView knot that the HomeViewModel is it's ViewModel? I do not define it anywhere in my code.
Why does the Binding on the Name Property work but binding to the HomePage Property doesn't? Both of them are defined in the BaseViewModel class.
Update 1:
RelayCommand class:
public class RelayCommand : ICommand {
public event EventHandler CanExecuteChanged;
readonly Action<object> _action;
readonly Predicate<object> _predicate;
public RelayCommand(Action<object> action, Predicate<object> predicate) {
_action = action;
_predicate = predicate;
}
public RelayCommand(Action<object> action) {
_action = action;
_predicate = ((x) => true);
}
public bool CanExecute(object parameter) {
return _predicate(parameter);
}
public void Execute(object parameter) {
_action(parameter);
}
}
Update 2:
What's the actual problem?
<Button Content="{Binding Name}" Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
CommandParameter="{Binding HomePage}"/>
The Content gets bound properly but the CommandParameter (HomePage) which should be of Type BaseViewModel won't get validated through the Command's CanExecute. Both the Properties, Name and HomePage are defined inside the BaseViewModel.
Update 3:
<Button Content="{Binding Name}" Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
CommandParameter="{Binding DataContext.HomePage, ElementName=Test}"/>
In your there is the next lines:
<DataTemplate DataType="{x:Type home:HomeViewModel}">
<home:Home/>
</DataTemplate>
meaning that the visual form of HomeViewModel is Home.
Your Binding works fine, I think your problem is the command itself. I don't know what is RelayCommand but i think your bug is from there.
RelayCommand should be something like this:
public abstract class BaseViewModel : INotifyPropertyChanged
{
private ICommand _f1KeyCommand;
public ICommand F1KeyCommand
{
get
{
if (_f1KeyCommand == null)
_f1KeyCommand = new DelegateCommand(F1KeyCommandCallback, CanExecute);
return _f1KeyCommand;
}
}
/// <summary>
/// Fired if F1 is pressend and 'CanExecute' returns true
/// </summary>
private void F1KeyCommandCallback(object obj)
{
Console.WriteLine("F1KeyCommandCallback fired");
}
// ....
}
This class allows delegating the commanding logic to methods passed as parameters,and enables a View to bind commands to objects that are not part of the element tree:
public class DelegateCommand : ICommand
{
#region Data Members
private Action<object> execute;
private Predicate<object> canExecute;
private event EventHandler CanExecuteChangedInternal;
#endregion
#region Ctor
public DelegateCommand(Action<object> execute)
: this(execute, DefaultCanExecute)
{
}
public DelegateCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
{
throw new ArgumentNullException("execute");
}
if (canExecute == null)
{
throw new ArgumentNullException("canExecute");
}
this.execute = execute;
this.canExecute = canExecute;
}
#endregion
#region Properties
public event EventHandler CanExecuteChanged
{
add
{
CommandManager.RequerySuggested += value;
this.CanExecuteChangedInternal += value;
}
remove
{
CommandManager.RequerySuggested -= value;
this.CanExecuteChangedInternal -= value;
}
}
#endregion
#region Public Methods
public bool CanExecute(object parameter)
{
return this.canExecute != null && this.canExecute(parameter);
}
public void Execute(object parameter)
{
this.execute(parameter);
}
public void OnCanExecuteChanged()
{
EventHandler handler = this.CanExecuteChangedInternal;
if (handler != null)
{
handler.Invoke(this, EventArgs.Empty);
}
}
public void Destroy()
{
this.canExecute = _ => false;
this.execute = _ => { return; };
}
#endregion
#region Private Methods
private static bool DefaultCanExecute(object parameter)
{
return true;
}
#endregion
}
In your view:
<controls:ModernButton Background="Dark"
Text="{Binding Name}"
Command="{Binding F1KeyCommand"
CommandParameter="{Binding}"/>
Is there any way that i can invoke a command and pass the SelectedItem as parameter to ViewModel when the selection change occurs?
XAML:
<telerik:GridViewComboBoxColumn ItemsSource="{Binding StatusList, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" SelectedValueMemberPath="StatusName" DisplayMemberPath="StatusName" DataMemberBinding="{Binding Shipped, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" IsVisible="{Binding IsExist, Mode=TwoWay}">
</telerik:GridViewComboBoxColumn>
I tried like adding Interation Triggers but i couldn't able to find the exact event to pass the SelectedItem as parameter,
<i:Interaction.Triggers>
<i:EventTrigger EventName="ContextMenuClosing">
<i:InvokeCommandAction Command="{Binding StatusDropdownCommand, Mode=OneWay}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
ViewModel:
public ICommand StatusDropdownCommand { get { return new RelayCommand(StatusDropdown); } }
void StatusDropdown()
{
}
Kindly help.
Updated Code:
<telerik:GridViewComboBoxColumn ItemsSource="{Binding StatusList, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" SelectedValueMemberPath="StatusName" DisplayMemberPath="StatusName" DataMemberBinding="{Binding Shipped, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" IsVisible="{Binding IsExist, Mode=TwoWay}">
<i:Interaction.Triggers>
<Converter:RoutedEventTrigger RoutedEvent="Selector.SelectionChanged" >
<Converter:CustomCommandAction Command="{Binding SelectionChangedCommand}" />
</Converter:RoutedEventTrigger>
</i:Interaction.Triggers>
</telerik:GridViewComboBoxColumn>
Issue Occured:
Seems that subscription to Selector.SelectionChanged routed event should do the job.
<i:Interaction.Triggers>
<local:RoutedEventTrigger RoutedEvent="Selector.SelectionChanged">
<local:CustomCommandAction Command="{Binding SelectionChangedCommand}" />
</local:RoutedEventTrigger>
</i:Interaction.Triggers>
You need custom trigger to handle attached events:
public class RoutedEventTrigger : EventTriggerBase<DependencyObject>
{
RoutedEvent _routedEvent;
public RoutedEvent RoutedEvent
{
get { return _routedEvent; }
set { _routedEvent = value; }
}
public RoutedEventTrigger()
{
}
protected override void OnAttached()
{
Behavior behavior = base.AssociatedObject as Behavior;
FrameworkElement associatedElement = base.AssociatedObject as FrameworkElement;
if (behavior != null)
{
associatedElement = ((IAttachedObject)behavior).AssociatedObject as FrameworkElement;
}
if (associatedElement == null)
{
throw new ArgumentException("Routed Event trigger can only be associated to framework elements");
}
if (RoutedEvent != null)
{
associatedElement.AddHandler(RoutedEvent, new RoutedEventHandler(this.OnRoutedEvent));
}
}
void OnRoutedEvent(object sender, RoutedEventArgs args)
{
base.OnEvent(args);
}
protected override string GetEventName()
{
return RoutedEvent.Name;
}
}
Also you may use your own action for triggering your command:
public sealed class CustomCommandAction : TriggerAction<DependencyObject>
{
public static readonly DependencyProperty CommandParameterProperty =
DependencyProperty.Register("CommandParameter", typeof(object), typeof(CustomCommandAction), null);
public static readonly DependencyProperty CommandProperty = DependencyProperty.Register(
"Command", typeof(ICommand), typeof(CustomCommandAction), null);
public ICommand Command
{
get
{
return (ICommand)this.GetValue(CommandProperty);
}
set
{
this.SetValue(CommandProperty, value);
}
}
public object CommandParameter
{
get
{
return this.GetValue(CommandParameterProperty);
}
set
{
this.SetValue(CommandParameterProperty, value);
}
}
protected override void Invoke(object parameter)
{
if (this.AssociatedObject != null)
{
ICommand command = this.Command;
if (command != null)
{
if (this.CommandParameter != null)
{
if (command.CanExecute(this.CommandParameter))
{
command.Execute(this.CommandParameter);
}
}
else
{
if (command.CanExecute(parameter))
{
command.Execute(parameter);
}
}
}
}
}
}