我是 WPF 的新手,所以以下问题的答案可能很明显,但对我来说不是。我需要显示一个图像,用户可以在其中设置标记(例如:您可能想用矩形在照片上标记一个人的脸),但是在缩放图像时标记需要保持它们的相对位置。
目前我通过使用 aCanvas并将a设置ImageBrush为背景来做到这一点。这将显示图像,我可以Label在图像顶部添加诸如 a (作为矩形的替代品)之类的元素。但是当我设置这样的标签时,它的位置是绝对的,所以当底层图片被缩放时(因为用户将窗口拖得更大)Label,它会保持在它的绝对位置(比如 100,100)而不是移动到新位置它与底层图像“同步”。
简而言之:当我在一个人的眼睛上设置一个标记时,缩放窗口后它不应该在这个人的耳朵上。
关于如何在 WPF 中做到这一点的任何建议?也许首先Canvas是错误的方法?我可以在代码中保留一组标记,并在每次调整窗口大小时重新计算它们的位置,但我希望有一种方法可以让 WPF 为我完成这项工作:-)
我很想听听您对此的意见。谢谢
虽然这篇文章很旧并且已经回答了,但它仍然可以对其他人有所帮助,所以我会添加我的答案。
我想出了两种方法来保持元素的相对位置 Canvas
这个想法是在 [0,1] 范围内提供两个值 (x,y),它们将定义元素相对于Canvas. 这些 (x,y) 值将用于计算和设置正确的Canvas.Left和Canvas.Top值。
为了放置中心的元素的相对位置,我们将需要ActualWidth和ActualHeight的Canvas 和元素。
多值转换器RelativePositionConverter:
当与Canvas.Left和绑定时,此转换器可用于相对定位 X 和/或 Y 位置Canvas.Top。
public class RelativePositionConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values?.Length < 2
|| !(values[0] is double relativePosition)
|| !(values[1] is double size)
|| !(parameter is string)
|| !double.TryParse((string)parameter, out double relativeToValue))
{
return DependencyProperty.UnsetValue;
}
return relativePosition * relativeToValue - size / 2;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Run Code Online (Sandbox Code Playgroud)
示例用法RelativePositionConverter:
甲Canvas宽度和高度绑定到一个Image。TheCanvas有一个子元素 - anEllipse与Canvas(and Image)保持相对位置。
<Grid Margin="10">
<Image x:Name="image" Source="Images/example-graph.png" />
<Canvas Background="#337EEBE8" Width="{Binding ElementName=image, Path=ActualWidth}" Height="{Binding ElementName=image, Path=ActualHeight}">
<Ellipse Width="35" Height="35" StrokeThickness="5" Fill="#D8FFFFFF" Stroke="#FFFBF73C">
<Canvas.Left>
<MultiBinding Converter="{StaticResource RelativePositionConverter}" ConverterParameter="0.461">
<Binding RelativeSource="{RelativeSource FindAncestor, AncestorType=Canvas}" Path="ActualWidth" />
<Binding RelativeSource="{RelativeSource Self}" Path="ActualWidth" />
</MultiBinding>
</Canvas.Left>
<Canvas.Top>
<MultiBinding Converter="{StaticResource RelativePositionConverter}" ConverterParameter="0.392">
<Binding RelativeSource="{RelativeSource FindAncestor, AncestorType=Canvas}" Path="ActualHeight" />
<Binding RelativeSource="{RelativeSource Self}" Path="ActualHeight" />
</MultiBinding>
</Canvas.Top>
</Ellipse>
</Canvas>
</Grid>
Run Code Online (Sandbox Code Playgroud)
附加属性RelativeXProperty,RelativeYProperty以及RelativePositionProperty:
RelativeXProperty并且RelativeYProperty可用于通过两个单独的附加属性控制 X 和/或 Y 相对定位。RelativePositionProperty 可用于通过单个附加属性控制 X 和 Y 相对定位。public static class CanvasExtensions
{
public static readonly DependencyProperty RelativeXProperty =
DependencyProperty.RegisterAttached("RelativeX", typeof(double), typeof(CanvasExtensions), new PropertyMetadata(0.0, new PropertyChangedCallback(OnRelativeXChanged)));
public static readonly DependencyProperty RelativeYProperty =
DependencyProperty.RegisterAttached("RelativeY", typeof(double), typeof(CanvasExtensions), new PropertyMetadata(0.0, new PropertyChangedCallback(OnRelativeYChanged)));
public static readonly DependencyProperty RelativePositionProperty =
DependencyProperty.RegisterAttached("RelativePosition", typeof(Point), typeof(CanvasExtensions), new PropertyMetadata(new Point(0, 0), new PropertyChangedCallback(OnRelativePositionChanged)));
public static double GetRelativeX(DependencyObject obj)
{
return (double)obj.GetValue(RelativeXProperty);
}
public static void SetRelativeX(DependencyObject obj, double value)
{
obj.SetValue(RelativeXProperty, value);
}
public static double GetRelativeY(DependencyObject obj)
{
return (double)obj.GetValue(RelativeYProperty);
}
public static void SetRelativeY(DependencyObject obj, double value)
{
obj.SetValue(RelativeYProperty, value);
}
public static Point GetRelativePosition(DependencyObject obj)
{
return (Point)obj.GetValue(RelativePositionProperty);
}
public static void SetRelativePosition(DependencyObject obj, Point value)
{
obj.SetValue(RelativePositionProperty, value);
}
private static void OnRelativeXChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (!(d is FrameworkElement element)) return;
if (!(VisualTreeHelper.GetParent(element) is Canvas canvas)) return;
canvas.SizeChanged += (s, arg) =>
{
double relativeXPosition = GetRelativeX(element);
double xPosition = relativeXPosition * canvas.ActualWidth - element.ActualWidth / 2;
Canvas.SetLeft(element, xPosition);
};
}
private static void OnRelativeYChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (!(d is FrameworkElement element)) return;
if (!(VisualTreeHelper.GetParent(element) is Canvas canvas)) return;
canvas.SizeChanged += (s, arg) =>
{
double relativeYPosition = GetRelativeY(element);
double yPosition = relativeYPosition * canvas.ActualHeight - element.ActualHeight / 2;
Canvas.SetTop(element, yPosition);
};
}
private static void OnRelativePositionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (!(d is FrameworkElement element)) return;
if (!(VisualTreeHelper.GetParent(element) is Canvas canvas)) return;
canvas.SizeChanged += (s, arg) =>
{
Point relativePosition = GetRelativePosition(element);
double xPosition = relativePosition.X * canvas.ActualWidth - element.ActualWidth / 2;
double yPosition = relativePosition.Y * canvas.ActualHeight - element.ActualHeight / 2;
Canvas.SetLeft(element, xPosition);
Canvas.SetTop(element, yPosition);
};
}
}
Run Code Online (Sandbox Code Playgroud)
的实施例的使用RelativeXProperty和RelativeYProperty:
<Grid Margin="10">
<Image x:Name="image" Source="Images/example-graph.png" />
<Canvas Background="#337EEBE8" Width="{Binding ElementName=image, Path=ActualWidth}" Height="{Binding ElementName=image, Path=ActualHeight}">
<Ellipse Width="35" Height="35" StrokeThickness="5" Fill="#D8FFFFFF" Stroke="#FFFBF73C"
local:CanvasExtensions.RelativeX="0.461"
local:CanvasExtensions.RelativeY="0.392">
</Ellipse>
</Canvas>
</Grid>
Run Code Online (Sandbox Code Playgroud)
示例用法RelativePositionProperty:
<Grid Margin="10">
<Image x:Name="image" Source="Images/example-graph.png" />
<Canvas Background="#337EEBE8" Width="{Binding ElementName=image, Path=ActualWidth}" Height="{Binding ElementName=image, Path=ActualHeight}">
<Ellipse Width="35" Height="35" StrokeThickness="5" Fill="#D8FFFFFF" Stroke="#FFFBF73C"
local:CanvasExtensions.RelativePosition="0.461,0.392">
</Ellipse>
</Canvas>
</Grid>
Run Code Online (Sandbox Code Playgroud)
和听到的是它的外观:
本Ellipse是一个孩子Canvas保持相对位置的Canvas(和Image)。

好吧,这似乎有效。这是我所做的:
以下是转换器的一些示例代码:
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;
}
}
Run Code Online (Sandbox Code Playgroud)
以下是在 XAML 中创建绑定的方法(请注意,我的画布被声明为<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>
Run Code Online (Sandbox Code Playgroud)
然而,更有用的是在代码中创建标签:
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);
}
Run Code Online (Sandbox Code Playgroud)
所以基本上,这几乎就像我的第一个想法:使用相对位置而不是绝对位置并在每次调整大小时重新计算所有位置,只有这样它才能由 WPF 完成。正是我想要的,谢谢马丁!
但是请注意,这些示例仅在内部的图像ImageBrush与周围的尺寸完全相同时才有效Canvas,因为这种相对定位不考虑边距等。我将不得不调整
| 归档时间: |
|
| 查看次数: |
9998 次 |
| 最近记录: |