0

I have an application where I need to perform a fairly expensive calculation based on the actual width and height of a control. I have bound ActualWidth and ActualHeight of this control to properties in my code. I find that Actualheight is being changed in small steps. I assume this is because of the Layout Engine.

A stripped down version of the xAML is

<UserControl
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
    xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:sup="clr-namespace:RowAnimation.Support"
    xmlns:vm="clr-namespace:RowAnimation.ViewModels"
    xmlns:System="clr-namespace:System;assembly=mscorlib"
    xmlns:local="clr-namespace:RowAnimation.Views"
    x:Name="RowerAnimation"
    x:Class="RowAnimation.Views.RowerAnimationControl"
             mc:Ignorable="d" 
             d:DataContext="{d:DesignInstance {x:Type vm:RowerAnimationControlVM}}"
             d:DesignHeight="497" d:DesignWidth="741">
    <Grid>
        <DockPanel >
            <Grid Margin="5" x:Name="canvas1" ClipToBounds="True" VerticalAlignment="Top">
                <sup:DataPiping.DataPipes>
                    <sup:DataPipeCollection>
                        <sup:DataPipe Source="{Binding ActualWidth, Mode=OneWay, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Grid}}}"
                                    Target="{Binding ViewportWidth, Mode=OneWayToSource}"/>
                        <sup:DataPipe Source="{Binding ActualHeight, Mode=OneWay, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Grid}}}"
                                    Target="{Binding ViewportHeight, Mode=OneWayToSource}"/>
                    </sup:DataPipeCollection>
                </sup:DataPiping.DataPipes>
                <Path x:Name="gridLines" Stroke="#FF6083E2" Data="{Binding GridPath, Mode=OneWay}"/>
            </Grid>
        </DockPanel>
    </Grid>
</UserControl>

The GridPath is set in my ViewModel when the ViewportHeight or ViewportWidth property in the ViewModel is changed.

When I set a breakpoint in my ViewModel I find that ViewportWidth is changed once but the ViewportHeight is changed multiple times in small increments. When I comment out the method that calculates the GridPath then the VieportHeight break point hits only once. So it looks like the setting of GridPath triggers a new Layout calculation.

In my actual implementation I also have a Grid docked to the bottom of the control, which is why I have the Dockpanel in the XAML.

Since I am using MVVM I use DataPiping from this Stackoverflow link to get the ActualHeight and ActualWidth in my ViewModel.

Does anybody have an idea what is going on and how I might avoid it? As it is the setting of the GridPath (and I actually have more PathGeometries in my actual implementation) can make resizing my window take more than 8 seconds.

In addition:

Hm, It seems that I don't understand Geometries enough. Here is the code that I use to populate the grid:

private void PopulateGrid()
{
    PathGeometry path = new PathGeometry();
    Rect rect = new Rect(converter.NormalizePoint(xMin, yMin), converter.NormalizePoint(xMax, yMax));
    RectangleGeometry border = new RectangleGeometry(rect);
    path.AddGeometry(border);
    for (double y = 0; y < yMax; y += 30) {
        LineGeometry line = new LineGeometry(converter.NormalizePoint(xMin, y), converter.NormalizePoint(xMax, y));
        path.AddGeometry(line);
    }
    GridPath = path;
}

When I comment out the first 4 lines then the ActualHeight is not set and I see no Grid in my user control. The rectangle causes the Layout engine to work but just the LineGeometry doesn't.

Note that xMin, yMin, xMax and xMin are in world coordinates and the NormalizePoint converts them to screen coordinates, based on the current ViewportWidth and ViewportHeight.

  • I'm not sure *exactly* what you are trying to do here, so this might not apply, but if you want to scale a path to the size of a control, just put it inside a `ViewBox`. That will handle all the scaling and redrawing for you automatically. – Bradley Uffner Nov 17 '17 at 14:06
  • A ViewBox will scale everything, including the Line Thickness. I don't want that. Still I may want to experiment with that if I cannot get another solution. – Joost Zalmstra Nov 17 '17 at 15:52
  • hmm.. you could use a `Geometry` instead of a `Path`. If you apply a render transform to a `Geometry` it will just scale the coordinates, as the line style is specified by a separate `Pen` that isn't scaled. That starts to get more complicated though. – Bradley Uffner Nov 17 '17 at 16:02
  • Something like this https://social.msdn.microsoft.com/Forums/vstudio/en-US/c58ae40a-150c-4b01-9d87-5747824e200f/scaletransform-and-line-width?forum=wpf – Bradley Uffner Nov 17 '17 at 16:03
  • As you see I found the answer already. However your suggestion has definitely merits. I was not aware that `Path` has the `Stretch` property. I am not sure it would work in my case because I have multiple `Path` elements and they would probably not all need to fill the whole space. – Joost Zalmstra Nov 18 '17 at 06:42

1 Answers1

0

I found the issue. In the XAML above I have

<Grid Margin="5" x:Name="canvas1" ClipToBounds="True" VerticalAlignment="Top">

With the VerticalAlignment="Top" and the Height (implicitly) set to Auto I tell the system set the grids vertical size by measuring the content. I am not completely sure why this gives exactly the effects I see but it is clearly not what I want. What I want is that the Grid fills the space available in the DockPanel and then tell my ViewModel what that size is.

The solution is simple: Remove the VerticalAlignment="Top" which implicitly sets VerticalAlignment="Stretch" and now it does exactly what I want.