Check if specified element is already the logical child of another element - c#

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

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
The grid can contains a large number of elements but the only last will be visible to user.
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).
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.
Try to use snoop to figure out who is the parent of your element (containing in _elementInputs).
Here are some useful links (first, second).
Update 2
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.
Since the _elementInputs is a field, I still can't understand where is _elementInputs came from (can't see that in code).
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.

Related

Xamarin Forms - CustomRenderer Not Working After Navigating Away

I have a custom renderer in my iOS Native code for my main page. It works perfectly fine when the app start up, and renders Navbar items using the iOS System icons which is what I want. However, if I navigate away from the main page, when I navigate back the RightBarButtonItems array only contains two uninstantiated objects, I put in a check (RightNavItems.Title == null) to continue when this was the case to see what would happen, and indeed the items are not rendered, if I navigate away and back again the app crashes since the RightBarButtonItems array is now empty.
Why is it that the toolbar items are uninitialised when navigating back to the main page? What is the proper way to deal with navigation in a custom renderer like this?
Here is the code for the custom renderer:
public class ItemsPageRenderer : PageRenderer
{
public new ItemsPage Element
{
get { return (ItemsPage)base.Element; }
}
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
var rightNavList = new List<UIBarButtonItem>();
var navigationItem = this.NavigationController.TopViewController.NavigationItem;
for (var i = 0; i < Element.ToolbarItems.Count; i++)
{
var reorder = (Element.ToolbarItems.Count - 1);
var ItemPriority = Element.ToolbarItems[reorder - i].Priority;
UIBarButtonItem RightNavItems = navigationItem.RightBarButtonItems[i];
if (RightNavItems.Title == null)
continue;
if (RightNavItems.Title.ToLower() == "add")
{
rightNavList.Add(new UIBarButtonItem(UIBarButtonSystemItem.Add)
{
Action = RightNavItems.Action,
Target = RightNavItems.Target
});
}
else if (RightNavItems.Title.ToLower() == "edit")
{
rightNavList.Add(new UIBarButtonItem(UIBarButtonSystemItem.Edit)
{
Action = RightNavItems.Action,
Target = RightNavItems.Target
});
}
else
{
rightNavList.Add(RightNavItems);
}
}
navigationItem.SetRightBarButtonItems(rightNavList.ToArray(), false);
}
}
Check this code snippet
var rightNavList = new List<UIBarButtonItem>();
var navigationItem = this.NavigationController.TopViewController.NavigationItem;
for (var i = 0; i < Element.ToolbarItems.Count; i++)
{
var reorder = (Element.ToolbarItems.Count - 1);
var ItemPriority = Element.ToolbarItems[reorder - i].Priority;
UIBarButtonItem RightNavItems = navigationItem.RightBarButtonItems[i];
if (RightNavItems.Title == null)
continue;
if (RightNavItems.Title.ToLower() == "add")
{
rightNavList.Add(new UIBarButtonItem(UIBarButtonSystemItem.Add)
{
Action = RightNavItems.Action,
Target = RightNavItems.Target
});
}
else if (RightNavItems.Title.ToLower() == "edit")
{
rightNavList.Add(new UIBarButtonItem(UIBarButtonSystemItem.Edit)
{
Action = RightNavItems.Action,
Target = RightNavItems.Target
});
}
else
{
rightNavList.Add(RightNavItems);
}
}
navigationItem.SetRightBarButtonItems(rightNavList.ToArray(), false);
You change the item appearance from title to icon when first entering the page , however , when second entering the page , the condition RightNavItems.Title == null is true , so it jump out the loop without adding any item to the list , so navigationItem.SetRightBarButtonItems add a null array at last.
Solution
Modify as below
if (RightNavItems.Title == null)
{
rightNavList.Add(RightNavItems); //add this line.
continue;
}

WPF C# Dynamic event routing in programmatically created element

I've provided, in a seperated class, some methods to create an element dynamically. Creating that element dynamically is not the problem but routing the event.
My thoughts where that the "path" (like namespace.class.method) is submitted via a parameter in the creating method.
But up to now, I was not able to attach such an event listener. I did many researches using Google and StackOverflow, but without any success.
I post the source code of the method below.
The code in the if-section for "EventHandler" is not empty was my last attempt.
Is it possible that my thoughts will work or is it only possible to attach event handlers in the main class that is initializing the app.
static public WrapPanel comboBox(string Name, List<string> Items, bool MultiSelection = false, string EventHandler="",string Label="", int Width = 0)
{
WrapPanel lPanel = new WrapPanel();
lPanel.Name = "stPanel_" + Name;
if (Label != "")
{
Label lLabel = new Label();
lLabel.Content = Label;
lPanel.Children.Add(lLabel);
if (Width != 0)
{
lPanel.Width = Width;
}
}
dynamic lComboBox;
if (MultiSelection)
{
lComboBox = new ComboBoxAdv();
lComboBox.AllowMultiSelect = true;
}
else lComboBox = new ComboBox();
lComboBox.Name = Name;
foreach (string Item in Items) lComboBox.Items.Add(Item);
lPanel.Children.Add(lComboBox);
ComboBox dkaf = new ComboBox();
if (EventHandler != "")
{
string lClass = "";
string lMethod = EventHandler.Split('.').Last();
for (int lCounter = 0; lCounter < EventHandler.Split('.').Length - 1; lCounter++)
lClass += EventHandler.Split('.')[lCounter] + ".";
lClass = lClass.Substring(0, lClass.Length - 1);
object dynamicObject;
Type objectType = Type.GetType(lClass);
dynamicObject = Activator.CreateInstance(objectType);
System.Reflection.MethodInfo method = objectType.GetMethod(lMethod);
//method.Invoke(dynamicObject, new object[] { });
lComboBox.Click += new RoutedEventHandler(method);
}
return lPanel;
}
Best regards
Chris

Left Align UICollectionView Cell When it has one item in Xamarin.ios

I am creating collection view with several size of labels. These labels all have the same height but their widths are changed dynamically.
This is the code of my collection view layout:
EstimatedItemSize = new CGSize(50f, 35f);
MinimumInteritemSpacing = 10f;
MinimumLineSpacing = 10f;
public override UICollectionViewLayoutAttributes[] LayoutAttributesForElementsInRect(CGRect rect)
{
var attributes = base.LayoutAttributesForElementsInRect(rect);
for (var i = 1; i < attributes.Length; ++i)
{
var currentLayoutAttributes = attributes[i];
var previousLayoutAttributes = attributes[i - 1];
var maximumSpacing = MinimumInteritemSpacing;
var previousLayoutEndPoint = previousLayoutAttributes.Frame.Right;
if (previousLayoutEndPoint + maximumSpacing + currentLayoutAttributes.Frame.Size.Width >= CollectionViewContentSize.Width)
{
continue;
}
var frame = currentLayoutAttributes.Frame;
frame.X = previousLayoutEndPoint + maximumSpacing;
currentLayoutAttributes.Frame = frame;
}
return attributes;
}
My question is: When I have one item in my collection view it's displayed in the center, and LayoutAttributesForElementsInRect method will not be called. But I need to display it on the left side.
If I change EstimatedItemSize = new CGSize(50f, 35f) to ItemSize = new CGSize(50f, 35f) it displays correctly but then the width is not changed dynamically.
You can add some codes to change the position of the first cell, when you use the EstimatedItemSize, like this:
public override UICollectionViewLayoutAttributes[] LayoutAttributesForElementsInRect(CoreGraphics.CGRect rect)
{
var attributes = base.LayoutAttributesForElementsInRect(rect);
//Add these lines to change the first cell's position of the collection view.
var firstCellFrame = attributes[0].Frame;
firstCellFrame.X = 0;
attributes[0].Frame = firstCellFrame;
for (var i = 1; i < attributes.Length; ++i)
{
var currentLayoutAttributes = attributes[i];
var previousLayoutAttributes = attributes[i - 1];
var maximumSpacing = MinimumInteritemSpacing;
var previousLayoutEndPoint = previousLayoutAttributes.Frame.Right;
if (previousLayoutEndPoint + maximumSpacing + currentLayoutAttributes.Frame.Size.Width >= CollectionViewContentSize.Width)
{
continue;
}
var frame = currentLayoutAttributes.Frame;
frame.X = previousLayoutEndPoint + maximumSpacing;
currentLayoutAttributes.Frame = frame;
}
return attributes;
}
It works fine like this:

Acces a TreeView Form inside GridView to populate ComboBox collection

I have a following problem with Windows Form Application. I have two forms: CategoryTree.cs form with TreeView inside and a ProjectForm.cs with DataGridView inside. I would like to populate DataGridViewComboBoxColumn Collection with TreeView nodes text e.g. I have tree which looks like this:
Science Fiction
Movie1
Movie2
Horror
Movie1
Movie2
Action
Movie1
Movie2
and so on. I want those categories to be Items in the ComboBoxColumn. My code looks as follows in the CategoryTree form
public partial class CategoryTree : Form
{
//adding nodes to the tree
private void button2_Click(object sender, EventArgs e)
{
TreeNode newone = new TreeNode();
newone.ForeColor = Color.Orange;
newone.NodeFont = new Font(catTreeView.Font, FontStyle.Bold);
newone.Text = nameBox.Text;
catTreeView.Nodes.Add(newone);
//and code for adding child and grandchild and ...
}
//other stuff
}
In the DataGridView I have the following method
public void AddComboBox()
{
int i;
CategoryTree cat_Form = new CategoryTree();
//Set the cat_Form Active
cat_Form = ActiveForm as CategoryTree;
if (cat_Form != null)
{
i = cat_Form.catTreeView.Nodes.Count; //here i get exception
if(i != 0)
{
var newone2 = new DataGridViewComboBoxColumn();
newone2.HeaderText = "Main category";
newone2.Name = "ColNr_" + nofCols;
string comboTxt;
for(int j=0; j<i; j++){
comboTxt = catTreeView.Nodes[j].Text;
newone2.Items.Add(comboTxt);
}
mainProjectGrid.Columns.Add(newone2);
nofCols += 1;
}
}
}
Unfortunately this code generates 'System.NullReferenceException'. The catTreeView modifier is set to 'public'. I had many different approaches to the problem, but none of them worked.
As you may see my questoin is - how can I get to the catTreeView object from the ProjectForm form and populate combobox?
I'm absolute beginner so answers with example code will be always appreciated.
To Whom It May Concern. I found the solution to this problem. In TreeView I added one button programed this way
private void applyBtn_Click(object sender, EventArgs e)
{
ProjectForm newProject1 = new ProjectForm( catTreeView );
newProject1.MdiParent = this.ParentForm;
newProject1.Text = "blabla";
newProject1.Show();
}
Next I added this to the ProjectForm.cs
public ProjectForm(TreeView catView)
{
InitializeComponent();
holalala = catView;
}
public void AddComboBox()
{
int i;
if (holalala != null)
{
i = holalala.Nodes.Count;
if (i != 0)
{
var newone2 = new DataGridViewComboBoxColumn();
newone2.HeaderText = "Main category";
newone2.Name = "ColNr_" + nofCols;
string comboTxt;
for (int j = 0; j < i; j++)
{
comboTxt = holalala.Nodes[j].Text;
newone2.Items.Add(comboTxt);
}
mainProjectGrid.Columns.Add(newone2);
nofCols += 1;
}
}
}
This solution is pretty simple and works fine. I think I can now add few lines to not just add Columns but also to update them with new Nodes that may appear in the TreeView. I will work on that later :).

How to Clone a Windows Forms Controls even with non-Serializable properties?

How to Clone or Serialize a Windows Forms Control?
When I am trying to Clone windows forms controls using this code "CloneControl(Control ct1)", it allows me to duplicate controls with some Serializable properties, not with all properties.
public Form1()
{
InitializeComponent();
Columns = new DataGridViewTextBoxColumn[2];
for (int i = 0; i < 2; i++)
{
Columns[i] = new System.Windows.Forms.DataGridViewTextBoxColumn();
//
// Columns[i]
//
Columns[i].HeaderText = "j" + (i + 1);
Columns[i].Name = "Column" + (i + 1);
Columns[i].Width = 50;
}
dataGridView1 = new System.Windows.Forms.DataGridView();
dataGridView1.Name = "dataGridView1";
dataGridView1.Location = new System.Drawing.Point(100, 100);
dataGridView1.RowHeadersWidth = 50;
dataGridView1.RowTemplate.Height = 25;
dataGridView1.Size = new System.Drawing.Size(55 + 50 * 2, 25 + dataGridView1.RowTemplate.Height * 2);
dataGridView1.Anchor = System.Windows.Forms.AnchorStyles.None;
dataGridView1.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
dataGridView1.Columns.AddRange(Columns);
dataGridView1.TabIndex = 3;
dataGridView1.AllowUserToAddRows = false;
dataGridView1.Rows.Add();
dataGridView1.Rows.Add();
dataGridView1.Rows[0].HeaderCell.Value = "i" + 1;
dataGridView1.Rows[1].HeaderCell.Value = "i" + 2;
dataGridView1.Rows[0].Cells[0].Value = "value1";
Controls.Add(dataGridView1);
Control cloned1 = CloneControl(dataGridView1);
cloned1.SetBounds(cloned1.Location.X, cloned1.Location.Y + 300, cloned1.Width, ct1.Height);
Controls.Add(cloned1);
cloned1.Show();
}
public Control CloneControl(Control ct1)
{
Hashtable PropertyList = new Hashtable();
PropertyDescriptorCollection Properties = TypeDescriptor.GetProperties(ct1);
Assembly controlAsm = Assembly.LoadWithPartialName(ct1.GetType().Namespace);
Type controlType = controlAsm.GetType(ct1.GetType().Namespace + "." + ct1.GetType().Name);
Control cloned1 = (Control)Activator.CreateInstance(controlType);
foreach (PropertyDescriptor pr1 in Properties)
{
if (pr1.PropertyType.IsSerializable)
{
PropertyList.Add(pr1.Name, pr1.GetValue(ct1));
}
if (PropertyList.Contains(pr1.Name))
{
try
{
Object obj = PropertyList[pr1.Name];
pr1.SetValue(cloned1, obj);
}
catch (Exception ex)
{
}
}
}
return ct2;
}
If you run the code... the you will get
As you can see in the main method I create a clone of dataGridView1, which has a few properties.
And actually each cell value is null in a cloned dataGridView.
Also size of a columns are not cloned!
You may have a question: if Visual Studio or SharpDeveloper as IDE which is written in C# can handle this problem, then it might be possible to write that kind of code! Right?
In Visual Studio When you are trying drag and drop controls, or copy and paste controls, it not only duplicates that controls with all properties (including Serializable or non-Serializable) but also it changes the name of control itself from "dataGridView1" to "dataGridView2" as well as in SharpDeveloper!
What should I do?
What kind of method should I create?
Maybe another control has a many non-Serializable properties!
How to duplicate all of them?
Please anyone.....
Like #Hans mentioned in the comment, Clone is not that easy. If you want to get some identical controls with only a bit different, you'd better use a function to define general behavior and pass the different properties in as parameters. For example, we define a function with some general properties which apply to DataGridView:
private void InitDataGridView(DataGridView dataGridView, string name)
{
dataGridView.Name = name;
// configure other properties here
dataGridView.Location = new System.Drawing.Point(100, 100);
dataGridView.RowHeadersWidth = 50;
dataGridView.RowTemplate.Height = 25;
dataGridView.Size = new System.Drawing.Size(55 + 50 * 2, 25 + dataGridView1.RowTemplate.Height * 2);
dataGridView.Anchor = System.Windows.Forms.AnchorStyles.None;
dataGridView.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
// remember to initialize your columns, or pass it in as a parameter
dataGridView.Columns.AddRange(Columns);
dataGridView.AllowUserToAddRows = false;
dataGridView.Rows.Add();
dataGridView.Rows.Add();
dataGridView.Rows[0].HeaderCell.Value = "i" + 1;
dataGridView.Rows[1].HeaderCell.Value = "i" + 2;
dataGridView.Rows[0].Cells[0].Value = "value1";
}
public Form1()
{
InitializeComponent();
var dataGridView1 = new DataGridView();
var dataGridView2 = new DataGridView();
InitDataGridView(dataGridView1, "dataGridView1");
InitDataGridView(dataGridView2, "dataGridView2");
}
IDE (e.g. Visual Studio) is using PropertyDescriptors, DesignerSerializationVisibility and ShouldSerializeValue, but DataGrid Rows are something special, because you cannot add them at design time! IDE cannot copy something that is not there, so, the solution must be different (if you want to clone controls beyond what IDE/Designer can do - see other answers and comments for this). Try my code (everything except grid rows got cloned without the extra check - the columns got cloned).
foreach(PropertyDescriptor pd in TypeDescriptor.GetProperties(src)) {
if(!pd.ShouldSerializeValue(src)) {
if(src is DataGridView && pd.Name == "Rows")
CopyDataGridRows((DataGridView)src, (DataGridView)dst);
continue; }
Note: The above can be done better (by check for the class at the end), but is as it is to be obvious.
using System;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
namespace CloneControls {
public partial class Form1: Form {
public Form1() { InitializeComponent(); }
private void Form1_Load(object sender, EventArgs e) {
dataGridView1.Rows.Add();
dataGridView1.Rows.Add();
foreach(Control c in splitContainer1.Panel1.Controls)
splitContainer1.Panel2.Controls.Add((Control)Clone(c));
}
static object Clone(object o) {
return Copy(o, Activator.CreateInstance(o.GetType()));
}
static object Copy(object src, object dst) {
IList list = src as IList;
if(list != null) {
IList to = dst as IList;
foreach(var x in list)
to.Add(Clone(x));
return dst; }
foreach(PropertyDescriptor pd in TypeDescriptor.GetProperties(src)) {
if(!pd.ShouldSerializeValue(src)) {
if(src is DataGridView && pd.Name == "Rows")
CopyDataGridRows((DataGridView)src, (DataGridView)dst);
continue; }
switch(pd.SerializationVisibility) {
default: continue;
case DesignerSerializationVisibility.Visible:
if(pd.IsReadOnly) continue;
pd.SetValue(dst, pd.GetValue(src));
continue;
case DesignerSerializationVisibility.Content:
Copy(pd.GetValue(src), pd.GetValue(dst));
continue;
}
}
return dst;
}
static void CopyDataGridRows(DataGridView src, DataGridView dst) {
foreach(DataGridViewRow row in src.Rows)
if(!row.IsNewRow) dst.Rows.Add((DataGridViewRow)Clone(row));
}
}
}
I think I made more better method here.
This Method at first checks interface of property: if it is ICollection then it does the first job.
After this one loop ends in the method "DeepClone()", then it is necessary to do another loop without checking PropertyType Interface... I mean I could not mix these two operation into one loop?!
Also You can detect that there will be some kind of Run-time Exceptions and for this reason I put this code into try-catch block...
Control cloned1 = (Control)DeepClone(dataGridView1);
cloned1.SetBounds(cloned1.Location.X, cloned1.Location.Y + 300, cloned1.Width, ct1.Height);
Controls.Add(cloned1);
cloned1.Show();
public dynamic DeepClone(dynamic ob1)
{
dynamic ob2 = null;
if (ob1.GetType().IsSerializable && !ob1.GetType().IsArray)
{
if (ob1 != null)
{
using (MemoryStream ms = new MemoryStream())
{
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(ms, ob1);
ms.Position = 0;
ob2 = formatter.Deserialize(ms);
}
}
}
else
{
if (ob1.GetType().IsArray)
{
var r1 = ob1.Rank;
object[] d1 = new object[r1];
long[] V1 = new long[r1];
for (int i = 0; i < r1; i++)
{
V1[i] = 0;
d1[i] = ob1.GetUpperBound(i) + 1;
}
ob2 = Activator.CreateInstance(ob1.GetType(), d1);
for (long i = 0; i <= ob2.Length; i++)
{
ob2.SetValue(DeepClone(ob1.GetValue(V1)), V1);
for (int j = 0; j <= V1.GetUpperBound(0); j++)
{
if (V1[j] < ob2.GetUpperBound(j))
{
V1[j]++;
break;
}
else
{
V1[j] = 0;
}
}
}
}
else
{
PropertyInfo[] P1 = ob1.GetType().GetProperties();
ob2 = Activator.CreateInstance(ob1.GetType());
foreach (PropertyInfo p1 in P1)
{
try
{
if (p1.PropertyType.GetInterface("System.Collections.ICollection", true) != null)
{
dynamic V2 = p1.GetValue(ob1) as IEnumerable;
MethodInfo gm1 = p1.PropertyType.GetMethods().Where(m => m.Name == "Add").Where(p => p.GetParameters().Count() == 1).First(f => V2[0].GetType().IsSubclassOf(f.GetParameters()[0].ParameterType) || f.GetParameters()[0].ParameterType == V2[0].GetType());
if (V2[0].GetType().IsSubclassOf(gm1.GetParameters()[0].ParameterType) || gm1.GetParameters()[0].ParameterType == V2[0].GetType())
{
for (int i = 0; i < V2.Count; i++)
{
dynamic V3 = DeepClone(V2[i]);
gm1.Invoke(p1.GetValue(ob2), new[] {V3});
}
}
}
}
catch (Exception ex)
{
}
}
foreach (PropertyInfo p1 in P1)
{
try
{
if (p1.PropertyType.IsSerializable && p1.CanWrite)
{
var v2 = p1.GetValue(ob1);
p1.SetValue(ob2, v2);
}
if (!p1.PropertyType.IsSerializable && p1.CanWrite)
{
dynamic V2 = p1.GetValue(ob1);
if (p1.PropertyType.GetMethod("Clone") != null)
{
dynamic v1 = V2.Clone();
p1.SetValue(ob2, v1);
}
}
}
catch (Exception ex)
{
}
}
}
}
return ob2;
}
You may say that this Method does not copy some kind of property, But it does copy of main properties and the Cloned control will look like an original control!
Trying to clone a control is overkill except if you really need a totally generic control clone method. Most of the time, you only need to clone a specific control and you have an easy access to the code that created it (see the Form designer generated code, and the setup code you wrote yourself).
But nevertheless, I once used a trick to duplicate many controls at once in order to fill the new tabs of a TabControl, choosing one out of ten tab designs.
I also wanted to use the Form design tool of the C# IDE to edit and modify the 10 template.
So, besides my Tab control form, and using the VS IDE, I created 10 "control factory dummy forms" in my project. I put a dummy Panel control in each of it.
Each time I had to dynamically create a new Tab, I simply instantiated a new dummy window of the desired style. Then I simply moved the Parent pane to my ControlTab (using the Controls.Add() method of the new tab).
This way, you must link the event handlers after the Tab creation (after the controls move). And the event handler's code should be written in you main window class, otherwise you will have "this" reference problems.
Obviously, you will have to store control references somewhere, to be able to access them. The easiest way to do this is to just keep track of each "dummy template Form" you instantiate and to set the "modifier" of your controls to be "public". You can use the Tag property of the destination tab page to store that reference. But, to avoid many casts, it is better to declare an array of each form class, and to store the references there.

Categories

Resources