I want to write a ViewModel that always knows the current state of some read-only dependency properties from the View.
Specifically, my GUI contains a FlowDocumentPageViewer, which displays one page at a time from a FlowDocument. FlowDocumentPageViewer exposes two read-only dependency properties called CanGoToPreviousPage and CanGoToNextPage. I want my ViewModel to always know the values of these two View properties.
I figured I could do this with a OneWayToSource databinding:
<FlowDocumentPageViewer
CanGoToNextPage="{Binding NextPageAvailable, Mode=OneWayToSource}" ...>
If this was allowed, it would be perfect: whenever the FlowDocumentPageViewer's CanGoToNextPage property changed, the new value would get pushed down into the ViewModel's NextPageAvailable property, which is exactly what I want.
Unfortunately, this doesn't compile: I get an error saying 'CanGoToPreviousPage' property is read-only and cannot be set from markup. Apparently read-only properties don't support any kind of databinding, not even databinding that's read-only with respect to that property.
I could make my ViewModel's properties be DependencyProperties, and make a OneWay binding going the other way, but I'm not crazy about the separation-of-concerns violation (ViewModel would need a reference to the View, which MVVM databinding is supposed to avoid).
FlowDocumentPageViewer doesn't expose a CanGoToNextPageChanged event, and I don't know of any good way to get change notifications from a DependencyProperty, short of creating another DependencyProperty to bind it to, which seems like overkill here.
How can I keep my ViewModel informed of changes to the view's read-only properties?
Yes, I've done this in the past with the ActualWidth and ActualHeight properties, both of which are read-only. I created an attached behavior that has ObservedWidth and ObservedHeight attached properties. It also has an Observe property that is used to do the initial hook-up. Usage looks like this:
<UserControl ...
SizeObserver.Observe="True"
SizeObserver.ObservedWidth="{Binding Width, Mode=OneWayToSource}"
SizeObserver.ObservedHeight="{Binding Height, Mode=OneWayToSource}"
So the view model has Width and Height properties that are always in sync with the ObservedWidth and ObservedHeight attached properties. The Observe property simply attaches to the SizeChanged event of the FrameworkElement. In the handle, it updates its ObservedWidth and ObservedHeight properties. Ergo, the Width and Height of the view model is always in sync with the ActualWidth and ActualHeight of the UserControl.
Perhaps not the perfect solution (I agree - read-only DPs should support OneWayToSource bindings), but it works and it upholds the MVVM pattern. Obviously, the ObservedWidth and ObservedHeight DPs are not read-only.
UPDATE: here's code that implements the functionality described above:
public static class SizeObserver
{
public static readonly DependencyProperty ObserveProperty = DependencyProperty.RegisterAttached(
"Observe",
typeof(bool),
typeof(SizeObserver),
new FrameworkPropertyMetadata(OnObserveChanged));
public static readonly DependencyProperty ObservedWidthProperty = DependencyProperty.RegisterAttached(
"ObservedWidth",
typeof(double),
typeof(SizeObserver));
public static readonly DependencyProperty ObservedHeightProperty = DependencyProperty.RegisterAttached(
"ObservedHeight",
typeof(double),
typeof(SizeObserver));
public static bool GetObserve(FrameworkElement frameworkElement)
{
frameworkElement.AssertNotNull("frameworkElement");
return (bool)frameworkElement.GetValue(ObserveProperty);
}
public static void SetObserve(FrameworkElement frameworkElement, bool observe)
{
frameworkElement.AssertNotNull("frameworkElement");
frameworkElement.SetValue(ObserveProperty, observe);
}
public static double GetObservedWidth(FrameworkElement frameworkElement)
{
frameworkElement.AssertNotNull("frameworkElement");
return (double)frameworkElement.GetValue(ObservedWidthProperty);
}
public static void SetObservedWidth(FrameworkElement frameworkElement, double observedWidth)
{
frameworkElement.AssertNotNull("frameworkElement");
frameworkElement.SetValue(ObservedWidthProperty, observedWidth);
}
public static double GetObservedHeight(FrameworkElement frameworkElement)
{
frameworkElement.AssertNotNull("frameworkElement");
return (double)frameworkElement.GetValue(ObservedHeightProperty);
}
public static void SetObservedHeight(FrameworkElement frameworkElement, double observedHeight)
{
frameworkElement.AssertNotNull("frameworkElement");
frameworkElement.SetValue(ObservedHeightProperty, observedHeight);
}
private static void OnObserveChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
var frameworkElement = (FrameworkElement)dependencyObject;
if ((bool)e.NewValue)
{
frameworkElement.SizeChanged += OnFrameworkElementSizeChanged;
UpdateObservedSizesForFrameworkElement(frameworkElement);
}
else
{
frameworkElement.SizeChanged -= OnFrameworkElementSizeChanged;
}
}
private static void OnFrameworkElementSizeChanged(object sender, SizeChangedEventArgs e)
{
UpdateObservedSizesForFrameworkElement((FrameworkElement)sender);
}
private static void UpdateObservedSizesForFrameworkElement(FrameworkElement frameworkElement)
{
// WPF 4.0 onwards
frameworkElement.SetCurrentValue(ObservedWidthProperty, frameworkElement.ActualWidth);
frameworkElement.SetCurrentValue(ObservedHeightProperty, frameworkElement.ActualHeight);
// WPF 3.5 and prior
////SetObservedWidth(frameworkElement, frameworkElement.ActualWidth);
////SetObservedHeight(frameworkElement, frameworkElement.ActualHeight);
}
}
I use a universal solution which works not only with ActualWidth and ActualHeight, but also with any data you can bind to at least in reading mode.
The markup looks like this, provided ViewportWidth and ViewportHeight are properties of the view model
<Canvas>
<u:DataPiping.DataPipes>
<u:DataPipeCollection>
<u:DataPipe Source="{Binding RelativeSource={RelativeSource AncestorType={x:Type Canvas}}, Path=ActualWidth}"
Target="{Binding Path=ViewportWidth, Mode=OneWayToSource}"/>
<u:DataPipe Source="{Binding RelativeSource={RelativeSource AncestorType={x:Type Canvas}}, Path=ActualHeight}"
Target="{Binding Path=ViewportHeight, Mode=OneWayToSource}"/>
</u:DataPipeCollection>
</u:DataPiping.DataPipes>
<Canvas>
Here is the source code for the custom elements
public class DataPiping
{
#region DataPipes (Attached DependencyProperty)
public static readonly DependencyProperty DataPipesProperty =
DependencyProperty.RegisterAttached("DataPipes",
typeof(DataPipeCollection),
typeof(DataPiping),
new UIPropertyMetadata(null));
public static void SetDataPipes(DependencyObject o, DataPipeCollection value)
{
o.SetValue(DataPipesProperty, value);
}
public static DataPipeCollection GetDataPipes(DependencyObject o)
{
return (DataPipeCollection)o.GetValue(DataPipesProperty);
}
#endregion
}
public class DataPipeCollection : FreezableCollection<DataPipe>
{
}
public class DataPipe : Freezable
{
#region Source (DependencyProperty)
public object Source
{
get { return (object)GetValue(SourceProperty); }
set { SetValue(SourceProperty, value); }
}
public static readonly DependencyProperty SourceProperty =
DependencyProperty.Register("Source", typeof(object), typeof(DataPipe),
new FrameworkPropertyMetadata(null, new PropertyChangedCallback(OnSourceChanged)));
private static void OnSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((DataPipe)d).OnSourceChanged(e);
}
protected virtual void OnSourceChanged(DependencyPropertyChangedEventArgs e)
{
Target = e.NewValue;
}
#endregion
#region Target (DependencyProperty)
public object Target
{
get { return (object)GetValue(TargetProperty); }
set { SetValue(TargetProperty, value); }
}
public static readonly DependencyProperty TargetProperty =
DependencyProperty.Register("Target", typeof(object), typeof(DataPipe),
new FrameworkPropertyMetadata(null));
#endregion
protected override Freezable CreateInstanceCore()
{
return new DataPipe();
}
}
If anyone else is interested, I coded up an approximation of Kent's solution here:
class SizeObserver
{
#region " Observe "
public static bool GetObserve(FrameworkElement elem)
{
return (bool)elem.GetValue(ObserveProperty);
}
public static void SetObserve(
FrameworkElement elem, bool value)
{
elem.SetValue(ObserveProperty, value);
}
public static readonly DependencyProperty ObserveProperty =
DependencyProperty.RegisterAttached("Observe", typeof(bool), typeof(SizeObserver),
new UIPropertyMetadata(false, OnObserveChanged));
static void OnObserveChanged(
DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
FrameworkElement elem = depObj as FrameworkElement;
if (elem == null)
return;
if (e.NewValue is bool == false)
return;
if ((bool)e.NewValue)
elem.SizeChanged += OnSizeChanged;
else
elem.SizeChanged -= OnSizeChanged;
}
static void OnSizeChanged(object sender, RoutedEventArgs e)
{
if (!Object.ReferenceEquals(sender, e.OriginalSource))
return;
FrameworkElement elem = e.OriginalSource as FrameworkElement;
if (elem != null)
{
SetObservedWidth(elem, elem.ActualWidth);
SetObservedHeight(elem, elem.ActualHeight);
}
}
#endregion
#region " ObservedWidth "
public static double GetObservedWidth(DependencyObject obj)
{
return (double)obj.GetValue(ObservedWidthProperty);
}
public static void SetObservedWidth(DependencyObject obj, double value)
{
obj.SetValue(ObservedWidthProperty, value);
}
// Using a DependencyProperty as the backing store for ObservedWidth. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ObservedWidthProperty =
DependencyProperty.RegisterAttached("ObservedWidth", typeof(double), typeof(SizeObserver), new UIPropertyMetadata(0.0));
#endregion
#region " ObservedHeight "
public static double GetObservedHeight(DependencyObject obj)
{
return (double)obj.GetValue(ObservedHeightProperty);
}
public static void SetObservedHeight(DependencyObject obj, double value)
{
obj.SetValue(ObservedHeightProperty, value);
}
// Using a DependencyProperty as the backing store for ObservedHeight. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ObservedHeightProperty =
DependencyProperty.RegisterAttached("ObservedHeight", typeof(double), typeof(SizeObserver), new UIPropertyMetadata(0.0));
#endregion
}
Feel free to use it in your apps. It works well. (Thanks Kent!)
Here is another solution to this "bug" which I blogged about here:
OneWayToSource Binding for ReadOnly Dependency Property
It 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. I call it PushBinding and it can be set on any read-only Dependency Property like this
<TextBlock Name="myTextBlock"
Background="LightBlue">
<pb:PushBindingManager.PushBindings>
<pb:PushBinding TargetProperty="ActualHeight" Path="Height"/>
<pb:PushBinding TargetProperty="ActualWidth" Path="Width"/>
</pb:PushBindingManager.PushBindings>
</TextBlock>
Download Demo Project Here.
It contains source code and short sample usage.
One last note, since .NET 4.0 we are even further away from built-in-support for this, since a OneWayToSource Binding reads the value back from the Source after it has updated it
I like Dmitry Tashkinov's solution!
However it crashed my VS in design mode. That's why I added a line to OnSourceChanged method:
private static void OnSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (!((bool)DesignerProperties.IsInDesignModeProperty.GetMetadata(typeof(DependencyObject)).DefaultValue))
((DataPipe)d).OnSourceChanged(e);
}
I think it can be done a bit simpler:
xaml:
behavior:ReadOnlyPropertyToModelBindingBehavior.ReadOnlyDependencyProperty="{Binding ActualWidth, RelativeSource={RelativeSource Self}}"
behavior:ReadOnlyPropertyToModelBindingBehavior.ModelProperty="{Binding MyViewModelProperty}"
cs:
public class ReadOnlyPropertyToModelBindingBehavior
{
public static readonly DependencyProperty ReadOnlyDependencyPropertyProperty = DependencyProperty.RegisterAttached(
"ReadOnlyDependencyProperty",
typeof(object),
typeof(ReadOnlyPropertyToModelBindingBehavior),
new PropertyMetadata(OnReadOnlyDependencyPropertyPropertyChanged));
public static void SetReadOnlyDependencyProperty(DependencyObject element, object value)
{
element.SetValue(ReadOnlyDependencyPropertyProperty, value);
}
public static object GetReadOnlyDependencyProperty(DependencyObject element)
{
return element.GetValue(ReadOnlyDependencyPropertyProperty);
}
private static void OnReadOnlyDependencyPropertyPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
SetModelProperty(obj, e.NewValue);
}
public static readonly DependencyProperty ModelPropertyProperty = DependencyProperty.RegisterAttached(
"ModelProperty",
typeof(object),
typeof(ReadOnlyPropertyToModelBindingBehavior),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public static void SetModelProperty(DependencyObject element, object value)
{
element.SetValue(ModelPropertyProperty, value);
}
public static object GetModelProperty(DependencyObject element)
{
return element.GetValue(ModelPropertyProperty);
}
}
I have created a custom TextBlock that changes the Visibility after some seconds specified by a DependencyProperty ShowTime:
<customUserControl:AutoHideTextBlock Text="AutoHideTextBlock" Visibility="{Binding IsVisibleEnabled, Converter={StaticResource BoolToVisConverter}}" ShowTime="3"/>
This is a nice solution and it works, the problem is that I have several other elements that needs the same behaviour and I cannot really make it a CustomUserControl for all of them, I have created the following class to help me with that:
public class AutoHideExtension
{
public static readonly DependencyProperty VisibilityListenerProperty =
DependencyProperty.RegisterAttached(
"VisibilityListener",
typeof(bool),
typeof(AutoHideExtension),
new PropertyMetadata(false, VisibilityChanged));
public static double GetVisibilityListener(DependencyObject obj)
=> (double)obj.GetValue(VisibilityListenerProperty);
public static void SetVisibilityListener(DependencyObject obj, double value)
=> obj.SetValue(VisibilityListenerProperty, value);
private static void VisibilityChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var element = (FrameworkElement)d;
if (element.Visibility == Visibility.Collapsed || !IsLoaded)
{
return;
}
DispatcherTimer timer = new DispatcherTimer(DispatcherPriority.Background)
{
Interval =
new TimeSpan(
0,
0,
ShowTime)
};
timer.Tick += (senderEvent, args) =>
{
element.Visibility = Visibility.Collapsed;
timer.Stop();
};
timer.Start();
}
}
The idea is that I can attach this new property to any element and change the visibility after the specified time, something like follows:
<TextBlock Text="TextToHide"
helpers:AutoHideExtension.VisibilityListener="{Binding ChangesSavedEnabled}"/>
The problem is that I don't know how to specify the ShowTime as property in the extension class, and that this is simply not working since is not changing the Visibility.
Any Ideas or suggestions on how I can continue forward with this?
Thanks in advance!
Your dependency property VisibilityListener should get and set a bool value:
public static bool GetVisibilityListener(DependencyObject obj)
=> (bool)obj.GetValue(VisibilityListenerProperty);
public static void SetVisibilityListener(DependencyObject obj, bool value)
=> obj.SetValue(VisibilityListenerProperty, value);
You could then define another attached property for the ShowTime, or you could define a Blend behaviour that contains two properties:
<TextBlock xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
Text="TextToHide">
<i:Interaction.Behaviors>
<local:AutoHideExtensionBehavior VisibilityListener="{Binding ChangesSavedEnabled}" ShowTime="3" />
</i:Interaction.Behaviors>
</TextBlock>
Is it possible to detect when a Xaml binding is set on a DependencyProperty in Silverlight?
E.g. if I had a custom user-control with single dependency property and a binding declared like this:
public class MyControl : UserControl
{
public static readonly DependencyProperty TestProperty =
DependencyProperty.Register("Test",
typeof(object), typeof(MyControl),
new PropertyMetadata(null));
public object Test
{
get { return GetValue(TestProperty); }
set { SetValue(TestProperty, value); }
}
}
<MyControl Test="{Binding APropInViewModel}>
</MyControl>
Can I in the MyControl code to something like this?
// Ctor
public MyControl()
{
TestProperty.BindingChanged += new EventHandler(...)
}
e.g. can I get a notification of binding?
NOTE:
This is to solve a tricky order of precedence problem, described here so just checking for new values in the DependencyPropertyChanged handler won't work - because the property changed handler doesn't fire!!
It is possible for value changes in this binding. You can detect changes using propertychanged callback method which is static for Dependency properties.
public static readonly DependencyProperty TestProperty =
DependencyProperty.Register("Test",
typeof(object), typeof(MyControl),
new PropertyMetadata(null, TestChangedCallbackHandler));
private static void TestChangedCallbackHandler(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
MyControl obj = sender as MyControl;
Test = args.NewValue;
}
However, this might cause following event listening cases. If you want to listen changes on this dependency property is explained in this link :
Listen DepencenyProperty value changes
public void RegisterForNotification(string propertyName, FrameworkElement element, PropertyChangedCallback callback)
{
Binding b = new Binding(propertyName) { Source = element };
var prop = System.Windows.DependencyProperty.RegisterAttached(
"ListenAttached" + propertyName,
typeof(object),
typeof(UserControl),
new System.Windows.PropertyMetadata(callback));
element.SetBinding(prop, b);
}
and call like this
this.RegisterForNotification("Test", this, TestChangedCallback);
I have created a very Simple attached Property:
public static class ToolBarEx
{
public static readonly DependencyProperty FocusedExProperty =
DependencyProperty.RegisterAttached(
"FocusedEx", typeof(bool?), typeof(FrameworkElement),
new FrameworkPropertyMetadata(false, FocusedExChanged));
private static void FocusedExChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is ToolBar)
{
if (e.NewValue is bool)
{
if ((bool)e.NewValue)
{
(d as ToolBar).Focus();
}
}
}
}
public static bool? GetFocusedEx(DependencyObject obj)
{
return (bool)obj.GetValue(FocusedExProperty);
}
public static void SetFocusedEx(DependencyObject obj, bool? value)
{
obj.SetValue(FocusedExProperty, value);
}
}
Setting this in Xaml works perfectly fine, but if I try setting it within a Style:
I receive an ArguemntNullException during the Runtime (saying: "Value cannot be null.
Parameter name: property").
I cannot figure what is wrong here. Any hint is appriciated!
A common mistake made when registering attached dependency properties is to incorrectly specify the ownerType argument. This must always be the registering class, ToolBarEx here:
public static readonly DependencyProperty FocusedExProperty =
DependencyProperty.RegisterAttached(
"FocusedEx", typeof(bool?), typeof(ToolBarEx),
new FrameworkPropertyMetadata(false, FocusedExChanged));
And just for avoiding unnecessary code in the property changed handler you could safely cast NewValue to bool:
private static void FocusedExChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var toolBar = d as ToolBar;
if (toolBar != null && (bool)e.NewValue)
{
toolBar.Focus();
}
}
I have next dependecy property:
public static DependencyProperty IsInReadModeProperty =
DependencyProperty.Register("IsInReadMode", typeof(bool),
typeof(RegCardSearchForm), new PropertyMetadata(false, ReadModeChanged));
and I have next method which handle Property changed event:
public static void ReadModeChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
if ((bool)e.NewValue)
{
btnSearch.Visibility = Visibility.Collapsed;
btnExport.Visibility = Visibility.Collapsed;
cbExportWay.Visibility = Visibility.Collapsed;
}
}
But compiler give me error that i cannot acces non static buttons (btnSearch and e.t.c ) in static context.
I want change visibility of buttons when property value changed. How can I resolve this situation?
Since (non-attached) DependencyProperties are restricted to being set on their owner type you can just create an instance method to hold your logic and call that from the static method by casting the DependencyObject:
public static readonly DependencyProperty IsInReadModeProperty = DependencyProperty.Register(
"IsInReadMode",
typeof(bool),
typeof(RegCardSearchForm),
new UIPropertyMetadata(false, ReadModeChanged));
private static void ReadModeChanged(DependencyObject dObj, DependencyPropertyChangedEventArgs e)
{
RegCardSearchForm form = dObj as RegCardSearchForm;
if (form != null)
form.ReadModeChanged((bool)e.OldValue, (bool)e.NewValue);
}
protected virtual void ReadModeChanged(bool oldValue, bool newValue)
{
// TODO: Add your instance logic.
}
One way could be to extend a class from DependencyObject that would contain the set/get of the controls that you want to manipulate. And process it in ReadModeChanged event by accessing DependencyObject.
This example might help.
... example derives from DependencyObject to create a new abstract class. The class then registers an attached property and includes support members for that attached property.
If ReadModeChanged is a static method of the container for your buttons, then just make it an instance method of the container.
These things do have to be static in order for a DependencyProperty to work properly However, the parameter to your PropertyChanged handler is probably the thing you need: it's the instance whose property has just changed. I suspect this would work for you:
public static void ReadModeChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
if ((bool)e.NewValue)
{
RegCardSearchForm c = (RegCardSearchForm)d;
c.btnSearch.Visibility = Visibility.Collapsed;
c.btnExport.Visibility = Visibility.Collapsed;
c.cbExportWay.Visibility = Visibility.Collapsed;
}
}