Windowscommunitytoolkit: Adding support for bindable ActualHeight/ActualWidth of FrameworkElement.

Created on 8 Dec 2017  路  22Comments  路  Source: windows-toolkit/WindowsCommunityToolkit

I'm submitting a...

[ ] Regression (a behavior that used to work and stopped working in a new release)
[ ] Bug report  <!-- Please search GitHub for a similar issue or PR before submitting -->
[X] Feature request <!-- Please file a UserVoice request and include the link below https://wpdev.uservoice.com/forums/110705-universal-windows-platform/category/193402-uwp-community-toolkit -->
[ ] Sample app request
[ ] Documentation issue or request
[ ] Question of Support request => Please do not submit support request here, instead see https://github.com/Microsoft/UWPCommunityToolkit/blob/master/contributing.md#question

UserVoice link


Not adding a UserVoice link because I already wrote the code, so there's no more effort required from anyone. I'm opening the issue just to discuss whether you think that this may be useful or not.

Current behavior


As of FrameworkElement's documentation

ActualHeight does not raise property change notifications and it should be thought of as a regular CLR property and not a dependency property.

This applies to ActualWidth too, meaning that it's not possible to bind to those properties.

Expected behavior


To be honest, I never had to do this but today, working on the sample page for #1678, I ended up needing something similar as I wanted to be able to resize a List's header and see my ScrollBar move alongside with it.

The expected behavior, then, is to be able to bind something to those properties in any FrameworkElement.

Since I already wrote the code, here's what I'm proposing: my idea is to have an attached behavior (first version here) that just subscribes to AssociatedObject's SizeChanged event and, when one of the dimensions change, it raises a PropertyChanged event.

Here's a simple use case:

<ListView Name="listView"
          extensions:ScrollViewerEx.VerticalScrollBarMargin="{Binding ActualHeight, ElementName=BindableActualSizeBehavior, Converter={StaticResource DoubleTopThicknessConverter}}">
    <ListView.Header>
        <controls:ScrollHeader Mode="Sticky">
            <Grid x:Name="MyHeaderGrid"
                MinHeight="100">
                <interactivity:Interaction.Behaviors>
                    <behaviors:BindableActualSizeBehavior x:Name="BindableActualSizeBehavior" />
                </interactivity:Interaction.Behaviors>

As you can see, now we can bind to BindableActualSizeBehavior's ActualHeight as it just acts as a wrapper that gets a notification when SizeChanged event is fired.

I'm waiting for your feedback before creating a new PR.

Minimal reproduction of the problem with instructions


Bind anything to any FrameworkElement's ActualHeight and you'll see that it's updated only the first time, ignoring following changes.

Environment

Nuget Package(s): 

Package Version(s): 

Windows 10 Build Number:
- [ ] Anniversary Update (14393) 
- [ ] Creators Update (15063)
- [ ] Fall Creators Update (16299)
- [ ] Insider Build (xxxxx)

App min and target version:
- [ ] Anniversary Update (14393) 
- [ ] Creators Update (15063)
- [ ] Fall Creators Update (16299)
- [ ] Insider Build (xxxxx)

Device form factor:
- [ ] Desktop
- [ ] Mobile
- [ ] Xbox
- [ ] Surface Hub
- [ ] IoT

Visual Studio 
- [ ] 2017 15.3
- [ ] 2017 15.4

Most helpful comment

@nmetulev Are we talking about something like this?

<Rectangle x:Name="TargetObject"
                   Grid.Row="0"
                   Grid.Column="0"
                   extensions:FrameworkElementEx.EnableActualSizeBinding="true"
                   Fill="{StaticResource Brush-White}"
                   Stroke="{StaticResource Brush-Grey-04}"
                   StrokeThickness="1" />
...
<Run Text="{Binding ElementName=TargetObject, Path=(extensions:FrameworkElementEx.ActualHeight)}" />

@Vijay-Nirmal I get what your saying but I think it's outside the scope of this issue :)

All 22 comments

+1 I really would like this. Although I presume there are some complications with this approach. This would make reactive design easier to implement.

I use a similar helper but its not a behaviour rather its a helper control

<Grid>
    <Grid.Resources>
        <helper:ActualSizePropertyProxy x:Name="proxyImage" Element="{Binding ElementName=imgSplash}" />
    </Grid.Resources>
    <Grid Grid.Column="2" Height="{Binding ElementName=proxyImage, Path=ActualHeightValue}">
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
    </Grid>
</Grid>

BTW its not something i wrote.. something i found a while ago

Usually, devs need to perform a mathematical operation to ActualSize before binding the value. I think we need a separate Helper to achieve it. Something like a converter (ConverterParameter is the equation to execute) or CalcBinding for UWP.

I agree with that statement, but it shouldn鈥檛 be locked to a specific converter. It should be free to bind to and we can write our own converters/helpers

I'm curious if we can post our own property change event for a FrameworkElement with reflection or something.

Then we would't need to make it a behavior:

<ListView Name="listView"
          extensions:ScrollViewerEx.VerticalScrollBarMargin="{Binding ActualHeight, ElementName=MyHeaderGrid, Converter={StaticResource DoubleTopThicknessConverter}}">
    <ListView.Header>
        <controls:ScrollHeader Mode="Sticky">
            <Grid x:Name="MyHeaderGrid"
                MinHeight="100"
                                extensions:FrameworkElementEx.BindActualSize="True">
                                ...

@jefhai I'm actually interested in the complications you're talking about: do you think that going this route can cause issues of any kind?

@hermitdave found that class too after you posted it, it's basically the same concept that I used but it doesn't require the Interactivity namespace, so it may be a better option. I don't think it would be hard to migrate my code from a behavior to a helper control.

@Vijay-Nirmal I'm not sure of what you mean, but in my sample code I'm already using a converter on bound value.

@michael-hawker that was the very first thing I tried, I may even have the code somewhere. Couldn't find any PropertyChanged in typeof(FrameworkElement) so I went for the behavior.

@ST-Apps I mean, we should create a converter to perform a mathematical operation while binding because usually, devs need to perform a mathematical operation to ActualSize.

{Binding Path = ActualHeight, ElementName=BindableActualSizeBehavior, 
Converter={StaticResource MathConverter}, ConverterParameter="(x / 2 ) + 10"}

Here x should be replaced by Path

(or)

We can create a UWP version of CalcBinding

{helper:Bind (BindableActualSizeBehavior.ActualHeight / 2) + 10"}

The benefit of this approach

  • Simpler than a MathConverter
  • Perform multiple binding operations
    {helper:Bind (BindableActualSizeBehavior.ActualHeight / BindableActualSizeBehavior.ActualWidth)"}
  • Can perform many operations that would typically require Converter. For Eg:
    {helper:Bind ( ! IsChecked and ( A > B ) )"}

I'd try to avoid using behaviors if possible. Maybe creating new Attached Properties for height and weight that are being updated on SizeChanged event and bind to those.

@nmetulev Are we talking about something like this?

<Rectangle x:Name="TargetObject"
                   Grid.Row="0"
                   Grid.Column="0"
                   extensions:FrameworkElementEx.EnableActualSizeBinding="true"
                   Fill="{StaticResource Brush-White}"
                   Stroke="{StaticResource Brush-Grey-04}"
                   StrokeThickness="1" />
...
<Run Text="{Binding ElementName=TargetObject, Path=(extensions:FrameworkElementEx.ActualHeight)}" />

@Vijay-Nirmal I get what your saying but I think it's outside the scope of this issue :)

@ST-Apps Yes, it is outside the scope of this issue but just want to point out, without a MathConverter this helper will not help devs to reduce the number of lines required to bind ActualSize. Also, It may increase the number of lines when devs implement IValueConverter.

The actual size proxy method is much simpler to use than extension methods

@hermitdave Personally I like extension method over actual size proxy method because ActualSizePropertyProxy method takes more lines

<Grid.Resources>
        <helper:ActualSizePropertyProxy x:Name="proxyImage" Element="{Binding ElementName=imgSplash}" />
</Grid.Resources>
<Grid Height="{Binding ElementName=proxyImage, Path=ActualHeightValue}">

Where extension method will perform same operation inline

<Grid Height="{Binding ElementName=TargetObject, Path=(extensions:FrameworkElementEx.ActualHeight)}" />

@Vijay-Nirmal actual lines of code is irrelevant. Simplicity beats long winded un-obvious ways any day.

So, what do you guys plan to do with this?

@Vijay-Nirmal I like your second example; it should fit into one bindable expression. I shouldn't have to write a bunch of helper scaffolding, especially in XAML, just to get this working. We already struggle with very dense XAML in our projects and our state of business doesn't give us enough time to abstract everything into reusable controls.

@ST-Apps I would love to replicate the exact nature iOS iPad apps have, which is, you don't have to build for screen size. I personally hate GUI building, but it's made worse when my implementations look decent on one screen and awful on another. So I think having my UI elements scale to fit well enough on all screen sizes is a big win. I think binding to ActualHeight is a step in this direction.

Practical use cases:

  • Positioning one image on top of another and keep their scale and design aspect ratio.
  • Positioning Text on an image and converting the font size to fit the image dimensions.
  • Resizing styles to fit their contents, like gradient views on top of ListViews.

All the above scenarios are benefited by the ability to bind to ActualHeight. In my mind, it would eliminate multiple lines of code and reduce the complexities of XAML reuse, i.e. my colleagues can simply copy and move Xaml to get the same effect and customize it to our customer's liking without having to dig into the source code to find the size change methods etc.

@ST-Apps

@jefhai I'm actually interested in the complications you're talking about: do you think that going this route can cause issues of any kind?

I read somewhere that binding to ActualHeight is not a good idea because it can cause issues with UpdateLayout mechanisms not knowing when to stop reacting to UI Element size changes. Basically only do your work in size changed because that's the end state for an update cycle? Not too familiar with all the challenges of implementing this feature.

This issue seems inactive. It will automatically be closed in 14 days if there is no activity.

@hermitdave @nmetulev
I'm waiting for your instructions then.

@jefhai I understand what you mean.
The current code uses the SizeChanged event to update the properties, we should be quite safe :)

The proxy method requires me to have code in 3 different places in my file to support the feature (the sender element, the proxy in the page resources, and the receiving element). The extensions can do this in two (the sender and the receiver). Therefore, I like the extension method approach, IMO it's cleaner and more intuitive.

+1 to Extension Approach as outlined here.

I messed around with trying to use reflection, but that was getting really messy fast. @ST-Apps did you have that example working already in code?

@michael-hawker tried with reflection too, just wasted time because it's not gonna work anytime soon :)
The example you linked is taken from the sample app in my branch, so it's already working.
I'll try to get a PR as soon as possible.

@ST-Apps no worries, I wanted to play with it a bit, and I learned a couple of things I can use in another one of my projects (I think). I'll take a look at the PR!

Was this page helpful?
0 / 5 - 0 ratings