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.
我是WPF的新手,所以下面问题的答案可能很明显,但不是我。我需要显示一个图像,用户可以在其上设置标记(例如:您可能希望在带有矩形的照片上标记人物的脸部),但标记需要在缩放图像时保持其相对位置。
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.
目前我通过使用Canvas并将ImageBrush设置为Background来实现此目的。这将显示图像,我可以在图像顶部添加像Label(作为矩形的替换)之类的元素。但是当我设置这样的标签时,它的位置是绝对的,所以当底层图片缩放时(因为用户拖动窗口更大),Label停留在它的绝对位置(比如100,100)而不是移动到新的位置使其与底层图像保持“同步”。
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 :-)
有关如何在WPF中执行此操作的任何建议?也许Canvas首先是错误的做法?我可以在代码中保留一组标记,并在每次调整窗口大小时重新计算它们的位置,但我希望有一种方法可以让WPF为我工作:-)
I am interested in hearing your opinions on this. Thanks
我有兴趣听取你对此的意见。谢谢
3 个解决方案
#1
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.
在我的头脑中你可以写一个转换器类,它将占用一个百分比并返回一个绝对位置。例如,如果您的窗口是200 X 200,并且在将窗口缩放到400 X 400时将标签放置在100 X 100,则标签将保持原样(根据您的原始问题)。但是,如果您使用转换器,那么您可以将标签位置设置为其父容器大小的50%,然后随着窗口缩放,标签将随之移动。
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.
这个问题给出了百分比转换器的一些代码。
#2
Okay that seems to work. Here's what I did:
好吧,这似乎有效。这是我做的:
- Wrote a custom converter
- 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
写了一个自定义转换器
每次用户点击画布时,我都会创建一个新的Label(稍后会与UserComponent交换),使用我的转换器类创建绑定并进行初始计算以从鼠标的绝对位置获取画布的相对位置指针
Here's some sample code for the converter:
以下是转换器的一些示例代码:
public class PercentageConverter : IValueConverter
{
/// <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" ... >
):
以下是如何在XAML中创建绑定(请注意,我的画布声明为
<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!
所以基本上,它几乎就像我的第一个想法:使用相对位置而不是绝对位置并重新计算每个调整大小的所有位置,只有这样才能由WPF完成。正是我想要的,谢谢马丁!
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
但请注意,这些示例仅在ImageBrush内的Image与周围Canvas具有完全相同的尺寸时才有效,因为此相对定位不考虑边距等。我将不得不调整它
#3
very very usefull answer. Just modify one line that PercentageConverter class should inherit IValueConverter. Thanks.
非常有用的答案。只需修改PercentageConverter类应该继承IValueConverter的一行。谢谢。
#1
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.
在我的头脑中你可以写一个转换器类,它将占用一个百分比并返回一个绝对位置。例如,如果您的窗口是200 X 200,并且在将窗口缩放到400 X 400时将标签放置在100 X 100,则标签将保持原样(根据您的原始问题)。但是,如果您使用转换器,那么您可以将标签位置设置为其父容器大小的50%,然后随着窗口缩放,标签将随之移动。
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.
这个问题给出了百分比转换器的一些代码。
#2
Okay that seems to work. Here's what I did:
好吧,这似乎有效。这是我做的:
- Wrote a custom converter
- 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
写了一个自定义转换器
每次用户点击画布时,我都会创建一个新的Label(稍后会与UserComponent交换),使用我的转换器类创建绑定并进行初始计算以从鼠标的绝对位置获取画布的相对位置指针
Here's some sample code for the converter:
以下是转换器的一些示例代码:
public class PercentageConverter : IValueConverter
{
/// <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" ... >
):
以下是如何在XAML中创建绑定(请注意,我的画布声明为
<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!
所以基本上,它几乎就像我的第一个想法:使用相对位置而不是绝对位置并重新计算每个调整大小的所有位置,只有这样才能由WPF完成。正是我想要的,谢谢马丁!
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
但请注意,这些示例仅在ImageBrush内的Image与周围Canvas具有完全相同的尺寸时才有效,因为此相对定位不考虑边距等。我将不得不调整它
#3
very very usefull answer. Just modify one line that PercentageConverter class should inherit IValueConverter. Thanks.
非常有用的答案。只需修改PercentageConverter类应该继承IValueConverter的一行。谢谢。