Binding to ActualWidth does not work - silverlight

In a Silverlight 3.0 application I'm attempting to create a rectangle in a canvas and have it stretch the whole width of the canvas. I have attempted to do this by binding to the ActualWidth property of a parent container (seem sample below), however while I don't see any binding errors the value is not being bound. The rectangle is not visible as its width is zero. In addition tried binding to the ActualWidth of the canvas that contains my rectangle but this made no difference.
I did find this bug logged on Microsoft Connect but there were no workarounds listed.
Has anyone been able to solve this issue or can they point to solution?
Edit: The original code sample was not accurate of what I'm trying to achieve, updated for more clarity.
<UserControl>
<Border BorderBrush="White"
BorderThickness="1"
CornerRadius="4"
HorizontalAlignment="Center">
<Grid x:Name="GridContainer">
<Rectangle Fill="Aqua"
Width="150"
Height="400" />
<Canvas>
<Rectangle Width="{Binding Path=ActualWidth, ElementName=GridContainer}"
Height="30"
Fill="Red" />
</Canvas>
<StackPanel>
<!-- other elements here -->
</StackPanel>
</Grid>
</Border>
</UserControl>

What are you trying to do that requires you to databind to the ActualWidth property?
This is a known issue with Silverlight, and there is no simple workaround.
One thing that could be done is to set up the visual tree in such a way that you do not need to actually set the Width of the Rectangle, and just allow it to stretch to the appropriate size. So in the example above, if you remove the Canvas (or change the Canvas to some other Panel) and leave the Rectangle's HorizontalAlignment set to Stretch, it will take up all of the available width (effectively the Width of the Grid).
However, this may not be possible in your particular case, and it may really be necessary to set up the databinding. It has already been established that this is not possible directly, but with the help of a proxy object, we can set up the required binding. Consider this code:
public class ActualSizePropertyProxy : FrameworkElement, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public FrameworkElement Element
{
get { return (FrameworkElement)GetValue(ElementProperty); }
set { SetValue(ElementProperty, value); }
}
public double ActualHeightValue
{
get{ return Element == null? 0: Element.ActualHeight; }
}
public double ActualWidthValue
{
get { return Element == null ? 0 : Element.ActualWidth; }
}
public static readonly DependencyProperty ElementProperty =
DependencyProperty.Register("Element", typeof(FrameworkElement), typeof(ActualSizePropertyProxy),
new PropertyMetadata(null,OnElementPropertyChanged));
private static void OnElementPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((ActualSizePropertyProxy)d).OnElementChanged(e);
}
private void OnElementChanged(DependencyPropertyChangedEventArgs e)
{
FrameworkElement oldElement = (FrameworkElement)e.OldValue;
FrameworkElement newElement = (FrameworkElement)e.NewValue;
newElement.SizeChanged += new SizeChangedEventHandler(Element_SizeChanged);
if (oldElement != null)
{
oldElement.SizeChanged -= new SizeChangedEventHandler(Element_SizeChanged);
}
NotifyPropChange();
}
private void Element_SizeChanged(object sender, SizeChangedEventArgs e)
{
NotifyPropChange();
}
private void NotifyPropChange()
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("ActualWidthValue"));
PropertyChanged(this, new PropertyChangedEventArgs("ActualHeightValue"));
}
}
}
We can use this in xaml as follows:
<Grid x:Name="LayoutRoot">
<Grid.Resources>
<c:ActualSizePropertyProxy Element="{Binding ElementName=LayoutRoot}" x:Name="proxy" />
</Grid.Resources>
<TextBlock x:Name="tb1" Text="{Binding ActualWidthValue, ElementName=proxy}" />
</Grid>
So we are Binding TextBlock.Text to the ActualWidthValue on the proxy object. The proxy object in turn provides the ActualWidth of the Element, which is provided by another Binding.
This is not a simple solution to the problem, but it is the best that I can think of for how to databind to ActualWidth.
If you explained your scenario a bit more, it may be possible to come up with a simpler solution. DataBinding may not be required at all; would it be possible to just set the property from code in a SizeChanged event handler?

Using the mechanism of attached properties, properties which represent ActualHeight and ActualWidth and are updated by SizeChanged event can be defined. Its usage will look like the following.
<Grid local:SizeChange.IsEnabled="True" x:Name="grid1">...</Grid>
<TextBlock Text="{Binding ElementName=grid1,
Path=(local:SizeChange.ActualHeight)}"/>
Technical details can be found at the following:
http://darutk-oboegaki.blogspot.com/2011/07/binding-actualheight-and-actualwidth.html
The advantage of this solution compared to others is in that the attached properties defined in the solution (SizeChange.ActualHeight and SizeChange.ActualWidth) can be used for any FrameworkElement without creating any sub class. This solution is reusable and less invasive.
In the event that the link becomes stale, here is the SizeChange Class as shown on the link:
// Declare SizeChange class as a sub class of DependencyObject
// because we need to register attached properties.
public class SizeChange : DependencyObject
{
#region Attached property "IsEnabled"
// The name of IsEnabled property.
public const string IsEnabledPropertyName = "IsEnabled";
// Register an attached property named "IsEnabled".
// Note that OnIsEnabledChanged method is called when
// the value of IsEnabled property is changed.
public static readonly DependencyProperty IsEnabledProperty
= DependencyProperty.RegisterAttached(
IsEnabledPropertyName,
typeof(bool),
typeof(SizeChange),
new PropertyMetadata(false, OnIsEnabledChanged));
// Getter of IsEnabled property. The name of this method
// should not be changed because the dependency system
// uses it.
public static bool GetIsEnabled(DependencyObject obj)
{
return (bool)obj.GetValue(IsEnabledProperty);
}
// Setter of IsEnabled property. The name of this method
// should not be changed because the dependency system
// uses it.
public static void SetIsEnabled(DependencyObject obj, bool value)
{
obj.SetValue(IsEnabledProperty, value);
}
#endregion
#region Attached property "ActualHeight"
// The name of ActualHeight property.
public const string ActualHeightPropertyName = "ActualHeight";
// Register an attached property named "ActualHeight".
// The value of this property is updated When SizeChanged
// event is raised.
public static readonly DependencyProperty ActualHeightProperty
= DependencyProperty.RegisterAttached(
ActualHeightPropertyName,
typeof(double),
typeof(SizeChange),
null);
// Getter of ActualHeight property. The name of this method
// should not be changed because the dependency system
// uses it.
public static double GetActualHeight(DependencyObject obj)
{
return (double)obj.GetValue(ActualHeightProperty);
}
// Setter of ActualHeight property. The name of this method
// should not be changed because the dependency system
// uses it.
public static void SetActualHeight(DependencyObject obj, double value)
{
obj.SetValue(ActualHeightProperty, value);
}
#endregion
#region Attached property "ActualWidth"
// The name of ActualWidth property.
public const string ActualWidthPropertyName = "ActualWidth";
// Register an attached property named "ActualWidth".
// The value of this property is updated When SizeChanged
// event is raised.
public static readonly DependencyProperty ActualWidthProperty
= DependencyProperty.RegisterAttached(
ActualWidthPropertyName,
typeof(double),
typeof(SizeChange),
null);
// Getter of ActualWidth property. The name of this method
// should not be changed because the dependency system
// uses it.
public static double GetActualWidth(DependencyObject obj)
{
return (double)obj.GetValue(ActualWidthProperty);
}
// Setter of ActualWidth property. The name of this method
// should not be changed because the dependency system
// uses it.
public static void SetActualWidth(DependencyObject obj, double value)
{
obj.SetValue(ActualWidthProperty, value);
}
#endregion
// This method is called when the value of IsEnabled property
// is changed. If the new value is true, an event handler is
// added to SizeChanged event of the target element.
private static void OnIsEnabledChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
// The given object must be a FrameworkElement instance,
// because we add an event handler to SizeChanged event
// of it.
var element = obj as FrameworkElement;
if (element == null)
{
// The given object is not an instance of FrameworkElement,
// meaning SizeChanged event is not available. So, nothing
// can be done for the object.
return;
}
// If IsEnabled=True
if (args.NewValue != null && (bool)args.NewValue == true)
{
// Attach to the element.
Attach(element);
}
else
{
// Detach from the element.
Detach(element);
}
}
private static void Attach(FrameworkElement element)
{
// Add an event handler to SizeChanged event of the element
// to take action when actual size of the element changes.
element.SizeChanged += HandleSizeChanged;
}
private static void Detach(FrameworkElement element)
{
// Remove the event handler from the element.
element.SizeChanged -= HandleSizeChanged;
}
// An event handler invoked when SizeChanged event is raised.
private static void HandleSizeChanged(object sender, SizeChangedEventArgs args)
{
var element = sender as FrameworkElement;
if (element == null)
{
return;
}
// Get the new actual height and width.
var width = args.NewSize.Width;
var height = args.NewSize.Height;
// Update values of SizeChange.ActualHeight and
// SizeChange.ActualWidth.
SetActualWidth(element, width);
SetActualHeight(element, height);
}
}

My solution is to declare my own DependencyProperty called RealWidth and update its value on the SizeChanged event. You can then bind to RealWidth, which will update, unlike the ActualWidth property.
public MyControl()
{
InitializeComponent();
SizeChanged += HandleSizeChanged;
}
public static DependencyProperty RealWidthProperty =
DependencyProperty.Register("RealWidth", typeof (double),
typeof (MyControl),
new PropertyMetadata(500D));
public double RealWidth
{
get { return (double) GetValue(RealWidthProperty); }
set { SetValue(RealWidthProperty, value); }
}
private void HandleSizeChanged(object sender, SizeChangedEventArgs e)
{
RealWidth = e.NewSize.Width;
}

Why not create a simple panel control that inherits from ContentPresenter and actually can provide the current size.
public class SizeNotifyPanel : ContentPresenter
{
public static DependencyProperty SizeProperty =
DependencyProperty.Register("Size",
typeof (Size),
typeof (SizeNotifyPanel),
null);
public Size Size
{
get { return (Size) GetValue(SizeProperty); }
set { SetValue(SizeProperty, value); }
}
public SizeNotifyPanel()
{
SizeChanged += (s, e) => Size = e.NewSize;
}
}
It should then be used as wrapper for the actual content.
<local:SizeNotifyPanel x:Name="Content">
<TextBlock Text="{Binding Size.Height, ElementName=Content}" />
</local:SizeNotifyPanel>
Worked for me like a charm and looks clean.

Based on #darutk's answer, here's an attached property-based solution which does the job very elegantly.
public static class SizeBindings
{
public static readonly DependencyProperty ActualHeightProperty =
DependencyProperty.RegisterAttached("ActualHeight", typeof (double), typeof (SizeBindings),
new PropertyMetadata(0.0));
public static readonly DependencyProperty ActualWidthProperty =
DependencyProperty.RegisterAttached("ActualWidth", typeof (Double), typeof (SizeBindings),
new PropertyMetadata(0.0));
public static readonly DependencyProperty IsEnabledProperty =
DependencyProperty.RegisterAttached("IsEnabled", typeof (bool), typeof (SizeBindings),
new PropertyMetadata(false, HandlePropertyChanged));
private static void HandlePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var element = d as FrameworkElement;
if (element == null)
{
return;
}
if ((bool) e.NewValue == false)
{
element.SizeChanged -= HandleSizeChanged;
}
else
{
element.SizeChanged += HandleSizeChanged;
}
}
private static void HandleSizeChanged(object sender, SizeChangedEventArgs e)
{
var element = sender as FrameworkElement;
SetActualHeight(element, e.NewSize.Height);
SetActualWidth(element, e.NewSize.Width);
}
public static bool GetIsEnabled(DependencyObject obj)
{
return (bool)obj.GetValue(IsEnabledProperty);
}
public static void SetIsEnabled(DependencyObject obj, bool value)
{
obj.SetValue(IsEnabledProperty, value);
}
public static Double GetActualWidth(DependencyObject obj)
{
return (Double) obj.GetValue(ActualWidthProperty);
}
public static void SetActualWidth(DependencyObject obj, Double value)
{
obj.SetValue(ActualWidthProperty, value);
}
public static double GetActualHeight(DependencyObject obj)
{
return (double)obj.GetValue(ActualHeightProperty);
}
public static void SetActualHeight(DependencyObject obj, double value)
{
obj.SetValue(ActualHeightProperty, value);
}
}
Use it like this:
<Grid>
<Border x:Name="Border" behaviors:SizeBindings.IsEnabled="True"/>
<Border MinWidth="{Binding (behaviors:SizeBindings.ActualWidth), ElementName=Border}"/>
</Grid>

I've tested the updated xaml that you publishing using a TestConverter to see what value gets passed to the width and it is working for me (I am using VS 2010 B2). To use the TestConverter just set a breakpoint in the Convert method.
public class TestConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value;
}
}
A value of 150 was passed in and the Rectangle had a width of 150.
Were you expecting something different?

This is an as an aside answer which may help someone for binding to the ActualWidth.
My process didn't need a change event, it needed an end result of a value in its current state. So I created a dependency property called Target on my custom control/process as a FrameworkElement and the consumer xaml would bind to the actual object in question.
When it was time for the calculation the code could pulled the actual object and extracted it's ActualWidth from it.
Dependency Property on Control
public FrameworkElement Target
{
get { return (FrameworkElement)GetValue(TargetProperty);}
set { SetValue(TargetProperty, value);}
}
// Using a DependencyProperty as the backing store for Target.
// This enables animation, styling, binding, general access etc...
public static readonly DependencyProperty TargetProperty =
DependencyProperty.Register("Target", typeof(FrameworkElement),
typeof(ThicknessWrapper),
new PropertyMetadata(null, OnTargetChanged));
XAML on Consumer side showing a binding to a Rectangle
<local:ThicknessWrapper Target="{Binding ElementName=thePanel}"/>
<Rectangle x:Name="thePanel" HorizontalAlignment="Stretch" Height="20" Fill="Blue"/>
Code to Acquire
double width;
if (Target != null)
width = Target.ActualWidth; // Gets the current value.

Based on KeithMahoney's answer, it works fine on my UWP App and solves my problem. However, I cannot see my control in Design time because both the initial values of ActualWidthValue and ActualHeightValue are not provided in Design time. Although it works fine in running time, it is inconvenient for designing the layout of my control. With a little modification, this problem can be solved.
In his c# code for both properties ActualWidthValue and ActualHeightValue, add
set {;}
to let us can provide dummy values from XAML code. Although it is no use for running time, it can be used for design time.
In the declaration of Resources of his XAML code, provide c:ActualSizePropertyProxy suitable values for ActualWidthValue and ActualHeightValue such as
ActualHeightValue="800" ActualWidthValue="400"
Then it will show you a 400x800 control in design time.

Related

WPF: Why is my markup extension binding not working?

Working Example with "Binding":
I have a UserControl which I use like this in my MainWindow:
<userControls:NoMarkupControl/>
The ViewModel of my MainWindow contains this property:
private string _exampleText = "example";
public string ExampleText
{
get { return _exampleText; }
set
{
_exampleText = value;
OnPropertyChanged();
}
}
inside the UserControl I bind my ViewModel to this property:
<TextBlock Text="{Binding ExampleText}"/>
as a result "example" gets displayed when I start the app. Everything works.
Not working example with Custom Markup Extension:
Now I have a MarkupExtension:
public class ExampleTextExtension : MarkupExtension
{
private static readonly List<DependencyProperty> StorageProperties = new List<DependencyProperty>();
private readonly object _parameter;
private DependencyProperty _dependencyProperty;
public ExampleTextExtension(object parameter)
{
_parameter = parameter;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
var target = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
DependencyObject targetObject;
if (target?.TargetObject is DependencyObject dependencyObject &&
target.TargetProperty is DependencyProperty)
{
targetObject = dependencyObject;
}
else
{
return this;
}
_dependencyProperty = SetUnusedStorageProperty(targetObject, _parameter);
return GetLocalizedText((string)targetObject.GetValue(_dependencyProperty));
}
private static string GetLocalizedText(string text)
{
return text == null ? null : $"markup: {text}";
}
private static DependencyProperty SetUnusedStorageProperty(DependencyObject obj, object value)
{
var property = StorageProperties.FirstOrDefault(p => obj.ReadLocalValue(p) == DependencyProperty.UnsetValue);
if (property == null)
{
property = DependencyProperty.RegisterAttached("Storage" + StorageProperties.Count, typeof(object), typeof(ExampleTextExtension), new PropertyMetadata());
StorageProperties.Add(property);
}
if (value is MarkupExtension markupExtension)
{
var resolvedValue = markupExtension.ProvideValue(new ServiceProvider(obj, property));
obj.SetValue(property, resolvedValue);
}
else
{
obj.SetValue(property, value);
}
return property;
}
private class ServiceProvider : IServiceProvider, IProvideValueTarget
{
public object TargetObject { get; }
public object TargetProperty { get; }
public ServiceProvider(object targetObject, object targetProperty)
{
TargetObject = targetObject;
TargetProperty = targetProperty;
}
public object GetService(Type serviceType)
{
return serviceType.IsInstanceOfType(this) ? this : null;
}
}
}
Again I have a UserControl which I use like this in my MainWindow:
<userControls:MarkupControl/>
The ViewModel of my MainWindow stays the same like above.
inside the UserControl I bind to my TextBlock Text property like this:
<TextBlock Text="{markupExtensions:ExampleText {Binding ExampleText}}"/>
as a result my UserControl displays nothing. I would have expected to display "markup: example"
The binding somehow does not work in this case.
Does anybody know how to fix this?
Additional information:
it works when used like this (dependency property MarkupText is created in user control):
<userControls:MarkupControl MarkupText={markupExtensions:ExampleText {Binding ExampleText}}/>
<TextBlock Text="{Binding Text, ElementName=MarkupControl}"/>
Firstly, you need to refactor your extension to simplify the implementation. You don't need a static context here. Getting rid of the class context will make the tracking of the created attached properties obsolete. You can drop the related collection safely. In your case, it's more efficient to store values in an instance context. Attached properties are also a convenient solution to store values per instance especially in a static context.
Secondly, you got a timing issue. The first time the extension is called, the Binding is not initialized properly: it doesn't provide the final value of the Binding.Source.
Additionally, your current implementation does not support property changes.
To fix this, you would have to track the Binding.Target updates when a value is sent from the Binding.Source (for a default BindingMode.OneWay). You can achieve this by listening to the Binding.TargetUpdated event (as stated in my previous comment) or register a property changed handler with the attached property (recommended).
To support two way binding, you would also have to track the target property (the property your MarkupExtension is assigned to).
A fixed and improved version could look as follows:
public class ExampleTextExtension : MarkupExtension
{
private static DependencyProperty ResolvedBindingSourceValueProperty = DependencyProperty.RegisterAttached(
"ResolvedBindingSourceValue",
typeof(object),
typeof(ExampleTextExtension),
new PropertyMetadata(default(object), OnResolvedBindingSourceValueChanged));
// Use attached property to store the target object
// for reference from a static context without dealing with class level members that are shared between instances.
private static DependencyProperty TargetPropertyProperty = DependencyProperty.RegisterAttached(
"TargetProperty",
typeof(DependencyProperty),
typeof(ExampleTextExtension),
new PropertyMetadata(default));
private Binding Binding { get; }
// Accept BindingBase to support MultiBinding etc.
public ExampleTextExtension(Binding binding)
{
this.Binding = binding;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
var provideValueTargetService = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
if (provideValueTargetService?.TargetObject is not DependencyObject targetObject
|| provideValueTargetService?.TargetProperty is not DependencyProperty targetProperty)
{
return this;
}
targetObject.SetValue(ExampleTextExtension.TargetPropertyProperty, targetProperty);
AttachBinding(targetObject);
return string.Empty;
}
private static string GetLocalizedText(string text)
=> String.IsNullOrWhiteSpace(text)
? string.Empty
: $"markup: {text}";
// By now, only supports OneWay binding
private void AttachBinding(DependencyObject targetObject)
{
switch (this.Binding.Mode)
{
case BindingMode.OneWay:
case BindingMode.Default:
HandleOneWayBinding(targetObject); break;
default: throw new NotSupportedException();
}
}
private void HandleOneWayBinding(DependencyObject targetObject)
{
BindingOperations.SetBinding(targetObject, ExampleTextExtension.ResolvedBindingSourceValueProperty, this.Binding);
}
// Property changed handler to update the target of this extension
// with the localized value
private static void OnResolvedBindingSourceValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
string localizedText = GetLocalizedText(e.NewValue as string);
var targetProperty = d.GetValue(ExampleTextExtension.TargetPropertyProperty) as DependencyProperty;
d.SetValue(targetProperty, localizedText);
}
}
Remarks
There are better solutions to introduce localization without compromising the general syntax or legacy code. For example, introducing this MarkupExtension to existing code will break this code as all relevant data bindings (C# and XAML) have to be modified.
The most common approach is to use satellite assemblies and localized resources. Instead of converting text values during data binding you should localize the value source directly (so that the Binding transfers already localized values).
In other words, make sure that the data source is localized. Let the binding source expose the text by fetching it from a localized repository.

Binding within an AttachedProperty of Type Collection to an other Element

I want to create an AttachedProperty of Type Collection, which contains references to other existing elements, as shown below:
<Window x:Class="myNamespace.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:myNamespace"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<ContentPresenter>
<ContentPresenter.Content>
<Button>
<local:DependencyObjectCollectionHost.Objects>
<local:DependencyObjectCollection>
<local:DependencyObjectContainer Object="{Binding ElementName=myButton}"/>
</local:DependencyObjectCollection>
</local:DependencyObjectCollectionHost.Objects>
</Button>
</ContentPresenter.Content>
</ContentPresenter>
<Button x:Name="myButton" Grid.Row="1"/>
</Grid>
</Window>
Therefore I've created a generic class, called ObjectContainer, to gain the possibility to do so with Binding:
public class ObjectContainer<T> : DependencyObject
where T : DependencyObject
{
static ObjectContainer()
{
ObjectProperty = DependencyProperty.Register
(
"Object",
typeof(T),
typeof(ObjectContainer<T>),
new PropertyMetadata(null)
);
}
public static DependencyProperty ObjectProperty;
[Bindable(true)]
public T Object
{
get { return (T)this.GetValue(ObjectProperty); }
set { this.SetValue(ObjectProperty, value); }
}
}
public class DependencyObjectContainer : ObjectContainer<DependencyObject> { }
public class DependencyObjectCollection : Collection<DependencyObjectContainer> { }
public static class DependencyObjectCollectionHost
{
static DependencyObjectCollectionHost()
{
ObjectsProperty = DependencyProperty.RegisterAttached
(
"Objects",
typeof(DependencyObjectCollection),
typeof(DependencyObjectCollectionHost),
new PropertyMetadata(null, OnObjectsChanged)
);
}
public static DependencyObjectCollection GetObjects(DependencyObject dependencyObject)
{
return (DependencyObjectCollection)dependencyObject.GetValue(ObjectsProperty);
}
public static void SetObjects(DependencyObject dependencyObject, DependencyObjectCollection value)
{
dependencyObject.SetValue(ObjectsProperty, value);
}
public static readonly DependencyProperty ObjectsProperty;
private static void OnObjectsChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
var objects = (DependencyObjectCollection)e.NewValue;
if (objects.Count != objects.Count(d => d.Object != null))
throw new ArgumentException();
}
}
I'm not able to establish any binding within the Collection. I think I've already figured out, what the problem is. The elements in the Collection have no DataContext related to the Binding. However, I've no clue what I can do against it.
EDIT:
Fixed the missing Name Property of the Button.
Note: I know that the binding cannot work, because every Binding which doesn't declare a Source explicitly will use it's DataContext as it's Source. Like I already mentioned: We don't have such a DataContext within my Collection and there's no VisualTree where the non-existing FrameworkElement could be part of ;)
Maybe someone had a similiar problem in the past and found a suitable solution.
EDIT2 related to H.B.s post:
With the following change to the items within the collection it seems to work now:
<local:DependencyObjectContainer Object="{x:Reference myButton}"/>
Interesting behavior:
When the OnObjectsChanged Event-Handler is called, the collection contains zero elements ... I assume that's because the creation of the elements (done within the InitializeComponent method) hasn't finished yet.
Btw. As you H.B. said the use of the Container class is unnecessary when using x:Reference. Are there any disadvantages when using x:Reference which I don't see at the first moment?
EDIT3 Solution:
I've added a custom Attached Event in order to be notified, when the Collection changed.
public class DependencyObjectCollection : ObservableCollection<DependencyObject> { }
public static class ObjectHost
{
static KeyboardObjectHost()
{
ObjectsProperty = DependencyProperty.RegisterAttached
(
"Objects",
typeof(DependencyObjectCollection),
typeof(KeyboardObjectHost),
new PropertyMetadata(null, OnObjectsPropertyChanged)
);
ObjectsChangedEvent = EventManager.RegisterRoutedEvent
(
"ObjectsChanged",
RoutingStrategy.Bubble,
typeof(RoutedEventHandler),
typeof(KeyboardObjectHost)
);
}
public static DependencyObjectCollection GetObjects(DependencyObject dependencyObject)
{
return (DependencyObjectCollection)dependencyObject.GetValue(ObjectsProperty);
}
public static void SetObjects(DependencyObject dependencyObject, DependencyObjectCollection value)
{
dependencyObject.SetValue(ObjectsProperty, value);
}
public static void AddObjectsChangedHandler(DependencyObject dependencyObject, RoutedEventHandler h)
{
var uiElement = dependencyObject as UIElement;
if (uiElement != null)
uiElement.AddHandler(ObjectsChangedEvent, h);
else
throw new ArgumentException(string.Format("Cannot add handler to object of type: {0}", dependencyObject.GetType()), "dependencyObject");
}
public static void RemoveObjectsChangedHandler(DependencyObject dependencyObject, RoutedEventHandler h)
{
var uiElement = dependencyObject as UIElement;
if (uiElement != null)
uiElement.RemoveHandler(ObjectsChangedEvent, h);
else
throw new ArgumentException(string.Format("Cannot remove handler from object of type: {0}", dependencyObject.GetType()), "dependencyObject");
}
public static bool CanControlledByKeyboard(DependencyObject dependencyObject)
{
var objects = GetObjects(dependencyObject);
return objects != null && objects.Count != 0;
}
public static readonly DependencyProperty ObjectsProperty;
public static readonly RoutedEvent ObjectsChangedEvent;
private static void OnObjectsPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
Observable.FromEvent<NotifyCollectionChangedEventArgs>(e.NewValue, "CollectionChanged")
.DistinctUntilChanged()
.Subscribe(args =>
{
var objects = (DependencyObjectCollection)args.Sender;
if (objects.Count == objects.Count(d => d != null)
OnObjectsChanged(dependencyObject);
else
throw new ArgumentException();
});
}
private static void OnObjectsChanged(DependencyObject dependencyObject)
{
RaiseObjectsChanged(dependencyObject);
}
private static void RaiseObjectsChanged(DependencyObject dependencyObject)
{
var uiElement = dependencyObject as UIElement;
if (uiElement != null)
uiElement.RaiseEvent(new RoutedEventArgs(ObjectsChangedEvent));
}
}
You can use x:Reference in .NET 4, it's "smarter" than ElementName and unlike bindings it does not require the target to be a dependency property.
You can even get rid of the container class, but your property needs to have the right type so the ArrayList can directly convert to the property value instead of adding the whole list as an item. Using x:References directly will not work.
xmlns:col="clr-namespace:System.Collections;assembly=mscorlib"
<local:AttachedProperties.Objects>
<col:ArrayList>
<x:Reference>button1</x:Reference>
<x:Reference>button2</x:Reference>
</col:ArrayList>
</local:AttachedProperties.Objects>
public static readonly DependencyProperty ObjectsProperty =
DependencyProperty.RegisterAttached
(
"Objects",
typeof(IList),
typeof(FrameworkElement),
new UIPropertyMetadata(null)
);
public static IList GetObjects(DependencyObject obj)
{
return (IList)obj.GetValue(ObjectsProperty);
}
public static void SetObjects(DependencyObject obj, IList value)
{
obj.SetValue(ObjectsProperty, value);
}
Further writing the x:References as
<x:Reference Name="button1"/>
<x:Reference Name="button2"/>
will cause some more nice errors.
I think the answer can be found in the following two links:
Binding.ElementName Property
XAML Namescopes and Name-related APIs
Especially the second states:
FrameworkElement has FindName, RegisterName and UnregisterName methods. If the object you call these methods on owns a XAML namescope, the methods call into the methods of the relevant XAML namescope. Otherwise, the parent element is checked to see if it owns a XAML namescope, and this process continues recursively until a XAML namescope is found (because of the XAML processor behavior, there is guaranteed to be a XAML namescope at the root). FrameworkContentElement has analogous behaviors, with the exception that no FrameworkContentElement will ever own a XAML namescope. The methods exist on FrameworkContentElement so that the calls can be forwarded eventually to a FrameworkElement parent element.
So the issue in your sample caused by the fact that your classes are DependencyObjects at most but none of them is FrameworkElement. Not being a FrameworkElement it cannot provide Parent property to resolve name specified in Binding.ElementName.
But this isn't end. In order to resolve names from Binding.ElementName your container not only should be a FrameworkElement but it should also have FrameworkElement.Parent. Populating attached property doesn't set this property, your instance should be a logical child of your button so it will be able to resolve the name.
So I had to make some changes into your code in order to make it working (resolving ElementName), but at this state I do not think it meets your needs. I'm pasting the code below so you can play with it.
public class ObjectContainer<T> : FrameworkElement
where T : DependencyObject
{
static ObjectContainer()
{
ObjectProperty = DependencyProperty.Register("Object", typeof(T), typeof(ObjectContainer<T>), null);
}
public static DependencyProperty ObjectProperty;
[Bindable(true)]
public T Object
{
get { return (T)this.GetValue(ObjectProperty); }
set { this.SetValue(ObjectProperty, value); }
}
}
public class DependencyObjectContainer : ObjectContainer<DependencyObject> { }
public class DependencyObjectCollection : FrameworkElement
{
private object _child;
public Object Child
{
get { return _child; }
set
{
_child = value;
AddLogicalChild(_child);
}
}
}
public static class DependencyObjectCollectionHost
{
static DependencyObjectCollectionHost()
{
ObjectsProperty = DependencyProperty.RegisterAttached
(
"Objects",
typeof(DependencyObjectCollection),
typeof(DependencyObjectCollectionHost),
new PropertyMetadata(null, OnObjectsChanged)
);
}
public static DependencyObjectCollection GetObjects(DependencyObject dependencyObject)
{
return (DependencyObjectCollection)dependencyObject.GetValue(ObjectsProperty);
}
public static void SetObjects(DependencyObject dependencyObject, DependencyObjectCollection value)
{
dependencyObject.SetValue(ObjectsProperty, value);
}
public static readonly DependencyProperty ObjectsProperty;
private static void OnObjectsChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
((Button) dependencyObject).Content = e.NewValue;
var objects = (DependencyObjectCollection)e.NewValue;
// this check doesn't work anyway. d.Object was populating later than this check was performed
// if (objects.Count != objects.Count(d => d.Object != null))
// throw new ArgumentException();
}
}
Probably you still can make this working by implementing INameScope interface and its FindName method particularly but I haven't tried doing that.

Setting a ViewModel Property Value From XAML

I have a view that is declared in XAML (see below). The associated view-model is created automatically using MEF. I want to be able to do something like this:
<local:MyControl Owner={x:Static local:Owners.ProjectOwner} />
The desired net effect is for some view-model property to be set equal to Owners.ProjectOwner.
I can achieve the required result using hacky code-behind but would rather do this through bindings or some similar manner. Can anyone suggest a way of doing this?
UPDATE
I resigned myself to writing a behaviour. But rather than put in all the effort solely for one specific case, I have genericised my solution and I include it below in case anyone's interested. It's a Blend behaviour (System.Windows.Interactivity.dll) but could just as easily be a conventional attached behaviour.
using System;
using System.Windows;
using System.Windows.Interactivity;
namespace BlendBehaviors
{
public class SetViewModelPropertyBehavior : Behavior<FrameworkElement>
{
public static readonly DependencyProperty PropertyNameProperty =
DependencyProperty.Register("PropertyName", typeof(string), typeof(SetViewModelPropertyBehavior));
public static readonly DependencyProperty PropertyValueProperty =
DependencyProperty.Register("PropertyValue", typeof(object), typeof(SetViewModelPropertyBehavior));
public SetViewModelPropertyBehavior()
{ }
public string PropertyName
{
get { return (string)GetValue(PropertyNameProperty); }
set { SetValue(PropertyNameProperty, value); }
}
public object PropertyValue
{
get { return GetValue(PropertyValueProperty); }
set { SetValue(PropertyValueProperty, value); }
}
protected override void OnAttached()
{
base.OnAttached();
var ao = AssociatedObject;
SetViewModel(ao.DataContext);
ao.DataContextChanged += FrameworkElement_DataContextChanged;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.DataContextChanged -= FrameworkElement_DataContextChanged;
}
private void FrameworkElement_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
SetViewModel(e.NewValue);
}
private void SetViewModel(object viewModel)
{
SetViewModelProperty(viewModel, PropertyName, PropertyValue);
}
private static void SetViewModelProperty(object viewModel, string propertyName, object propertyValue)
{
if (viewModel == null || propertyName == null) {
return;
}
var info = viewModel.GetType().GetProperty(propertyName);
if (info != null && CanAssignValue(propertyValue, info.PropertyType)) {
info.SetValue(viewModel, propertyValue, null);
}
}
private static bool CanAssignValue(object value, Type targetType)
{
if (value == null) {
return !targetType.IsValueType || Nullable.GetUnderlyingType(targetType) != null;
}
return targetType.IsAssignableFrom(value.GetType());
}
}
}
Then use it like this:
<local:MyControl>
<i:Interaction.Behaviors>
<bb:SetViewModelPropertyBehavior PropertyName="Owner" PropertyValue="{x:Static local:Owners.ProjectOwner}" />
<bb:SetViewModelPropertyBehavior PropertyName="AnotherProperty" PropertyValue="{StaticResource MyResourceKey}" />
</i:Interaction.Behaviors>
</local:MyControl>
The target of any WPF binding must be a DependencyProperty. The source can be a DependencyProperty, a CLR object that implements INotifyPropertyChanged, or just some object. The target and source can be swapped around by altering the Binding.Mode property.
But in this case one of the items in your binding is a statically-resolved property (Owners.ProjectOwner). Therefore, it's not a DependencyProperty. Therefore, it can only appear as a source. Therefore, what you're binding it to (the target) must be a DependencyProperty. Therefore, it cannot be a property on your view model (assuming you've not created DependencyObject-based view models, which would be a mistake).
So, you cannot directly bind a property on your VM to the static property. You could write an attached behavior that does this for you though.

Why doesn't this Silverlight 4 DependencyObject's DependencyProperty getting data bound?

I have no idea why data binding is not happening for certain objects in my Silverlight 4 application. Here's approximately what my XAML looks like:
<sdk:DataGrid>
<u:Command.ShortcutKeys>
<u:ShortcutKeyCollection>
<u:ShortcutKey Key="Delete" Command="{Binding Path=MyViewModelProperty}"/>
</u:ShortcutKeyCollection>
</u:Command.ShortcutKeys>
</sdk:DataGrid>
The data context is set just fine since other data bindings that I have set on the grid are working just fine. The Command.ShortcutKeys is an attached DependencyProperty that is declared as follows:
public static readonly DependencyProperty ShortcutKeysProperty = DependencyProperty.RegisterAttached(
"ShortcutKeys", typeof(ShortcutKeyCollection),
typeof(Command), new PropertyMetadata(onShortcutKeysChanged));
private static void onShortcutKeysChanged(
DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
var shortcuts = args.NewValue as ShortcutKeyCollection;
if (obj is UIElement && shortcuts != null)
{
var element = obj as UIElement;
shortcuts.ForEach(
sk => element.KeyUp += (s, e) => sk.Command.Execute(null));
}
}
public static ShortcutKeyCollection GetShortcutKeys(
DependencyObject obj)
{
return (ShortcutKeyCollection)obj.GetValue(ShortcutKeysProperty);
}
public static void SetShortcutKeys(
DependencyObject obj, ShortcutKeyCollection keys)
{
obj.SetValue(ShortcutKeysProperty, keys);
}
I know this attached property is working just fine since the event handlers are firing. However, the Command property of the ShortcutKey objects are not getting data bound. Here's the definition of ShortcutKey:
public class ShortcutKey : DependencyObject
{
public static readonly DependencyProperty KeyProperty = DependencyProperty.Register(
"Key", typeof(Key), typeof(ShortcutKey), null);
public static readonly DependencyProperty CommandProperty = DependencyProperty.Register(
"Command", typeof(ICommand), typeof(ShortcutKey), null);
public Key Key
{
get { return (Key)GetValue(KeyProperty); }
set { SetValue(KeyProperty, value); }
}
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
}
public class ShortcutKeyCollection : ObservableCollection<ShortcutKey> { }
The property that is getting bound to has its value set in the constructor of my view model, and its type is ICommand. So why isn't my Command property getting data bound? Also, have you found an effective way to debug data binding issues in Silverlight?
Edit:
At least one thing that was wrong was that ShortcutKey derived from DependencyObject instead of FrameworkElement, which is apparently the only root class that binding can be applied to. However, even after that change, the binding continued to not work properly.
You need to specify the Source of the Binding, since the DataContext is not inherited by members of the ObservableCollection.
edit:
Try setting the ShortcutKey.DataContext in onShortcutKeysChanged:
private static void onShortcutKeysChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
var shortcuts = args.NewValue as ShortcutKeyCollection;
if (obj is FrameworkElement && shortcuts != null)
{
var element = obj as FrameworkElement;
ForEach(ShortcutKey sk in shortcuts)
{
sk.DataContext = element.DataContext;
element.KeyUp += (s, e) => sk.Command.Execute(null));
}
}
}
It looks like unless an object is inserted into the visual tree, no DataContext inheritance takes place, and thus no data binding works. I couldn't find a way to get the container's data context to be passed to the ShortcutKey objects, so as a workaround, I set up the binding in the code behind.
Hopefully someone else has a different answer that will show me how I won't have to resort to setting up this data binding in the code.

Pushing read-only GUI properties back into ViewModel

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);
}
}

Resources