我有一个Controls.Canvas
,上面有几个形状,并希望添加以给定点为中心的文本标签(我正在绘制带有标记顶点的树)。在WPF中以编程方式执行此操作的最简单方法是什么?
我已尝试设置RenderTransform
并致电Controls.Canvas.SetLeft
等,但未将标签置于我想要的位置。 WPF似乎仅支持在给定的左,右,顶部和底部坐标处定位,而不是以给定坐标为中心,Width
属性为NaN
且ActualWidth
属性为{{1}当我构建0.0
。
答案 0 :(得分:15)
您可以通过将标签的边距绑定到标签的ActualWidth
和ActualHeight
,并将这些值乘以-0.5来实现此目的。这会使标签向左移动一半宽度;它将标签向上移动一半高度。
以下是一个例子:
XAML:
<Window x:Class="CenteredLabelTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:CenteredLabelTest"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:CenterConverter x:Key="centerConverter"/>
</Window.Resources>
<Canvas>
<TextBlock x:Name="txt" Canvas.Left="40" Canvas.Top="40" TextAlignment="Center" Text="MMMMMM">
<TextBlock.Margin>
<MultiBinding Converter="{StaticResource centerConverter}">
<Binding ElementName="txt" Path="ActualWidth"/>
<Binding ElementName="txt" Path="ActualHeight"/>
</MultiBinding>
</TextBlock.Margin>
</TextBlock>
<Rectangle Canvas.Left="39" Canvas.Top="39" Width="2" Height="2" Fill="Red"/>
</Canvas>
</Window>
红色矩形突出显示标签“MMMMMM”居中的坐标(40,40)。
转换器:
public class CenterConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values[0] == DependencyProperty.UnsetValue || values[1] == DependencyProperty.UnsetValue)
{
return DependencyProperty.UnsetValue;
}
double width = (double) values[0];
double height = (double)values[1];
return new Thickness(-width/2, -height/2, 0, 0);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
结果如下:
为了以编程方式执行此操作,请定义附加属性Mover.MoveToMiddle
,如下所示:
public class Mover : DependencyObject
{
public static readonly DependencyProperty MoveToMiddleProperty =
DependencyProperty.RegisterAttached("MoveToMiddle", typeof (bool), typeof (Mover),
new PropertyMetadata(false, PropertyChangedCallback));
public static void SetMoveToMiddle(UIElement element, bool value)
{
element.SetValue(MoveToMiddleProperty, value);
}
public static bool GetMoveToMiddle(UIElement element)
{
return (bool) element.GetValue(MoveToMiddleProperty);
}
private static void PropertyChangedCallback(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
FrameworkElement element = sender as FrameworkElement;
if (element == null)
{
return;
}
if ((bool)e.NewValue)
{
MultiBinding multiBinding = new MultiBinding();
multiBinding.Converter = new CenterConverter();
multiBinding.Bindings.Add(new Binding("ActualWidth") {Source = element});
multiBinding.Bindings.Add(new Binding("ActualHeight") {Source = element});
element.SetBinding(FrameworkElement.MarginProperty, multiBinding);
}
else
{
element.ClearValue(FrameworkElement.MarginProperty);
}
}
}
将Mover.MoveToMiddle
设置为true
意味着该框架元素的边距自动绑定到其实际宽度和高度,以便框架元素移动到其中心点。
您可以在XAML代码中使用它,如下所示:
<Window x:Class="CenteredLabelTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:CenteredLabelTest"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:CenterConverter x:Key="centerConverter"/>
</Window.Resources>
<Canvas>
<TextBlock Canvas.Left="40" Canvas.Top="40" TextAlignment="Center" Text="MMMMMM"
local:Mover.MoveToMiddle="True"/>
<Rectangle Canvas.Left="39" Canvas.Top="39" Width="2" Height="2" Fill="Red"/>
</Canvas>
</Window>
另一种方法是绑定到RenderTransform
而不是Margin
。在这种情况下,转换器将返回
return new TranslateTransform(-width / 2, -height / 2);
并且附加属性的回调方法将包含以下行:
if ((bool)e.NewValue)
{
...
element.SetBinding(UIElement.RenderTransformProperty, multiBinding);
}
else
{
element.ClearValue(UIElement.RenderTransformProperty);
}
此替代方案的优点是附加属性的效果在Visual Studio设计器中可见(在设置Margin属性时不是这种情况)。
答案 1 :(得分:11)
这也有效,但约束力较小。
public class CenterOnPoint
{
public static readonly DependencyProperty CenterPointProperty =
DependencyProperty.RegisterAttached("CenterPoint", typeof (Point), typeof (CenterOnPoint),
new PropertyMetadata(default(Point), OnPointChanged));
public static void SetCenterPoint(UIElement element, Point value)
{
element.SetValue(CenterPointProperty, value);
}
public static Point GetCenterPoint(UIElement element)
{
return (Point) element.GetValue(CenterPointProperty);
}
private static void OnPointChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var element = (FrameworkElement)d;
element.SizeChanged -= OnSizeChanged;
element.SizeChanged += OnSizeChanged;
var newPoint = (Point)e.NewValue;
element.SetValue(Canvas.LeftProperty, newPoint.X - (element.ActualWidth / 2));
element.SetValue(Canvas.TopProperty, newPoint.Y - (element.ActualHeight / 2));
}
private static void OnSizeChanged(object sender, SizeChangedEventArgs e)
{
var element = (FrameworkElement) sender;
var newPoint = GetCenterPoint(element);
element.SetValue(Canvas.LeftProperty, newPoint.X - (e.NewSize.Width / 2));
element.SetValue(Canvas.TopProperty, newPoint.Y - (e.NewSize.Height / 2));
}
}
你就这样使用它......
label.SetValue(CenterOnPoint.CenterPointProperty, new Point(100, 100));
答案 2 :(得分:1)
对不起Jon,我昨天在Twitter上一直都不明白你的问题。 这是我在F#中尝试的方法! @cammcad
#r @“C:\ Program Files(x86)\ Reference 大会\微软\框架\ 3.0 \ PresentationFramework.dll” #r @“C:\ Program Files(x86)\ Reference Assemblies \ Microsoft \ Framework \ v3.0 \ WindowsBase.dll” #r @“C:\ Program Files(x86)\ Reference Assemblies \ Microsoft \ Framework \ v3.0 \ PresentationCore.dll”
open System
open System.IO
open System.Windows
open System.Windows.Shapes
open System.Windows.Media
open System.Windows.Controls
open System.Windows.Markup
open System.Xml
(* Add shape and label to canvas at specific location *)
let addShapeAndLabel_at_coordinate (label: string) (coordinate: float * float) (c: Canvas) =
let btn = Button(Content=label,Foreground=SolidColorBrush(Colors.White))
let template =
"<ControlTemplate xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'
TargetType=\"Button\">" +
"<Grid>" +
" <Ellipse Width=\"15\" Height=\"15\" Fill=\"Orange\" HorizontalAlignment=\"Center\"/>" +
" <ContentPresenter HorizontalAlignment=\"Center\" " + "VerticalAlignment=\"Center\"/> " +
"</Grid>" +
"</ControlTemplate>"
btn.Template <- XamlReader.Parse(template) :?> ControlTemplate
c.Children.Add(btn) |> ignore
let textsize =
FormattedText(label,CultureInfo.GetCultureInfo("enus"),
FlowDirection.LeftToRight,Typeface("Verdana"),32.0,Brushes.White)
|> fun x -> x.MinWidth, x.LineHeight
let left,top = coordinate
let middle_point_width = fst(textsize) / 2.0
let middle_point_height = snd(textsize) / 2.0
Canvas.SetLeft(btn,left - middle_point_width)
Canvas.SetTop(btn,top - middle_point_height)
let shell = new Window(Width=300.0,Height=300.0)
let canvas = new Canvas(Width=300.0,Height=300.0,Background=SolidColorBrush(Colors.Green))
addShapeAndLabel_at_coordinate "Tree Node 1" (100.0,50.0) canvas
addShapeAndLabel_at_coordinate "TreeNode 2" (150.0, 75.) canvas
shell.Content <- canvas
[<STAThread>] ignore <| (new Application()).Run shell
答案 3 :(得分:0)
要在给定区域(例如,矩形)中居中放置文本,只需用Grid
将其换行即可。请参见this answer中的示例。可以使用Left
,Top
,Width
和Height
属性将网格放置在画布内的任何位置。文本将始终位于网格的中心。
此逻辑可以封装在自定义的FrameworkElement like this中。
要使文本在点(x, y)
处居中,您可以计算适当的矩形:
var text = new CenteredTextBlock
{
Text = "Hello",
Width = maxWidth,
Height = maxHeight,
};
Canvas.SetLeft(text, x - maxWidth / 2);
Canvas.SetTop(text, y - maxHeight / 2);
Canvas.Children.Add(text);
其中(maxWidth, maxHeight)
是文本允许的最大大小。