In WPF, can you use a Command.CanExecute to set the ToolTip? - wpf

This is not real code, I know. But it is what I would like to do.
MyBinding.CanExecute += (s, e) =>
{
e.CanExecute = Something.Allow;
if (!e.CanExecute)
e.ToolTip = Something.Reason;
}
Is there a simple way to do it?
Thank you.

From your question, I assume you are doing this from a ViewModel. If so, the simplest thing to do is to have an observable "CanExecute" property for your command, and another string "Reason" property for your tooltip.
Then, you listen for the PropertyChanged event within the ViewModel. When the CanExecute property changes, you simply update the reason.
Here is some sample code, which simply sets the CanExecute property to false when the command is executed:
public MyViewModel()
: base()
{
this.PropertyChanged += (s, e) =>
{
if (e.PropertyName == "SomeCommandCanExecute")
{
if (mSomeCommandCanExecute)
this.Reason = "Some Command Can Execute";
else
this.Reason = "Some Command Cannot Execute Because....";
}
};
}
private RelayCommand mSomeCommand = null;
private Boolean mSomeCommandCanExecute = true;
public RelayCommand SomeCommand
{
get
{
if (mSomeCommand == null)
{
mSomeCommand = new RelayCommand(
cmd => this.ExecuteSomeCommand(),
cmd => this.SomeCommandCanExecute);
}
return mSomeCommand;
}
}
public Boolean SomeCommandCanExecute
{
get { return mSomeCommandCanExecute; }
set { SetProperty("SomeCommandCanExecute", ref mSomeCommandCanExecute, value); }
}
private void ExecuteSomeCommand()
{
this.SomeCommandCanExecute = false;
}
private string mReason = "Some Command Can Execute";
public string Reason
{
get { return mReason; }
set { SetProperty("Reason", ref mReason, value); }
}
And then in your View:
<StackPanel>
<Button Command="{Binding SomeCommand}"
ToolTip="{Binding Reason}"
Content="Some Command"/>
<TextBlock Text="{Binding Reason}"
ToolTip="{Binding Reason}" />
</StackPanel>
Note that you won't see the ToolTip on the disabled button when CanExecute is set to false, which is why I added the TextBlock to show it. You will see the ToolTip on the TextBlock.

This is the best way I believe to accomplish this.
This is the Command definition:
class CustomCommand : RoutedUICommand, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string m_Reason;
public string Reason
{
get { return m_Reason; }
set
{
if (m_Reason == value)
return;
m_Reason = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Reason"));
}
}
}
public class MyCommands
{
public static CustomCommand DoThis = new CustomCommand();
public static CommandBinding DoThisBinding = new CommandBinding
{ Command = DoThis };
public static void SetupCommands()
{
DoThisBinding.CanExecute += (s, e) =>
{
var _Something = DoSomeTest(e.Parameter);
e.CanExecute = _Something.Allow;
if (!e.CanExecute)
(e.Command as CustomCommand).Reason = _Something.Reason;
}
}
}
And this is the XAML implementation:
xmlns:commands="MyNamespace.WhereAreCommands"
<Button Command="{x:Static commands:MyCommands.DoThis}"
ToolTip="{Binding Path=Reason,
Source={x:Static commands:MyCommands.DoThis}}">
Click</Button>

Related

INotifyPropertyChanged is not working in WPF

I need to enable and disable a datagrid through button click. This is how I do it:
MainWindow
public bool IsReadOnly = true;
private MainWindowEngine mwe;
public MainWindow()
{
InitializeComponent();
InitialSettings();
DataContext = mwe;
}
private void InitialSettings()
{
_mwe = new MainWindowEngine();
IsReadOnly = bool.Parse(_mwe.IsReadOnly);
DataGridCommands.IsReadOnly = IsReadOnly;
DataGridReaders.IsReadOnly = IsReadOnly;
}
private void EnableEdit(object sender, RoutedEventArgs e)
{
IsReadOnly = !IsReadOnly;
_mwe.IsReadOnly = IsReadOnly.ToString();
}
xaml
<Button
Name="ButtonEdit"
Grid.Column="1"
Content="Edit"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Margin="5 0 0 0"
Width="75" Click="EnableEdit"
/>
class MainWindowEngine : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _isReadOnly = "true";
public string IsReadOnly
{
get => _isReadOnly;
set
{
if (_isReadOnly != value)
{
_isReadOnly = value;
OnIsReadOnlyChanged(_isReadOnly);
}
}
}
protected void OnIsReadOnlyChanged(string value)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(value));
}
}
Through debugger, it is hitting the breakpoints but it doesn't make my datagrid isReadOnly property to false or true.
Change it like shown below. Pass the name of the property instead of its value to the method that fires the PropertyChanged event. Also change the name of the method to something that reflects its general purpose, i.e. to notify about the change of any property, not just IsReadOnly.
public string IsReadOnly
{
get => _isReadOnly;
set
{
if (_isReadOnly != value)
{
_isReadOnly = value;
OnPropertyChanged(nameof(IsReadOnly));
}
}
}
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
See the documentation -
https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.inotifypropertychanged?view=net-5.0
You need to notify the name of the property that changed, not the value directly.
Edit -
If you do not want to write out your property names always, you can invoke the PropertyChanged event using the CallerMemberName as below -
public string IsReadOnly
{
get => _isReadOnly;
set
{
if (_isReadOnly != value)
{
_isReadOnly = value;
OnPropertyChanged(); // no longer need to pass the property name
}
}
}
protected void OnPropertyChanged([CallerMemberName]string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
PropertyChangedEventArgs as its argument takes a string contanining property name that's changed, not its value.
So correct body of OnIsReadOnlyChanged will look like
protected void OnIsReadOnlyChanged()
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsReadOnly)));
}
You can make it even more general by defining method:
protected void RaisePropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
And use it like
RaisePropertyChanged(nameof(IsReadOnly));

WPF selectedItem on Menu or get commandparameter in viewmodel

I am searching for hours to fix a simple problem. I wanted to work with "SelectedItem" on my menuItems, but after hours of stackoverflow I saw that's impossible. I found a lot about "CommandParameter" but I don't understand how it works.
This is what I want to do: I have a menu with "background1, background2,..." . If you select a background in the menu, I want to set that selected background as background.
It's a schoolproject, so we have to use MVVM and no codebehind is allowed.
How can I "use" the Commandparameter in my ViewModel?
This is my mainWindow.xaml:
<Toolbar>
<Menu>
<MenuItem Header="Background" ItemsSource="{Binding Backgrounds}">
<MenuItem.ItemTemplate>
<DataTemplate>
<MenuItem Header="{Binding Name}" Command="{Binding ChangeBackgroundCommand}" CommandParameter="{Binding Name}"/>
</DataTemplate>
</MenuItem.ItemTemplate>
</MenuItem>
</Menu>
</Toolbar>
This is a part of my mainWindowViewModel:
public MainWindowViewModel()
{
//load data
BackgroundDataService bds = new BackgroundDataService();
Backgrounds = bds.GetBackgrounds();
//connect command
WijzigBackgroundCommand = new BaseCommand(WijzigBackground);
}
private void ChangeBackground()
{
//I want here the name of the selected menuItem (by parameter?)
}
}
We use a baseCommand class(I don't want to change this class because its standard I think):
class BaseCommand : ICommand
{
Action actie;
public BaseCommand(Action Actie)
{
actie = Actie;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
actie.Invoke();
}
}
I use stackoverflow a lot :-) this is my firts post/question, I hope it's clear
Try this:
class BaseCommand<T> : ICommand
{
private readonly Action<T> _executeMethod = null;
private readonly Func<T, bool> _canExecuteMethod = null;
public BaseCommand(Action<T> executeMethod)
: this(executeMethod, null)
{
}
public BaseCommand(Action<T> executeMethod, Func<T, bool> canExecuteMethod)
{
_executeMethod = executeMethod;
_canExecuteMethod = canExecuteMethod;
}
public bool CanExecute(T parameter)
{
if (_canExecuteMethod != null)
{
return _canExecuteMethod(parameter);
}
return true;
}
public void Execute(T parameter)
{
if (_executeMethod != null)
{
_executeMethod(parameter);
}
}
public event EventHandler CanExecuteChanged;
bool ICommand.CanExecute(object parameter)
{
if (parameter == null &&
typeof(T).IsValueType)
{
return (_canExecuteMethod == null);
}
return CanExecute((T)parameter);
}
void ICommand.Execute(object parameter)
{
Execute((T)parameter);
}
}
Use it like this:
public MainWindowViewModel()
{
// ...
// connect command
WijzigBackgroundCommand = new BaseCommand<YourBackgroundClass>(
(commandParam) => WijzigBackground(commandParam),
(commandParam) => CanWijzigBackground(commandParam));
}
private void WijzigBackground(YourBackgroundClass param)
{
// Use 'param'
}
private bool CanWijzigBackground(YourBackgroundClass param)
{
// Use 'param'
}

Wpf binding is not working properly

My ViewModel
public class MyViewModel : ViewModelBase
{
// INotifyPropertyChanged is implemented in ViewModelBase
private String _propX;
public String PropX
{
get { return _propX; }
set
{
if (_propX != value)
{
_propX = value;
RaisePropertyChanged(() => PropX);
}
}
}
private String _propY;
public String ServerIP
{
get { return _propY; }
set
{
if (_propY != value)
{
_propY = value;
RaisePropertyChanged(() => ServerIP);
}
}
}
public A()
{
this._propY = "000.000.000.000";
this._propY = "000.000.000.000";
}
}
// EDIT
// This is the command that resets the properties
private RelayCommand _resetFormCommand;
public ICommand ResetConnectionFormCommand
{
get
{
if (_resetFormCommand == null)
{
_resetFormCommand = new RelayCommand(param => this.ExecuteResetFormCommand(), param => this.CanExecuteResetFormCommand);
}
return _resetFormCommand;
}
}
private bool CanExecuteResetFormCommand
{
get
{
return !String.IsNullOrWhiteSpace(this._propX) ||
!String.IsNullOrWhiteSpace(this._propY);
}
}
private void ExecuteResetFormCommand()
{
this._propX = "";
this._propY = "";
}
My View xaml
<TextBox Name="propX" Text="{Binding PropX }" PreviewTextInput="textBox_PreviewTextInput" />
<TextBox Name="propY" Text="{Binding PropY }" PreviewTextInput="textBox_PreviewTextInput" />
<Border>
<Button Content="Reset" Name="resetBtn" Command="{Binding ResetFormCommand}" />
</Border>
My View code behind
private MyViewModel vm;
public ConnectionUserControl()
{
InitializeComponent();
vm = new MyViewModel();
this.DataContext = vm;
}
private void textBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
ValidateInput(sender as TextBox, e);
}
The reset command resets the properties in my view model but the textboxes still contain their values, the binding is not working properly :(
Am i missing something here?
You should reset the properties, not the private members:
private void ExecuteResetFormCommand()
{
this.PropX = "";
this.PropY = "";
}
How are you resetting the values? You may be overriding the databinding when you reset the values. It would be helpful if you post the code that gets executed when the button is clicked.
In your xaml-code you have to set the binding like:
<TextBox Name="propX" Text="{Binding PropX, Mode=TwoWay}" .../>
binding has to be two way in order for textbox to update itself from viewmodel
In your Code-behind, you have a property ServerIP, which I think you wanted to be named as PropY, since your TextBox binds to a PropY property.
<TextBox Name="propY" Text="{Binding PropY }" PreviewTextInput="textBox_PreviewTextInput" /
Also, you should be assigning the value to your property in your ExecuteResetFormCommand command, and not your private member since the private member does not trigger INotifyPropertyChanged
private void ExecuteResetFormCommand()
{
this.PropX = "";
this.PropY = ""; // <-- Currently you have PropY declared as ServerIP
}

RadioButton IsChecked loses binding

I'm triying to bind to a RadioButton.IsChecked property, and it only works once. After that, the binding doesn't work anyore, and I have no idea why this happens. Can anyone help out with this? Thanks!
This is my code.
C#
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
}
public class ViewModel
{
private bool _isChecked1 = true;
public bool IsChecked1
{
get { return _isChecked1; }
set
{
if (_isChecked1 != value)
{
_isChecked1 = value;
}
}
}
private bool _isChecked2;
public bool IsChecked2
{
get { return _isChecked2; }
set
{
if (_isChecked2 != value)
{
_isChecked2 = value;
}
}
}
}
XAML:
<Grid>
<StackPanel>
<RadioButton Content="RadioButton1" IsChecked="{Binding IsChecked1}" />
<RadioButton Content="RadioButton2" IsChecked="{Binding IsChecked2}" />
</StackPanel>
</Grid>
It's an unfortunate known bug. I'm assuming this has been fixed in WPF 4.0 given the new DependencyObject.SetCurrentValue API, but have not verified.
Here is a working solution: http://pstaev.blogspot.com/2008/10/binding-ischecked-property-of.html. It's a shame that Microsoft didn't correct this error.
Just a follow-up to Kent's answer here...this has in fact been fixed in WPF 4.0., I'm leveraging this behavior in my current project. The radio button that is de-activated now gets its binding value set to false, rather than breaking the binding.
I guess you need to implement the INotifyPropertyChanged interface
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
private bool _isChecked1 = true;
public bool IsChecked1
{
get { return _isChecked1; }
set
{
if (_isChecked1 != value)
{
_isChecked1 = value;
NotifyPropertyChanged("IsChecked1");
}
}
} // and the other property...
:)

How to bind SelectionStart Property of Text Box?

I use this:
<TextBox x:Name="Test"/>
<TextBlock Text="{Binding SelectionStart, ElementName=Test}"/>
but it always shows 0.
How can I treat it?
Thank you.
I ran into this problem (SelectionStart and SelectionLength are not dependency properties) and decided to make a TextBox with bindable selection start and end:
public class SelectionBindingTextBox : TextBox
{
public static readonly DependencyProperty BindableSelectionStartProperty =
DependencyProperty.Register(
"BindableSelectionStart",
typeof(int),
typeof(SelectionBindingTextBox),
new PropertyMetadata(OnBindableSelectionStartChanged));
public static readonly DependencyProperty BindableSelectionLengthProperty =
DependencyProperty.Register(
"BindableSelectionLength",
typeof(int),
typeof(SelectionBindingTextBox),
new PropertyMetadata(OnBindableSelectionLengthChanged));
private bool changeFromUI;
public SelectionBindingTextBox() : base()
{
this.SelectionChanged += this.OnSelectionChanged;
}
public int BindableSelectionStart
{
get
{
return (int)this.GetValue(BindableSelectionStartProperty);
}
set
{
this.SetValue(BindableSelectionStartProperty, value);
}
}
public int BindableSelectionLength
{
get
{
return (int)this.GetValue(BindableSelectionLengthProperty);
}
set
{
this.SetValue(BindableSelectionLengthProperty, value);
}
}
private static void OnBindableSelectionStartChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
var textBox = dependencyObject as SelectionBindingTextBox;
if (!textBox.changeFromUI)
{
int newValue = (int)args.NewValue;
textBox.SelectionStart = newValue;
}
else
{
textBox.changeFromUI = false;
}
}
private static void OnBindableSelectionLengthChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
var textBox = dependencyObject as SelectionBindingTextBox;
if (!textBox.changeFromUI)
{
int newValue = (int)args.NewValue;
textBox.SelectionLength = newValue;
}
else
{
textBox.changeFromUI = false;
}
}
private void OnSelectionChanged(object sender, RoutedEventArgs e)
{
if (this.BindableSelectionStart != this.SelectionStart)
{
this.changeFromUI = true;
this.BindableSelectionStart = this.SelectionStart;
}
if (this.BindableSelectionLength != this.SelectionLength)
{
this.changeFromUI = true;
this.BindableSelectionLength = this.SelectionLength;
}
}
}
You cannot bind to SelectionStart because it is not a DependencyProperty.
This could be an alternate solution:
View:
<TextBox Text="{Binding Text}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<mvvml:EventToCommand Command="{Binding TextBoxSelectionChangedCommand}"
PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
ViewModel:
#region TextBoxSelectionChangedCommand
RelayCommand<RoutedEventArgs> _TextBoxSelectionChangedCommand = null;
public ICommand TextBoxSelectionChangedCommand {
get {
if (_TextBoxSelectionChangedCommand == null) {
_TextBoxSelectionChangedCommand = new RelayCommand<RoutedEventArgs>((r) => TextBoxSelectionChanged(r), (r) => true);
}
return _TextBoxSelectionChangedCommand;
}
}
protected virtual void TextBoxSelectionChanged(RoutedEventArgs _args) {
YourCursorPositionVariable = (_args.OriginalSource as System.Windows.Controls.TextBox).SelectionStart;
}
#endregion
I agree you has to cast TextBox component type in ViewModel and it's a kind of coupling, but create a custom component will impose to bind on a specific property as well.
As far as I am aware, this feature has not been included in Silverlight 2.0.
Read this article for a work-around solution.

Resources