-2

I am developing a WPF in C# and I want to draw a specific line multiple times, without losing its previous trails. I have 10 buttons inside my Gridand its time I press one I want a line to be drawn. For the line, I use the var redLine and each time I press a button, it receives a specific pair of coordinates.

I use this code to draw the line:

public partial class MainWindow : Window {

    private Line redLine = new Line();
    SolidColorBrush redBrush = new SolidColorBrush(Colors.Red);

    public MainWindow()
    {
        redLine.StrokeThickness = 4;
        redLine.Stroke = redBrush;
    }

   private void button1_Click(object sender, RoutedEventArgs e) {
       redLine.X1 = 237; 
       redLine.Y1 = 382;
       redLine.X2 = 288;
       redLine.Y2 = 409;
       //draw the line
       MainGrid.Children.Add(redLine);
   }

   private void button2_Click(object sender, RoutedEventArgs e) {
       redLine.X1 = 130; 
       redLine.Y1 = 323;
       redLine.X2 = 238;
       redLine.Y2 = 690;
       //draw the line
       MainGrid.Children.Add(redLine);
   }
} 

But every time I press button1 and then button2 I get this error (it also happens for the rest of the buttons):

ERROR Specified Visual is already a child of another Visual or the root of a CompositionTarget.

I do want to keep both lines and NOT to remove the first one in order to draw the second. Any ideas on how to solve it?

NOTE I do not want to declare each line (there are about 11 lines in the whole program) inside each buttonX_Click method.

Haris H
  • 125
  • 2
  • 15
  • _"I do not want to declare each line (there are about 11 lines in the whole program) inside each buttonX_Click method."_ Why not? It would probably solve the problem. Is it purely so you don't have to repeat the instantiation / setting of width/brush etc? – James Thorpe Feb 16 '17 at 15:53
  • 1
    Shouldn't it be `CheckBox`es to show/hide lines? In WPF you can use data templates to visualize collection of items (in your case lines). See [this](http://stackoverflow.com/a/23564452/1997232). Simply add new items to collection for new lines to appears in the view. – Sinatr Feb 16 '17 at 15:58
  • @JamesThorpe I don't find it "correct" from a programming aspect to declare each line over and over again. – Haris H Feb 16 '17 at 17:17
  • I didn't say it was the end solution, I was just wondering if that was the reason for not redeclaring it. If that _is_ the reason, there's other methods around, like having each button invoke a function providing just the coordinates, and have that single function create the line, set the properties and add it. – James Thorpe Feb 16 '17 at 17:19
  • @Sinatr I am trying to create sth similar to an "image map". So I don't want a CheckBox, as it will be always displayed, unlike buttons. – Haris H Feb 16 '17 at 17:20
  • @JamesThorpe The problem still occurs. I tried creating a new method that receives a Line and draws it, but I get the same error. – Haris H Feb 16 '17 at 17:26

2 Answers2

1

The error message is pretty clear, the Line is already a child element of MainGrid. You can't add it a second time.

You'll have to create a new Line before adding it to MainGrid:

private void button1_Click(object sender, RoutedEventArgs e)
{
    var newLine = new Line
    {
       Stroke = redBrush,
       StrokeThickness = 4,
       X1 = 237,
       Y1 = 382,
       X2 = 288,
       Y2 = 409
    };
    MainGrid.Children.Add(newLine);
}

Obviously you also don't need the private Line redLine member any more.

Clemens
  • 123,504
  • 12
  • 155
  • 268
  • I know it is a child element of MainGrid. But I was hoping to avoid creating a new line each time (as I mentioned there are 11 lines used many times among the 10 buttons. Isn't anything else I can do? – Haris H Feb 16 '17 at 17:15
  • You can't add a single Line element twice to any parent control. There are however other ways to "draw" things in WPF. Start reading here: [Shapes and Basic Drawing in WPF Overview](https://msdn.microsoft.com/en-us/library/ms747393(v=vs.110).aspx). – Clemens Feb 16 '17 at 17:28
0

I suggest following this example's approach (MVVM). It adds new lines randomly, by clicking a Button.

Main Window

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        DataContext = new ViewModelLine();
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        ViewModelLine vm = (ViewModelLine)DataContext;

        // use other information and decide how you want to add the line
        Random ran = new Random();
        vm.Models.Add(new ModelLine() { X1=ran.Next(1,600), X2= ran.Next(1, 600), Y1= ran.Next(1, 600), Y2= ran.Next(1, 600) });
    }
}

View

<DockPanel >
    <Button Content="Add a New Line" DockPanel.Dock="Top" Click="Button_Click"/>
    <ItemsControl DockPanel.Dock="Bottom" DataContext="{Binding}" ItemsSource="{Binding Models}">
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <Line X1="{Binding X1}" X2="{Binding X2}" Y1="{Binding Y1}" Y2="{Binding Y2}" 
                      Stroke="Black" StrokeThickness="2"/>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <Canvas />
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
    </ItemsControl>
</DockPanel> 

View Model

public class ViewModelLine
{
    public ViewModelLine()
    {
        _models = new ObservableCollection<ModelLine>(); 
    }
    ObservableCollection<ModelLine> _models;
    public ObservableCollection<ModelLine> Models { get { return _models; } set { _models = value; } }

}

Model

public class ModelLine : INotifyPropertyChanged
{ 
    int _x1;
    public int X1 { get { return _x1; } set { _x1 = value; RaisePropertyChanged("X1"); } }

    int _x2;
    public int X2 { get { return _x2; } set { _x2 = value; RaisePropertyChanged("X2"); } }

    int _y1;
    public int Y1 { get { return _y1; } set { _y1 = value; RaisePropertyChanged("Y1"); } }

    int _y2;
    public int Y2 { get { return _y2; } set { _y2 = value; RaisePropertyChanged("Y2"); } }

    public event PropertyChangedEventHandler PropertyChanged;
    void RaisePropertyChanged(string propname)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propname));
    }
}

Note that the ModelLine can contain other properties such as Stroke and Thickness.

rmojab63
  • 3,513
  • 1
  • 15
  • 28
  • Thanks for your effort and time. I will just declare each line in each separate buttonX_click method – Haris H Feb 17 '17 at 09:34