views:

73

answers:

4

Hi all,

I'd like to resize a circle on my canvas with the help of a slider. This circle can be moved around on the canvas by some drag&drop stuff I did in code behind, so its position is not fixed.

I have bound the slider's value to an ellipse's height and width. Unfortunately, when I use the slider, the circle gets resized with its top left point (actually the top left point of the rectangle it's sitting in) staying the same during the operation.

I would like to resize it with its center point being constant during the operation. Is there an easy way to do this in XAML? BTW, I already tried ScaleTransform, but it didn't quite do what I wanted.

Thanks a bunch! :-)

Jan

<Canvas x:Name="MyCanvas">

    <!-- this is needed for some adorner stuff I do in code behind -->
    <AdornerDecorator Canvas.Left="10"
                      Canvas.Top="10">
        <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>

    <Slider x:Name="mySlider"
            Maximum="100"
            Minimum="0"
            Width="100"
            Value="10"
            Canvas.Left="150"
            Canvas.Top="10" />
    <Slider x:Name="myRotationSlider"
            Maximum="360"
            Minimum="0"
            Width="100"
            Value="0"
            Canvas.Left="150"
            Canvas.Top="50" />
</Canvas>
+3  A: 

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?

Martin
Thanks for your answer! It showed me that my question was incomplete ;-) The circle's position on the canvas is not fixed, that's why I didn't find a way to use a ValueConverter...
Jan
Wait, this would work. You'd have to have a value converter that does `value/2*-1`
Will
Hi Martin! Thanks - I'm going to test this on Wednesday and report back :)
Jan
Hi guys! It works - thanks a lot for your help and patience :)
Jan
+1  A: 

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>
Will
Thanks Will! Unfortunately, I do care about the Border ;-) So yea, I have tried ScaleTransform already, but that also messed with my Adorners (which I left out here for brevity)... Any other ideas? :-)
Jan
@Jan Tough situation. You are probably going to have to create a custom control in that case. The reason being that you cannot bind to Canvas.Left and Canvas.Top. If you could, you could use a converter on the binding to take the value (say slider value 10), negate it and divide it by 2. That would move the circle's origin to match its increase in width and height. Since you cannot do this, you could create a custom control that performs this calculation and updates the ellipse accordingly.
Will
Hold on, I'm wrong. Made a mistake in my test. You can bind to Canvas.Left/Top and use a value converter to translate an increase in size to a change in the canvas position.
Will
@Jan added a new solution--just slap the ellipse in a grid and you're done.
Will
Hi Will! Thanks for your solution :) My problem with this might be that I need to move the whole thing around on the canvas. The way I see it, the Ellipse might be centered in my Grid and scaled automatically, but the Grid's size will change again with its top left corner being constant, thus moving the circle's center again... But maybe I'm wrong, so please correct me :)
Jan
@Jan The grid does not move. The center of the Grid becomes the center of the ellipse. The center of the ellipse only moves if it gets bigger than the Grid. So as long as you keep the grid the exact size and position of the ellipse at its largest diameter then you shouldn't have any issues at all. Slap my code in Kaxaml and take it for a spin.
Will
Hm, but how would I then be able to move the whole thing around on my Canvas? Forgive me if the solution becomes immediately obvious through testing - I won't be able to test this until Wednesday...
Jan
@Jan your question didn't have (iirc) much information in your desire to move the ellipse around the canvas. But, if you need to do this, you would have to move the GRID around the canvas. You can even move it off the canvas. For instance, if your grid is 100x100 and you need the ellipse centered at 0,0 on the canvas, the grid should start at -50, -50.
Will
Hi Will! Yeah, sorry 'bout that, I had forgotten to mention that requirement first and edited it in a bit later...
Jan
+1  A: 

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.

John Bowen
ScaleTransform basically scaled up everything I had, so my Adorners (which I left out of the code here for brevity) got bigger as I scaled, and also the stroke thickness (Border size) increased. I only want to resize the circle itself however, with stroke thickness and Adorner size left constant...
Jan
So from your responses it sounds like you want to set a variable center point for your Ellipse rather than Top,Left. You can get this effect with adding a Binding with a Converter to Margin on the AdornerDecorator to set the Margin to (-ActualWidth/2,-ActualHeight/2,0,0). This will essentially move the elements up and left to center over the point you set as Top,Left.
John Bowen
Hi John! Sounds interesting - thanks for your solution :) This might come in very helpful if Martin's solution using Bindings with Canvas.Top/.Left doesn't work for me...
Jan
A: 

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.

Quartermeister