Copy TabControl Tab Yes I have checked this.
I'm trying to duplicate a tabcontol but this tab should have it's own 'behaviour'.
TabControl tc = TC_Fields;
TabPage tpOld = tc.TabPages[0];
TabPage tpNew = new TabPage();
fields += 1;
tpNew.Name = "Field_" + fields;
tpNew.Text = "Field-" + fields;
foreach (Control c in tpOld.Controls)
{
Control cNew = (Control) Activator.CreateInstance(c.GetType());
PropertyDescriptorCollection pdc = TypeDescriptor.GetProperties(c);
foreach (PropertyDescriptor entry in pdc)
{
object val = entry.GetValue(c);
if (entry.Name == "Name")
{
val = (String) val + fields;
}
entry.SetValue(cNew, val);
}
tpNew.Controls.Add(cNew);
}
tc.TabPages.Add(tpNew);
I've tried above code, so it gives the new control a new "id", yet I am still not able to click the controls in the new tab, and they only mimic what's in the first tab.
Is there a way I can copy all controls yet not mimic the controls they were copied of?
Your code has at least two issues not related to the actual question. Let's fix two of them first:
You set all properties even those you should not set; most notably you should not touch the "WindowTarget" property! It is for internal use only and messing with it will stop at least some controls from working. I found I couldn't check a CheckBox, for example.
Your code only will create visible controls if the orginal TabPage is also the current one. All controls on all other pages are invisible and if you try to clone any other TabPage all control will be added just fine but remain invisible.
Let's add a filter :
if(entry.Name != "WindowTarget") entry.SetValue(cNew, val);
and make the new controls visible:
cNew.Visible = true;
tpNew.Controls.Add(cNew);
Note that this is a simplistic solution as it will also make those controls visible that were invisible originally. You could instead show the page you want to clone or make a list of invisible controls; with their names you could find the cloned counterparts..
The last issue is also important but goes beyond the question: The code only clones the controls directly on the page, not any neseted controls (like RadioButtons in a GroupBox !). To do so you would have to write a recursive version!
Now for the actual question: How can you give the cloned controls their own behaviour?
After cloning all their properties they are like freshly added controls, i.e. they have no event handlers at all.
So you need to
write new event handlers for the new controls' events
hook them up
While this is not really hard there are quite a few problems..
We know the naming scheme and so we can know which controls we want to supply with events. The event names are free to choose but to hook them up we need to know which of the new controls we are dealing with..
Here is and example that hooks up the first clone of a Button cb_hello with a Click event:
if (cNew.Name == "cb_hello1") cNew.Click += buttonNew_Click;
The code in the event demonstrates more issues:
private void buttonNew_Click(object sender, EventArgs e)
{
Button btn = sender as Button;
Console.WriteLine(btn.Name + " says hiho");
btn.Parent.Controls["panel41"].BackColor = Color.ForestGreen;
((RadioButton)btn.Parent.Controls["radioButton11"]).Checked = true;
}
While casting the sender to Button is quite normal we run into trouble when we try to access any of the controls we have just cloned:
Since they were dynamically created we can't access any of them with variables. Instead we need to use the TabPage's Controls collection to find them.
We can set BackColor or any other property inherited from Control but to set RadioButton.Checked we need to cast to RadioButton; and we need to access the control from the Controls collection, for example by its Name.
Once you upgrade to recursive cloning you will want to use Controls.Find(name, true) to include the nested controls..
As you can see it can be done but it will take a little more effort than coding the original controls and the code feels somewhat fragile as we introduce hidden dependencies: All those references to the cloned names rely on the original names!
Final note: While the regular properiets get cloned, the data and structure containers do not, i.e. all Items, ListViewItems or Rows, Columns collections etc, etc are not cloned!
TabControl tc = TC_Fields;
TabPage tpOld = tc.SelectedTab;
TabPage tpNew = new TabPage();
fields += 1;
tpNew.Name = "Field_" + fields;
tpNew.Text = "Field-" + fields;
foreach (Control c in tpOld.Controls)
{
Control cNew = (Control) Activator.CreateInstance(c.GetType());
PropertyDescriptorCollection pdc = TypeDescriptor.GetProperties(c);
foreach (PropertyDescriptor entry in pdc)
{
object val = entry.GetValue(c);
if (entry.Name == "Name")
{
val = (String) val + fields;
}
else if (entry.Name == "Location" || entry.Name == "Text" || entry.Name == "Bounds" || entry.Name == "Enabled"
|| entry.Name == "Visible" || entry.Name == "Checked" || entry.Name == "CheckState")
{
//Nothing to do, but do continue!
}
else if (entry.Name == "Controls")
{
Control.ControlCollection controllsInside = (Control.ControlCollection) val;
foreach (Control controllInside in controllsInside)
{
Control cNewInside = (Control) Activator.CreateInstance(controllInside.GetType());
PropertyDescriptorCollection pdcInside = TypeDescriptor.GetProperties(controllInside);
foreach (PropertyDescriptor entryInside in pdcInside)
{
object valInside = entryInside.GetValue(controllInside);
if (entryInside.Name == "Name")
{
valInside = (String) valInside + fields;
}
else if (entryInside.Name == "Location" || entryInside.Name == "Text" || entryInside.Name == "Bounds" || entryInside.Name == "Enabled"
|| entryInside.Name == "Visible" || entryInside.Name == "Checked" || entryInside.Name == "CheckState")
{
//Nothing to do, but do continue!
}
else
{
continue;
}
entryInside.SetValue(cNewInside, valInside);
}
cNew.Controls.Add(cNewInside);
}
}
else
{
continue;
}
entry.SetValue(cNew, val);
}
tpNew.Controls.Add(cNew);
}
tc.TabPages.Add(tpNew);
TC_Fields.SelectedIndex = fields - 1;
This will also do the trick :) #TaW already put the solution on there, but here is some copy paste code :)
Related
I have a form that contains a TableLayoutPanel with various controls and labels in it. One of them is a custom control that inherits from ComboBox that has extra auto-complete behavior (auto-completes on any text rather than just left to right). I didn't write the code for this control, so I'm not super familiar with how it works, but essentially upon clicking on the Combobox, it adds a ListBox below the ComboBox, within the same Panel of the TableLayoutPanel, that covers the normal drop down.
Unfortunately, the TableLayoutPanel prevents the ListBox from being fully visible when added, and only one item is shown. The goal is to get it to look like a normal ComboBox which would drop down to cover any controls below it.
Is there any way to allow a control that is in a TableLayoutPanel to overlap the TableLayoutPanel to get this to work as I want? I want to avoid any controls moving around due to the TableLayoutPanel growing to accommodate the ListBox.
Relevant code from the control:
void InitListControl()
{
if (listBoxChild == null)
{
// Find parent - or keep going up until you find the parent form
ComboParentForm = this.Parent;
if (ComboParentForm != null)
{
// Setup a messaage filter so we can listen to the keyboard
if (!MsgFilterActive)
{
Application.AddMessageFilter(this);
MsgFilterActive = true;
}
listBoxChild = listBoxChild = new ListBox();
listBoxChild.Visible = false;
listBoxChild.Click += listBox1_Click;
ComboParentForm.Controls.Add(listBoxChild);
ComboParentForm.Controls.SetChildIndex(listBoxChild, 0); // Put it at the front
}
}
}
void ComboListMatcher_TextChanged(object sender, EventArgs e)
{
if (IgnoreTextChange > 0)
{
IgnoreTextChange = 0;
return;
}
InitListControl();
if (listBoxChild == null)
return;
string SearchText = this.Text;
listBoxChild.Items.Clear();
// Don't show the list when nothing has been typed
if (!string.IsNullOrEmpty(SearchText))
{
foreach (string Item in this.Items)
{
if (Item != null && Item.ToLower().Contains(SearchText.ToLower()))
{
listBoxChild.Items.Add(Item);
listBoxChild.SelectedIndex = 0;
}
}
}
if (listBoxChild.Items.Count > 0)
{
Point PutItHere = new Point(this.Left, this.Bottom);
Control TheControlToMove = this;
PutItHere = this.Parent.PointToScreen(PutItHere);
TheControlToMove = listBoxChild;
PutItHere = ComboParentForm.PointToClient(PutItHere);
TheControlToMove.Anchor = ((System.Windows.Forms.AnchorStyles)
((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right)));
TheControlToMove.BringToFront();
TheControlToMove.Show();
TheControlToMove.Left = PutItHere.X;
TheControlToMove.Top = PutItHere.Y;
TheControlToMove.Width = this.Width;
int TotalItemHeight = listBoxChild.ItemHeight * (listBoxChild.Items.Count + 1);
TheControlToMove.Height = Math.Min(ComboParentForm.ClientSize.Height - TheControlToMove.Top, TotalItemHeight);
}
else
HideTheList();
}
Images:
Desired behavior
Current behavior
Going on the suggestion from TaW, I came up with a tentative solution. This form isn't re-sizable but does auto-size so that it looks ok if the user changes their DPI in Windows.
To resolve this, I moved the control out of the TableLayoutPanel to an arbitrary position in the Parent of the TableLayoutPanel. On form loading, I summed the coordinates of the TableLayoutPanel and an empty panel in the cell that I wanted the control to be located on top of. This worked for my needs but it feels like a kludge.
The better solution is probably to use Control.PointToScreen and Control.PointToClient methods, however I wasn't able to get these methods to give me the correct coordinates.
I need to access a grid in a list-view data-template, but when using this code the program reaches the foreach loop and don't execute it
foreach (Grid firstgrid in Active_list.Items)
{
var item = Active_list.ItemContainerGenerator.ContainerFromItem(firstgrid);
var ch = AllChildren(item);
var tag = url;
var control = (Grid)ch.First(c => c.Tag == tag);
if (firstgrid.GetType() == typeof(Grid))
{
if ((String)firstgrid.Tag == url)
{
foreach (ProgressBar prg in firstgrid.Children)
{
if (prg.GetType() == typeof(ProgressBar))
{
prg.IsIndeterminate = false;
}
}
foreach (TextBlock txt in firstgrid.Children)
{
if (txt.GetType() == typeof(TextBlock))
{
txt.Visibility = Visibility.Visible;
}
}
}
}
}
This code Active_list.Items won't give you any control but your actual data. If you want to access specific control in you list view, you need to go through your visual tree and find it manually. I think it's not a good practice to manually change controls inside list view...
But if you really want to do it this way I recommend you to check out this topic with similar question: How to access a specific item in a Listbox with DataTemplate?
I've found a few answers around that work fine with modifying .Text, .Checked values and so, but none of them worked when I tried changing the .Value property. I can't get that to work on progress bars.
Last I tried:
foreach (Control c in this.Controls)
{
if (c.Name == "test" && c is ProgressBar)
{
((ProgressBar)c).Value = 23;
}
}
Am I missing a using statement or something?
Assuming that your progressbar control is named "test" (all lowercase letters) and is placed directly on the surface of your form (not inside a groupbox,panel or other control container) then this code should work and simplify your work
foreach (var c in this.Controls.OfType<ProgressBar>().Where(x => x.Name == "test")
{
c.Value = 23;
}
instead if the ProgressBar is placed inside a control container (like a panel) the above code should be changed to loop over the controls collection of the container
foreach (var c in this.panel1.Controls.OfType<ProgressBar>().Where(x => x.Name == "test")
{
c.Value = 23;
}
As pointed out in the comment by KingKing, if you are absolutely sure that a control named "test" exists in your groupbox then a simple lookup in the controls collection should result in your progressbar. Looping is not necessary in this case
ProgressBar pb = this.groupBox1.Controls["test"] as ProgressBar;
if(pb != null) pb.Value = 23;
The trick here is that Controls is not a List<> or IEnumerable but a ControlCollection.
I recommend using an extension of Control. Add this class to your project:
public static class ControlExtensionMethods
{
public static IEnumerable<Control> All(this System.Windows.Forms.Control.ControlCollection controls)
{
foreach (Control control in controls)
{
foreach (Control grandChild in control.Controls.All())
yield return grandChild;
yield return control;
}
}
}
Then you can do :
foreach(var textbox in this.Controls.All())
{
// Apply logic to a control
}
Source: Click
How can we change the ReadOnly property of all textBoxes in a winform that is true to false i'm trying using this code but this prompt me object null reference error...
private void TextBoxesReadOnlyTrue(Control.ControlCollection cc)
{
foreach (Control ctrl in cc)
{
TextBox tb = ctrl as TextBox;
if (tb.ReadOnly)
{
tb.ReadOnly = false;
}
}
}
That's because not all the controls in cc are TextBoxes. So when you try converting them to a TextBox, the variable is null. When a variable is null, you cannot access any properties on that variable, or you'll get an error. So anytime a variable can be null, you MUST first test whether it is null.
Here's the modified if command that you'll want to use to fix your problem:
if (tb != null && tb.ReadOnly) { tb.ReadOnly = false; }
So i appologize that i overlooked that your TextBoxes can be contained in other container controls. Yes, that means you need to do 1 of 2 things: 1: You can move the TextBoxes outside the GroupBox. haha. I'm just joking. Yes, that can solve that problem but then you have worse problems. The correct way is to recursively call your method for every control that has controls in its Controls property. Every control has this property but it seems it is empty (but not null) in controls that are not containers. (I just learned today that every control has this Controls property, so i've updated my code to reflect this.)
So for this real solution, i suggest something similar to this:
private void TextBoxesReadOnlyTrue(Control.ControlCollection cc)
{
foreach (Control ctrl in cc)
{
TextBox tb = ctrl as TextBox;
if (tb != null && tb.ReadOnly)
{ tb.ReadOnly = false; continue; }
if (ctrl.Controls != null && ctrl.Controls.Count > 0)
{ TextBoxesReadOnlyTrue(ctrl.Controls); }
// this recursively calls this same method for every control ...
// that is a container control that contains more controls, ...
// such as GroupBoxes, Panels, etc.
}
}
first you would like to use a function like this:
Recursive get controls
then you do the following
private IEnumerable<T> GetControls<T>(Control.ControlCollection ctrls)
{
foreach (object ctrl in ctrls)
{
foreach (var item in GetControls<T>(((Control)ctrl).Controls))
{
yield return item;
}
if (ctrl is T)
yield return (T)ctrl;
}
}
foreach(var txtbox in GetControls<TextBox>(form.Controls)
{
txtbox.ReadOnly = false;
}
In the constructor i did:
if (listBox1.Items != null)
{
listBox1.Focus();
}
But when im running the program i cant move with the keyboards up down in listBox since the focus is on a button somewhere else in the Form. I need to click with the mouse on the listBox to get the focus.
Another problem i want that when the user add a new item to the listBox the focus will be automatic on the last added item. For this problem this is the code where im adding a new item to the listBox:
private void KeysValuesUpdate()
{
using (var w = new StreamWriter(keywords_path_file))
{
crawlLocaly1 = new CrawlLocaly();
crawlLocaly1.StartPosition = FormStartPosition.CenterParent;
DialogResult dr = crawlLocaly1.ShowDialog(this);
if (dr == DialogResult.OK)
{
if (LocalyKeyWords.ContainsKey(mainUrl))
{
LocalyKeyWords[mainUrl].Clear();
LocalyKeyWords[mainUrl].Add(crawlLocaly1.getText());
}
else
{
LocalyKeyWords[mainUrl] = new List<string>();
LocalyKeyWords[mainUrl].Add(crawlLocaly1.getText());
}
Write(w);
ClearListBox();
}
if (dr == DialogResult.Cancel)
{
Write(w);
}
}
}
private void ClearListBox()
{
data.Clear();
listBox1.DataSource = null;
string sb;
foreach (KeyValuePair<string, List<string>> kvp in LocalyKeyWords)
{
for (int i = 0; i < kvp.Value.Count(); i++)
{
sb = "Url: " + kvp.Key + " --- " + "Local KeyWord: " + kvp.Value[i] + Environment.NewLine;
data.Add(sb.ToString());
}
}
listBox1.DataSource = data;
}
The question is why i cant set the focus in any of the cases on the listBox items ?
In the first case in the constructor the focus i want it to be on the last item in the list and also each time im adding a new item so the focus will be on the last added item.
Most likely, the item is being selected, you just can't tell because a different control has the focus. There are a couple of different ways that you can solve this, depending on the design of your application.
For the first part of the question, you should set the Focus in the Page/Form Load event, since at the constructor level controls are under initialization process.
Set the focus to the ListView first whenever your form is displayed. The user typically sets focus to controls by clicking on them. However, you can also specify which controls gets the focus programmatically. One way of doing this is by setting the tab index of the control to 0 (the lowest value indicates the control that will have the initial focus). A second possibility is to use the following line of code in your form's Load event, or immediately after you set the Selected property:
listBox1.Select();
The problem with this solution is that the selected item will no longer appear highlighted when the user sets focus to a different control on your form (such as a textbox or a button).
For the second part of the question, selecting last added item in the ListBox, use the following code:
listBox1.SelectedIndex = listBox1.Items.Count - 1;
listBox1.SetFocus();
Looks like your ClearListBox method is actually a UpdateListBox method.
listBox1.DataSource = data;
listBox1.SelectedIndex = <index of newitem>;
// or
listBox1.SelectedItem = "text of new item";
listBox1.SetFocus();
If the new item is the last item, its index is listBox1.Items.Count - 1.