Rakr
Rakr

Reputation: 125

Dynamically Create button with style: Specified element is already the logical child of another element. Disconnect it first

My application can navigate to 3 different pages that all are being displayed in 1 window (one at a time) . All the pages that get displayed in the same window are user controls.

Whenever i click button "A" a new button gets created and added to a stackpanel. So basically if i press 5 times i get 5 buttons nicely placed next to each other. The code below navigates the screen from page B back to A (assuming i am already on page B), here i call up a method that iterates trough the list of saved buttons.

To make it short i wish to save the items created in a list or something else so when this window gets loaded again i can simply load the values (buttons) back in.

Page B:

private void btnNoProduction_Click(object sender, RoutedEventArgs e)
{
    MainWindow main = new MainWindow();

    Switcher.Switch(main);       
    main.reloadDynamicRegistrationContent();
}

Page A (mainwindow) where i iterate over the list with the saved buttons.

public void reloadDynamicRegistrationContent()
{
    for (int i = 0; i < GlobalVar._listReg.Count; i++)
    {
        spHorizontal.Children.Add(GlobalVar._listReg[i]);  //ERROR Specified Element is already the logical.....
    }
}

I need some sort of way to save these buttons (List but i keep getting the error Specified element is already the logical child of another element. Disconnect it first. wpf. It fails when i try to access the list from the static class.

Code to create a button dynamically:

private void btnTestRegistration_Click(object sender, RoutedEventArgs e)
{
    Button newBtn = new Button();
    newBtn.Name = "btnRegistration" + registrationCount.ToString();
    newBtn.Width = 151;
    newBtn.Height = 73;
    newBtn.Click += new RoutedEventHandler(this.newBtn_Click);

    Thickness margin = newBtn.Margin;
    margin.Left = 10;
    margin.Right = 5;
    newBtn.Margin = margin;

    Style style = this.FindResource("ButtonStyleRegistration") as Style;
    newBtn.Style = style;

    //force build template
    newBtn.ApplyTemplate();

    var tb = VisualTreeNavigator.FindVisualChild<TextBlock>(newBtn, "tbRegistration1");
    tb.Text = "text cause 1 " + registrationCount.ToString();

    var tb2 = VisualTreeNavigator.FindVisualChild<TextBlock>(newBtn, "tbRegistration2");
    tb2.Text = "text cause 2 " + registrationCount.ToString();

    spHorizontal.Children.Add(newBtn);
    GlobalVar.AddButtonToList(newBtn); 
    registrationCount++;
}

So far everything gets added properly but i am unable to save the bunch of buttons that got created whenever i wish to return to this screen later on.

GlobalVar is a static class where i have a list. Here i attempt to save the contents of all the buttons that are being created in a list.

 public class GlobalVar
{
    public static List<Button> _listReg = new List<Button>();

    public static void AddButtonToList(Button b)
    {
        _listReg.Add(b);
    }
}

Upvotes: 0

Views: 910

Answers (1)

Uri London
Uri London

Reputation: 10797

In WPF each UI element can participate only once in any logical tree. You cannot have a <Button> element as a child (Child property, element in Children, Content property etc.) more than once.

It appears that MainWindow hasn't been disposed. Even if you are disposing it (i.e. MainWindow goes out of scope, and you close it) - which I'm not sure you do from your code - it isn't necessarily disposed as the garbage collection doesn't necessarily dispose the elements immediatley. There is also a possibility that the buttons keeps reference to their parent window (the spHorizontal, and eventually all the way to MainWindow) and since GlobalVar is kept alive - the MainWindow is never being disposed at all, and you actually have a leak here.

If you aren't dismissing the main window at all, and simply trying to have multiple copies of the MainWindow running simultanously then what you are trying to do is impossible. You'll have to serialize the list of buttons somehow (basically, the text you put on the Text Boxes), and then create new buttons with the same text.

If you are dismissing the MainWindow, then before closing the Window make sure to remove the list of buttons from spHorizontal, using spHorizontal.Children.Remove

Saying all that, your program has numerous opportunities for improvements. Specifically - why not just hiding the main window (this.IsVisible=false), and then showing it again when necessary? Or, why wouldn't you store spHorizontal in your GlobalVar singleton (after removing it from the tree).

Also, you'll be much better off having all the properties set in btnTestRegistration_Click method part of the Style which you apply to the button. I'm not a anti code-behind fanatic, but this is clearly a place where you should use XAML.

Heck, if that was my code, I would just had an ObservableCollection with list of items (each item represent a button), then a <ItemsControl> bound to the collection, and setting ItemsPanelTemplate to be a StackPanel.

<ItemsControl ItemsSource="...">
  <!-- Items Source above should point to the list of logical items representing the buttons. -->
  <ItemsControl.ItemsPanel>
    <ItemsPanelTemplate>
      <!-- this is the spHorizontal that you have -->
      <StackPanel Orientation="Vertical" />
    </ItemsPanelTemplate>
  </ItemsControl.ItemsPanel>
  <ItemsControl.ItemTemplate>
    <DataTemplate>
      <!-- this is a template for the button: -->
      <Button ... />
    </DataTemplate>
  </ItemsControl.ItemTemplate>
</ItemsControl>

Upvotes: 1

Related Questions