2012-03-19 16 views
15

Tengo un Controls.Canvas con varias formas y me gustaría agregar etiquetas de texto centradas en puntos determinados (estoy dibujando un árbol con vértices etiquetados). ¿Cuál es la forma más simple de hacer esto programáticamente en WPF?Centro de texto en un punto dado en un lienzo de WPF

He intentado configurar RenderTransform y llamando al Controls.Canvas.SetLeft etc. pero tampoco coloco la etiqueta donde la quiero. WPF parece admitir el posicionamiento solo en las coordenadas izquierda, derecha, superior e inferior y no centrada en una determinada coordenada, y la propiedad Width es NaN y la propiedad es 0.0 cuando construyo el Canvas.

Respuesta

14

Puede lograr esto vinculando el margen de la etiqueta a ActualWidth y ActualHeight de la etiqueta y multiplicando estos valores por -0.5. Esto mueve la etiqueta a la mitad de su ancho; y mueve la etiqueta hacia arriba a la mitad de su altura.

Aquí es un ejemplo:

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> 

Los aspectos más destacados rectángulo rojo la coordenada (40, 40) sobre el que se centra la etiqueta "MMMMMM".

Convertidor:

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

El resultado es el siguiente:

centered label

Con el fin de hacer que la programación, defina una propiedad adjunta Mover.MoveToMiddle, así:

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

} 

configuración Mover.MoveToMiddle a true significa que el margen de ese elemento de marco se vincula automáticamente a su ancho y altura real de modo que el elemento de marco se mueva a su punto central.

que usaría en su código XAML como esto:

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

Una alternativa sería la de obligar a RenderTransform en lugar de Margin. En este caso, el convertidor volvería

return new TranslateTransform(-width/2, -height/2); 

y el método de devolución de llamada de la propiedad adjunta contendría estas líneas:

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

Esta alternativa tiene la ventaja de que el efecto de la propiedad adjunta es visible en el Visual Diseñador de estudio (que no es el caso cuando se establece la propiedad Margen).

7

Esto también funciona, con menos encuadernación.

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

Y lo usa así ...

label.SetValue(CenterOnPoint.CenterPointProperty, new Point(100, 100)); 
+4

personas Puede ver estas respuestas y pensar que es demasiado complicado para los simples requisitos de la pregunta, pero esto no solo le muestra cómo centrar una etiqueta. Este código define un comportamiento que centrará cualquier 'FrameworkElement' en un punto y lo mantendrá centrado, incluso cuando el tamaño de ese elemento pueda estar cambiando. –

+0

Esto funciona realmente bien. Desde XAML el uso es: Observe la conversión automática de "100,100" a Point. –

+1

Funciona perfectamente, incluso con XAML que no sea WPF, p. Ej. Windows 10 universal, gracias! –

1

Lo siento Jon, no entendí tu pregunta ayer en Twitter. ¡Así es como puedo probarlo en F #! @cammcad

#r @ "C: \ Archivos de programa (x86) \ Referencia Asambleas \ Microsoft \ Framework \ v3.0 \ PresentationFramework.dll" #r @ "C: \ Archivos de programa (x86) \ Referencia asambleas \ Microsoft \ Framework \ v3.0 \ WindowsBase.dll" #r @ "C: \ archivos de programa (x86) \ Referencia asambleas \ 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 
Cuestiones relacionadas