Now, I'm mocking up the MessageBox. I build the Close Button in Style.Template in .xaml, but I don't know how to binding the command with CloseCommand. Whether it can bingding with System Close Command?
.cs (define a custom control):
internal sealed class MessageBoxModule : Window
{
#region Constructor
static MessageBoxModule()
{
DefaultStyleKeyProperty.OverrideMetadata(
typeof(MessageBoxModule),
new FrameworkPropertyMetadata(typeof(MessageBoxModule)));
}
public MessageBoxModule()
{
WindowStartupLocation = System.Windows.WindowStartupLocation.CenterScreen;
AllowsTransparency = true;
WindowStyle = System.Windows.WindowStyle.None;
ShowInTaskbar = false;
try
{
Resources.Source = new Uri(#"/Wpf.Controls;component/Themes/Generic.xaml", UriKind.Relative);
}
catch
{ }
...
.xaml (is a ResourceDictionary file, provide a style for MessageBoxModule):
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Wpf.Controls">
<Style TargetType="{x:Type local:MessageBoxModule}">
<Setter Property="Template" >
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MessageBoxModule}">
<Border ...>
<Button x:Name="CloseButton".../>
...
</Border>
...
see the red button, I don't know how to binding a command for it:
This is The Entire solution:
.cs:
internal sealed class MessageBoxModule : Window
{
public MessageBoxModule()
{
InputGestureCollection inputGestures = new InputGestureCollection();
inputGestures.Add(new KeyGesture(Key.F4, ModifierKeys.Alt));
CloseCommand = new RoutedCommand(
"CloseCommand",
typeof(MessageBoxModule),
inputGestures);
CommandBindings.Add(new CommandBinding(CloseCommand, CloseCommandExecuted));
}
public static readonly DependencyProperty CloseCommandProperty =
DependencyProperty.Register(
"CloseCommand",
typeof(RoutedCommand),
typeof(MessageBoxModule));
public RoutedCommand CloseCommand
{
get { return (RoutedCommand)GetValue(CloseCommandProperty); }
set { SetValue(CloseCommandProperty, value); }
}
public void CloseCommandExecuted(object sender, ExecutedRoutedEventArgs e)
{
Close();
}
}
.xaml:
<ResourceDictionary >
<Style TargetType="{x:Type local:MessageBoxModule}">
<Setter Property="Template" >
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MessageBoxModule}">
<Border ...>
<Button x:Name="CloseButton" Command="{TemplateBinding CloseCommand}"/>
...
Expose dependency property of type ICommand from class MessageBoxModule:
public ICommand CloseCommand
{
get { return (ICommand)GetValue(CloseCommandProperty); }
set { SetValue(CloseCommandProperty, value); }
}
public static readonly DependencyProperty CloseCommandProperty =
DependencyProperty.Register("CloseCommand", typeof(ICommand),
typeof(MessageBoxModule));
Use TemplateBinding to bind to Command like this:
<Button x:Name="CloseButton" Command="{TemplateBinding CloseCommand}"/>
Since command is exposed, you can bind to it from outside like this:
<local:MessageBoxModule CloseCommand="{Binding ViewModelCommand}"/>
Assuming you already have View model in place and it already contains ICommand to which you want to bind close button.
#SubmarineX
Create a DataContext for the button Something like
public class ButtonViewModel{
private RelayCommand _CloseCommand;
public ICommand CloseCommand
{
get { return _CloseCommand?? (_CloseCommand= new RelayCommand(p => Close())); }
}
void Close(){
//Here u cn write the logic which close the window from this view model or raise an event which handled by your button container
}
}
public class RelayCommand : ICommand
{
#region Fields
private readonly Action<object> _execute;
private readonly Predicate<object> _canExecute;
#endregion // Fields
#region Constructors
public RelayCommand(Action<object> execute)
: this(execute, null)
{
}
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null) throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
#endregion // Constructors
#region ICommand Members [DebuggerStepThrough]
public bool CanExecute(object parameter)
{
return _canExecute == null || _canExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
_execute(parameter);
}
#endregion // ICommand Members }
}
in xaml
<Button x:Name="CloseButton" Command="{Binding Close}"/>
Ensure that the DataContext is availble for the button
Related
I have just started learning WPF yesterday and my goal is to create window with simple grid with hotel booking information. For now there are just room number, number of guests, dates and "Action" columns. In the "Actions" column there is "Save" button. It should be able to save updates or create new booking when clicked in new row. The problem is when I click "Save" button SaveBooking method is not invoked. I'm also not sure how to properly bind to CurrentBooking object. As I am new to WPF I tried to figure it out from few tutorials. Here's what I've created.
XAML:
<Window x:Class="HotelApp.MainWindow"
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:HotelApp"
mc:Ignorable="d"
Title="MainWindow" Height="800" Width="1000">
<Grid>
<TabControl>
<TabItem Header="Bookings">
<DataGrid AutoGenerateColumns = "False" ItemsSource="{Binding Bookings}">
<DataGrid.Columns>
<DataGridTextColumn Header = "Room" Binding = "{Binding Room, Mode=TwoWay}" />
<DataGridTextColumn Header = "Floor" Binding = "{Binding NumOfGuests, Mode=TwoWay}" />
<DataGridTextColumn Header = "From" Binding = "{Binding From, Mode=TwoWay}"/>
<DataGridTextColumn Header = "To" Binding = "{Binding To, Mode=TwoWay}"/>
<DataGridTemplateColumn Header = "Actions">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Content="Save" Command="{Binding DataContext.SaveBookingCommand }" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</TabItem>
<TabItem Header="Guests" />
</TabControl>
</Grid>
</Window>
MODEL:
public class BookingModel : ObservableObject
{
private int _room;
public int Room
{
get => _room;
set
{
if (value != _room)
{
_room = value;
OnPropertyChanged("Room");
}
}
}
private int _numOfGuests;
public int NumOfGuests
{
get => _numOfGuests;
set
{
_numOfGuests = value;
OnPropertyChanged("NumOfGuests");
}
}
private DateTime _from;
public DateTime From
{
get => _from;
set
{
_from = value;
OnPropertyChanged("From");
}
}
private DateTime _to;
public DateTime To
{
get => _to;
set
{
_to = value;
OnPropertyChanged("To");
}
}
}
VIEWMODEL:
public class MainWindowVM : ObservableObject
{
private readonly IBookingService _bookingService;
private ICommand _saveBookingCommand;
public ICommand SaveBookingCommand
{
get
{
if (_saveBookingCommand == null)
{
_saveBookingCommand = new RelayCommand(
param => SaveBooking(),
param => (CurrentBooking != null)
);
}
return _saveBookingCommand;
}
}
private ObservableCollection<BookingModel> _Bookings { get; set; }
private BookingModel _currentBookng;
public BookingModel CurrentBooking
{
get { return _currentBookng; }
set
{
if (value != _currentBookng)
{
_currentBookng = value;
OnPropertyChanged("CurrentBooking");
}
}
}
public ObservableCollection<BookingModel> Bookings
{
get { return _Bookings; }
set { _Bookings = value; }
}
public MainWindowVM(IBookingService bookingService)
{
_bookingService = bookingService;
BrowseBookings();
}
public void BrowseBookings()
{
var bookings = _bookingService.Browse().Select(x => new BookingModel { Room = x.Room.RoomId, NumOfGuests = x.NumOfGuests, From = x.From, To = x.To });
Bookings = new ObservableCollection<BookingModel>(bookings);
}
private void SaveBooking()
{
// send CurrentBooking to service
}
}
RelayCommand:
public class RelayCommand : ICommand
{
#region Fields
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
#endregion // Fields
#region Constructors
public RelayCommand(Action<object> execute)
: this(execute, null)
{
}
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
#endregion // Constructors
#region ICommand Members
[DebuggerStepThrough]
public bool CanExecute(object parameters)
{
return _canExecute == null ? true : _canExecute(parameters);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameters)
{
_execute(parameters);
}
#endregion // ICommand Members
}
Your command is in the datacontext of the entire datagrid MainWindowVM.
Your button's datacontext is that of the row - a BookingModel.
You need some relativesource on that binding.
In principle that looks like this:
{Binding DataContext.ParentVMProperty,
RelativeSource={RelativeSource AncestorType={x:Type typeOfAncestor}}}
And your type, in this case, will be DataGrid.
You can also bind selecteditem on the datagrid and when they click the button ensure that is selected using the datagrid properties for selection.
Or
You can have a commandparameter on the command which is
CommandParameter="{Binding .}"
Relaycommand usually comes in two flavours one being RelayCommand
Maybe I missed it but I don't see that in your implementation. I'd suggest you go grab the source code for MVVM Light and paste into your solution for a more complete implementation. Or just add the nuget package if you're not using .net core. You want the commandwpf namespace version of relaycommand.
You left out a lot of code, so I don't know which nuget package you used for your ObservableObject. Anywho, I faked the ObservableObject and got the binding working. The main problem was that you were trying to bind SaveBookingCommand at the BookingModel level, when in your code you have it written in the MainWindowVM level.
You can easily fix this by parenting your MainWindowVM in your BookingModel, and change your binding to be Command={Binding Parent.SaveBookingCommand}.
Here's some pointers to the edits that I made:
MainWindow.xaml.cs:
<DataTemplate>
<Button Content="Save" Command="{Binding Parent.SaveBookingCommand}" />
</DataTemplate>
BookingModel.cs:
public class BookingModel : ObservableObject
{
public MainWindowVM Parent { get; private set; }
public BookingModel()
{
this.Parent = null;
}
public BookingModel(MainWindowVM parent)
{
this.Parent = parent;
}
// ... you know the rest
MainWindowVM.cs:
public MainWindowVM : ObservableObject
{
public void BrowseBookings()
{
// NOTICE that I added 'this' as the parameter argument to connect MainWindowVM to the BookingModel.
var bookings = _bookingService.Browse().Select(x => new BookingModel(this) { Room = x.Room, NumOfGuests = x.NumOfGuests, From = x.From, To = x.To });
Bookings = new ObservableCollection<BookingModel>(bookings);
CurrentBooking = Bookings.First();
}
// ... you know the rest
I have a custom ItemsControl just like that
public class MyControl : ItemsControl { }
My Template would something like
<Style TargetType="{x:Type local:MyControl}">
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<TextBox Text="{Binding Text}" />
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
Now I want to act when either when ENTER or SPACE got pressed so I'd do something like this
<TextBox Text="{Binding Text}">
<TextBox.InputBindings>
<KeyBinding Key="Space"
Command="{Binding KeyPressedCommand}"
CommandParameter="{x:Static Key.Space}"/>
</TextBox.InputBindings>
</TextBox>
But how do I bind it to my Control?
One of ways to solve it is using MVVM pattern and RelayCommand class from tutorial.
RelayCommand.cs
public class RelayCommand : ICommand
{
private readonly Action<object> _execute;
private readonly Func<object, bool> _canExecute;
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
{
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter) => _canExecute == null || _canExecute(parameter);
public void Execute(object parameter) => _execute(parameter);
}
Then you should set DataContext of your Window (or UserControl), it requred for resolving bindings in xaml.
One of ways doing it:
MainWindow.xaml
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
Setup Text Property and KeyPressedCommand from "something like this" example in the MainViewModel class.
MainViewModel.cs
public class MainViewModel : INotifyPropertyChanged
{
private bool _text;
public bool Text
{
get => _text;
set
{
_text = value;
OnPropertyChanged(nameof(Text));
// Text changed!
}
}
public ICommand KeyPressedCommand => new RelayCommand(obj =>
{
if (obj is Key key) {
// do something here with the 'key' provided by CommandParameter
}
});
public MainViewModel()
{
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
I am very new to WPF and relatively new to C# programming (programming in general), and I'm trying to develop a WPF application.
I have tried to go through several posts similar to this, but I can't seem to find the answer of why this is not working.
So, I'm having a hard time understanding the MVVM architecture, how and what it requires to switch between multiple user controls binded to a single <ContentControl />.
From what I understand and read so far, is that I have to bind the view model like this:
<ContentControl Content="{Binding ApplicationViewModel}"/>
So here is what I want to a achieve:
An ApplicationWindow.xaml with sidebar menu on the left side that will be shown at all times when the application is running, and a <ContentControl/> on the remaining space. Buttons shown on the sidebar menu will be:
Main (will show MainView.xaml User Control, should be the default User Control)
Settings (will show SettingsView.xaml User Control)
Exit (will close the application)
I understand that I need to bind the buttons to ICommand commands, and I understand the concept of a RelayCommand.cs class.
So let's jump into the simplified code of my idea and figure out what I need to understand and what I may have misunderstood in the process.
What MainView.xaml and SettingsView.xaml contain are not important right now, as I'm just trying to figure out how to show them in my application.
Here's the ApplicationWindow.xaml:
<Window x:Class="WpfApp1.ApplicationWindow"
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:WpfApp1"
xmlns:v="clr-namespace:WpfApp1.View"
xmlns:vm="clr-namespace:WpfApp1.ViewModel"
mc:Ignorable="d"
Title="ApplicationWindow" Height="1080" Width="1920"
WindowStyle="None" WindowState="Maximized">
<Window.Resources>
<DataTemplate DataType="{x:Type vm:MainViewModel}">
<v:MainView/>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:SettingsViewModel}">
<v:SettingsView/>
</DataTemplate>
</Window.Resources>
<DockPanel>
<!--Menu bar on the left-->
<Border DockPanel.Dock="Left">
<StackPanel Orientation="Vertical" Background="Gray" Width="120">
<Button Content="Main" Command="{Binding ShowMainCommand}"/>
<Button Content="Settings" Command="{Binding ShowSettingsCommand}"/>
<Button Content="Exit" Command="{Binding ExitApplicationCommand}"/>
</StackPanel>
</Border>
<!--The content control that view the current view-->
<ContentControl Content="{Binding ApplicationViewModel}"/>
</DockPanel>
</Window>
Note: DataContext is set to ApplicationViewModel.cs in App.xaml.cs by overriding the OnStartup() method.
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
ApplicationWindow app = new ApplicationWindow
{
DataContext = new ApplicationViewModel()
};
app.Show();
}
}
Here's the ApplicationViewModel.cs:
public class ApplicationViewModel : ViewModelBase
{
#region Fields
private List<ViewModelBase> _viewModels;
private ViewModelBase _currentViewModel;
private ICommand _showMainCommand;
private ICommand _showSettingsCommand;
private ICommand _exitApplicationCommmand;
#endregion
#region Constructor
public ApplicationViewModel()
{
ViewModels = new List<ViewModelBase>
{
new MainViewModel(),
new SettingsViewModel()
};
CurrentViewModel = ViewModels[0];
}
#endregion
#region Public Properties
public List<ViewModelBase> ViewModels
{
get
{
return _viewModels;
}
set
{
if (_viewModels != value)
{
_viewModels = value;
OnPropertyChanged(nameof(ViewModels));
}
}
}
public ViewModelBase CurrentViewModel
{
get
{
return _currentViewModel;
}
set
{
if(_currentViewModel != value)
{
_currentViewModel = value;
OnPropertyChanged(nameof(CurrentViewModel));
}
}
}
#endregion
#region Commands
public ICommand ShowMainCommand
{
get
{
if(_showMainCommand == null)
{
_showMainCommand = new RelayCommand(action => ShowMain());
}
return _showMainCommand;
}
}
public ICommand ShowSettingsCommand
{
get
{
if (_showSettingsCommand == null)
{
_showSettingsCommand = new RelayCommand(action => ShowSettings());
}
return _showSettingsCommand;
}
}
public ICommand ExitApplicationCommand
{
get
{
if (_exitApplicationCommmand == null)
{
_exitApplicationCommmand = new RelayCommand(action => ExitApplication());
}
return _exitApplicationCommmand;
}
}
#endregion
#region Private Methods
private void ShowMain()
{
CurrentViewModel = ViewModels[0];
}
private void ShowSettings()
{
CurrentViewModel = ViewModels[1];
}
private void ExitApplication()
{
MessageBoxResult result = MessageBox.Show("Are you sure you want to exit?", "Exit", MessageBoxButton.YesNo);
if (result == MessageBoxResult.Yes)
{
System.Windows.Application.Current.Shutdown();
}
}
#endregion
}
So, from what I understand, the ApplicationWindow.xaml should be able to determine which view to show out from what the CurrentViewModel is set to.
For the sake of information (or miss-information), here are ViewModelBase.cs:
public class ViewModelBase : INotifyPropertyChanged
{
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
And RelayCommand.cs:
public class RelayCommand : ICommand
{
#region Fields
private readonly Action<object> _execute;
private readonly Predicate<object> _canExecute;
#endregion
#region Constructors
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
public RelayCommand(Action<object> execute) : this(execute, null)
{
}
#endregion
#region ICommand
public bool CanExecute(object parameters)
{
return _canExecute == null ? true : _canExecute(parameters);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameters)
{
_execute(parameters);
}
#endregion
}
I hope my thought process on this was clear to you, and that one of you smart programmers out there can help solving this, and help me understand why this isn't turning out as I want it to.
In case of what I'm trying to do is harder than Elon Musk's project on making life multiplanetary, feel free to explain why and suggest me a better way to
Your Content control binding should be pointed at the actual property you change when switching ViewModels
<ContentControl Content="{Binding CurrentViewModel}"/>
I've been stuck on this problem for a few hours. I am attempting to implement an MVVM-style Word Add-In in WPF. I am not using an MVVM toolkit. I have a WPF user control that is docked within a WinForm. While I am able to see the WPF user control within the win form and interact with it, my generic RelayCommand<T> that is bound to a WPF button won't execute when I click the button. The RelayCommand lives in ViewModel.cs and the DataContext for the view is set through the code-behind. I'm sure I'm doing something silly, but can't figure out what it is and therefore not sure why RelayCommand property's get{} won't get executed. Please see the code below. Thanks in advance for the help!
RelayCommand.cs (code snippet excludes namespace and includes statements)
/// <summary>
/// RelayCommand
/// </summary>
/// <typeparam name="T">Generic Parameter</typeparam>
public class RelayCommand<T> : ICommand where T : class
{
#region Constructors
/// <summary>
/// RelayCommand constructor
/// </summary>
/// <param name="exec">Delegate that encapsulates a method that takes in a single parameter and returns void</param>
/// <param name="canExec">Delegate that encapsulates a method that defines a set of criteria and returns a true if criteria is met; else false</param>
public RelayCommand(Action<T> execute, Predicate<T> canExecute = null)
{
if (execute == null)
throw new ArgumentNullException("execute is null");
_canExecute = canExecute;
_execute = execute;
}
#endregion
#region Members
/// <summary>
/// Execute method
/// </summary>
/// <param name="param">Parameter</param>
public void Execute(object param)
{
T obj = param as T;
if(obj != null)
{
_execute(obj);
}
}
/// <summary>
/// CanExec is a method that shows whether or not execution can happen
/// </summary>
/// <param name="param">Parameter</param>
/// <returns>true if can execute; else false</returns>
public bool CanExecute(object param)
{
if (_canExecute == null)
return true;
T obj = param as T;
return obj == null || _canExecute(obj);
}
/// <summary>
/// CanExec event changed
/// </summary>
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
#endregion
#region Fields
private readonly Predicate<T> _canExecute;
private readonly Action<T> _execute;
#endregion
}
SubmissionUserControl.xaml (only the pertinent snippet. excludes some code)
<Button Grid.Column="2" x:Name="SubmitButton" Command="{Binding Path=SubmitCommentCommand}"
Content="Submit" HorizontalAlignment="Right" Margin="5"/>
SubmissionUserControl.xaml.cs (contains snippet where I reference the ViewModel)
ViewModel viewModel;
public SubmissionUserControl()
{
InitializeComponent();
viewModel = new ViewModel();
DataContext = viewModel;
}
ViewModel.cs (excludes some code. only shows the pertinent RelayCommand)
/// <summary>
/// SubmitCommentCommand responsible for interacting with UI to submit a comment.
/// </summary>
/// <returns>Returns a RelayCommand that executes a method to Save comments from the comment box</returns>
public ICommand SubmitCommentCommand
{
get
{
return _submitCommentCommand ?? (_submitCommentCommand = new RelayCommand<object>(param => this.SaveComment()));
}
}
To give you a more detailed start into MVVM and RelayCommands:
You do not have to declare your ViewModel in Xaml, this is mostly done programmatically on application root level, maybe with some DI.
When sticking to this MSDN Article your RelayCommand should look like this:
public class RelayCommand : ICommand
{
#region Fields
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
#endregion // Fields
#region Constructors
public RelayCommand(Action<object> execute)
: this(execute, null)
{
}
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
#endregion // Constructors
#region ICommand Members
[DebuggerStepThrough]
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
_execute(parameter);
}
#endregion // ICommand Members
}
Additionally you can define a generic RelayCommand to handle Commandparameters like this:
public class GenericRelayCommand<T> : ICommand
{
private readonly Action<T> _execute;
public Predicate<T> CanExecuteFunc { get; private set; }
public GenericRelayCommand(Action<T> execute) : this(execute, p => true)
{}
public GenericRelayCommand(Action<T> execute, Predicate<T> canExecuteFunc)
{
_execute = execute;
CanExecuteFunc = canExecuteFunc;
}
public bool CanExecute(object parameter)
{
var canExecute = CanExecuteFunc((T)parameter);
return canExecute;
}
public void Execute(object parameter)
{
_execute((T)parameter);
}
public event EventHandler CanExecuteChanged
{
add
{
CommandManager.RequerySuggested += value;
}
remove
{
CommandManager.RequerySuggested -= value;
}
}
}
In your ViewModel the RelayCommands should be definied like this (I implemented INotifyPropertyChanged as well for further WPF Xaml Property handling example):
public class ViewModel : INotifyPropertyChanged
{
private string _comment;
public string Comment
{
get { return _comment; }
set { _comment = value; OnPropertyChanged("Comment"); }
}
public GenericRelayCommand<string> SubmitComment1Command { get; set; }
public RelayCommand SubmitComment2Command { get; set; }
public ViewModel()
{
Comment = "Hello World!";
SubmitComment1Command = new GenericRelayCommand<string>(OnSubmitComment1);
SubmitComment2Command = new RelayCommand(OnSubmitComment2);
}
private void OnSubmitComment1(string obj)
{
//Save Comment Mock with CommandParameter
MessageBox.Show(obj);
}
private void OnSubmitComment2(object obj)
{
//Save Comment Mock with Property
MessageBox.Show(Comment);
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
I put your Button Example into a fresh WPF Application like this:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Width="525"
Height="350">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0"
Orientation="Horizontal">
<TextBox Name="textBox"
Width="200"
Text="{Binding Comment,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}" />
<Button x:Name="SubmitButton1"
Grid.Column="0"
Margin="5"
HorizontalAlignment="Right"
Command="{Binding Path=SubmitComment1Command}"
CommandParameter="{Binding ElementName=textBox,
Path=Text}"
Content="Submit1" />
</StackPanel>
<Button x:Name="SubmitButton2"
Grid.Column="1"
Margin="5"
HorizontalAlignment="Right"
Command="{Binding Path=SubmitComment2Command}"
Content="Submit2" />
</Grid>
</Window>
And set the DataContext like this for simplicity reasons (As stated before, this is normally done through some kind of DI at root level):
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
Then everything should work fine.
I solved this issue by telling the Model about data context in XAML instead of .cs file.
First: Tell Model the namespace in which you placed your view model, mine was like below:
xmlns:Local="clr-namespace:MKPL.Views.A01.S020"
Second: Add your ViewModel in XAML Resources like:
<UserControl.Resources>
<Local:ViewModel x:Key="dvm"/>
</UserControl.Resources>
Third: Add DataContext to the parent container,in my case that is Grid.
<Grid x:Name="LayoutRoot" DataContext="{Binding Source={StaticResource dvm}}">
Fourth: In your button code add the data context like:
<Button Grid.Column="2" x:Name="SubmitButton" Command="{Binding Path=SubmitCommentCommand, Source={StaticResource dvm}}"
Content="Submit" HorizontalAlignment="Right" Margin="5"/>
Hope it will help you
I'm trying to send a variable from the ViewModel as a parameter to a command. The command looks like this:
public class EditPersonCommand : ICommand
{
private bool _CanExecute = false;
public bool CanExecute(object parameter)
{
PersonModel p = parameter as PersonModel;
CanExecuteProperty = (p != null) && (p.Age > 0);
return CanExecuteProperty;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter) { }
private bool CanExecuteProperty
{
get { return _CanExecute; }
set
{
if (_CanExecute != value)
{
_CanExecute = value;
EventHandler can_execute = CanExecuteChanged;
if (can_execute != null)
{
can_execute.Invoke(this, EventArgs.Empty);
}
}
}
}
}
The ViewModel looks like this:
public class PersonViewModel : ViewModelBase
{
private PersonModel _PersonModel;
private EditPersonCommand _EditPersonCommand;
///<remarks>
/// must use the parameterless constructor to satisfy <Window.Resources>
///</remarks>
public PersonViewModel()
: this(new PersonModel())
{
}
public PersonViewModel(PersonModel personModel)
{
_PersonModel = personModel;
}
public ICommand EditPersonCommand
{
get
{
if (_EditPersonCommand == null)
{
_EditPersonCommand = new EditPersonCommand();
}
return _EditPersonCommand;
}
}
}
The xaml looks like this:
<Button Content="Edit" HorizontalAlignment="Right" Height="20" Width="80"
Command="{Binding EditPersonCommand}"
CommandParameter="{Binding _PersonModel}" />
I've tried creating a property in the ViewModel instead of using the private local variable name, but that didnt work either. The object parameter always shows null in the call to CanExecute and the button is never enabled. If I change the CommandParameter value to Hello, then I receive Hello in the call to CanExecute, so I'm not sure why the variable doesnt work. Any help would be appreciated.
Update: I've also tried making a public property to the model (which I dont really want to expose the model, but just tried it to see if it works, but it doesnt).
// Added this to the ViewModel
public PersonModel PersonModelProp
{
get
{
return _PersonModel;
}
set
{
_PersonModel = value;
OnPropertyChanged("PersonModelProp");
}
}
And changed the xaml to this:
<Button Content="Edit" HorizontalAlignment="Right" Height="20" Width="80"
Command="{Binding EditPersonCommand}"
CommandParameter="{Binding PersonModelProp}" />
But still no luck. The ViewModel does implement INotifyPropertyChanged
Is the CommandParameter always null or are you only checking the first time it is being executed?
It appears that the order in which you declare your properties matters in this case since setting the Command property causes the CanExecute to fire immediately before the CommandParameter has been set.
Try moving the CommandParameter property before the Command property:
<Button Content="Edit" HorizontalAlignment="Right" Height="20" Width="80"
CommandParameter="{Binding PersonModelProp}"
Command="{Binding EditPersonCommand}" />
Also, see here and here.
Edit
To ensure that your events are being raised properly you should raise the CanExecuteChanged event when the PersonModelProp value changes.
The Command:
public class EditPersonCommand : ICommand
{
public bool CanExecute(object parameter)
{
PersonModel p = parameter as PersonModel;
return p != null && p.Age > 0;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
//command implementation
}
public void RaiseCanExecuteChanged()
{
var handler = CanExecuteChanged;
if(handler != null)
{
handler(this, EventArgs.Empty);
}
}
}
And the view model:
public class PersonViewModel : ViewModelBase
{
private PersonModel _PersonModel;
private EditPersonCommand _EditPersonCommand;
///<remarks>
/// must use the parameterless constructor to satisfy <Window.Resources>
///</remarks>
public PersonViewModel()
: this(new PersonModel())
{
_EditPersonCommand = new EditPersonCommand();
}
public PersonViewModel(PersonModel personModel)
{
_PersonModel = personModel;
}
public ICommand EditPersonCommand
{
get
{
return _EditPersonCommand;
}
}
public PersonModel PersonModelProp
{
get
{
return _PersonModel;
}
set
{
_PersonModel = value;
OnPropertyChanged("PersonModelProp");
EditPersonCommand.RaiseCanExecuteChanged();
}
}
}
Two points to the answer:
First, as #akton mentioned, you can only bind to public properties. It doesn't have to be a DependencyProperty though.
Second, which took me some tome to figure out, is that you have to set the binding for the CommandParameter before the Command property. i.e.
<Button Content="Edit" HorizontalAlignment="Right" Height="20" Width="80"
CommandParameter="{Binding PersonModelProp}"
Command="{Binding EditPersonCommand}" />
Hope this helps :)
_PersonModel is private and so cannot be accessed. Create a public property that exposes it and bind to that in the CommandParameter. Remember to make the property a dependency property (technically not required but it helps) and the ViewModel should implement INotifyProperty changed and fire the PropertyChanged event so the binding is updated.
I think you have a problem in your EditPersonCommand (it not fired ok).I check it with relayCommand and it work!
This is the code:
ViewModel:
public class PersonViewModel : ViewModelBase
{
private PersonModel _PersonModel;
private ICommand _EditPersonCommand;
///<remarks>
/// must use the parameterless constructor to satisfy <Window.Resources>
///</remarks>
public PersonViewModel()
: this(new PersonModel())
{
}
public PersonViewModel(PersonModel personModel)
{
PersonModelProp = personModel;
}
public ICommand EditPersonCommand
{
get
{
if (_EditPersonCommand == null)
{
_EditPersonCommand = new RelayCommand(ExecuteEditPerson,CanExecuteEditPerson);
}
return _EditPersonCommand;
}
}
private bool CanExecuteEditPerson(object parameter)
{
PersonModel p = parameter as PersonModel;
return (p != null) && (p.Age > 0);
}
private void ExecuteEditPerson(object o)
{
}
public PersonModel PersonModelProp
{
get
{
return _PersonModel;
}
set
{
_PersonModel = value;
NotifyPropertyChanged("PersonModelProp");
}
}
}
And this RelayCommand (Fire events ok!)
public class RelayCommand : ICommand
{
#region Constants and Fields
private readonly Predicate<object> canExecute;
private readonly Action<object> execute;
#endregion
#region Constructors and Destructors
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;
}
#endregion
#region Events
public event EventHandler CanExecuteChanged
{
add
{
CommandManager.RequerySuggested += value;
}
remove
{
CommandManager.RequerySuggested -= value;
}
}
#endregion
#region Implemented Interfaces
#region ICommand
[DebuggerStepThrough]
public bool CanExecute(object parameter)
{
return this.canExecute == null || this.canExecute(parameter);
}
public void Execute(object parameter)
{
this.execute(parameter);
}
#endregion
#endregion
}
Xmal:
<Button Content="Edit" HorizontalAlignment="Right" Height="20" Width="80"
CommandParameter="{Binding PersonModelProp}"
Command="{Binding EditPersonCommand}" />