Check if specified element is already the logical child of another element

4.5k Views Asked by At

I a beginner in C# and WPF. I'm programming plugin for a node based software called vvvv. I have implemented sliders, buttons and other simple ui elements. The following code shows how a sliders node look in c# :

using System.Windows;
using System.Windows.Controls;
using System.Windows.Markup;
using System.Xml;
using VVVV.PluginInterfaces.V2;

namespace VVVV.Packs.UI.Nodes.WPF
{
    [PluginInfo(Author = "lecloneur", Category = "WPF", Help = "WPF Slider", Name = "Slider", AutoEvaluate = false)]

    public class WPFSlider : GenericNode, IPluginEvaluate
    {
        [Input("SizeX", DefaultValue = 120, Order = 9, MinValue = 0)]
        public IDiffSpread<int> SizeX;

        [Input("SizeY", DefaultValue = 120, Order = 9, MinValue = 0)]
        public IDiffSpread<int> SizeY;

        [Input("Orientation", Order = 1, DefaultEnumEntry = "Horizontal")]
        public IDiffSpread<Orientation> OrientationIn;

        [Output("Value", Order = 2)]
        public ISpread<double> ValueOut;

        int elements_count = 0;

        public void Evaluate(int SpreadMax)
        {
            UIElementOut.SliceCount = SpreadMax;
            ValueOut.SliceCount = SpreadMax;
            for (int i = 0; i < SpreadMax; i++)
            {
                if (UIElementOut == null || !(UIElementOut[0] is Slider) || elements_count < SpreadMax || OrientationIn.IsChanged || SizeX.IsChanged || SizeY.IsChanged)
                {
                    CreateElement(i);
                }
                OutputData(i);
                Transformation(i, (Slider)UIElementOut[i]);
            }
            elements_count = SpreadMax;
        }

        private void CreateElement(int i)
        {
            UIElementOut[i] = new Slider();
            var uiElement = (Slider)UIElementOut[i];
            uiElement.Minimum = 0;
            uiElement.Maximum = 1;
            uiElement.Orientation = OrientationIn[i];
            uiElement.IsMoveToPointEnabled = true;
            uiElement.Width = SizeX[i]; ;
            uiElement.Height = SizeY[i];
            uiElement.VerticalAlignment = VerticalAlignment.Center;
            uiElement.HorizontalAlignment = HorizontalAlignment.Center;

            XmlReader XmlRead = XmlReader.Create("Styles/SliderStyle.xaml");
            ResourceDictionary myResourceDictionary = (ResourceDictionary)XamlReader.Load(XmlRead);
            XmlRead.Close();
            Style uiElementStyle = myResourceDictionary["SliderStyle"] as Style;
            uiElement.Style = uiElementStyle;
        }

        private void OutputData(int i)
        {
            var uiElement = (Slider)UIElementOut[i];
            ValueOut[i] = uiElement.Value;
        }
    } 
}

Now I'm trying to implement a tabcontrol where I could dynamically create tabitem and input UIElement into it. As far as I understand, I can only add one things to a tabitem. So I was thinking about creating a grid everytime I need to and fill it with all the incoming UIElement.

    public void Evaluate(int SpreadMax)
    {
        SpreadMax = 1;

        UIElementOut.SliceCount = 1;

        for (var i = 0; i < SpreadMax; i++)
        {
            if (UIElementOut == null || !(UIElementOut[i] is TabControl))
                UIElementOut[i] = new TabControl();
                var uiElement = (TabControl)UIElementOut[i];
                uiElement.Height = 200;
                uiElement.Width = 500;
        }



        Grid grid;

        int[] _elementCounts = new int[_elementInputs.SliceCount];

        for (var i = 0; i < _elementInputs.SliceCount; i++)
        {
            if (_elementInputs[i] == null || !(_elementInputs[i] is UIElement))
            {
                grid = new Grid();

                for (var j = 0; j < _elementInputs[i].IOObject.SliceCount; j++)
                {
                    if (_elementInputs[i].IOObject[j] != null)
                    {
                        UIElement test = new UIElement();
                        test = _elementInputs[i].IOObject[j];
                        grid.Children.Add(test);
                    }
                }
                _elementCounts[i] = _elementInputs[i].IOObject.SliceCount;
                ValueOut[i] = _elementCounts[i];


                if (((TabControl)UIElementOut[0]).Items.Count <= i)
                {
                    ((TabControl)UIElementOut[0]).Items.Add(new TabItem { Header = _nameInputs[i].IOObject[0], Content = grid });
                }

                if (_nameInputs[i].IOObject.IsChanged)
                {
                    ((TabItem)((TabControl)UIElementOut[0]).Items[i]).Header = _nameInputs[i].IOObject[0];
                }

                if (_elementInputs[i].IOObject.IsChanged)
                {
                    ((TabItem)((TabControl)UIElementOut[0]).Items[i]).Content = grid;
                }
            }
        }

        for (var i = ((TabControl)UIElementOut[0]).Items.Count - 1; ((TabControl)UIElementOut[0]).Items.Count > _elementInputs.SliceCount; i--)
        {
            ((TabControl)UIElementOut[0]).Items.RemoveAt(i);
        }
    }

I searched a lot but can't find any idea how to solve the error. Apparently adding elements to a the grid throw "specified element is already the logical child of another element";

1

There are 1 best solutions below

12
On

Hi please try the next method on a several visual objects (child) and check if the resulted object is the same reference. Here is a usefull link with explanations and more...

Extension class code:

public static class VisualTreeHelperExtensions
{
    public static T FindParent<T>(this DependencyObject child) where T : DependencyObject
    {
        while (true)
        {
            //get parent item
            DependencyObject parentObject = VisualTreeHelper.GetParent(child);

            //we've reached the end of the tree
            if (parentObject == null) return null;

            //check if the parent matches the type we're looking for
            T parent = parentObject as T;
            if (parent != null)
                return parent;
            child = parentObject;
        }
    }
}

Example:

var dataGrid1 = dependencyObject1.FindParent<DataGrid>();
var dataGrid2 = dependencyObject2.FindParent<DataGrid>();
var isSameObject = dataGrid1 == dataGrid2;

Updates

  1. The grid can contains a large number of elements but the only last will be visible to user.
  2. The error is coming from the elemnt you want to add itself, that elemnt is belong to another control (some another control has the element as a child).
  3. Find the parent element of the element you want to add, remove the element from the current parent's children collection, and add this element as the new child of your grid.
  4. Try to use snoop to figure out who is the parent of your element (containing in _elementInputs).
  5. Here are some useful links (first, second).

Update 2

  1. As I can understand you have a third party infrastructure in your project because I can't resolve the type of _elementInputs and UIElementOut collections.
  2. Since the _elementInputs is a field, I still can't understand where is _elementInputs came from (can't see that in code).
  3. The code to add a completely new element is wrong:

Correct code(in my opinion)

                //here is in my opinion the correct algorithm in this case
                var elementFromInputs = _elementInputs[i] as UIElement;
                if (elementFromInputs == null) continue;
                //try to find parent of type Panel
                var parentPanel = elementFromInputs.FindParent<Panel>();
                //try to find parent of type ContentControl
                var parentContentControl = elementFromInputs.FindParent<ContentControl>();
                if (parentPanel == null && parentContentControl == null)
                {
                    //add if there is no any parents
                    grid.Children.Add(elementFromInputs);
                }
                if (parentPanel != null)
                {
                    //remove element from parent's collection
                    parentPanel.Children.Remove(elementFromInputs);
                }
                if(parentContentControl != null)
                {
                    //reset parent content to release element
                    parentContentControl.Content = null;
                }
                grid.Children.Add(elementFromInputs);

Update 3

You've pasted the code (from the correct code section) inside the if which condition is the _elementInputs[i] == null || !(_elementInputs[i] is UIElement) that means filtering all your UIElements out of the scope. Since I'm not familiar with the vvvv concepts I don't know what do you have inside the _elementInputs array, but if you have the UIElements there you need past the code I gave you before the if with condition _elementInputs[i] == null || !(_elementInputs[i] is UIElement).

Please update your question with the next clarifications: 1. What is inside the _elementInputs[i]? 2. What is inside the _elementInputs[i].IOObject? 3. What are UIElements you want to add to the grid? 4. Please run the next method and write me the comment what do you have in your grid and TabControl controls.

Test code

public void Evaluate(int SpreadMax)
{
    SpreadMax = 1;

    UIElementOut.SliceCount = 1;

    for (var i = 0; i < SpreadMax; i++)
    {
        if (UIElementOut == null || !(UIElementOut[i] is TabControl))
            UIElementOut[i] = new TabControl();
            var uiElement = (TabControl)UIElementOut[i];
            uiElement.Height = 200;
            uiElement.Width = 500;
    }

    Grid grid = new Grid();
    var listOfElements = new List<UIElements>
    {
            new Button {Background = Brushes.Tomato, Content = "Click Me"},
            new Button {Background = Brushes.Yellow, Content = "Click Me"},
            new Button {Background = Brushes.Green, Content = "Click Me"},
            new Button {Background = Brushes.Blue, Content = "Click Me"}
    };
    listOfElements.ForEach(button => grid.Children.Add(button));
    ((TabControl)UIElementOut[0]).Items.Add(new TabItem { Header = "Objects", Content = grid });
}

I'll be glad to help if you will have problems with the code. Regards.