johnildergleidisson
johnildergleidisson

Reputation: 2117

Pushing ActualWidth/ActualHeight from a custom control to TemplateBinding

I have set up a simple example to try to achieve a simple thing: exposing a dependency property in a custom control that exposes a ActualWidth/ActualHeight of a control within this custom control.

In order to attempt to achieve that I have:

customcontrol.cs

public class CustomControl : ContentControl
{
    static CustomControl()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl), new FrameworkPropertyMetadata(typeof(CustomControl)));
    }

    public static readonly DependencyProperty RenderedWidthProperty = DependencyProperty.Register(
        "RenderedWidth", typeof (double), typeof (CustomControl), new PropertyMetadata(default(double)));

    public double RenderedWidth
    {
        get { return (double) GetValue(RenderedWidthProperty); }
        set { SetValue(RenderedWidthProperty, value); }
    }

    public static readonly DependencyProperty RenderedHeightProperty = DependencyProperty.Register(
        "RenderedHeight", typeof (double), typeof (CustomControl), new PropertyMetadata(default(double)));

    public double RenderedHeight
    {
        get { return (double) GetValue(RenderedHeightProperty); }
        set { SetValue(RenderedHeightProperty, value); }
    }
}

generic.xaml

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:Custom_Control_Pushing_ActualWidth">


    <Style TargetType="{x:Type local:CustomControl}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:CustomControl}">
                    <Grid local:SizeObserver.Observe="True"
                            local:SizeObserver.ObservedWidth="{Binding RenderedWidth, RelativeSource={RelativeSource TemplatedParent}}"
                            local:SizeObserver.ObservedHeight="{Binding RenderedHeight, RelativeSource={RelativeSource TemplatedParent}}">
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

viewmodel.cs

class ViewModel
{
    private double width;
    private double height;

    public double Width
    {
        get { return width; }
        set
        {
            width = value; 
            Console.WriteLine("Width: {0}", value);
        }
    }

    public double Height
    {
        get { return height; }
        set
        {
            height = value;
            Console.WriteLine("Height: {0}", value);
        }
    }
}

mainwindow.xaml

<Window x:Class="Custom_Control_Pushing_ActualWidth.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:Custom_Control_Pushing_ActualWidth"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <local:ViewModel />
    </Window.DataContext>
    <Grid>
        <local:CustomControl RenderedWidth="{Binding Width, Mode=OneWayToSource}" RenderedHeight="{Binding Height, Mode=OneWayToSource}" />
    </Grid>
</Window>

And I use SizeObserver from this SO answer.

However although I see the code in the dependency property being updated in the size observer, the bound viewmodel property's setter doesn't get set with the values. Something's wrong with my binding and I don't know what it is.

How can I correctly bind the DependencyProperty with the ViewModel's property?

Upvotes: 0

Views: 1565

Answers (1)

Szabolcs D&#233;zsi
Szabolcs D&#233;zsi

Reputation: 8843

Change this:

<Grid local:SizeObserver.Observe="True" 
      local:SizeObserver.ObservedWidth="{Binding RenderedWidth, RelativeSource={RelativeSource TemplatedParent}}"
      local:SizeObserver.ObservedHeight="{Binding RenderedHeight, RelativeSource={RelativeSource TemplatedParent}}">
</Grid>

To this:

<Grid local:SizeObserver.Observe="True" 
      local:SizeObserver.ObservedWidth="{Binding RenderedWidth, RelativeSource={RelativeSource TemplatedParent}, Mode=OneWayToSource}"
      local:SizeObserver.ObservedHeight="{Binding RenderedHeight, RelativeSource={RelativeSource TemplatedParent}, Mode=OneWayToSource}">
</Grid>

So add Mode=OneWayToSource to the end of the binding. This way the Width property in the ViewModel is properly updating for me.

The reason behind this is not entirely clear for me, but I think that the default binding mode of the ObservedWidth and ObservedHeight attached properties are OneWay. So they only update the target properties (ObservedWidth, ObservedHeight) when the source properties (RenderedWidth, RenderedHeight) change.

You want the exact opposite. With the OneWayToSource modification the changes in the ActualWidth and ActualHeight properties will nicely propagate up to your ViewModel.

Upvotes: 1

Related Questions