user2654247 user2654247 - 1 month ago 22
C# Question

WPF Position TextBlock/Label on the Line center

Im trying to represent weighted graph with MVVM inside canvas
so im representing graphs vertices and edges as observable collections and putting them into canvas ItemsControl. But I cant find any reasonable way to position text that represents weight on the center of line(graphs edge)

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


What i want to get

Answer

Add to your EdgeItem class (or whatever's in your EdgeItems collection).

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

I'm assuming EdgeItem has a Weight property -- if not, Weight is a stand-in for whatever property you want to display in the center of the line. I would have thought Canvas.Left and Canvas.Top would work, but for me they don't in this case.

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

Second Method

Alternatively, don't add the Margin, Left, and Top properties to EdgeItem, and generate the margin Thickness with a value converter instead. I'm not crazy about having those properties on EdgeItem, but then again on the other hand, a value converter on {Binding} is a problem with raising PropertyChanged, if you happen to alter V1 or V2 at runtime. The solution to that would be to make it a multi value converter and bind V1 and V2 separately with a multi binding. I just got lazy about writing that 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>

Converter:

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