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;
}
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
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:
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 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.