【问题标题】:Center text at a given point on a WPF Canvas在 WPF 画布上的给定点居中文本
【发布时间】:2012-03-19 21:32:49
【问题描述】:

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

我尝试设置RenderTransform 并调用Controls.Canvas.SetLeft 等,但都没有将标签放在我想要的位置。 WPF 似乎只支持在给定的左、右、顶和底坐标上定位,而不是以给定坐标为中心,Width 属性为NaNActualWidth 属性为0.0,当我构造Canvas 时.

【问题讨论】:

    标签: c# .net wpf f#


    【解决方案1】:

    您可以通过将标签的边距绑定到标签的ActualWidthActualHeight 并将这些值乘以-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 属性时不是这种情况)。

    【讨论】:

      【解决方案2】:

      这也有效,绑定更少。

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

      【讨论】:

      • 人们可能会看到这些答案并认为对于问题的简单要求来说太复杂了,但这不仅仅是向您展示如何使标签居中。这段代码定义了一种行为,它将任何FrameworkElement 居中并保持居中,即使该元素的大小可能发生变化。
      • 这很好用。从 XAML 中的用法是: 注意“100,100”到 Point 的自动转换。
      • 完美运行,即使使用非 WPF XAML,例如Windows 10 通用版,谢谢!
      • 这个解决方案也很容易适应其他目的,例如你可以在没有太多修改的情况下做一个“RightAlign”。
      【解决方案3】:

      抱歉,Jon,我昨天在 Twitter 上一直没听懂你的问题。 这是我在 F# 中尝试的方法! @cammcad

      #r @"C:\Program Files (x86)\Reference 程序集\Microsoft\Framework\v3.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
      

      【讨论】:

        【解决方案4】:

        要在给定区域(例如矩形)中居中文本,您可以简单地用Grid 包裹它。请参阅this answer 中的示例。可以使用LeftTopWidthHeight 属性将网格放置在画布内的任何位置。文本将始终位于网格的中心。

        这个逻辑可以封装在一个自定义的FrameworkElementlike 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) 是文本的最大允许大小。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-07-26
          • 1970-01-01
          • 2020-11-16
          • 1970-01-01
          • 2011-01-03
          相关资源
          最近更新 更多