c# .net wpf f#

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