WPF расположить текстовый блок / метку по центру строки

#c# #wpf #xaml #canvas #binding

#c# #wpf #xaml #холст #привязка

Вопрос:

Я пытаюсь представить взвешенный граф с MVVM внутри canvas, поэтому я представляю вершины и ребра графов как наблюдаемые коллекции и помещаю их в canvas ItemsControl. Но я не могу найти какой-либо разумный способ расположить текст, представляющий вес, по центру строки (край графика)

мой canvas xaml:

 <Canvas Background="Linen" ClipToBounds="True"
            Grid.Row="0" Grid.Column="0">
        <ItemsControl ItemsSource="{Binding EdgeItems}">
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <Canvas/>
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Line Stroke="Black" StrokeThickness="4" 
                          X1="{Binding V1.X}" Y1="{Binding V1.Y}"
                          X2="{Binding V2.X}" Y2="{Binding V2.Y}"/>                                                        
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </Canvas>
  

Что я хочу получить

Ответ №1:

Добавьте в свой EdgeItem класс (или что-то еще в вашей EdgeItems коллекции).

     //  When V1 or V2 changes, raise PropertyChanged("Margin")
    public Thickness Margin => new Thickness(Left, Top, 0, 0);
    public double Left => Math.Min(V1.X, V2.X);
    public double Top => Math.Min(V1.Y, V2.Y);
  

Я предполагаю, что EdgeItem имеет Weight свойство — если нет, Weight является заменой для любого свойства, которое вы хотите отобразить в центре строки. Я бы подумал, что Canvas.Left и Canvas.Top это сработает, но для меня в данном случае это не так.

 <ItemsControl.ItemTemplate>
    <DataTemplate>
        <Grid>
            <Line 
                Stroke="Black" 
                StrokeThickness="4" 
                X1="{Binding V1.X}" 
                Y1="{Binding V1.Y}"
                X2="{Binding V2.X}" 
                Y2="{Binding V2.Y}"
                />
            <Label 
                Background="#ccffffff"
                Content="{Binding Weight}" 
                Margin="{Binding Margin}"
                HorizontalAlignment="Center" 
                VerticalAlignment="Center" 
                />
        </Grid>
    </DataTemplate>
</ItemsControl.ItemTemplate>
  

Второй способ

В качестве альтернативы, не добавляйте Margin свойства Left Top , EdgeItem и в Thickness , а вместо этого сгенерируйте поле, используя преобразователь значений. Я не в восторге от включения этих свойств EdgeItem , но опять же, с другой стороны, при включенном преобразователе значений {Binding} возникает проблема с повышением PropertyChanged , если вы случайно измените V1 или V2 во время выполнения. Решением этого было бы сделать его преобразователем нескольких значений и привязать V1 и V2 отдельно с помощью множественной привязки. Я просто поленился писать этот XAML.

 <ItemsControl.ItemTemplate>
    <DataTemplate>
        <Grid>
            <Line 
                Stroke="Black" 
                StrokeThickness="4" 
                X1="{Binding V1.X}" 
                Y1="{Binding V1.Y}"
                X2="{Binding V2.X}" 
                Y2="{Binding V2.Y}"
                />
            <Label 
                Background="#ccffffff"
                Content="{Binding Weight}" 
                Margin="{Binding Converter={local:EdgeItemMargin}}"
                HorizontalAlignment="Center" 
                VerticalAlignment="Center" 
                />
        </Grid>
    </DataTemplate>
</ItemsControl.ItemTemplate>
  

Конвертер:

 public class EdgeItemMargin : MarkupExtension, IValueConverter
{
    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        return this;
    }

    public object Convert(
        object value, 
        Type targetType, 
        object parameter, 
        CultureInfo culture)
    {
        var edge = (EdgeItem)value;

        return new Thickness(
            Math.Min(edge.V1.X, edge.V2.X), 
            Math.Min(edge.V1.Y, edge.V2.Y), 
            0, 0);
    }

    public object ConvertBack(
        object value, 
        Type targetType, 
        object parameter, 
        CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}
  

Комментарии:

1. Просто любопытно: почему вы выводите EdgeItemMargin из формы MarkupExtension?

2. Привычка @HenrikHansen. Таким образом, им можно присвоить именованные и типизированные (но не привязанные) параметры, хотя в данном случае в этом нет необходимости.