Search code examples
wpflineitemtemplateitemsource

WPF: Dynamically created line does not appear at defined position


I have a class that records the startpoint and endpoint of a line. The elements of this class are added to an Observable Collection, which serves as the ItemsSource of my ItemsControl.

Here is the relevant part of the XAML:

<ItemsControl Name="PathControl">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Line
                                Name="Path"
                                Stroke="Red"
                                StrokeThickness="4"
                                X1="{Binding
                                    StartCoordinates.X,
                                    Mode=TwoWay,
                                    UpdateSourceTrigger=PropertyChanged}"

                                Y1="{Binding
                                    StartCoordinates.Y,
                                    Mode=TwoWay,
                                    UpdateSourceTrigger=PropertyChanged}"
                                X2="{Binding
                                    EndCoordinates.X,
                                    Mode=TwoWay,
                                    UpdateSourceTrigger=PropertyChanged}"

                                Y2="{Binding
                                    endCoordinates.Y,
                                    Mode=TwoWay,
                                    UpdateSourceTrigger=PropertyChanged}" />
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

And here is the corresponding class:

public class VisualPath: INotifyPropertyChanged
{

    public VisualPath(Point start, Point end)
    {
        this.startCoordinates = start;
        this.endCoordinates = end;
    }


    Point startCoordinates;
    public Point StartCoordinates
    {
        get
        {
            return startCoordinates;
        }

        set
        {
            startCoordinates = value;
            OnPropertyChanged();
        }
    }

    Point endCoordinates;
    public Point EndCoordinates
    {
        get
        {
            return endCoordinates;
        }

        set
        {
            endCoordinates = value;
            OnPropertyChanged();
        }
    }


    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

This is the relevant part of my MainWindow:

public MainWindow()
{          
    InitializeComponent();
    PathControl.ItemsSource = PathElements = new ObservableCollection<VisualPath>();           
}

I would like to create multiple pairs of buttons and connect them with the lines. To do this, I record the absolute position of two buttons relative to the grid with a click event. I store the position of the first button in a Position variable (tempPoint) and when I click on the second button (the target) a new line is supposed to be added to the collection (which happens) with the position of the 1st button as its X1 and Y1 (start point) from tempPoint and the position of the second button as its X2 and Y2 (end point).

Here are the methods handling the click events:

private void firstButton_Click(object sender, RoutedEventArgs e)
{
    Button pathInitiator = sender as Button;
    if (pathInitiator != null)
    {
        tempPoint = pathInitiator.TranslatePoint(new Point(0, 0), MainGrid);
    }
    else
    {
        MessageBox.Show("Not Button");
    }
}

private void secondButton_Click(object sender, RoutedEventArgs e)
{
    Button pathTerminator = sender as Button;
    if (pathTerminator != null)
    {
        GeneralTransform end = pathTerminator.TransformToVisual(this);
        Point endPoint = end.Transform(new Point(0, 0));
        PathElements.Add(new VisualPath(tempPoint, endPoint));
        //PathElements.Add(new VisualPath(new Point(0, 0), new Point(200, 200)));
    }

}

Although the position of the first instance of first button is usually captured correctly, the end point of the line is always misplaced and any other instances of the first button as well. The lines displayed always appear to have flipped or shifted, even when I tried to add multiple lines on top of each other (as shown by the lines commented out), the new lines always appear underneath the previous one. Is my wiring bad, or there is something I don't get with how lines work in WPF?

EDIT: Thanks to the comments, now the first instance of the pair works the way it should:

The edges of the buttons are connected by a line as expected

But any other instances of the pair are misplaced: The pair is not connected, although the very same method is used


Solution

  • If you want the lines to end up on top of each other you could set the ItemsPanelTemplate of the ItemsControl to a Grid:

    <ItemsControl Name="PathControl">
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <Grid />
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <StackPanel>
                    <Line
                            Name="Path"
                            Stroke="Red"
                            StrokeThickness="4"
                            X1="{Binding
                                StartCoordinates.X,
                                Mode=TwoWay,
                                UpdateSourceTrigger=PropertyChanged}"
    
                            Y1="{Binding
                                StartCoordinates.Y,
                                Mode=TwoWay,
                                UpdateSourceTrigger=PropertyChanged}"
                            X2="{Binding
                                EndCoordinates.X,
                                Mode=TwoWay,
                                UpdateSourceTrigger=PropertyChanged}"
    
                            Y2="{Binding
                                EndCoordinates.Y,
                                Mode=TwoWay,
                                UpdateSourceTrigger=PropertyChanged}" />
                    <TextBlock Text="{Binding EndCoordinates.Y}" />
                </StackPanel>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>