I have a dependency property on a usercontrol that is an enum. I bind to it and set it in the main window view but it does not change.
This is the usercontrol and the icon is the enum
<local:GoogleMaterialIcon Icon="AccountBalance"/>
Here is the enum
public enum Icon
{
_3DRotation,
Accessibility
};
here is the dp
/// <summary>
/// Dependency Property used to back the <see cref="Icon"/> Property
/// </summary>
public static readonly DependencyProperty IconProperty =
DependencyProperty.Register("Icon",
typeof(Icon),
typeof(GoogleMaterialIcon),
new PropertyMetadata(null));
and finally the property
public Icon Icon
{
get { return (Icon)GetValue(IconProperty); }
set
{
SetValue(IconProperty, value);
}
}
I place a breakpoint inside the set icon but it never runs. also the enum is in its own file. every time i run it shows me the wrong icon because the dp reverts to the first enum and never updates
update: full code of the usercontrol behind
public partial class GoogleMaterialIcon : UserControl
{
/// <summary>
/// Dependency Property used to back the <see cref="Icon"/> Property
/// </summary>
public static readonly DependencyProperty IconProperty =
DependencyProperty.Register("Icon",
typeof(Icon),
typeof(GoogleMaterialIcon),
new PropertyMetadata(null));
/// <summary>
/// Constructor
/// </summary>
public GoogleMaterialIcon()
{
InitializeComponent();
}
/// <summary>
/// Select a predefined icon to use
/// </summary>
public Icon Icon
{
get { return (Icon)GetValue(IconProperty); }
set
{
SetValue(IconProperty, value);
}
}
}
Obviosly under some - at least for me - strange circumstances, even Microsoft only comes up with an solution rather than an explanation.
In your PropertyMetadata you are missing the PropertyChanged-Event
Unfortunately i cannot explain in deep, whats happening here. But using the PropertyChanged-Event of the DependencyProperty seems for me an acceptable workaround.
Related
I am trying to use validation to display validation errors on windows elements (actually text boxes), but I failed to get the text boxes that are not focused/edited to update their validation when conditions for failure changed (neither using INotifyDataErrorInfo nor IDataErrorInfo).
Let's say, TextBox1 validate to Error when TextBox2 holds a specific path. Now after changing the path in TextBox2, TextBox1 should clear its error automatically, but this just did not happen, I always hat to enter the TextBox and change its content for validation to update...
Therefore I intended to use Behaviors in order to bind them to a Validation Boolean value and let the behavior set the TextBox in the appropriate VisualState using the default Validation States (Valid, InvalidFocused, InvalidUnfocused).
This is my Behavior (currently only a PoC so no Dependency Property):
/// <summary>
/// Behavior for setting the visual style depending on a validation value
/// </summary>
public class TextBoxValidationBindingBehavior : BehaviorBase<TextBox>
{
/// <summary>
/// Setup the behavior
/// </summary>
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.TextChanged += this.AssociatedObject_TextChanged;
}
/// <summary>
/// Set visual state
/// </summary>
private void AssociatedObject_TextChanged(object sender, TextChangedEventArgs e)
{
TextBox textBox = this.AssociatedObject as TextBox;
if (textBox.Text == "Test")
{
VisualStateManager.GoToState(textBox, "Valid", false);
}
else
{
if (textBox.Focus())
{
VisualStateManager.GoToState(textBox, "InvalidFocused", true);
}
else
{
VisualStateManager.GoToState(textBox, "InvalidUnfocused", true);
}
}
}
/// <summary>
/// Clean-up the behavior
/// </summary>
protected override void OnCleanup()
{
this.AssociatedObject.TextChanged -= this.AssociatedObject_TextChanged;
base.OnCleanup();
}
}
And the TextBox definition:
<TextBox Grid.Row = "0"
Grid.Column = "1"
Margin = "0, 2, 0, 2"
VerticalAlignment = "Stretch"
VerticalContentAlignment = "Center"
Text = "{Binding NewBookName}">
<b:Interaction.Behaviors>
<behavior:TextBoxValidationBindingBehavior />
</b:Interaction.Behaviors>
</TextBox>
Setting breakpoints I can see that the code gets called as expected. But the VisualStateManager.GoToState has absolutely no impact on the TextBox!
If I define a template for the text box and set custom VisualStates the behavior will work. However, the point was not to redefine visual states for the TextBox but rather to use the existing states just by associating a Behavior bound top a validation boolean and a message to display...
I'd really appreciate any hint!!! Also, I'd be happy to provide more information if required.
For the time being, I had to give up on just setting the Visual States 😣
I needed to create a Behavior that will when bound to the control add an ad-hoc ValidationRule. In principle:
For the custom validation to work (the point is only to get the default visual style on an error), the TextChanged event needs to disable the validation, update the source, then enable validation and re-update the source for the validation to happen
The IsBindingValid dependency property when changed will also update the source to trigger validation
All in all, this works:
/// <summary>
/// Behavior for setting the visual style depending on a validation value
/// </summary>
public class TextBoxValidationBindingBehavior : BehaviorBase<TextBox>
{
#region Internal Validation Class
/// <summary>
/// A validation rule that validates according to a dependency property binding rather than on control content
/// </summary>
private class BindingValidationRule : ValidationRule
{
#region Initialization
/// <summary>
/// Constructor
/// </summary>
/// <param name="that">Behavior holding this class</param>
public BindingValidationRule(TextBoxValidationBindingBehavior that) { this._that = that; }
#endregion
/// <summary>
/// Reference to behavior holding the object
/// </summary>
private readonly TextBoxValidationBindingBehavior _that;
/// <summary>
/// Flag indication that the next validation check is to be disabled / set to true
/// </summary>
public bool DisableValidationOnce = true;
/// <summary>
/// Validates the control
/// </summary>
/// <param name="value">Value to validate (ignored)</param>
/// <param name="cultureInfo">Culture Information</param>
/// <returns>Returns the <see cref="ValidationResult"/> of this validation check</returns>
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
if (this._that is { } that)
{
ValidationResult validationResult;
if (that.IsBindingValid || this.DisableValidationOnce)
validationResult = new ValidationResult(true, null);
else
validationResult = new ValidationResult(false, that.ErrorText);
// Re-enable validation
this.DisableValidationOnce = false;
// Set Error Tooltip
that.AssociatedObject.ToolTip = validationResult.IsValid ? null : new ToolTip() { Content = validationResult.ErrorContent };
// return result
return validationResult;
}
else throw new Exception($"Internal TextBoxValidationBindingBehavior error.");
}
}
#endregion
#region DepProp: IsBindingValid
public static readonly DependencyProperty IsBindingValidProperty = DependencyProperty.Register("IsBindingValid", typeof(bool), typeof(TextBoxValidationBindingBehavior), new PropertyMetadata(false, IsBindingValidProperty_PropertyChanged));
public bool IsBindingValid
{
get => (bool)this.GetValue(IsBindingValidProperty);
set => this.SetValue(IsBindingValidProperty, value);
}
private static void IsBindingValidProperty_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is TextBoxValidationBindingBehavior _this)
{
// Avoid unnecessary notification propagation (the prop probably changed du to us updating the source property)
if (_this._isValidating) return;
// Trigger validation
if (_this.AssociatedObject is { } textBox && textBox.GetBindingExpression(TextBox.TextProperty) is { } bindingExpression)
bindingExpression.UpdateSource();
}
}
#endregion
#region DepProp: ErrorText
public static readonly DependencyProperty ErrorTextProperty = DependencyProperty.Register("ErrorText", typeof(string), typeof(TextBoxValidationBindingBehavior), new PropertyMetadata("Error"));
public string ErrorText
{
get => (string)this.GetValue(ErrorTextProperty);
set => this.SetValue(ErrorTextProperty, value);
}
#endregion
#region Private properties
/// <summary>
/// The custom validation rule to handle bound validation
/// </summary>
private BindingValidationRule _bindingValidationRule { get; set; }
/// <summary>
/// Indicate if validation already happening to avoid verbose notifications in the application
/// </summary>
private bool _isValidating;
#endregion
/// <summary>
/// Setup the behavior
/// </summary>
protected override void OnAttached()
{
base.OnAttached();
// Set handler(s)
this.AssociatedObject.TextChanged += this.AssociatedObject_TextChanged;
// Create custom validation rule
this._bindingValidationRule = new BindingValidationRule(this);
// Add rule
if (this.AssociatedObject is { } textBox && BindingOperations.GetBinding(textBox, TextBox.TextProperty) is { } binding)
{
// We must be able to handle updating the source in order to set value bypassing validation
if (binding.UpdateSourceTrigger == UpdateSourceTrigger.PropertyChanged) throw new Exception("Cannot set UpdateSourceTrigger to PropertyChanged when using TextBoxValidationBindingBehavior");
// Add custom validation rule
binding.ValidationRules.Add(this._bindingValidationRule);
}
}
/// <summary>
/// Set visual state
/// </summary>
private void AssociatedObject_TextChanged(object sender, TextChangedEventArgs e)
{
if (this.AssociatedObject is { } textBox && textBox.GetBindingExpression(TextBox.TextProperty) is { } bindingExpression)
{
this._isValidating = true;
// Remove validation before updating source (or validation will prevent source from updating if it errors)
this._bindingValidationRule.DisableValidationOnce = true;
// Update Source
bindingExpression.UpdateSource();
// Ensure we are not disabled (if UpdateSource did not call Validation)
this._bindingValidationRule.DisableValidationOnce = false;
// Trigger validation
bindingExpression.UpdateSource();
this._isValidating = false;
}
}
/// <summary>
/// Clean-up the behavior
/// </summary>
protected override void OnCleanup()
{
this.AssociatedObject.TextChanged -= this.AssociatedObject_TextChanged;
// Remove rule
if (this.AssociatedObject is { } textBox && BindingOperations.GetBinding(textBox, TextBox.TextProperty) is { } binding)
{
binding.ValidationRules.Remove(this._bindingValidationRule);
}
base.OnCleanup();
}
}
And the XAML code:
<TextBox Grid.Row = "0"
Grid.Column = "1"
Margin = "0, 2, 0, 2"
VerticalAlignment = "Stretch"
VerticalContentAlignment = "Center"
Text = "{Binding NewBookName}">
<b:Interaction.Behaviors>
<behavior:TextBoxValidationBindingBehavior IsBindingValid = "{Binding IsValidName}"
ErrorText = "Invalid name or a book with the same name already exists."/>
</b:Interaction.Behaviors>
</TextBox>
However, there are a few things I really don't like about this way of proceeding:
This procedure is very verbose in that it triggers potentially a lot of notification bindings every time it updates the source just to validate the content
It may not be thread-safe?!
While it is theoretically possible to use it with other validation rules, it would probably need a lot of code to get it to work
I find this quite hacky...
I hope this can help others or if one has a better idea: YOU ARE WELCOME!!! 😊
I have an application that display different datasets (users, nationality, etc) on the screen using radOutlookbar.
I have manage to load the required views in each item to display the data with no problem.
I then built views for each dataset (users, nationality, etc) to display the details about each selected item (i.e:user) within the displayed datasets.
Case:
First, I need to display the respective view for each dataset when I click on it's item.
Second, The displayed view will have an option to edit/add the displayed details.
I want to achieve this scenario using state-base-navigation.
So,
I have a PRISM region inside ItemsControl with ItemsPanelTemplate of grid to host the loaded views, basically I load the views for each dataset.
Question,
How should I show/hide the respective view according to the selected dataset using VSM?
Question 2:
Should I be able to define another nested state inside the loaded view to enable the scenario of edit/add details for each view?
If someone have any idea to do this, will be of great help to have a starting code.
Best regards
May be there's other schemes to access VSM but I prefer to create AttachedProperty for it. Let me explain.
Here is VisualState manager
/// <summary>
/// Class will allow to change VisualSate on ViewModel via attached properties
/// </summary>
public static class VisualStateManagerEx
{
private static PropertyChangedCallback callback = new PropertyChangedCallback(VisualStateChanged);
/// <summary>
/// Gets the state of the visual.
/// </summary>
/// <param name="obj">The obj.</param>
/// <returns></returns>
public static string GetVisualState(DependencyObject obj)
{
return (string)obj.GetValue(VisualStateProperty);
}
/// <summary>
/// Sets the state of the visual.
/// </summary>
/// <param name="obj">The obj.</param>
/// <param name="value">The value.</param>
public static void SetVisualState(DependencyObject obj, string value)
{
obj.SetValue(VisualStateProperty, value);
}
/// <summary>
/// DP for 'VisualState'
/// </summary>
public static readonly DependencyProperty VisualStateProperty =
DependencyProperty.RegisterAttached(
"VisualState",
typeof(string),
typeof(VisualStateManagerEx),
new PropertyMetadata(null, VisualStateManagerEx.callback)
);
/// <summary>
/// Visuals the state changed.
/// </summary>
/// <param name="d">The d.</param>
/// <param name="e">The <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> instance containing the event data.</param>
public static void VisualStateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
//Control changeStateControl = d as Control;
FrameworkElement changeStateControl = d as FrameworkElement;
if (changeStateControl == null)
throw (new Exception("VisualState works only on Controls type"));
if (Application.Current.Dispatcher.CheckAccess() == false)
{
// Wrong thread
System.Diagnostics.Debug.WriteLine("[VisualStateManagerEx] 'VisualStateChanged' event received on wrong thread -> re-route via Dispatcher");
Application.Current.Dispatcher.BeginInvoke(
//() => { VisualStateChanged(d, e); }
VisualStateManagerEx.callback
, new object[] { d, e }); //recursive
}
else
{
if (string.IsNullOrEmpty(e.NewValue.ToString()) == false)
{
//VisualStateManager.GoToState(changeStateControl, e.NewValue.ToString(), true);
VisualStateManager.GoToElementState(changeStateControl, e.NewValue.ToString(), true);
System.Diagnostics.Debug.WriteLine("[VisualStateManagerEx] Visual state changed to " + e.NewValue.ToString());
}
}
}
}
now - in XAML you attach it to your ViewModel like this:
<UserControl
xmlns:VSManagerEx=clr-namespace:Namespace.namespace;assembly=Assembly01"
VSManagerEx:VisualStateManagerEx.VisualState="{Binding Path=ViewModelVisualState, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
...
...
Now your VSM in XAML is bound to ViewModelVisualState property in ViewModelBase (or whatever will be bound to DataContext of this UserControl. So Actually in your ViewModelBase you using is like this:
/// <summary>
/// Base class for all 'view-models'
/// </summary>
[Export(typeof(ViewModelBase))]
public abstract class ViewModelBase : INavigationAware, INotifyPropertyChanged
{
private SynchronizationContext parentSyncContent;
#region VisualState
private string viewModelVisualState = string.Empty;
/// <summary>
/// Gets or sets the state of the view model visual.
/// </summary>
/// <value>
/// The state of the view model visual.
/// </value>
public virtual string ViewModelVisualState
{
get { return viewModelVisualState; }
set
{
viewModelVisualState = value;
RaisePropertyChanged(this, "ViewModelVisualState");
}
}
#endregion
/// <summary>
/// Raises the property changed.
/// </summary>
/// <param name="Sender">The sender.</param>
/// <param name="PropertyName">Name of the property.</param>
public void RaisePropertyChanged(object Sender, string PropertyName)
{
parentSyncContent.Post((state) =>
{
if (PropertyChanged != null)
PropertyChanged(Sender, new PropertyChangedEventArgs(PropertyName));
}, null);
}
...
...
So - in any ViewModel that inherit from this ViewModelBase could declare it own VMS states and manage them like this:
[Export(typeof(IViewModel1))
public ViewModel1 : ViewModelBase, IViewModel1
{
private const string VM_STATE_WORKING = "WorkingState";
internal void StartWorking()
{
this.ViewModelVisualState = VM_STATE_WORKING;
...
...
Regards question 2: No - you don't need to declare any additional Views inside anything. Read PRISM documentation about Navigation. There's great examples on how to create View/ViewModel that support various presentation logic.
Is this helpful to you ?
My application's main component is a tab control which holds N number of views and those views' datacontext is a separate ViewModel object. I have a statusbar at the bottom of the app and it contains a few textboxes. I want one of the textboxes to reflect a timestamp for the currently selected tab. The timestamp is a property of the ViewModel object that's set as the view's datacontext.
I'm a WPF newb and not really sure how to bind that property to the status bar.
Make sure your ViewModel implements INotifyPropertyChanged.
For example...
/// <summary>
/// Sample ViewModel.
/// </summary>
public class ViewModel : INotifyPropertyChanged
{
#region Public Properties
/// <summary>
/// Timestamp property
/// </summary>
public DateTime Timestamp
{
get
{
return this._Timestamp;
}
set
{
if (value != this._Timestamp)
{
this._Timestamp = value;
// NOTE: This is where the ProperyChanged event will get raised
// which will result in the UI automatically refreshing itself.
OnPropertyChanged("Timestamp");
}
}
}
#endregion
#region INotifyPropertyChanged Members
/// <summary>
/// Event
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Raise the PropertyChanged event.
/// </summary>
protected void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
#region Private Fields
private DateTime _Timestamp;
#endregion
}
Something like:
<TextBox Text="{Binding ElementName=tabControl, Path=SelectedItem.DataContext.Timestamp}" />
A little depending on if your tabcontrol's itemssource is databound or not.
In a Silverlight MVVMLight 4.0 application I have a listbox, a textbox and a checkbox.
The listbox's ItemsSource is bound to a list of objects in the viewmodel.
The listbox's SelectedItem is two-way bound to an object (SelectedActivity) in the viewmodel.
Both the textbox's Text and the checkbox's IsSelected properties are two-way bound to the SelectedActivity object (Name and Selected properties) in the viewmodel.
There is no codebehind.
This works fine: changing the Name in the textbox or checking/unchecking the checkbox and then tabbing will change the underlying property of the object.
But when I change the name (or the checked state) and then immediatelly click another item in the list, the change is not registered.
Does anybody have a workaround for this?
kind regards,
Karel
This is the XAML:
<ListBox Height="251" HorizontalAlignment="Left" Margin="11,39,0,0" Name="activitiesListBox" ItemsSource="{Binding Activities.Items}" VerticalAlignment="Top" Width="139"
SelectedItem="{Binding Activities.SelectedActivity, Mode=TwoWay}">
This is the Activities class holding the items bound to the list:
public class CLJActivitiesViewModel : ViewModelBase
{
/// <summary>
/// Initializes a new instance of the ActivitiesViewModel class.
/// </summary>
public CLJActivitiesViewModel()
{
////if (IsInDesignMode)
////{
//// // Code runs in Blend --> create design time data.
////}
////else
////{
//// // Code runs "for real": Connect to service, etc...
////}
}
#region items
/// <summary>
/// The <see cref="Items" /> property's name.
/// </summary>
public const string ItemsPropertyName = "Items";
private ObservableCollection<CLJActivityViewModel> m_Items = null;
/// <summary>
/// Gets the Items property.
/// TODO Update documentation:
/// Changes to that property's value raise the PropertyChanged event.
/// This property's value is broadcasted by the Messenger's default instance when it changes.
/// </summary>
public ObservableCollection<CLJActivityViewModel> Items
{
get
{
return m_Items;
}
set
{
if (m_Items == value)
{
return;
}
var oldValue = m_Items;
m_Items = value;
RaisePropertyChanged(ItemsPropertyName, oldValue, value, true);
}
}
#endregion
#region SelectedActivity
/// <summary>
/// The <see cref="SelectedActivity" /> property's name.
/// </summary>
public const string SelectedActivityPropertyName = "SelectedActivity";
private CLJActivityViewModel m_SelectedActivity = null;
/// <summary>
/// Gets the SelectedActivity property.
/// TODO Update documentation:
/// Changes to that property's value raise the PropertyChanged event.
/// This property's value is broadcasted by the Messenger's default instance when it changes.
/// </summary>
public CLJActivityViewModel SelectedActivity
{
get
{
return m_SelectedActivity;
}
set
{
if (m_SelectedActivity == value)
{
return;
}
var oldValue = m_SelectedActivity;
m_SelectedActivity = value;
RaisePropertyChanged(SelectedActivityPropertyName, oldValue, value, true);
}
}
#endregion
public override void Cleanup()
{
// Clean own resources if needed
base.Cleanup();
}
}
I ran into the kinda the same issue. I had to trigger the update as the user was entering text so that I could do some validation.
An easy way to achieve that is to create a custom behaviour that you can then add to any TextBox.
Mine is as follows:
public static class TextChangedBindingBehavior
{
public static readonly DependencyProperty InstanceProperty =
DependencyProperty.RegisterAttached("Instance", typeof(object), typeof(TextChangedBindingBehavior), new PropertyMetadata(OnSetInstanceCallback));
public static object GetInstance(DependencyObject obj)
{
return (object)obj.GetValue(InstanceProperty);
}
public static void SetInstance(DependencyObject obj, object value)
{
obj.SetValue(InstanceProperty, value);
}
private static void OnSetInstanceCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var textBox = d as TextBox;
if (textBox != null)
{
textBox.TextChanged -= OnTextChanged;
textBox.TextChanged += OnTextChanged;
}
}
private static void OnTextChanged(object sender, TextChangedEventArgs e)
{
var textBox = (TextBox)sender;
if(!DesignerProperties.GetIsInDesignMode(textBox))
{
textBox.GetBindingExpression(TextBox.TextProperty).UpdateSource();
}
}
}
and you set it to the TextBox like that (Behaviors is the namespace where I put the class above):
<TextBox Behaviors:TextChangedBindingBehavior.Instance="" Text="{Binding Name, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" />
I've ran into the issue like that with TextBox, but didn't see it affecting check box. TextBox issue is happening because bound text gets updated then focus is lost. That is why if you tab first and then change your selection it works as you expect. If you change selection directly bound text doesn't get updated since focus lost message arrives too late.
One way of dealing with this issue is to force binding update every time user types text in the text box. You can make custom behaviour to keep it mvvm.
I want to be able to route the double-click of a grid to a Command. I'm using Rx to simulate the double click but I can't figure out what control to attach the mouse handler to (the mouse event on the e.Row object in DataGrid.RowLoading event doesn't seem to work).
Anyone got any ideas?
Rx code for handling the Double click is as follows:
Observable.FromEvent<MouseButtonEventArgs>(e.Row, "MouseLeftButtonDown").TimeInterval().Subscribe(evt =>
{
if (evt.Interval.Milliseconds <= 300)
{
// Execute command on double click
}
});
I changed this code from handling MouseLeftButtonDown to MouseLeftButtonUp and it works now. The row must have something else handling the button down events.
Observable.FromEvent<MouseButtonEventArgs>(e.Row, "MouseLeftButtonUp").TimeInterval().Subscribe(evt =>
{
if (evt.Interval != TimeSpan.Zero && evt.Interval.TotalMilliseconds <= 300)
{
// Execute command on double click
}
});
Full source code is included below:
CommandBehaviorBase.cs
using System;
using System.Windows;
using System.Windows.Input;
using System.Windows.Interactivity;
/// <summary>
/// Provides the base implementation of all Behaviors that can be attached to a <see cref="FrameworkElement"/> which trigger a command.
/// </summary>
/// <typeparam name="T">The type of control this behavior can be attached to, must derive from <see cref="FrameworkElement"/>.</typeparam>
public abstract class CommandBehaviorBase<T> : Behavior<T> where T : FrameworkElement
{
#region Constants and Fields
/// <summary>The DependencyProperty backing store for CommandParameter. This enables animation, styling, binding, etc...</summary>
public static readonly DependencyProperty CommandParameterProperty =
DependencyProperty.Register(
"CommandParameter",
typeof(object),
typeof(CommandBehaviorBase<T>),
new PropertyMetadata(null, OnCommandParameterPropertyChanged));
/// <summary>The DependencyProperty backing store for Command. This enables animation, styling, binding, etc...</summary>
public static readonly DependencyProperty CommandProperty = DependencyProperty.Register(
"Command", typeof(ICommand), typeof(CommandBehaviorBase<T>), new PropertyMetadata(null));
#endregion
/// <summary>
/// Gets or sets the command to execute
/// </summary>
public ICommand Command
{
get
{
return (ICommand)this.GetValue(CommandProperty);
}
set
{
this.SetValue(CommandProperty, value);
}
}
/// <summary>
/// Gets or sets the command parameter to execute with.
/// </summary>
public object CommandParameter
{
get
{
return this.GetValue(CommandParameterProperty);
}
set
{
this.SetValue(CommandParameterProperty, value);
}
}
/// <summary>
/// Gets or sets the command binding path (Hack for SL3).
/// </summary>
/// <remarks>This is a hack to overcome the fact that we cannot
/// bind to the Command dependency property due to a limitation in Silverlight 3.0
/// This shouldn't be necessary as in Silverlight 4.0 <see cref="DependencyObject"/> supports data binding hooray!</remarks>
public string CommandPath { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this mapping is currently enabled.
/// </summary>
public bool IsEnabled { get; set; }
/// <summary>
/// Implements the logic that disables the key mapping based on whether the command can be executed.
/// </summary>
/// <summary>
/// Updates the target object's IsEnabled property based on the commands ability to execute.
/// </summary>
public virtual void UpdateEnabledState()
{
if (this.Command == null && !string.IsNullOrEmpty(this.CommandPath))
{
this.Command = this.AssociatedObject.DataContext.GetPropertyPathValue<ICommand>(this.CommandPath, null);
}
if (this.AssociatedObject == null)
{
this.Command = null;
this.CommandParameter = null;
}
else if (this.Command != null)
{
this.IsEnabled = this.Command.CanExecute(this.CommandParameter);
}
}
/// <summary>
/// Executes the command, if it's set, providing the <see cref="CommandParameter"/>
/// </summary>
protected virtual void ExecuteCommand()
{
if (this.Command != null)
{
this.Command.Execute(this.CommandParameter);
}
}
/// <summary>
/// Attaches to the target <see cref="FrameworkElement"/> and sets up the command.
/// </summary>
protected override void OnAttached()
{
base.OnAttached();
this.UpdateEnabledState();
}
/// <summary>
/// Raised when the command parameter changes, re-evaluates whether the Command can execute
/// </summary>
/// <param name="sender">The KeyCommandBehavior that command parameter changed for.</param>
/// <param name="args">The parameter is not used.</param>
private static void OnCommandParameterPropertyChanged(
DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
((CommandBehaviorBase<T>)sender).UpdateEnabledState();
}
}
DoubleClickCommandBehavior.cs
using System;
using System.Linq;
using System.Windows;
using System.Windows.Input;
/// <summary>
/// Provides behavior for any clickable control and will execute a command when the control is double clicked.
/// Does not disable the control if the command cannot be executed.
/// </summary>
public class DoubleClickCommandBehavior : CommandBehaviorBase<FrameworkElement>
{
#region Constants and Fields
/// <summary>
/// Stores the observable that subscribes to click events.
/// </summary>
private IDisposable observable;
#endregion
#region Constructors and Destructors
/// <summary>
/// Initializes a new instance of the DoubleClickCommandBehavior class.
/// </summary>
public DoubleClickCommandBehavior()
{
// Default double-click interval is 220 milliseconds.
this.Interval = 220;
}
#endregion
/// <summary>
/// Gets or sets the double click interval in milliseconds.
/// </summary>
public int Interval
{
get;
set;
}
/// <summary>
/// Subscribes to the MouseLeftButtonUp of the data grid and times the intervals between the events,
/// if the time between clicks is less than the configured interval the command is executed.
/// </summary>
/// <remarks>Originally attached to MouseLeftButtonDown but the events were not firing.</remarks>
protected override void OnAttached()
{
base.OnAttached();
this.observable =
Observable.FromEvent<MouseButtonEventArgs>(this.AssociatedObject, "MouseLeftButtonUp").TimeInterval().
Subscribe(
evt =>
{
if (evt.Interval != TimeSpan.Zero && evt.Interval.TotalMilliseconds <= this.Interval)
{
this.UpdateEnabledState();
this.ExecuteCommand();
}
});
}
/// <summary>
/// Disposes of the observable
/// </summary>
protected override void OnDetaching()
{
if (this.observable != null)
{
this.observable.Dispose();
this.observable = null;
}
base.OnDetaching();
}
}
I'm having similar problems (though not using Rx to handle the double click, instead using a generic DoubleClickTrigger). My specific problem is more related to the fact that I'm not sure how or where to hook up my trigger.
I've tried something like the following:
<data:DataGrid.Resources>
<ControlTemplate x:Key="rowTemplate" TargetType="data:DataGridRow">
<data:DataGridRow>
<fxui:Interaction.Triggers>
<fxui:DoubleClickTrigger>
<Interactivity:InvokeCommandAction Command="{Binding Source={StaticResource selectCommand}}" CommandParameter="{Binding}"/>
</fxui:DoubleClickTrigger>
</fxui:Interaction.Triggers>
</data:DataGridRow>
</ControlTemplate>
</data:DataGrid.Resources>
<data:DataGrid.RowStyle>
<Style TargetType="data:DataGridRow">
<Setter Property="Template" Value="{StaticResource rowTemplate}"/>
</Style>
</data:DataGrid.RowStyle>
With no luck.