WPF: Resizing a circle, keeping the center point instead of TopLeft?

雨燕双飞 提交于 2019-11-30 19:11:45

You can bind your Canvas.Left and Canvas.Top to your Height and Width via a ValueConverter.

Specifically (edit):
Create a property each for the Canvas.Left and Canvas.Top and bind to these.
Store the old values for Width and Heigth or the old slider value.
Whenever the slider is changed, get the incremental change "dx" by subtracting the stored value.
(Don't forget to update the stored value...)
Add dx to Width and Height property.
And, as Will said, add dx/2*-1 to Canvas.Left and Canvas.Top properties.

Does that make sense?

Since you're using a Canvas, the location an element has is the location. If you want the Top,Left position to change you need to do it yourself. If you were using another Panel type, like a Grid, you could change the alignment of your Ellipse to place it in the same relative location no matter what the size. You could get that effect by adding a Grid inside your AdornerDecorator and centering the Ellipse but you'd also need to set the AdornerDecorator or Grid to a fixed size because they won't stretch in a Canvas.

The best solution you could use would be a ScaleTransform applied to the RenderTransform property with a RenderTransformOrigin of 0.5,0.5. You said you had problems with ScaleTransform but not what the problem was.

The problem is that you are using the SLIDER to adjust the width and height. Width and height are not calculated around RenderTransformOrigin; only RenderTransforms use that value.

Here's a corrected version (brb, kaxaml):

<Canvas x:Name="MyCanvas">
<!-- this is needed for some adorner stuff I do in code behind -->
    <AdornerDecorator Canvas.Left="50" Canvas.Top="50">
        <Ellipse
            x:Name="myEllipse"
            Width="10"
            Height="10"
            Fill="AliceBlue"
            RenderTransformOrigin="0.5 0.5"
            Stroke="Aquamarine">
            <Ellipse.RenderTransform>
                <TransformGroup>
                    <RotateTransform Angle="{Binding Path=Value, ElementName=myRotationSlider}"/>
                    <ScaleTransform
                        CenterX=".5"
                        CenterY=".5"
                        ScaleX="{Binding Path=Value, ElementName=mySlider}"
                        ScaleY="{Binding Path=Value, ElementName=mySlider}"/>
                </TransformGroup>
            </Ellipse.RenderTransform>
        </Ellipse>
    </AdornerDecorator>
    <Slider
        x:Name="mySlider"
        Width="100"
        Canvas.Left="150"
        Canvas.Top="10"
        Maximum="10"
        Minimum="0"
        SmallChange=".01"
        Value="1"/>
    <Slider
        x:Name="myRotationSlider"
        Width="100"
        Canvas.Left="150"
        Canvas.Top="50"
        Maximum="360"
        Minimum="0"
        Value="0"/>
</Canvas>

Of course, this will probably not work for you. Why? Well, the ScaleTransform I used zooms not only the circle but also the border; as the circle gets bigger the border does as well. Hopefully you won't care about this.

Also, realize when combining transforms (scale then rotate in this case) that they are applied in order, and one may affect how another is done. In your case, you would not notice this. But if, say, you were doing a rotate and translate, the order would be relevant.


Ah, what was I thinking? Just stick the ellipse in a Grid (simplest solution but other containers would work). The grid automatically takes care of centering the ellipse as it is resized. No need for any value converters! Here's the code:

<Canvas x:Name="MyCanvas">
<!-- this is needed for some adorner stuff I do in code behind -->
    <Grid Width="100" Height="100">
        <AdornerDecorator>
            <Ellipse
                x:Name="myEllipse"
                Width="{Binding Path=Value, ElementName=mySlider}"
                Height="{Binding Path=Value, ElementName=mySlider}"
                Fill="AliceBlue"
                RenderTransformOrigin="0.5 0.5"
                Stroke="Aquamarine">
                <Ellipse.RenderTransform>
                    <RotateTransform Angle="{Binding Path=Value, ElementName=myRotationSlider}"/>
                </Ellipse.RenderTransform>
            </Ellipse>
        </AdornerDecorator>
    </Grid>
    <Slider
        x:Name="mySlider"
        Width="100"
        Canvas.Left="150"
        Canvas.Top="10"
        Maximum="100"
        Minimum="0"
        Value="10"/>
    <Slider
        x:Name="myRotationSlider"
        Width="100"
        Canvas.Left="150"
        Canvas.Top="50"
        Maximum="360"
        Minimum="0"
        Value="0"/>
</Canvas>

Wrap your Ellipse in a Grid of the maximum size. As long as it is smaller, the Ellipse will be centered in the Grid:

    <Grid
        Canvas.Left="10"
        Canvas.Top="10"
        Width="100"
        Height="100">
        <AdornerDecorator>
            <Ellipse x:Name="myEllipse"
             Height="{Binding Path=Value, ElementName=mySlider}"
             Width="{Binding Path=Value, ElementName=mySlider}"
             Stroke="Aquamarine"
             Fill="AliceBlue"
             RenderTransformOrigin="0.5 0.5">
                <Ellipse.RenderTransform>
                    <RotateTransform Angle="{Binding Path=Value, ElementName=myRotationSlider}" />
                </Ellipse.RenderTransform>
            </Ellipse>
        </AdornerDecorator>
    </Grid>

You may need to adjust your dragging logic to handle dragging the Grid instead of the Ellipse itself.

Ark-kun

I've found a very easy way to do this in plain XAML: set Margin="-1000000". Read more here: Positioning an element inside the Canvas by its center (instead of the top left corner) using only XAML in WPF

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!