WPF execute ICommand using attached properties and behaviours - c#

So I'm learning about attached properties and attached behaviours. I have a TextBox and everytime the text is changed, I want to execute some ICommand SomeCommand in my ViewModel which I would bind in the view like this:
<TextBox ... TextUpdateCommand="{Binding Path=SomeCommand}" MonitorTextBoxProperty=true />
MonitorTextBoxProperty is just a property that listens to TextChanged event and then executes ICommand SomeCommand when TextChanged is fired. The ICommand SomeCommand that is executed should come from TextUpdateCommandProperty. How do I link this ICommand SomeCommand to execute it from OnTextBoxTextChanged?
Code:
//Property and behaviour for TextChanged event
public static int GetMonitorTextBoxProperty(DependencyObject obj)
{
return (int)obj.GetValue(MonitorTextBoxProperty);
}
public static void SetMonitorTextBoxProperty(DependencyObject obj, int value)
{
obj.SetValue(MonitorTextBoxProperty, value);
}
public static readonly DependencyProperty MonitorTextBoxProperty =
DependencyProperty.RegisterAttached("MonitorTextBox", typeof(bool), typeof(this), new PropertyMetadata(false, OnMonitorTextBoxChanged));
private static void OnMonitorTextBoxChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var textBox = (d as TextBox);
if (textBox == null) return;
textBox.TextChanged -= OnTextBoxTextChanged;
if ((bool)e.NewValue)
{
textBox.TextChanged += OnTextBoxTextChanged;
}
}
private static void OnTextBoxTextChanged(object sender, TextChangedEventArgs e)
{
// Execute ICommand by command.Execute()
}
// Property for attaching ICommand that is executed on TextChanged
public static int GetTextUpdateCommandProperty(DependencyObject obj)
{
return (int)obj.GetValue(TextUpdateCommandProperty);
}
public static void SetTextUpdateCommandProperty(DependencyObject obj, int value)
{
obj.SetValue(TextUpdateCommandProperty, value);
}
public static readonly DependencyProperty TextUpdateCommandProperty =
DependencyProperty.RegisterAttached("TextUpdateCommand", typeof(ICommand), typeof(this), new PropertyMetadata(null));

Just get the current value of the TextBox's TextUpdateCommand property using the attached property's get accessor. Try this:
private static void OnTextBoxTextChanged(object sender, TextChangedEventArgs e)
{
var textBox = sender as TextBox;
var command = GetTextUpdateCommandProperty(textBox);
if (command != null)
command.Execute(null);
}
You should also change the type of the accessors:
public static ICommand GetTextUpdateCommandProperty(DependencyObject obj)
{
return (ICommand)obj.GetValue(TextUpdateCommandProperty);
}
public static void SetTextUpdateCommandProperty(DependencyObject obj, ICommand value)
{
obj.SetValue(TextUpdateCommandProperty, value);
}
And typeof(this) should the typeof(TheClassName) in the call to DependencyProperty.RegisterAttached.

Related

Attached property cannot be bound

We have a WPF application which has a query count result displayed on the screen. We initially defined the result as a button so that when it was clicked, the application would display a detailed list of the query results. However, for reasons unrelated to this question, we now need this to be a border (basically, just the template for the original button). So far, I have set up my attached property:
public static class AttachedCommandBehavior
{
#region Command
public static DependencyProperty PreviewMouseLeftButtonUpCommandProperty = DependencyProperty.RegisterAttached(
"PreviewMouseLeftButtonUpCommand",
typeof(ICommand),
typeof(AttachedCommandBehavior),
new FrameworkPropertyMetadata(PreviewPreviewMouseLeftButtonUpChanged));
public static void SetPreviewMouseLeftButtonUpChanged(DependencyObject target, ICommand value)
{
target.SetValue(PreviewMouseLeftButtonUpCommandProperty, value);
}
public static ICommand GetPreviewMouseLeftButtonUpChanged(DependencyObject target)
{
return (ICommand)target.GetValue(PreviewMouseLeftButtonUpCommandProperty);
}
private static void PreviewPreviewMouseLeftButtonUpChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is UIElement element)
{
if (e.NewValue != null && e.OldValue == null)
{
element.PreviewMouseLeftButtonUp += element_PreviewMouseLeftButtonUp;
}
else if (e.NewValue == null && e.OldValue != null)
{
element.PreviewMouseLeftButtonUp -= element_PreviewMouseLeftButtonUp;
}
}
}
private static void element_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (sender is UIElement element)
{
if (element.GetValue(PreviewMouseLeftButtonUpCommandProperty) is ICommand command)
command.Execute(CommandParameterProperty);
}
}
#endregion
#region CommandParameter
public static DependencyProperty CommandParameterProperty = DependencyProperty.RegisterAttached(
"CommandParameter",
typeof(object),
typeof(AttachedCommandBehavior),
new FrameworkPropertyMetadata(CommandParameterChanged));
public static void SetCommandParameter(DependencyObject target, object value)
{
target.SetValue(CommandParameterProperty, value);
}
public static object GetCommandParameter(DependencyObject target)
{
return target.GetValue(CommandParameterProperty);
}
private static void CommandParameterChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is UIElement element)
{
element.SetValue(CommandParameterProperty, e.NewValue);
}
}
#endregion
}
And then in my XAML, I am trying to bind my command to the attached DependencyProperty:
<Border Background="{Binding BackgroundColor, Converter={StaticResource ColorNameToBrushConverter}}"
Cursor="{x:Static Cursors.Hand}"
local:AttachedCommandBehavior.PreviewMouseLeftButtonUpChanged="{Binding QueryClickedCommand}">
<Grid>...</Grid>
</Border>
However, my little blue squiggly line is telling me "A 'Binding' cannot be used within a 'Border' collection. A 'Binding' can only be set on a DependencyProperty of a DependencyObject." Being the daring programmer that I am, I boldly ignore the little blue squiggly and try to run anyway. At which point I get an exception:
System.Windows.Markup.XamlParseException: 'A 'Binding' cannot be set on the 'SetPreviewMouseLeftButtonUpChanged' property of type 'Viewbox'. A 'Binding' can only be set on a DependencyProperty of a DependencyObject.'
It turns out this was a naming convention problem. In copying/pasting/renaming/general indecision, I messed up the names of my getter and setter for the command property. Once I changed them all to match the correct pattern, my code runs.
#region Command
public static DependencyProperty PreviewMouseLeftButtonUpCommandProperty = DependencyProperty.RegisterAttached(
"PreviewMouseLeftButtonUpCommand",
typeof(ICommand),
typeof(AttachedCommandBehavior),
new FrameworkPropertyMetadata(PreviewMouseLeftButtonUpChanged));
public static void SetPreviewMouseLeftButtonUpCommand(DependencyObject target, ICommand value)
{
target.SetValue(PreviewMouseLeftButtonUpCommandProperty, value);
}
public static ICommand GetPreviewMouseLeftButtonUpCommand(DependencyObject target)
{
return (ICommand)target.GetValue(PreviewMouseLeftButtonUpCommandProperty);
}
private static void PreviewMouseLeftButtonUpChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is UIElement element)
{
if (e.NewValue != null && e.OldValue == null)
{
element.PreviewMouseLeftButtonUp += element_PreviewMouseLeftButtonUp;
}
else if (e.NewValue == null && e.OldValue != null)
{
element.PreviewMouseLeftButtonUp -= element_PreviewMouseLeftButtonUp;
}
}
}
private static void element_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (sender is UIElement element)
{
if (element.GetValue(PreviewMouseLeftButtonUpCommandProperty) is ICommand command)
command.Execute(CommandParameterProperty);
}
}
#endregion

Update Viewmodel prop when the user control lost focus

<UserControl UIHelper:FocusExtension.IsFocused="{Binding FocusUserControl,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Focusable="True" IsTabStop="True">
here I added an IsFocused prop to maintain the focus of usercontrol
public static class FocusExtension
{
public static bool GetIsFocused(DependencyObject obj)
{
return (bool)obj.GetValue(IsFocusedProperty);
}
public static void SetIsFocused(DependencyObject obj, bool value)
{
obj.SetValue(IsFocusedProperty, value);
}
public static readonly DependencyProperty IsFocusedProperty =
DependencyProperty.RegisterAttached(
"IsFocused", typeof(bool), typeof(FocusExtension),
new UIPropertyMetadata(false, OnIsFocusedPropertyChanged));
private static void OnIsFocusedPropertyChanged(
DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var uie = (UIElement)d;
if ((bool)e.NewValue)
{
uie.Focus();
}
}
}
this is my FocusExtension class
public bool FocusUserControl
{
get { return focusUserControl; }
set
{
focusUserControl = value;
OnPropertyChanged(new PropertyChangedEventArgs("FocusUserControl"));
}
}
this is my VM prop. which is binded, now in user control, I have a button. when I click on it a new user control will. open at that time I need to update the FocusUserControl to false. as the focus is not on that usercontrol anymore. I am stuck here please suggest something thanks for ur help.
**
Update i added something like this
**
private static void OnIsFocusedPropertyChanged(
DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
uie = (UIElement)d;
if ((bool)e.NewValue)
{
uie.LostKeyboardFocus += LostFocus;
uie.Focus();
}
}
static void LostFocus(object sender, RoutedEventArgs e)
{
uie.LostKeyboardFocus -= LostFocus;
// Here i need to update `FocusUserControl` to false
}
in LostFocus i just need to update FocusUserControl to false. how can i do it? really use some help.

How to define and raise an event inside a static method (a PropertyChangedCallback)

I've defined an event in a custom control (Field) named ValueChanged.
public static event EventHandler<ValueChangedEventArgs> ValueChanged;
And a dependency property Value.
public string Value
{
get => (string)GetValue(ValueProperty);
set => SetValue(ValueProperty, value);
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register(nameof(Value), typeof(string), typeof(Field),
new PropertyMetadata(OnValuePropertyChanged));
I need to fire my event when this value changes (if FireValueChanged is true).
private static void OnValuePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
bool fire = (bool)d.GetValue(FireValueChangedProperty);
if (fire) ValueChanged?.Invoke(d, new ValueChangedEventArgs($"{e.NewValue}", $"{e.OldValue}"));
}
This is the ValueChangedEventArgs class
public class ValueChangedEventArgs : EventArgs
{
public string NewValue { get; }
public string OldValue { get; }
//Other calculated properties...
public ValueChangedEventArgs(string newValue, string oldValue)
{
NewValue = newValue;
OldValue = oldValue;
}
}
But in my main window it says that
cannot set the handler because the event is a static event.
And when I try to compile it says that
the property 'ValueChanged' does not exist in the XML namespace 'clr-namespace: ...'.
If I try to set the event as non static, I cannot use it inside static OnValuePropertyChanged method.
You can access the control that the value was changed for in your OnValuePropertyChanged method like this (I've named the control class MyControl):
private static void OnValuePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
bool fire = (bool)d.GetValue(FireValueChangedProperty);
var ctrl = (MyControl)d;
if (fire)
ctrl.ValueChanged?.Invoke(d, new ValueChangedEventArgs($"{e.NewValue}", $"{e.OldValue}"));
}
Then you can remove the static and change the event to be an event on instance level:
public event EventHandler<ValueChangedEventArgs> ValueChanged;

ICommandSource and DelegateCommand

I am trying to make a usercontrol with some commands. If I wire up the commands in xaml using the approach shown here http://msdn.microsoft.com/en-us/library/vstudio/ms771361(v=vs.90).aspx it works but if I use the DelegateCommand from the Prism library CanExecuteChanged doesn't fire on the usercontrol and I cannot figure out why. I apologize I realize this is a lot of code. Execute fires correctly but CanExecute never does.
Thanks in advance.
Custom Control Xaml
<UserControl x:Class="Controls.LinkButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<TextBlock>
<Hyperlink x:Name="hyperLink" Click="Hyperlink_Click">
<Run x:Name="textRun"
Text="Click Me"/>
</Hyperlink>
</TextBlock>
</UserControl>
Custom Control Code Behind
public partial class LinkButton : UserControl, ICommandSource
{
public LinkButton()
: base()
{
InitializeComponent();
textRun.DataContext = this;
hyperLink.DataContext = this;
}
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register("Command", typeof(ICommand), typeof(LinkButton), new PropertyMetadata(null, new PropertyChangedCallback(CommandChanged)));
public object CommandParameter
{
get { return (object)GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
public static readonly DependencyProperty CommandParameterProperty =
DependencyProperty.Register("CommandParameter", typeof(object), typeof(LinkButton), new PropertyMetadata(null));
public IInputElement CommandTarget
{
get { return (IInputElement)GetValue(CommandTargetProperty); }
set { SetValue(CommandTargetProperty, value); }
}
public static readonly DependencyProperty CommandTargetProperty =
DependencyProperty.Register("CommandTarget", typeof(IInputElement), typeof(LinkButton), new PropertyMetadata(null));
private void Hyperlink_Click(object sender, RoutedEventArgs e)
{
if (Command != null)
{
RoutedCommand command = Command as RoutedCommand;
if (command != null)
{
command.Execute(CommandParameter, CommandTarget);
}
else
{
((ICommand)Command).Execute(CommandParameter);
}
}
}
public static EventHandler canExecuteChangedHandler;
private static void CommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
LinkButton lb = (LinkButton)d;
lb.HookUpCommand((ICommand)e.OldValue, (ICommand)e.NewValue);
}
// Add a new command to the Command Property.
private void HookUpCommand(ICommand oldCommand, ICommand newCommand)
{
// If oldCommand is not null, then we need to remove the handlers.
if (oldCommand != null)
{
RemoveCommand(oldCommand, newCommand);
}
AddCommand(oldCommand, newCommand);
}
// Remove an old command from the Command Property.
private void RemoveCommand(ICommand oldCommand, ICommand newCommand)
{
EventHandler handler = CanExecuteChanged;
oldCommand.CanExecuteChanged -= handler;
}
// Add the command.
private void AddCommand(ICommand oldCommand, ICommand newCommand)
{
EventHandler handler = new EventHandler(CanExecuteChanged);
canExecuteChangedHandler = handler;
if (newCommand != null)
{
newCommand.CanExecuteChanged += canExecuteChangedHandler;
}
}
private void CanExecuteChanged(object sender, EventArgs e)
{
if (this.Command != null)
{
RoutedCommand command = this.Command as RoutedCommand;
// If a RoutedCommand.
if (command != null)
{
if (command.CanExecute(CommandParameter, CommandTarget))
{
this.IsEnabled = true;
}
else
{
this.IsEnabled = false;
}
}
// If a not RoutedCommand.
else
{
if (Command.CanExecute(CommandParameter))
{
this.IsEnabled = true;
}
else
{
this.IsEnabled = false;
}
}
}
}
}
Window
<ctrl:LinkButton Command="{Binding LinkClicked}"/>
public partial class MainWindow : Window
{
public MainWindow()
{
LinkClicked = new DelegateCommand(DoSomething, CanDoSomething);
InitializeComponent();
DataContext = this;
LinkClicked.RaiseCanExecuteChanged();
}
public DelegateCommand LinkClicked { get; set; }
public void DoSomething()
{
MessageBox.Show("Did Something");
}
public bool CanDoSomething()
{
return false;
}
}
The problem here is you are calling LinkClicked.RaiseCanExecuteChanged(); immediately after setting DataContext and till then the LinkedCommand is not binded and hence CanExecuteChanged event of the DelegateCommand is null and hence RaiseCanExecuteChanged() does not do anything. so to avoid this call LinkClicked.RaiseCanExecuteChanged() in the loaded event of Window because till then binding will be updated. Though this is a dirty solution because you will have to do this everywhere where you will use this LinkButton and bind its Command.
The implementation of RaiseCanExecuteChanged is something like this
public void RaiseCanExecuteChanged()
{
if (CanExecuteChanged != null) //CanExecuteChanged is coming null in your case so the event is not fired.
CanExecuteChanged(this, new EventArgs());
}
Or the better solution is that you havent called CanExecute in AddCommand method, In actual Command implementation CanExecute is called
if (newCommand != null)
{
newCommand.CanExecuteChanged += CanExecuteChanged;
newCommand.CanExecute(CommandParameter); //you are missing this.
}
If you do this then there is no need to call RaiseCanExecuteChanged.

Why is this Command Dependency Set Value Property not triggered? (DevExpress.AgMenuItem)

I am using the free version of the DevExpress Silverlight Menu (AgMenu 8.4). Sadly the MenuItems of this menu have no "Command" and "CommandParameter" properties.
I decided to inherit from the MenuItem class and implement two DependencyProperties, "Command" and "CommandProperty".
The code for this looks like this:
public partial class MenuItem : DevExpress.AgMenu.AgMenuItem
{
public MenuItem()
{
InitializeComponent();
}
private Object _CommandParameter = null;
public Object CommandParameter
{
get { return _CommandParameter; }
set { _CommandParameter = value; } //This one is triggered. This is ok.
}
public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.Register("CommandParameter", typeof(Object), typeof(Gui.CustomControls.MenuItem), new PropertyMetadata(OnCommandParameterChanged));
private static void OnCommandParameterChanged(object sender, DependencyPropertyChangedEventArgs args)
{
//CommandParameter Object is arriving here. That is ok.
}
private ICommand _Command = null;
public ICommand Command
{
get { return _Command; }
set
{
//HERE is the problem.
//This one is NOT triggered. I dont' know why....?
_Command = value;
}
}
public static readonly DependencyProperty CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(Gui.CustomControls.MenuItem), new PropertyMetadata(OnCommandChanged));
private static void OnCommandChanged(object sender, DependencyPropertyChangedEventArgs args)
{
//ICommand Object is arriving here. That is also ok.
//I don't understand, why the ICommand Object is not arriving in the set value prop
}
}
Now I am using this two DPs in my XAML. This looks like this for one MenuItem:
<cc:MenuItem x:Name ="_mnuItemLogout"
DataContext ="{Binding Source={StaticResource ViewModel}}"
Header ="{Binding Source={StaticResource MenuProvider}, Path=GetSingleton.LogoutText, Mode=OneWay}"
IsEnabled ="{Binding Source={StaticResource MenuProvider}, Path=GetSingleton.LogoutEnabled, Mode=OneWay}"
Command ="{Binding Source={StaticResource ViewModel}, Path=Command_FormOpen}"
CommandParameter ="{gui:FormOpen e=Login}"
IsCheckable ="False"
>
</cc:MenuItem>
When I am testing my silverlight application, I assume that both, the "Command" and "CommandParameter" set value properties are called, and the values are set to _Command and _CommandParameter, but only the CommandParameter set value is called.
Strangely, both static procedures "OnCommandChanged" and "OnCommandParameterChanged" are called. While debugging, I can see, both expected objects (ICommand and CommandParameter) are arriving in this two procedures.
So my question is:
What am I doing wrong, that the ICommand Object is not set in the "Set ICommand" property?
Thank you.
This case is solved. What I needed to do, is not using DependencyProperties, but attached DependencyProperties.
The MenuItem of the DevExpress Silverlight menu now accepts Command and CommandParameter objects. The Command is triggered, when the LeftMouseButtonUp event is fired. The following is the working code. The XAML stays the same (see above). You just need to make a silverlight user control and inherit from DevExpress.AgMenu.AgMenuItem. You then use this as MenuItem, instead of the original.
using System;
using System.Windows;
using System.Windows.Input;
namespace Gui.CustomControls
{
public partial class MenuItem : DevExpress.AgMenu.AgMenuItem
{
public MenuItem()
{
InitializeComponent();
}
#region CommandParameter DependencyProperty
public static Object GetCommandParameter(DependencyObject obj)
{
return (Object)obj.GetValue(CommandParameterProperty);
}
public static void SetCommandParameter(DependencyObject obj, Object value)
{
obj.SetValue(CommandParameterProperty, value);
}
public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.RegisterAttached("CommandParameter", typeof(Object), typeof(Gui.CustomControls.MenuItem), new PropertyMetadata(OnCommandParameterChanged) );
private static void OnCommandParameterChanged(object sender, DependencyPropertyChangedEventArgs args)
{
DependencyObject _DependencyObject = (DependencyObject)sender;
if ((args.NewValue != null) && (_DependencyObject != null))
{
MenuItem.SetCommandParameter(_DependencyObject, args.NewValue);
}
}
#endregion
#region Command
private static void OnCommandChanged(object sender, DependencyPropertyChangedEventArgs args)
{
DependencyObject _DependencyObject = (DependencyObject)sender;
ICommand _ICommand = (ICommand)args.NewValue;
if ((_ICommand != null) && (_DependencyObject != null))
{
SetCommand(_DependencyObject, _ICommand);
}
}
public static ICommand GetCommand(DependencyObject obj)
{
return (ICommand)obj.GetValue(CommandProperty);
}
public static void SetCommand(DependencyObject obj, ICommand value)
{
obj.SetValue(CommandProperty, value);
}
// Using a DependencyProperty as the backing store for Command. This enables animation, styling, binding, etc...
public static readonly DependencyProperty CommandProperty = DependencyProperty.RegisterAttached("Command", typeof(ICommand), typeof(Gui.CustomControls.MenuItem), new PropertyMetadata(OnCommandChanged));
#endregion
#region LeftMouseButtonUp (Command Trigger)
protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonUp(e);
ICommand _ICommand = MenuItem.GetCommand(this);
Object _CommandParameter = MenuItem.GetCommandParameter(this);
if (_ICommand != null)
{
_ICommand.Execute(_CommandParameter);
}
}
#endregion
}
}

Categories

Resources