Executing a command on Checkbox.Checked or Unchecked - wpf

I have a checkbox control on a window. I'd like to execute a command that will call a method in the associated view model. I'll also need the value of the checkbox as well. I cannot seem to find a way to associate a command with a checkbox. Has anyone done this?

<CheckBox Content="CheckBox"
Command="{Binding YourCommand}"
CommandParameter="{Binding IsChecked, RelativeSource={RelativeSource Self}}" />

If you use MVVM, you can use event triggers like this:
<CheckBox IsChecked="{Binding ServiceOrderItemTask.IsCompleted, Mode=TwoWay}" Content="{Binding ServiceOption.Name}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Checked">
<i:InvokeCommandAction Command="{Binding DataContext.IsCompletedCheckedCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type t:RadGridView}}}" CommandParameter="{Binding}"/>
</i:EventTrigger>
<i:EventTrigger EventName="Unchecked">
<i:InvokeCommandAction Command="{Binding DataContext.IsCompletedUncheckedCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type t:RadGridView}}}" CommandParameter="{Binding}"/>
</i:EventTrigger>
</i:Interaction.Triggers>

This will work what you requires -
<CheckBox CommandParameter="{Binding}"
Command="{Binding DataContext.AddRemovePresetAssignmentCommand,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type UserControl}}}"
Content="{Binding Path=Name}">

Add System.Windows.Interactivity to your project references.
Add xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" to your XAML namespaces.
<CheckBox IsChecked="{Binding SomeBoolProperty, Mode=OneWay}" Content="Check Meee!">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Checked">
<i:InvokeCommandAction Command="{Binding MyOnCheckedCommand}"/>
</i:EventTrigger>
<i:EventTrigger EventName="Unchecked">
<i:InvokeCommandAction Command="{Binding MyOnUncheckedCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</CheckBox>
I implement INotifyPropertyChanged on my ViewModel as follows:
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string name) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
The SomeBoolProperty of my ViewModel then looks like this:
private bool _SomeBoolProperty = false;
public bool SomeBoolProperty {
get => _SomeBoolProperty;
set {
_SomeBoolProperty = value;
OnPropertyChanged(nameof(SomeBoolProperty));
}
}
I use RelayCommand as my command implementation from here
https://stackoverflow.com/a/22286816/336753.
The commands on my ViewModel then look like this:
public ICommand MyOnCheckedCommand { get; } = new RelayCommand(o => {
// Do something here.
SomeBoolProperty = true;
});
public ICommand MyOnUncheckedCommand { get; } = new RelayCommand(o => {
// Do something else here.
SomeBoolProperty = false;
});
I got to this question trying to find a way to reuse two commands I already had on my ViewModel. One called when checked and one when unchecked. I use them on some buttons too so did not want to add an extra parametrized command. People were asking here about ViewModel implementation so adding this answer to complete the one from Igor_S. Hope it helps.

I'm late... I used Rohit Vats answer and came up with this code.
The example is a working code extract and it is only here to help to understand every aspects. It is a pushpin that could be either active or inactive and it use a DelegateCommand. You could probably also use a RelayCommand or any other similar class to do the same job.
Command:
using System.Windows.Input;
namespace HQ.Wpf.Util.Command
{
public class StandardCommand
{
public static RoutedUICommand PinPropertyGrid = new RoutedUICommand("Pin property grid", "PinPropertyGrid", typeof(StandardCommand));
Xaml:
<CheckBox HorizontalAlignment="Right"
VerticalAlignment="Top"
Margin="2,0,3,0"
Command="{Binding CommandPinPropertyGrid}"
CommandParameter="{Binding IsChecked, RelativeSource={RelativeSource Self}}">
<CheckBox.Template>
<ControlTemplate TargetType="{x:Type CheckBox}">
<Grid>
<Image x:Name="ImagePushpin" Width="16" Height="16" Source="pack://application:,,,/WpfUtil;component/Images/PushpinUnpinned16x16.png" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="ImagePushpin" Property="Source" Value="pack://application:,,,/WpfUtil;component/Images/PushpinPinned16x16.png" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</CheckBox.Template>
</CheckBox>
Model:
public MainWindowViewModel()
{
CommandPinPropertyGrid = new DelegateCommand<bool>(PinPropertyGrid);
...
// ******************************************************************
public DelegateCommand<bool> CommandPinPropertyGrid { get; private set; }
public void PinPropertyGrid(bool pinned)
{
this.IsPropertyGridPinned = pinned;
}
DelegateCommand:
using System;
using System.Windows.Input;
namespace HQ.Wpf.Util.Command
{
/// <summary>
/// Represents a command that forwards the <c>Execute</c> and <c>CanExecute</c> calls to specified delegates.
/// </summary>
public class DelegateCommand<T> : ICommand
{
private readonly Action<T> _executeCallback;
private readonly Predicate<T> _canExecuteCallback;
/////////////////////////////////////////////////////////////////////////////////////////////////////
// OBJECT
/////////////////////////////////////////////////////////////////////////////////////////////////////
/// <summary>
/// Initializes a new instance of the <see cref="DelegateCommand<T>"/> class.
/// </summary>
/// <param name="executeCallback">The execute callback delegate.</param>
public DelegateCommand(Action<T> executeCallback)
: this(executeCallback, null)
{
// No-op
}
/// <summary>
/// Initializes a new instance of the <see cref="DelegateCommand<T>"/> class.
/// </summary>
/// <param name="executeCallback">The execute callback delegate.</param>
/// <param name="canExecuteCallback">The can execute callback delegate.</param>
public DelegateCommand(Action<T> executeCallback, Predicate<T> canExecuteCallback)
{
if (executeCallback == null)
throw new ArgumentNullException("executeCallback");
this._executeCallback = executeCallback;
this._canExecuteCallback = canExecuteCallback;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
// INTERFACE IMPLEMENTATION
/////////////////////////////////////////////////////////////////////////////////////////////////////
#region ICommand Members
/// <summary>
/// Defines the method that determines whether the command can execute in its current state.
/// </summary>
/// <param name="parameter">Data used by the command. If the command does not require data to be passed, this object can be set to <see langword="null"/>.</param>
/// <returns>
/// <c>true</c> if this command can be executed; otherwise, <c>false</c>.
/// </returns>
public bool CanExecute(object parameter)
{
return (this._canExecuteCallback == null) ? true : this._canExecuteCallback((T)parameter);
}
/// <summary>
/// Occurs when changes occur that affect whether or not the command should execute.
/// </summary>
public event EventHandler CanExecuteChanged
{
add
{
if (this._canExecuteCallback != null)
CommandManager.RequerySuggested += value;
}
remove
{
if (this._canExecuteCallback != null)
CommandManager.RequerySuggested -= value;
}
}
/// <summary>
/// Defines the method to be called when the command is invoked.
/// </summary>
/// <param name="parameter">Data used by the command. If the command does not require data to be passed, this object can be set to <see langword="null"/>.</param>
public void Execute(object parameter)
{
this._executeCallback((T)parameter);
}
#endregion // ICommand Members
}
}

When you need only the Status of the CheckBox (Checked or Unchecked), then you don't need a Parameter. You can detect the Status of the Checkbox when you use this code:
CheckBox box = e.OriginalSource as CheckBox;
if(box.IsChecked.Value)
DoThis();
else
DoAnotherMethod();
"e" is the ExecutedRoutedEventArgs-Parameter in the Command. You Need box.IsChecked.Value, because box.IsChecked is from Type bool?.

Related

Delete row button in WPF DataGrid requires two clicks instead of one

I have a DataGrid with a delete button for each row, hooked to the Delete command. The button needs to be clicked twice to delete the row, which is not what I would like.
According to Snoop the button's IsEnabled == false initially, the first click enables it. This would seem to be the problem, so how can I make the button enabled before the user clicks?
I tried using a trigger to change IsEnabled, this is commented out in the code below, it didn't work.
Window1.xaml
<Window x:Class="WpfApp1.Window1"
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"
mc:Ignorable="d"
Title="Window1" Height="450" Width="800">
<Grid>
<DataGrid HorizontalAlignment="Left" Height="399" Margin="10,10,0,0"
VerticalAlignment="Top" Width="772"
x:Name="dataGrid1"
ItemsSource="{Binding ProxyServers}"
CanUserAddRows="True"
CanUserDeleteRows="True"
>
<DataGrid.Columns>
<DataGridTemplateColumn Width="SizeToCells">
<DataGridTemplateColumn.CellStyle>
<Style TargetType="{x:Type DataGridCell}"
BasedOn="{StaticResource {x:Type DataGridCell}}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridCell}">
<Button Command="DataGrid.DeleteCommand"
IsEnabled="True" x:Name="deleteButton"
Content="X">
<!-- Make the button enable on mouse over?
Didn't work.
<Button.Style>
<Style TargetType="Button">
<Style.Triggers>
<Trigger Property="IsMouseOver"
Value="true">
<Setter Property="IsEnabled"
Value="true" />
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>-->
</Button>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="IsEnabled" Value="True"/>
</Style>
</DataGridTemplateColumn.CellStyle>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
Window1.xaml.cs
using System.ComponentModel;
using System.Windows;
namespace WpfApp1
{
public partial class Window1 : Window
{
BindingList<Proxy> proxyServers;
public Window1()
{
InitializeComponent();
dataGrid1.DataContext = this;
proxyServers = new BindingList<Proxy>();
proxyServers.Add(new Proxy() { LocalURL = "http://localhost" });
}
public BindingList<Proxy> ProxyServers { get => proxyServers; set => proxyServers = value; }
}
public class Proxy
{
string localURL;
public string LocalURL { get => localURL; set => localURL = value; }
}
}
Have you considered something more like:
<DataGridTemplateColumn Header="Delete">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Command="{Binding Deletecommand}"
You could use relativesource if you want the command in the parent view's daracontext.
Pass the bound row object with commandparameter.
To elaborate on #Andy's answer. This is what I had to do.
Add RelayCommand (this implementation is floating around the internet, I believe it comes from MVVMLite):
using System;
using System.Windows.Input;
namespace WpfApp1
{
public class RelayCommand<T> : ICommand
{
#region Fields
readonly Action<T> _execute = null;
readonly Predicate<T> _canExecute = null;
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of <see cref="DelegateCommand{T}"/>.
/// </summary>
/// <param name="execute">Delegate to execute when Execute is called on the command. This can be null to just hook up a CanExecute delegate.</param>
/// <remarks><seealso cref="CanExecute"/> will always return true.</remarks>
public RelayCommand(Action<T> execute)
: this(execute, null)
{
}
/// <summary>
/// Creates a new command.
/// </summary>
/// <param name="execute">The execution logic.</param>
/// <param name="canExecute">The execution status logic.</param>
public RelayCommand(Action<T> execute, Predicate<T> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
#endregion
#region ICommand Members
///<summary>
///Defines the method that determines whether the command can execute in its current state.
///</summary>
///<param name="parameter">Data used by the command. If the command does not require data to be passed, this object can be set to null.</param>
///<returns>
///true if this command can be executed; otherwise, false.
///</returns>
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute((T)parameter);
}
///<summary>
///Occurs when changes occur that affect whether or not the command should execute.
///</summary>
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
///<summary>
///Defines the method to be called when the command is invoked.
///</summary>
///<param name="parameter">Data used by the command. If the command does not require data to be passed, this object can be set to <see langword="null" />.</param>
public void Execute(object parameter)
{
_execute((T)parameter);
}
#endregion
}
}
Define my own DeleteCommand rather than using the build-in one from DataGrid. I did this in the code-behind but in my real project it's going in the view model.
private RelayCommand<object> _DeleteCommand;
public RelayCommand<object> DeleteCommand => _DeleteCommand ?? (_DeleteCommand = new RelayCommand<object>((object o) => {
var proxy = o as Proxy;
proxyServers.Remove(proxy);
}, (object o) => {
if (o is Proxy)
{
return true;
}
else
{
return false;
}
}));
I couldn't use RelayCommand<Proxy> because the new item row is not a Proxy object. If I didn't have the new item row I could use RelayCommand<Proxy>.
Remove this template definition in the XAML (because it is a ControlTemplate and not a DataTemplate, and it seems you need a DataTemplate if you want to pass the grid item to the command as a CommandParameter)
<DataGridTemplateColumn.CellStyle>
<Style TargetType="{x:Type DataGridCell}" BasedOn="{StaticResource {x:Type DataGridCell}}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate >
<Button Command="{Binding DeleteCommand,RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:Proxy}}}"
IsEnabled="True" x:Name="deleteButton" Content="X">
</Button>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="IsEnabled" Value="True"/>
</Style>
</DataGridTemplateColumn.CellStyle>
Add this instead
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Command="{Binding DeleteCommand,RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
CommandParameter="{Binding}"
IsEnabled="True" x:Name="deleteButton" Content="X">
</Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

WPF MouseLeave not firing binding by command

I started the MVVM project and set the View binding all events on the TextBlock from ViewModel. I have set 3 commands (MouseEnter, MouseLeave, and MouseDown). But only MouseEnter and MouseDown are firing, MouseLeave event not firing
This is the view
<Window x:Class="SilentUpdate.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:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:local="clr-namespace:SilentUpdate"
xmlns:res="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d"
Background="Gainsboro"
WindowStartupLocation="CenterScreen" WindowStyle="None"
Title="" Height="600" Width="1024">
<Grid>
<Grid.Resources>
<ResourceDictionary>
<Style TargetType="TextBlock" x:Key="IconText">
<Setter Property="FontSize" Value="32"/>
<Setter Property="Foreground" Value="#ADADAD"/>
<Setter Property="FontFamily" Value="pack://application:,,,/Resources/#Segoe UI Symbol"/>
<Setter Property="Margin" Value="10,10,10,10"/>
</Style>
<res:String x:Key="homeIcon"></res:String>
<res:String x:Key="settingsIcon"></res:String>
<res:String x:Key="previewIcon"></res:String>
<res:String x:Key="runIcon"></res:String>
<res:String x:Key="saveIcon"></res:String>
<res:String x:Key="exitIcon"></res:String>
<res:String x:Key="warningIcon"></res:String>
<res:String x:Key="folderIcon"></res:String>
</ResourceDictionary>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="50"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<StackPanel x:Name="Navibar" Orientation="Vertical" Grid.Row="0" Grid.Column="0" Grid.RowSpan="2" Background="#0096C1">
<TextBlock Name="home"
Foreground="{Binding HomeColor}"
Text="{StaticResource homeIcon}"
Style="{StaticResource IconText}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseEnter">
<i:InvokeCommandAction Command="{Binding EventMouseOver}"
CommandParameter="{Binding ElementName=home}" />
</i:EventTrigger>
<i:EventTrigger EventName="MouseLeave">
<i:InvokeCommandAction Command="{Binding MouseLeaveButton}"
CommandParameter="{Binding ElementName=home}" />
</i:EventTrigger>
<i:EventTrigger EventName="MouseDown">
<i:InvokeCommandAction Command="{Binding HomeCommand}"
CommandParameter="{Binding ElementName=home}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBlock>
<TextBlock Name="settings"
Text="{StaticResource settingsIcon}"
Style="{StaticResource IconText}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseEnter">
<i:InvokeCommandAction Command="{Binding EventMouseOver}"
CommandParameter="{Binding ElementName=settings}" />
</i:EventTrigger>
<i:EventTrigger EventName="MouseLeave">
<i:InvokeCommandAction Command="{Binding MouseLeaveButton}"
CommandParameter="{Binding ElementName=settings}" />
</i:EventTrigger>
<i:EventTrigger EventName="MouseDown">
<i:InvokeCommandAction Command="{Binding SettingCommand}"
CommandParameter="{Binding ElementName=settings}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBlock>
<TextBlock Name="preview"
IsEnabled="{Binding PreviewStatus, Mode=OneWay}"
Text="{StaticResource previewIcon}"
Style="{StaticResource IconText}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseEnter">
<i:InvokeCommandAction Command="{Binding EventMouseOver}"
CommandParameter="{Binding ElementName=preview}" />
</i:EventTrigger>
<i:EventTrigger EventName="MouseLeave">
<i:InvokeCommandAction Command="{Binding MouseLeaveButton}"
CommandParameter="{Binding ElementName=preview}" />
</i:EventTrigger>
<i:EventTrigger EventName="MouseDown">
<i:InvokeCommandAction Command="{Binding PreviewCommand}"
CommandParameter="{Binding ElementName=preview}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBlock>
<TextBlock Name="run"
IsEnabled="{Binding RunStatus, Mode=OneWay}"
Text="{StaticResource runIcon}"
Style="{StaticResource IconText}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseEnter">
<i:InvokeCommandAction Command="{Binding EventMouseOver}"
CommandParameter="{Binding ElementName=run}" />
</i:EventTrigger>
<i:EventTrigger EventName="MouseLeave">
<i:InvokeCommandAction Command="{Binding MouseLeaveButton}"
CommandParameter="{Binding ElementName=run}" />
</i:EventTrigger>
<i:EventTrigger EventName="MouseDown">
<i:InvokeCommandAction Command="{Binding RunCommand}"
CommandParameter="{Binding ElementName=run}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBlock>
<TextBlock Name="save"
IsEnabled="{Binding SaveStatus, Mode=OneWay}"
Text="{StaticResource saveIcon}"
Style="{StaticResource IconText}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseEnter">
<i:InvokeCommandAction Command="{Binding EventMouseOver}"
CommandParameter="{Binding ElementName=save}" />
</i:EventTrigger>
<i:EventTrigger EventName="MouseLeave">
<i:InvokeCommandAction Command="{Binding MouseLeaveButton}"
CommandParameter="{Binding ElementName=save}" />
</i:EventTrigger>
<i:EventTrigger EventName="MouseDown">
<i:InvokeCommandAction Command="{Binding SaveCommand}"
CommandParameter="{Binding ElementName=save}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBlock>
<TextBlock Name="exit"
Text="{StaticResource exitIcon}"
Style="{StaticResource IconText}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseEnter">
<i:InvokeCommandAction Command="{Binding EventMouseOver}"
CommandParameter="{Binding ElementName=exit}" />
</i:EventTrigger>
<i:EventTrigger EventName="MouseLeave">
<i:InvokeCommandAction Command="{Binding MouseLeaveButton}"
CommandParameter="{Binding ElementName=exit}" />
</i:EventTrigger>
<i:EventTrigger EventName="MouseDown">
<i:InvokeCommandAction Command="{Binding ExitButtonCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBlock>
</StackPanel>
</Grid>
</Window>
And this is the ViewModel
using SilentUpdate.Helpers;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
namespace SilentUpdate.ViewModels
{
public class MainViewModel : INotifyPropertyChanged
{
#region Property change interface implement
public event PropertyChangedEventHandler PropertyChanged;
private void RaiseProperChanged (string prop)
{
if (PropertyChanged != null)
{
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(prop));
}
}
#endregion
#region Button Status
// To keep track the active click button
private TextBlock activeButton;
private Brush homeColor;
private bool previewStatus, runStatus, saveStatus;
public Brush HomeColor
{
get { return this.homeColor; }
set { this.homeColor = value; RaiseProperChanged("HomeColor"); }
}
public bool PreviewStatus
{
get { return this.previewStatus; }
set
{
this.previewStatus = value;
RaiseProperChanged("PreviewStatus");
}
}
public bool RunStatus
{
get { return this.runStatus; }
set
{
this.runStatus = value;
RaiseProperChanged("RunStatus");
}
}
public bool SaveStatus
{
get { return this.saveStatus; }
set
{
this.saveStatus = value;
RaiseProperChanged("SaveStatus");
}
}
#endregion
#region Event Binding
// Command for binding to the event
private ICommand exitButtonCommand, eventMouseOver, eventMouseLeave;
private ICommand homeCommand, settingCommand, previewCommand, runCommand, saveCommand;
private bool canExecute = true;
public bool CanExecute
{
get { return this.canExecute; }
set
{
if (this.canExecute == value)
{
return;
}
this.canExecute = value;
}
}
/// <summary>
/// Public for binding to Exit button
/// </summary>
public ICommand ExitButtonCommand
{
get { return this.exitButtonCommand; }
set
{
this.exitButtonCommand = value;
}
}
/// <summary>
/// Exit the application when Exit button click binding to ExitButtonCommand
/// </summary>
/// <param name="obj"></param>
public void ExitApp(object obj)
{
Application.Current.Shutdown();
}
/// <summary>
/// Public for binding to Home button
/// </summary>
public ICommand HomeCommand
{
get { return this.homeCommand; }
set
{
this.homeCommand = value;
}
}
/// <summary>
/// Actual home event, will display the home page
/// This will update the page binding variable
/// </summary>
/// <param name="obj"></param>
public void HomeEvent (object obj)
{
// Change the active button
ChangeActive((TextBlock)obj);
// Change the button status
PreviewStatus = false;
RunStatus = false;
SaveStatus = false;
HomeColor = new SolidColorBrush(Colors.White);
MessageBox.Show("HomeEvent");
}
/// <summary>
/// Public for binding to Setting button
/// </summary>
public ICommand SettingCommand
{
get { return this.settingCommand; }
set
{
this.settingCommand = value;
}
}
/// <summary>
/// Actual Setting event, will display the setting page
/// This will update the page binding variable
/// </summary>
/// <param name="obj"></param>
public void SettingEvent(object obj)
{
// Change the active button
ChangeActive((TextBlock)obj);
// Change the button status
PreviewStatus = true;
RunStatus = false;
SaveStatus = false;
MessageBox.Show("SettingEvent");
}
/// <summary>
/// Public for binding to Preview button
/// </summary>
public ICommand PreviewCommand
{
get { return this.previewCommand; }
set
{
this.previewCommand = value;
}
}
/// <summary>
/// Actual Preview event, will display the preview page
/// based on the Messenger sending to the page for the environment
/// setting by the setting page
/// This will update the page binding variable
/// </summary>
/// <param name="obj"></param>
public void PreviewEvent(object obj)
{
// Change the active button
ChangeActive((TextBlock)obj);
// Change the button status
PreviewStatus = true;
RunStatus = true;
SaveStatus = false;
MessageBox.Show("PreviewEvent");
}
/// <summary>
/// Public for binding to Run button
/// </summary>
public ICommand RunCommand
{
get { return this.runCommand; }
set
{
this.runCommand = value;
}
}
/// <summary>
/// Actual Preview event, will display the after run page
/// based on the Messenger sending to the page for the environment
/// setting by the setting page and the selection of the list file
/// from the Preview page
/// This will update the page binding variable
/// </summary>
/// <param name="obj"></param>
public void RunEvent(object obj)
{
// Change the active button
ChangeActive((TextBlock)obj);
// Change the button status
PreviewStatus = true;
RunStatus = true;
SaveStatus = true;
MessageBox.Show("RunEvent");
}
/// <summary>
/// Public for binding to Save button
/// </summary>
public ICommand SaveCommand
{
get { return this.saveCommand; }
set
{
this.saveCommand = value;
}
}
/// <summary>
/// Actual Save event, will generate the PDF file log
/// And launch the PDF file based on default application setting
/// </summary>
/// <param name="obj"></param>
public void SaveEvent(object obj)
{
ChangeActive((TextBlock)obj);
MessageBox.Show("SaveEvent");
}
/// <summary>
/// This event uses by all button to change color to active state
/// </summary>
public ICommand EventMouseOver
{
get { return this.eventMouseOver; }
set
{
this.eventMouseOver = value;
}
}
/// <summary>
/// Actual mouse enter event for binding command EventMouseOver
/// </summary>
/// <param name="obj">The target object of button</param>
public void MouseOverButton(object obj)
{
// Casting the object
TextBlock target = (TextBlock)obj;
if (activeButton == null)
{
// High light target
activeButton = target;
}
// High light target
HighLightText(target, true);
}
/// <summary>
/// This event uses by all button to change color to inactive state
/// </summary>
public ICommand EventMouseLeave
{
get { return this.eventMouseLeave; }
set { this.eventMouseLeave = value; }
}
/// <summary>
/// Actual mouse leave event for binding command EventMouseLeave
/// </summary>
/// <param name="obj">The target object of button</param>
private void MouseLeaveButton(object obj)
{
// Casting the object
TextBlock target = (TextBlock)obj;
if (target.Equals(activeButton) == false)
{
HighLightText(target, false);
}
HighLightText(activeButton, true);
}
/// <summary>
/// Update the canExecute property
/// </summary>
/// <param name="obj"></param>
public void ChangeCanExecute(object obj)
{
canExecute = !canExecute;
}
#endregion
#region Constructor
public MainViewModel()
{
exitButtonCommand = new RelayCommand(ExitApp, Param => this.canExecute);
eventMouseOver = new RelayCommand(MouseOverButton);
eventMouseLeave = new RelayCommand(MouseLeaveButton);
homeCommand = new RelayCommand(HomeEvent);
settingCommand = new RelayCommand(SettingEvent);
previewCommand = new RelayCommand(PreviewEvent);
runCommand = new RelayCommand(RunEvent);
saveCommand = new RelayCommand(SaveEvent);
// Set the Preview status disable when start
PreviewStatus = false;
// Set the Run status disable when start
RunStatus = false;
// Set the Save status disable when start
SaveStatus = false;
// HomeColor
HomeColor = new SolidColorBrush(Colors.White);
}
#endregion
#region Private Helper
private void HighLightText(TextBlock target, bool isHighlight)
{
if (isHighlight)
{
target.Foreground = new SolidColorBrush(Colors.White);
}
else
{
target.Foreground = new SolidColorBrush(System.Windows.Media.Color.FromArgb(68, 173, 173, 173));
}
}
private void ChangeActive (TextBlock target)
{
if (activeButton != null)
{
HighLightText(activeButton, false);
}
activeButton = target;
HighLightText(activeButton, true);
activeButton.ReleaseMouseCapture();
target.ReleaseMouseCapture();
HomeColor = new SolidColorBrush(System.Windows.Media.Color.FromArgb(68, 173, 173, 173));
}
#endregion
}
}
My mistake for the implementation, I should leave the UI logic in view not viewmodel. That solves the issue

Bind a button to the selected item

I have a button and want to change the click handler every time I change tabs. I was hoping to perform this with Binding.
<Window x:Class="BWCRenameUtility.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vpan="clr-namespace:BWCRenameUtility.View.VersionPanels"
Title="MainWindow" Height="526" Width="525">
<Grid>
<DockPanel>
<TextBlock Text="Foo" DockPanel.Dock="Top" TextWrapping="Wrap" Padding="10" />
<Grid DockPanel.Dock="Bottom">
<!-- This is not correct, how do I perform this binding correct? -->
<Button Content="Export..." HorizontalAlignment="Right" Margin="10" Click="{Binding SelectedItem.Content.PerformExport,ElementName=tabcontrol}" />
</Grid>
<TabControl Name="tabcontrol">
<TabItem Header="1.2.5">
<vpan:VersionPanel1_2_5/>
</TabItem>
<TabItem Header="1.2.8">
<vpan:VersionPanel1_2_8/> <!-- These can be of the same Type by inheritance -->
</TabItem>
</TabControl>
</DockPanel>
</Grid>
</Window>
As you can see, the Button.Click is not bound correctly and I want to know how this works in WPF.
You can achieve this with Commands, you will create a ICommand for each of toy TabItem ViewModels and bind the Buttons Command property to that Command
The RelayCommand is a very common way to handle stuff like this and can be used throughout your application
Relay command:
public class RelayCommand : ICommand
{
#region Fields
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="RelayCommand"/> class.
/// </summary>
/// <param name="execute">The execute.</param>
public RelayCommand(Action<object> execute) : this(execute, null) { }
/// <summary>
/// Initializes a new instance of the <see cref="RelayCommand"/> class.
/// </summary>
/// <param name="execute">The action to execute.</param>
/// <param name="canExecute">The can execute.</param>
/// <exception cref="System.ArgumentNullException">execute</exception>
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
#endregion
#region ICommand Members
/// <summary>
/// Defines the method that determines whether the command can execute in its current state.
/// </summary>
/// <param name="parameter">Data used by the command. If the command does not require data to be passed, this object can be set to null.</param>
/// <returns>
/// true if this command can be executed; otherwise, false.
/// </returns>
[DebuggerStepThrough]
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute(parameter);
}
/// <summary>
/// Occurs when changes occur that affect whether or not the command should execute.
/// </summary>
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
/// <summary>
/// Defines the method to be called when the command is invoked.
/// </summary>
/// <param name="parameter">Data used by the command. If the command does not require data to be passed, this object can be set to null.</param>
public void Execute(object parameter)
{
_execute(parameter);
}
#endregion
}
And you would use in the following fashion in your application
ViewModel or Control:
public class VersionPanel1_2_8 : VersionPanel
{
public ICommand MyCommand { get; internal set; }
public VersionPanel1_2_8()
{
MyCommand = new RelayCommand(x => MethodToExecute());
}
private void MethodToExecute()
{
}
}
Xaml:
<Window x:Class="BWCRenameUtility.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vpan="clr-namespace:BWCRenameUtility.View.VersionPanels"
Title="MainWindow" Height="526" Width="525">
<Grid>
<DockPanel>
<TextBlock Text="Foo" DockPanel.Dock="Top" TextWrapping="Wrap" Padding="10" />
<Grid DockPanel.Dock="Bottom">
<!-- This is not correct, how do I perform this binding correct? -->
<Button Content="Export..." HorizontalAlignment="Right" Margin="10" Command="{Binding SelectedItem.Content.MyCommand,ElementName=tabcontrol}" />
</Grid>
<TabControl Name="tabcontrol">
<TabItem Header="1.2.5">
<vpan:VersionPanel1_2_5/>
</TabItem>
<TabItem Header="1.2.8">
<vpan:VersionPanel1_2_8/> <!-- These can be of the same Type by inheritance -->
</TabItem>
</TabControl>
</DockPanel>
</Grid>
</Window>
You need to bind Button's Command property to your command in WPF like,
Command="{Binding SelectedItem.Content.PerformExport, ElementName=tabcontrol}"
Click is an event, if you want you can also do event to command binding (binding any event to command in your viewmodel) however that is not necessary in your case

Unable to fire TreeView properties events

I am having problem with the following code. I have a TreeView Control which is bound to a Collection. The TreeView does get populated with the desired results. HOwever the "IsSelected" property and ContextMenu's click Command is not firing. Following is the XAML code.
<UserControl x:Class="Plan.Views.PadView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:v="clr-namespace:Planner.Views"
xmlns:vm="clr-namespace:Planner.ViewModels"
<Grid>
<StackPanel Orientation="Vertical">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Row="1" Grid.Column="0" Orientation="Horizontal" OpacityMask="#FFECF5F5">
<TreeView ItemsSource="{Binding Pads}" Name="tree_View" Width="190">
<TreeView.ItemContainerStyle >
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding WellPadViewModel.IsSelected}" />
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Header="Rename" Command="{Binding RelativeSource={RelativeSource AncestorType={x:Type TreeView}}, Path=DataContext.RenameCommand}" />
</ContextMenu>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
</Trigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle >
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Members}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" >
<TextBlock.InputBindings>
<KeyBinding Key="F2" Command="{Binding RenameCommand}"/>
</TextBlock.InputBindings>
</TextBlock>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</StackPanel>
</Grid>
</StackPanel>
</Grid>
</UserControl>
And here is my ViewModel
using System;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Input;
using System.ComponentModel;
using WPFApplication;
namespace FieldPlanner.ViewModels
{
public class PlanViewModel : BaseViewModel
{
Collection<Pads> pads = new Collection<Pads>();
public PlanViewModel()
{
IsSelected = true;
pads = new Collection<Pad>();
}
private ICommand _RenameCommand;
public ICommand RenameCommand
{
get
{
if (_RenameCommand == null)
{
_RenameCommand = new RelayCommand1((o) =>
{
// Your logic should go here
MessageBox.Show("Please rename me");
});
}
return _RenameCommand;
}
}
public ObservableCollection<PadInfo> Members { get; set; }
private static object _selectedItem = null;
// This is public get-only here but you could implement a public setter which also selects the item.
// Also this should be moved to an instance property on a VM for the whole tree, otherwise there will be conflicts for more than one tree.
public static object SelectedItem
{
get { return _selectedItem; }
private set
{
if (_selectedItem != value)
{
_selectedItem = value;
OnSelectedItemChanged();
}
}
}
public static void OnSelectedItemChanged()
{
// Raise event / do other things
}
private bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
if (_isSelected != value)
{
_isSelected = value;
OnPropertyChanged("IsSelected");
if (_isSelected)
{
SelectedItem = this;
}
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = this.PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
/// <summary>
/// Class to hold the Pads info for a tree
/// </summary>
public class Pad
{
/// <summary>
/// Default Constructor
/// </summary>
public Pad()
{
this.Members = new ObservableCollection<PadInfo>();
}
/// <summary>
/// Name of the pad
/// </summary>
public string Name { get; set; }
/// <summary>
/// Members of the pad
/// </summary>
public ObservableCollection<PadInfo> Members { get; set; }
}
/// <summary>
/// Class to hold the well and slot IDs snapped to a pad
/// </summary>
public class PadInfo
{
/// <summary>
/// Slot ID
/// </summary>
public string SlotID { get; set; }
/// <summary>
/// Well ID
/// </summary>
public string WellID { get; set; }
}
public class RelayCommand1 : ICommand
{
#region Fields
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
#endregion // Fields
#region Constructors
public RelayCommand1(Action<object> execute)
: this(execute, null)
{
}
public RelayCommand1(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
}
}
How can I identify the issue?
You have two problems:
IsSelected:
<Setter Property="IsSelected" Value="{Binding WellPadViewModel.IsSelected}" />
In TreeViewItem DataContext is set to instance of Pad and Pad doesn't have property IsSelected You have to do sth like this:
<Setter Property="IsSelected" Value="{Binding DataContext.IsSelected, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}" />
Problem with ContextMenu is much more sirious. ContextMenu isn't in VisualTree so you cannot bind to RelativeSource. Solution is here WPF Relative source- Cannot find source for binding with reference
Best regards
Please set the Tag property in your DataTemplate to TreeViewItem. I have sth like this:
<DataTemplate>
<Grid Width="270" Height="20" Tag="{Binding DataContext, RelativeSource = {RelativeSource AncestorType={x:Type UserControl}}}">
...
<Grid.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Edit">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<command:EventToCommand Command="{Binding Tag.YOURCOMMAND}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</MenuItem>
</ContextMenu>
</Grid.ContextMenu>
</Grid>
</DataTemplate>
It should work.

TabItem header click

I have defined a control template/style for my tab items as follows:
<Style x:Key="TabItemStyle" TargetType="{x:Type TabItem}">
<Setter Property="Header" Value="{Binding Content.DataContext.Header, RelativeSource={RelativeSource Self}}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Grid Width="Auto" Height="Auto" x:Name="TabItemRoot" Margin="10,0,10,0">
<Button Command="{Binding Content.DataContext.HeaderClickedCommand}">
<ContentPresenter Margin="13,5,13,5"
x:Name="Content"
ContentSource="Header"
RecognizesAccessKey="True">
</ContentPresenter>
</Button>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
When I click on the tab header the command bound to the button is called OK, however, the click event appears to have eaten the SelectionChanged event and therefore the tab page doesn't change.
Is there a better way to implement this so I can call a VM method and get still get the tab page to change?
Update as per comment
The idea being that if the user clicks the header of the currently active tab it updates the active tabs content through changes in the VM. Thanks
Here is my way to implement such functionality, but whether it is better or not - it's matter of taste.
The main xaml looks so:
<TabControl ItemsSource="{Binding TabItems}">
<TabControl.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding Title}"/>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonDown">
<local:ExecuteCommandAction Command="{Binding HeaderClickCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Grid>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
There is no ControlTemplate, just DataTemplate with the attached property Interaction.Triggers, where the prefix i is defined in this string:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
This library can be found in MS Blend SDK or with the library mvvm light (on codeplex).
Also, don't confuse EventTriggers that has the prefix i and generic EventTriggers, they are different at some point, but I don't know exactly what the difference is, except that the EventTrigger class from the custom library work with Silverlight too.
In my example the trigger is subscribed to the event MouseLeftButtonDown and calls the special action class every time when the event is raised.
This action class is a custom class and it is defined in code:
/// <summary>
/// Behaviour helps to bind any RoutedEvent of UIElement to Command.
/// </summary>
[DefaultTrigger(typeof(UIElement), typeof(System.Windows.Interactivity.EventTrigger), "MouseLeftButtonDown")]
public class ExecuteCommandAction : TargetedTriggerAction<UIElement>
{
/// <summary>
/// Dependency property represents the Command of the behaviour.
/// </summary>
public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.RegisterAttached("CommandParameter",
typeof(object), typeof(ExecuteCommandAction), new FrameworkPropertyMetadata(null));
/// <summary>
/// Dependency property represents the Command parameter of the behaviour.
/// </summary>
public static readonly DependencyProperty CommandProperty = DependencyProperty.RegisterAttached("Command",
typeof(ICommand), typeof(ExecuteCommandAction), new FrameworkPropertyMetadata(null));
/// <summary>
/// Gets or sets the Commmand.
/// </summary>
public ICommand Command
{
get
{
return (ICommand)this.GetValue(CommandProperty);
}
set
{
this.SetValue(CommandProperty, value);
}
}
/// <summary>
/// Gets or sets the CommandParameter.
/// </summary>
public object CommandParameter
{
get
{
return this.GetValue(CommandParameterProperty);
}
set
{
this.SetValue(CommandParameterProperty, value);
}
}
/// <summary>
/// Invoke method is called when the given routed event is fired.
/// </summary>
/// <param name="parameter">
/// Parameter is the sender of the event.
/// </param>
protected override void Invoke(object parameter)
{
if (this.Command != null)
{
if (this.Command.CanExecute(this.CommandParameter))
{
this.Command.Execute(this.CommandParameter);
}
}
}
}
That is all. Now the command doesn't prevent the tabcontrol from selection of items.
Code-behind to test this xaml:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var items = new ObservableCollection<TabItemViewModel>
{
new TabItemViewModel("Item 1"), new TabItemViewModel("Item 2"), new TabItemViewModel("Item 3")
};
this.DataContext = new MainViewModel(){TabItems = items};
}
}
public class MainViewModel
{
public ObservableCollection<TabItemViewModel> TabItems { get; set; }
}
public class TabItemViewModel
{
public TabItemViewModel(string title)
{
this.Title = title;
this.HeaderClickCommand = new RelayCommand(() => MessageBox.Show("Clicked "+this.Title));
}
public string Title { get; set; }
public RelayCommand HeaderClickCommand { get; set; }
}
To call the command only when an item is selected, change this code:
<local:ExecuteCommandAction Command="{Binding HeaderClickCommand}"
CommandParameter="{Binding IsSelected, RelativeSource={RelativeSource FindAncestor, AncestorType=TabItem}}"/>
And this (the second parameter is the CanExecute delegate, it checks IsSelected == true):
this.HeaderClickCommand = new RelayCommand<bool>(b => {/*???*/}, b => b == true);

Resources