c# - Centro de texto en un punto determinado en un WPF Canvas
.net f# (3)
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?
He intentado configurar RenderTransform
y llamar a Controls.Canvas.SetLeft
etc., pero tampoco RenderTransform
la etiqueta donde la quiero. WPF parece ser compatible con 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 ActualWidth
es 0.0
cuando construyo el Canvas
.
Esto también funciona, con menos enlace.
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 usas así ...
label.SetValue(CenterOnPoint.CenterPointProperty, new Point(100, 100));
Puede lograr esto al vincular el margen de la etiqueta a la ActualWidth
y la ActualWidth
ActualHeight
de la etiqueta y multiplicar 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í hay 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>
El rectángulo rojo resalta la coordenada (40, 40) en la 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 así:
Para hacer eso programáticamente, defina una propiedad adjunta Mover.MoveToMiddle
, como esta:
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);
}
}
}
Establecer Mover.MoveToMiddle
en true
significa que el margen de ese elemento de marco se vincula automáticamente a su ancho y alto reales, de modo que el elemento de marco se mueva a su punto central.
Lo usarías en tu código XAML así:
<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 enlazar a RenderTransform
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 diseñador de Visual Studio (que no es el caso cuando se establece la propiedad Margen).
Lo siento, Jon. No entendí tu pregunta ayer en Twitter. ¡Así es como puedo probarlo en F #! @cammcad
#r @ "C: / Archivos de programa (x86) / Assemblies de referencia / Microsoft / Framework / v3.0 / PresentationFramework.dll" #r @ "C: / Archivos de programa (x86) / Assemblies de referencia / Microsoft / Framework / v3. 0 / WindowsBase.dll "#r @" C: / Archivos de programa (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