C# 将文本居中放置在WPF画布上的给定点上

C# 将文本居中放置在WPF画布上的给定点上,c#,.net,wpf,f#,C#,.net,Wpf,F#,我有一个Controls.Canvas,上面有几个形状,我想添加以给定点为中心的文本标签(我正在绘制一个带有标签顶点的树)。在WPF中以编程方式实现这一点的最简单方法是什么 我尝试过设置RenderTransform并调用Controls.Canvas.SetLeft等,但都没有将标签放置在我想要的位置。WPF似乎只支持在给定的左、右,顶部和底部坐标,不以给定坐标为中心,Width属性为NaN,ActualWidth属性为0.0当我构建画布时,可以通过将标签的边距绑定到ActualWidth来实

我有一个
Controls.Canvas
,上面有几个形状,我想添加以给定点为中心的文本标签(我正在绘制一个带有标签顶点的树)。在WPF中以编程方式实现这一点的最简单方法是什么


我尝试过设置
RenderTransform
并调用
Controls.Canvas.SetLeft
等,但都没有将标签放置在我想要的位置。WPF似乎只支持在给定的左、右,顶部和底部坐标,不以给定坐标为中心,
Width
属性为
NaN
ActualWidth
属性为
0.0
当我构建
画布时,可以通过将标签的边距绑定到
ActualWidth
来实现这一点标签的实际高度,并将这些值乘以-0.5。这会将标签向左移动一半宽度;它会将标签向上移动一半高度

以下是一个例子:

XAML:

结果如下所示:

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);
        }
    }

}
<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>

为了以编程方式执行此操作,请定义一个附加属性
Mover.movetomidle
,如下所示:

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);
        }
    }

}
<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>
Mover.movetomidle
设置为
true
意味着该框架元素的边距将自动绑定到其实际宽度和高度,以便将框架元素移动到其中心点

您可以在XAML代码中使用它,如下所示:

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);
        }
    }

}
<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>
附加属性的回调方法将包含以下行:

if ((bool)e.NewValue)
{
    ...
    element.SetBinding(UIElement.RenderTransformProperty, multiBinding);
}
else
{
    element.ClearValue(UIElement.RenderTransformProperty);
}

此替代方法的优点是,附加属性的效果在Visual Studio designer中可见(设置边距属性时不是这种情况)。

这也有效,绑定较少

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));

对不起,乔恩,我昨天在推特上一直不明白你的问题。 下面是我可以在F#中尝试的方法@卡姆卡德

#r@“C:\ProgramFiles(x86)\Reference 程序集\Microsoft\Framework\v3.0\PresentationFramework.dll“ #r@“C:\Program Files(x86)\Reference Assembly\Microsoft\Framework\v3.0\WindowsBase.dll” #r@“C:\Program Files(x86)\Reference Assembly\Microsoft\Framework\v3.0\PresentationCore.dll”

开放系统
开放系统
开放系统
打开System.Windows.Shapes
打开System.Windows.Media
打开System.Windows.Controls
打开System.Windows.Markup
开放系统.Xml
(*在特定位置向画布添加形状和标签*)
让addShapeAndLabel_在_坐标(标签:字符串)(坐标:float*float)(c:Canvas)=
让btn=按钮(内容=标签,前景=SolidColorBrush(Colors.White))
让模板=
"" +
"" +
" " +
"  " + 
"" +
""
模板控制模板
c、 Children.Add(btn)|>忽略
设textsize=
格式化文本(标签,CultureInfo.GetCultureInfo(“enus”),
FlowDirection.LeftToRight,字体(“Verdana”),32.0,画笔。白色)
|>乐趣x->x.MinWidth,x.LineHeight
左上=坐标
让中间点宽度=fst(textsize)/2.0
让中间点高度=snd(textsize)/2.0
Canvas.SetLeft(btn,左-中间点宽度)
Canvas.SetTop(btn,顶部-中间点高度)
让壳=新窗(宽度=300.0,高度=300.0)
让画布=新画布(宽度=300.0,高度=300.0,背景=SolidColorBrush(Colors.Green))
在坐标“树节点1”(100.0,50.0)画布上添加ShapeAndLabel_
在坐标“树节点2”(150.0,75.)画布上添加形状标签

shell.Content要将文本置于给定区域(比如矩形)的中心,只需通过
网格将其包裹起来即可。请参见中的示例。可以使用
Left
Top
Width
Height
属性将网格定位在画布内的任何位置。文本将始终位于网格的中心

此逻辑可以封装在自定义FrameworkElement中

要将文本居中放置在点
(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)
是允许的最大文本大小

人们可能会看到这些答案,认为这对于问题的简单要求来说太复杂了,但这不仅仅是向你展示如何将标签居中。此代码定义的行为将使任何
FrameworkElement
集中在一个点上,并使其保持集中,即使该元素的大小可能会发生变化。这非常有效。从XAML的用法是:注意“100100”到Point的自动转换。工作非常完美,即使使用非WPF XAML,例如Windows 10 universal,谢谢!此解决方案还可以轻松地用于其他目的,例如,您可以在不做太多修改的情况下执行“RightAlign”。