I try to use this xaml, to apply an event to command binding:
<telerik:RadGridView x:Name="xRadGridView"
prismcommands:SelectionChangedCommand.Command="{Binding SelectPersonCommand}"
ItemsSource="{Binding GridItems, Mode=TwoWay}">
</telerik:RadGridView>
I get the error:
'SelectionChangedCommand.Command' property is read-only and cannot be
set from markup.
I can bind to prismcommands:RowEditEndedCommand.Command with no problem.
Is there any chance to bind to SelectionChangedCommand.Command?
I use the same PrismCommands in a Silverlight project and it works there.
namespace RadEventToCommand.WPF.PrismCommands
{
public class RowEditEndedCommandBehavior : CommandBehaviorBase<RadGridView>
{
public RowEditEndedCommandBehavior(RadGridView gridView)
: base(gridView)
{
gridView.RowEditEnded +=new EventHandler<GridViewRowEditEndedEventArgs>(gridView_RowEditEnded);
}
void gridView_RowEditEnded(object sender, GridViewRowEditEndedEventArgs e)
{
CommandParameter = e;
ExecuteCommand();
}
}
}
--
namespace RadEventToCommand.WPF.PrismCommands
{
public static class SelectionChangedCommand
{
private static readonly DependencyProperty SelectionChangedCommandBehaviorProperty
= DependencyProperty.RegisterAttached(
"SelectionChangedCommandBehavior",
typeof(SelectionChangedCommandBehavior),
typeof(SelectionChangedCommand),
null);
public static readonly DependencyProperty CommandProperty
= DependencyProperty.RegisterAttached(
"Command",
typeof(ICommand),
typeof(SelectionChangedCommand),
new PropertyMetadata(OnSetCommandCallback));
public static readonly DependencyProperty CommandParameterProperty
= DependencyProperty.RegisterAttached(
"CommandParameter",
typeof(object),
typeof(SelectionChangedCommand),
new PropertyMetadata(OnSetCommandParameterCallback));
public static ICommand GetCommand(RadGridView gridView)
{
return gridView.GetValue(CommandProperty) as ICommand;
}
public static void SetCommandParameter(RadGridView gridView, object parameter)
{
gridView.SetValue(CommandParameterProperty, parameter);
}
public static object GetCommandParameter(RadGridView gridView)
{
return gridView.GetValue(CommandParameterProperty);
}
private static void OnSetCommandCallback
(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
RadGridView gridView = dependencyObject as RadGridView;
if (gridView != null)
{
SelectionChangedCommandBehavior behavior = GetOrCreateBehavior(gridView);
behavior.Command = e.NewValue as ICommand;
}
}
private static void OnSetCommandParameterCallback
(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
RadGridView gridView = dependencyObject as RadGridView;
if (gridView != null)
{
SelectionChangedCommandBehavior behavior = GetOrCreateBehavior(gridView);
behavior.CommandParameter = e.NewValue;
}
}
private static SelectionChangedCommandBehavior GetOrCreateBehavior(RadGridView gridView)
{
SelectionChangedCommandBehavior behavior =
gridView.GetValue(SelectionChangedCommandBehaviorProperty) as SelectionChangedCommandBehavior;
if (behavior == null)
{
behavior = new SelectionChangedCommandBehavior(gridView);
gridView.SetValue(SelectionChangedCommandBehaviorProperty, behavior);
}
return behavior;
}
}
}
--
namespace RadEventToCommand.WPF.PrismCommands
{
public class RowEditEndedCommandBehavior : CommandBehaviorBase<RadGridView>
{
public RowEditEndedCommandBehavior(RadGridView gridView)
: base(gridView)
{
gridView.RowEditEnded +=new EventHandler<GridViewRowEditEndedEventArgs>(gridView_RowEditEnded);
}
void gridView_RowEditEnded(object sender, GridViewRowEditEndedEventArgs e)
{
CommandParameter = e;
ExecuteCommand();
}
}
}
--
namespace RadEventToCommand.WPF.PrismCommands
{
public static class RowEditEndedCommand
{
private static DependencyProperty RowEditEndedCommandBehaviorProperty
= DependencyProperty.RegisterAttached(
"RowEditEndedCommandBehavior",
typeof(RowEditEndedCommandBehavior),
typeof(RowEditEndedCommand),
null);
public static DependencyProperty CommandProperty
= DependencyProperty.RegisterAttached(
"Command",
typeof(ICommand),
typeof(RowEditEndedCommand),
new PropertyMetadata(OnSetCommandCallback));
public static DependencyProperty CommandParameterProperty
= DependencyProperty.RegisterAttached(
"CommandParameter",
typeof(object),
typeof(RowEditEndedCommand),
new PropertyMetadata(OnSetCommandParameterCallback));
public static ICommand GetCommand(RadGridView gridView)
{
return gridView.GetValue(CommandProperty) as ICommand;
}
public static void SetCommand(RadGridView gridView, object parameter)
{
gridView.SetValue(CommandProperty, parameter);
}
public static void SetCommandParameter(RadGridView gridView, object parameter)
{
gridView.SetValue(CommandParameterProperty, parameter);
}
public static object GetCommandParameter(RadGridView gridView)
{
return gridView.GetValue(CommandParameterProperty);
}
private static void OnSetCommandCallback
(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
RadGridView gridView = dependencyObject as RadGridView;
if (gridView != null)
{
RowEditEndedCommandBehavior behavior = GetOrCreateBehavior(gridView);
behavior.Command = e.NewValue as ICommand;
}
}
private static void OnSetCommandParameterCallback
(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
RadGridView gridView = dependencyObject as RadGridView;
if (gridView != null)
{
RowEditEndedCommandBehavior behavior = GetOrCreateBehavior(gridView);
behavior.CommandParameter = e.NewValue;
}
}
private static RowEditEndedCommandBehavior GetOrCreateBehavior(RadGridView gridView)
{
RowEditEndedCommandBehavior behavior =
gridView.GetValue(RowEditEndedCommandBehaviorProperty) as RowEditEndedCommandBehavior;
if (behavior == null)
{
behavior = new RowEditEndedCommandBehavior(gridView);
gridView.SetValue(RowEditEndedCommandBehaviorProperty, behavior);
}
return behavior;
}
}
}
I had the source for the behavior copied over from a Silverlight project. It worked there. For some reason in WPF I need the additional method in SelectionChangedCommand
public static void SetCommand(RadGridView gridView, object parameter)
{
gridView.SetValue(CommandProperty, parameter);
}
I copied the code over to check if I could use a common codebase for Silverlight and WPF.
For the RadGridView, we are using the Interaction Triggers. The below code works for us.
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding SelectedItemChangedCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
I have usercontrol, and there is a DependencyProperty defined in it.
#region ImageUri
public static readonly DependencyProperty ImageUriProperty = DependencyProperty.Register(
"ImageUri",
typeof(string),
typeof(ScrollableCanvas),
new PropertyMetadata(new PropertyChangedCallback(ImageUriPropertyChangedCallback)));
private static void ImageUriPropertyChangedCallback(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
ScrollableCanvas main = sender as ScrollableCanvas;
if (main != null)
{
main.ImageUri = (string)e.NewValue;
}
}
public string ImageUri
{
get
{
return (string)GetValue(ImageUriProperty);
}
set
{
SetValue(ImageUriProperty, value);
UpdateImage();
}
}
#endregion
In the Xaml, I bind a value to it like this
<my:ScrollableCanvas Name="scrollableCanvas1" ImageUri="{Binding Path=LayerImage}" />
when I update the LayerImage in the viewmodel, the ImageUri property does not update.
Can some help on this? Thanks.
BTW: The value is updated when I set the LayerImage in the constructor of the viewmodel.
You shouldn't include your UpdateImage call in your setter, but rather in the property changed callback.
public static readonly DependencyProperty ImageUriProperty = DependencyProperty.Register(
"ImageUri",
typeof(string),
typeof(ScrollableCanvas),
new PropertyMetadata(new PropertyChangedCallback(ImageUriPropertyChangedCallback)));
private static void ImageUriPropertyChangedCallback(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
ScrollableCanvas main = sender as ScrollableCanvas;
if (main != null)
{
// Since ImageUri has already been called at this point, you can just update your image here...
main.UpdateImage();
}
}
public string ImageUri
{
get
{
return (string)GetValue(ImageUriProperty);
}
set
{
SetValue(ImageUriProperty, value);
}
}
How do you implement a Silverlight 4 command to execute when the user control loads instead of being mapped to an explicit button click?
Or simply add a trigger in xaml for your UserControl:
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<si:InvokeDataCommand Command="{Binding MyCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
Create a DependencyProperty of type ICommand:-
#region public ICommand LoadedCommand
public ICommand LoadedCommand
{
get { return GetValue(LoadedCommandProperty) as ICommand; }
set { SetValue(LoadedCommandProperty, value); }
}
public static readonly DependencyProperty LoadedCommandProperty =
DependencyProperty.Register(
"LoadedCommand",
typeof(ICommand),
typeof(MainPage),
new PropertyMetadata(null));
#endregion public ICommand LoadedCommand
Also add a something to act as the command parameter:-
#region public object LoadedCommandParameter
public object LoadedCommandParameter
{
get { return GetValue(LoadedCommandParameterProperty) as object; }
set { SetValue(LoadedCommandParameterProperty, value); }
}
public static readonly DependencyProperty LoadedCommandParameterProperty =
DependencyProperty.Register(
"LoadedCommandParameter",
typeof(object),
typeof(MainPage),
new PropertyMetadata(null));
#endregion public object LoadedCommandParameter
Now set up its execution like this:-
public UserControl1()
{
InitializeComponent();
Loaded += UserControl1_Loaded;
}
void UserControl1_Loaded(object sender, RoutedEventArgs e)
{
if (LoadedCommand != null && LoadedCommand.CanExecute(LoadedCommandParameter))
{
LoadedCommand.Execute(LoadedCommandParameter);
}
}
Now if your ViewModel (has a command called StartStuff) then:-
<UserControl1 LoadedCommand="{Binding StartStuff}" .... >
Thats a load of code. The concise version without the code behind
public class LoadedBehaviour
{
public static ICommand GetLoadedCommand(DependencyObject dependencyObject)
{
return (ICommand)dependencyObject.GetValue(LoadedCommandProperty);
}
public static void SetLoadedCommand(DependencyObject dependencyObject, ICommand value)
{
dependencyObject.SetValue(LoadedCommandProperty, value);
}
public static Action GetLoadedCommandExecutor(DependencyObject dependencyObject)
{
return (Action)dependencyObject.GetValue(LoadedCommandExecutorProperty);
}
public static void SetLoadedCommandExecutor(DependencyObject dependencyObject, Action value)
{
dependencyObject.SetValue(LoadedCommandExecutorProperty, value);
}
public static readonly DependencyProperty LoadedCommandProperty = DependencyProperty.Register("LoadedCommand", typeof(ICommand), typeof(FrameworkElement), new PropertyMetadata(OnPropertyChanged));
public static readonly DependencyProperty LoadedCommandExecutorProperty = DependencyProperty.Register("LoadedCommandExecutor", typeof(Action), typeof(FrameworkElement), new PropertyMetadata(OnPropertyChanged));
private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (!(d is FrameworkElement))
{
throw new ArgumentException("Loaded command can only be used on FrameworkElements");
var executor = GetLoadedCommandExecutor(d);
if(executor == null)
{
executor = () =>
{
var command = GetLoadedCommand(d);
command.Execute(e);
};
SetLoadedCommandExecutor(d, executor);
((FrameworkElement)d).Loaded += (obj, args) => executor();
}
}
}
I'm trying to overcome a limitation that doesn't allow me to bind to regular clr properties.
The solution I use uses custom dependency properties that in turn changes clr properties.
Here is the code
class BindableTextBox : TextBox
{
public static readonly DependencyProperty BoundSelectionStartProperty = DependencyProperty.Register("BoundSelctionStart", typeof(int), typeof(BindableTextBox),
new PropertyMetadata(new PropertyChangedCallback(BindableTextBox.onBoundSelectionStartChanged)));
private static void onBoundSelectionStartChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((TextBox)d).SelectionStart = (int)e.NewValue;
}
private static readonly DependencyProperty BoundSelectionLenghtProperty = DependencyProperty.Register("BoundSelectionLenght", typeof(int), typeof(BindableTextBox),
new PropertyMetadata(new PropertyChangedCallback(BindableTextBox.onBoundSelectionLenghtChanged)));
private static void onBoundSelectionLenghtChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((TextBox)d).SelectionLength = (int)e.NewValue;
}
public int BoundSelectionStart
{
get { return (int)GetValue(BoundSelectionStartProperty); }
set { SetValue(BoundSelectionStartProperty, value); }
}
public int BoundSelectionLenght
{
get { return (int)GetValue(BoundSelectionLenghtProperty); }
set { SetValue(BoundSelectionLenghtProperty, value); }
}
}
But when I try to bound something to BoundSelectionStart it says it says that I can only bind to DP.
<bindable:BindableTextBox Text="{Binding Name}" BoundSelectionStart="{Binding ElementName=slider1, Path=Value}" />
What is the problem?
You have a typo in the line:
public static readonly DependencyProperty BoundSelectionStartProperty = DependencyProperty.Register(...)
The first parameter should be "BoundSelectionStart" (2x e in Selection), not "BoundSelctionStart".
I'm trying to bind to a Readonly property with OneWayToSource as mode, but it seems this cannot be done in XAML:
<controls:FlagThingy IsModified="{Binding FlagIsModified,
ElementName=container,
Mode=OneWayToSource}" />
I get:
The property 'FlagThingy.IsModified' cannot be set because it does not have an accessible set accessor.
IsModified is a readonly DependencyProperty on FlagThingy. I want to bind that value to the FlagIsModified property on the container.
To be clear:
FlagThingy.IsModified --> container.FlagIsModified
------ READONLY ----- ----- READWRITE --------
Is this possible using just XAML?
Update: Well, I fixed this case by setting the binding on the container and not on the FlagThingy. But I'd still like to know if this is possible.
Some research results for OneWayToSource...
Option # 1.
// Control definition
public partial class FlagThingy : UserControl
{
public static readonly DependencyProperty IsModifiedProperty =
DependencyProperty.Register("IsModified", typeof(bool), typeof(FlagThingy), new PropertyMetadata());
}
<controls:FlagThingy x:Name="_flagThingy" />
// Binding Code
Binding binding = new Binding();
binding.Path = new PropertyPath("FlagIsModified");
binding.ElementName = "container";
binding.Mode = BindingMode.OneWayToSource;
_flagThingy.SetBinding(FlagThingy.IsModifiedProperty, binding);
Option # 2
// Control definition
public partial class FlagThingy : UserControl
{
public static readonly DependencyProperty IsModifiedProperty =
DependencyProperty.Register("IsModified", typeof(bool), typeof(FlagThingy), new PropertyMetadata());
public bool IsModified
{
get { return (bool)GetValue(IsModifiedProperty); }
set { throw new Exception("An attempt ot modify Read-Only property"); }
}
}
<controls:FlagThingy IsModified="{Binding Path=FlagIsModified,
ElementName=container, Mode=OneWayToSource}" />
Option # 3 (True read-only dependency property)
System.ArgumentException: 'IsModified' property cannot be data-bound.
// Control definition
public partial class FlagThingy : UserControl
{
private static readonly DependencyPropertyKey IsModifiedKey =
DependencyProperty.RegisterReadOnly("IsModified", typeof(bool), typeof(FlagThingy), new PropertyMetadata());
public static readonly DependencyProperty IsModifiedProperty =
IsModifiedKey.DependencyProperty;
}
<controls:FlagThingy x:Name="_flagThingy" />
// Binding Code
Same binding code...
Reflector gives the answer:
internal static BindingExpression CreateBindingExpression(DependencyObject d, DependencyProperty dp, Binding binding, BindingExpressionBase parent)
{
FrameworkPropertyMetadata fwMetaData = dp.GetMetadata(d.DependencyObjectType) as FrameworkPropertyMetadata;
if (((fwMetaData != null) && !fwMetaData.IsDataBindingAllowed) || dp.ReadOnly)
{
throw new ArgumentException(System.Windows.SR.Get(System.Windows.SRID.PropertyNotBindable, new object[] { dp.Name }), "dp");
}
....
This is a limitation of WPF and it is by design. It is reported on Connect here:
OneWayToSource binding from a readonly dependency property
I made a solution to dynamically be able to push read-only dependency properties to the source called PushBinding which I blogged about here. The example below does OneWayToSource Bindings from the read-only DP's ActualWidth and ActualHeight to the Width and Height properties of the DataContext
<TextBlock Name="myTextBlock">
<pb:PushBindingManager.PushBindings>
<pb:PushBinding TargetProperty="ActualHeight" Path="Height"/>
<pb:PushBinding TargetProperty="ActualWidth" Path="Width"/>
</pb:PushBindingManager.PushBindings>
</TextBlock>
PushBinding works by using two Dependency Properties, Listener and Mirror. Listener is bound OneWay to the TargetProperty and in the PropertyChangedCallback it updates the Mirror property which is bound OneWayToSource to whatever was specified in the Binding.
Demo Project can be Downloaded Here.
It contains source code and short sample usage.
Wrote this:
Usage:
<TextBox Text="{Binding Text}"
p:OneWayToSource.Bind="{p:Paths From={x:Static Validation.HasErrorProperty},
To=SomeDataContextProperty}" />
Code:
using System;
using System.Windows;
using System.Windows.Data;
using System.Windows.Markup;
public static class OneWayToSource
{
public static readonly DependencyProperty BindProperty = DependencyProperty.RegisterAttached(
"Bind",
typeof(ProxyBinding),
typeof(OneWayToSource),
new PropertyMetadata(default(Paths), OnBindChanged));
public static void SetBind(this UIElement element, ProxyBinding value)
{
element.SetValue(BindProperty, value);
}
[AttachedPropertyBrowsableForChildren(IncludeDescendants = false)]
[AttachedPropertyBrowsableForType(typeof(UIElement))]
public static ProxyBinding GetBind(this UIElement element)
{
return (ProxyBinding)element.GetValue(BindProperty);
}
private static void OnBindChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((ProxyBinding)e.OldValue)?.Dispose();
}
public class ProxyBinding : DependencyObject, IDisposable
{
private static readonly DependencyProperty SourceProxyProperty = DependencyProperty.Register(
"SourceProxy",
typeof(object),
typeof(ProxyBinding),
new PropertyMetadata(default(object), OnSourceProxyChanged));
private static readonly DependencyProperty TargetProxyProperty = DependencyProperty.Register(
"TargetProxy",
typeof(object),
typeof(ProxyBinding),
new PropertyMetadata(default(object)));
public ProxyBinding(DependencyObject source, DependencyProperty sourceProperty, string targetProperty)
{
var sourceBinding = new Binding
{
Path = new PropertyPath(sourceProperty),
Source = source,
Mode = BindingMode.OneWay,
};
BindingOperations.SetBinding(this, SourceProxyProperty, sourceBinding);
var targetBinding = new Binding()
{
Path = new PropertyPath($"{nameof(FrameworkElement.DataContext)}.{targetProperty}"),
Mode = BindingMode.OneWayToSource,
Source = source
};
BindingOperations.SetBinding(this, TargetProxyProperty, targetBinding);
}
public void Dispose()
{
BindingOperations.ClearAllBindings(this);
}
private static void OnSourceProxyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
d.SetCurrentValue(TargetProxyProperty, e.NewValue);
}
}
}
[MarkupExtensionReturnType(typeof(OneWayToSource.ProxyBinding))]
public class Paths : MarkupExtension
{
public DependencyProperty From { get; set; }
public string To { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
var provideValueTarget = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
var targetObject = (UIElement)provideValueTarget.TargetObject;
return new OneWayToSource.ProxyBinding(targetObject, this.From, this.To);
}
}
Have not tested it in styles and templates yet, guess it needs special casing.
Here is another implementation for binding to Validation.HasError
public static class OneWayToSource
{
public static readonly DependencyProperty BindingsProperty = DependencyProperty.RegisterAttached(
"Bindings",
typeof(OneWayToSourceBindings),
typeof(OneWayToSource),
new PropertyMetadata(default(OneWayToSourceBindings), OnBinidngsChanged));
public static void SetBindings(this FrameworkElement element, OneWayToSourceBindings value)
{
element.SetValue(BindingsProperty, value);
}
[AttachedPropertyBrowsableForChildren(IncludeDescendants = false)]
[AttachedPropertyBrowsableForType(typeof(FrameworkElement))]
public static OneWayToSourceBindings GetBindings(this FrameworkElement element)
{
return (OneWayToSourceBindings)element.GetValue(BindingsProperty);
}
private static void OnBinidngsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((OneWayToSourceBindings)e.OldValue)?.ClearValue(OneWayToSourceBindings.ElementProperty);
((OneWayToSourceBindings)e.NewValue)?.SetValue(OneWayToSourceBindings.ElementProperty, d);
}
}
public class OneWayToSourceBindings : FrameworkElement
{
private static readonly PropertyPath DataContextPath = new PropertyPath(nameof(DataContext));
private static readonly PropertyPath HasErrorPath = new PropertyPath($"({typeof(Validation).Name}.{Validation.HasErrorProperty.Name})");
public static readonly DependencyProperty HasErrorProperty = DependencyProperty.Register(
nameof(HasError),
typeof(bool),
typeof(OneWayToSourceBindings),
new FrameworkPropertyMetadata(default(bool), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
internal static readonly DependencyProperty ElementProperty = DependencyProperty.Register(
"Element",
typeof(UIElement),
typeof(OneWayToSourceBindings),
new PropertyMetadata(default(UIElement), OnElementChanged));
private static readonly DependencyProperty HasErrorProxyProperty = DependencyProperty.RegisterAttached(
"HasErrorProxy",
typeof(bool),
typeof(OneWayToSourceBindings),
new PropertyMetadata(default(bool), OnHasErrorProxyChanged));
public bool HasError
{
get { return (bool)this.GetValue(HasErrorProperty); }
set { this.SetValue(HasErrorProperty, value); }
}
private static void OnHasErrorProxyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
d.SetCurrentValue(HasErrorProperty, e.NewValue);
}
private static void OnElementChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue == null)
{
BindingOperations.ClearBinding(d, DataContextProperty);
BindingOperations.ClearBinding(d, HasErrorProxyProperty);
}
else
{
var dataContextBinding = new Binding
{
Path = DataContextPath,
Mode = BindingMode.OneWay,
Source = e.NewValue
};
BindingOperations.SetBinding(d, DataContextProperty, dataContextBinding);
var hasErrorBinding = new Binding
{
Path = HasErrorPath,
Mode = BindingMode.OneWay,
Source = e.NewValue
};
BindingOperations.SetBinding(d, HasErrorProxyProperty, hasErrorBinding);
}
}
}
Usage in xaml
<StackPanel>
<TextBox Text="{Binding Value, UpdateSourceTrigger=PropertyChanged}">
<local:OneWayToSource.Bindings>
<local:OneWayToSourceBindings HasError="{Binding HasError}" />
</local:OneWayToSource.Bindings>
</TextBox>
<CheckBox IsChecked="{Binding HasError, Mode=OneWay}" />
</StackPanel>
This implementation is specific to binding Validation.HasError
Here's another attached property solution based on SizeObserver detailed here Pushing read-only GUI properties back into ViewModel
public static class MouseObserver
{
public static readonly DependencyProperty ObserveProperty = DependencyProperty.RegisterAttached(
"Observe",
typeof(bool),
typeof(MouseObserver),
new FrameworkPropertyMetadata(OnObserveChanged));
public static readonly DependencyProperty ObservedMouseOverProperty = DependencyProperty.RegisterAttached(
"ObservedMouseOver",
typeof(bool),
typeof(MouseObserver));
public static bool GetObserve(FrameworkElement frameworkElement)
{
return (bool)frameworkElement.GetValue(ObserveProperty);
}
public static void SetObserve(FrameworkElement frameworkElement, bool observe)
{
frameworkElement.SetValue(ObserveProperty, observe);
}
public static bool GetObservedMouseOver(FrameworkElement frameworkElement)
{
return (bool)frameworkElement.GetValue(ObservedMouseOverProperty);
}
public static void SetObservedMouseOver(FrameworkElement frameworkElement, bool observedMouseOver)
{
frameworkElement.SetValue(ObservedMouseOverProperty, observedMouseOver);
}
private static void OnObserveChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
var frameworkElement = (FrameworkElement)dependencyObject;
if ((bool)e.NewValue)
{
frameworkElement.MouseEnter += OnFrameworkElementMouseOverChanged;
frameworkElement.MouseLeave += OnFrameworkElementMouseOverChanged;
UpdateObservedMouseOverForFrameworkElement(frameworkElement);
}
else
{
frameworkElement.MouseEnter -= OnFrameworkElementMouseOverChanged;
frameworkElement.MouseLeave -= OnFrameworkElementMouseOverChanged;
}
}
private static void OnFrameworkElementMouseOverChanged(object sender, MouseEventArgs e)
{
UpdateObservedMouseOverForFrameworkElement((FrameworkElement)sender);
}
private static void UpdateObservedMouseOverForFrameworkElement(FrameworkElement frameworkElement)
{
frameworkElement.SetCurrentValue(ObservedMouseOverProperty, frameworkElement.IsMouseOver);
}
}
Declare attached property in control
<ListView ItemsSource="{Binding SomeGridItems}"
ut:MouseObserver.Observe="True"
ut:MouseObserver.ObservedMouseOver="{Binding IsMouseOverGrid, Mode=OneWayToSource}">
WPF will not use the CLR property setter, but seems it does some odd validation based on it.
May be in your situation this can be ok:
public bool IsModified
{
get { return (bool)GetValue(IsModifiedProperty); }
set { throw new Exception("An attempt ot modify Read-Only property"); }
}
Hmmm... I'm not sure I agree with any of these solutions. How about specifying a coercion callback in your property registration that ignores external change? For instance, I needed to implement a read-only Position dependency property to get the position of a MediaElement control inside a user control. Here's how I did it:
public static readonly DependencyProperty PositionProperty = DependencyProperty.Register("Position", typeof(double), typeof(MediaViewer),
new FrameworkPropertyMetadata(0d, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault | FrameworkPropertyMetadataOptions.Journal, OnPositionChanged, OnPositionCoerce));
private static void OnPositionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var ctrl = d as MediaViewer;
}
private static object OnPositionCoerce(DependencyObject d, object value)
{
var ctrl = d as MediaViewer;
var position = ctrl.MediaRenderer.Position.TotalSeconds;
if (ctrl.MediaRenderer.NaturalDuration.HasTimeSpan == false)
return 0d;
else
return Math.Min(position, ctrl.Duration);
}
public double Position
{
get { return (double)GetValue(PositionProperty); }
set { SetValue(PositionProperty, value); }
}
In other words, simply ignore the change and return the value backed by a different member that does not have a public modifier. -- In the above example, MediaRenderer is actually the private MediaElement control.
The way I worked around this limitation was to expose only a Binding property in my class, keeping the DependencyProperty private altogether. I implemented a "PropertyBindingToSource" write-only property (this one not a DependencyProperty) which can be set to a binding value in the xaml. In the setter for this write-only property I call to BindingOperations.SetBinding to link the binding to the DependencyProperty.
For the OP's specific example, it would look like this:
The FlatThingy implementation:
public partial class FlatThingy : UserControl
{
public FlatThingy()
{
InitializeComponent();
}
public Binding IsModifiedBindingToSource
{
set
{
if (value?.Mode != BindingMode.OneWayToSource)
{
throw new InvalidOperationException("IsModifiedBindingToSource must be set to a OneWayToSource binding");
}
BindingOperations.SetBinding(this, IsModifiedProperty, value);
}
}
public bool IsModified
{
get { return (bool)GetValue(IsModifiedProperty); }
private set { SetValue(IsModifiedProperty, value); }
}
private static readonly DependencyProperty IsModifiedProperty =
DependencyProperty.Register("IsModified", typeof(bool), typeof(FlatThingy), new PropertyMetadata(false));
private void Button_Click(object sender, RoutedEventArgs e)
{
IsModified = !IsModified;
}
}
Notice that the static readonly DependencyProperty object is private. In the control I added a button whose click is handled by Button_Click.
The use of the FlatThingy control in my window.xaml:
<Window x:Class="ReadOnlyBinding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:ReadOnlyBinding"
mc:Ignorable="d"
DataContext="{x:Static local:ViewModel.Instance}"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Text="{Binding FlagIsModified}" Grid.Row="0" />
<local:FlatThingy IsModifiedBindingToSource="{Binding FlagIsModified, Mode=OneWayToSource}" Grid.Row="1" />
</Grid>
Note that I've also implemented a ViewModel for binding to that is not shown here. It exposes a DependencyProperty named "FlagIsModified" as you can glean from the source above.
It works great, allowing me to push information back into the ViewModel from the View in a loosely coupled manner, with the direction of that information flow explicitly defined.
You're doing the binding in the wrong direction right now. OneWayToSource will try and update FlagIsModified on container whenever IsModified changes on the control you are creating. You want the opposite, which is to have IsModified bind to container.FlagIsModified. For that you should use the binding mode OneWay
<controls:FlagThingy IsModified="{Binding FlagIsModified,
ElementName=container,
Mode=OneWay}" />
Full list of enumeration members: http://msdn.microsoft.com/en-us/library/system.windows.data.bindingmode.aspx