tags:

views:

2146

answers:

4

Hi folks,

I am new to WPF, so the answer to the following question might be obvious, however it isn't to me. I need to display an image where users can set markers on (As an example: You might want to mark a person's face on a photograph with a rectangle), however the markers need to keep their relative position when scaling the image.

Currently I am doing this by using a Canvas and setting an ImageBrush as Background. This displays the image and I can add elements like a Label (as replacement for a rectangle) on top of the image. But when I set a label like this, it's position is absolute and so when the underlying picture is scaled (because the user drags the window larger) the Label stays at it's absolute position (say, 100,100) instead of moving to the new position that keeps it "in sync" with the underlying image.

To cut the matter short: When I set a marker on a person's eye, it shouldn't be on the person's ear after scaling the window.

Any suggestions on how to do that in WPF? Maybe Canvas is the wrong approach in the first place? I could keep a collection of markers in code and recalculate their position every time the window gets resized, but I hope there is a way to let WPF do that work for me :-)

I am interested in hearing your opinions on this. Thanks

+1  A: 

Of the top of my head you could write a converter class that would take in a percentage and return an absolute position. As an example if your window was 200 X 200 and you placed the label at 100 X 100 when you scale the window to 400 X 400 the label would stay where it is (as per your original question). However if you used a converter so that instead you could set the labels position to 50% of its parent container's size then as the window scaled the label would move with it.

You may also need to use the same converter for width and height so that it increased in size to match as well.

Sorry for the lack of detail, if I get a chance I'll edit this with example code in a little while.


Edited to add

This question gives some code for a percentage converter.

Martin Harris
Thanks, I'm giving it a shot
Masterfu
A: 

Okay that seems to work. Here's what I did:

  1. Wrote a custom converter
  2. Every time a user clicks on the canvas, I create a new Label (will exchange that with a UserComponent later), create bindings using my converter class and do the initial calculations to get the relative position to the canvas from the absolute position of the mouse pointer

Here's some sample code for the converter:

public PercentageConverter() { }

    /// <summary>
    /// Calculates absolute position values of an element given the dimensions of the container and the relative
    /// position of the element, expressed as percentage
    /// </summary>
    /// <param name="value">Dimension value of the container (width or height)</param>
    /// <param name="parameter">The percentage used to calculate new absolute value</param>
    /// <returns>parameter * value as Double</returns>
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        //input is percentage
        //output is double
        double containerValue = System.Convert.ToDouble(value, culture.NumberFormat);
        double perc;
        if (parameter is String)
        {
            perc = double.Parse(parameter as String, culture.NumberFormat);
        }
        else
        {
            perc = (double)parameter;
        }
        double coord = containerValue * perc;
        return coord;
    }

    /// <summary>
    /// Calculates relative position (expressed as percentage) of an element to its container given its current absolute position
    /// as well as the dimensions of the container
    /// </summary>
    /// <param name="value">Absolute value of the container (width or height)</param>
    /// <param name="parameter">X- or Y-position of the element</param>
    /// <returns>parameter / value as double</returns>
    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        //output is percentage
        //input is double
        double containerValue = System.Convert.ToDouble(value, culture.NumberFormat);
        double coord = double.Parse(parameter as String, culture.NumberFormat);
        double perc = coord / containerValue;
        return perc;
    }

And here's how you can create bindings in XAML (note that my canvas is declared as <Canvas x:Name="canvas" ... >):

<Label Background="Red" ClipToBounds="True" Height="22" Name="label1" Width="60"
           Canvas.Left="{Binding Converter={StaticResource PercentageConverter}, ElementName=canvas, Path=ActualWidth, ConverterParameter=0.25}"
           Canvas.Top="{Binding Converter={StaticResource PercentageConverter}, ElementName=canvas, Path=ActualHeight, ConverterParameter=0.65}">Marker 1</Label>

More useful, however, is to create Labels in code:

private void canvas_MouseDown(object sender, MouseButtonEventArgs e)
    {
        var mousePos = Mouse.GetPosition(canvas);
        var converter = new PercentageConverter();

        //Convert mouse position to relative position
        double xPerc = (double)converter.ConvertBack(canvas.ActualWidth, typeof(Double), mousePos.X.ToString(), Thread.CurrentThread.CurrentCulture);
        double yPerc = (double)converter.ConvertBack(canvas.ActualHeight, typeof(Double), mousePos.Y.ToString(), Thread.CurrentThread.CurrentCulture);

        Label label = new Label { Content = "Label", Background = (Brush)new BrushConverter().ConvertFromString("Red")};

        //Do binding for x-coordinates
        Binding posBindX = new Binding();
        posBindX.Converter = new PercentageConverter();
        posBindX.ConverterParameter = xPerc;
        posBindX.Source = canvas;
        posBindX.Path = new PropertyPath("ActualWidth");
        label.SetBinding(Canvas.LeftProperty, posBindX);

        //Do binding for y-coordinates
        Binding posBindY = new Binding();
        posBindY.Converter = new PercentageConverter();
        posBindY.ConverterParameter = yPerc;
        posBindY.Source = canvas;
        posBindY.Path = new PropertyPath("ActualHeight");
        label.SetBinding(Canvas.TopProperty, posBindY);

        canvas.Children.Add(label);
    }

So basically, it's almost like my first idea: Use relative position instead of absolute and recalculate all positions on every resize, only this way it's being done by WPF. Just what I wanted, thanks Martin!

Note however, that these examples only work if the Image inside the ImageBrush has exactly the same dimensions as the surrounding Canvas, because this relative positioning does not take margins etc into account. I will have to tune that

Masterfu
A: 

very very usefull answer. Just modify one line that PercentageConverter class should inherit IValueConverter. Thanks.

A: 

Hi, Is it possible to get this code as an example ?

Wim
Hi Wim, there is already code inside my last answer, do you need something else?
Masterfu