I use this method to create a new TabPage in a TabControl (TabManager) if a TabPage with the specified text doesn't exist, or just select it if it already exists:
private void AddControls(UserControl uc, string TabCaption)
{
Boolean TabFound = false;
if (TabManager.TabCount == 0)
{
TabPage tp = new(TabCaption);
TabManager.TabPages.Add(tp);
uc.Dock = DockStyle.Fill;
tp.Controls.Add(uc);
TabManager.SelectedTab = tp;
}
else
{
TabPage tp = new(TabCaption);
foreach (TabPage tp1 in TabManager.TabPages)
{
if (tp1.Text == TabCaption)
{
TabFound = true;
}
}
if (TabFound != true)
{
TabManager.TabPages.Add(tp);
uc.Dock = DockStyle.Fill;
tp.Controls.Add(uc);
TabManager.SelectTab(tp);
//tp.Show();
//tp.BringToFront();
}
else
{
TabManager.SelectedTab = TabManager.TabPages[tp.Name];
return;
}
}
}
The problem is that the TabPage is not selected, instead an empty page is shown.
The offending code appears to be:
TabManager.SelectedTab = TabManager.TabPages[tp.Name];
As it will only show an empty TabPage.
I searched for documentation but found no solution so far.
If you create a new TabPage with the provided text (as in TabPage tp = new(TabCaption);), your tp object is not the same as an existing TabPage with the same Caption, so TabManager.SelectTab(tp); won't select it (not the same object).
You see a blank background because when you use the TabControl.SelectedTab() method and the TabPage specified doesn't exist, no TabPage is the Current, so you just see the TabControl background.
To determine whether to add or just select a TabPage, you can check whether the TabControl has no TabPages (as you're doing) and also verify whether a TabPage with the same Name already exists.
You can use the TabPageCollection.IndexOfKey() method to perform this check.
You should assign a Name to your new TabPage, not just a Caption, as it happens when you create a new TabPage in the Designer.
This simplifies the creation and/or selection of TabPages. Your code could then be:
private void AddControls(Control uc, string tabCaption)
{
if (TabManager.TabCount == 0 || TabManager.TabPages.IndexOfKey(tabCaption) < 0) {
var tp = new TabPage(tabCaption);
// or TabPage tp = new(tabCaption);
tp.Name = tabCaption;
uc.Dock = DockStyle.Fill;
tp.Controls.Add(uc);
TabManager.TabPages.Add(tp);
}
TabManager.SelectedTab = TabManager.TabPages[tabCaption];
}
Related
I have a windows form which include some textbox and labels.In my program I set all of them unvisible and when I press button it makes all of the labels and textbox visible with the code below and it works perfect.
List<Label> lbls = this.Controls.OfType<Label>().ToList();
foreach (var lbl in lbls)
{
if (lbl.Name.StartsWith("label"))
{
lbl.Visible = true;
}
}
List<TextBox> txts = this.Controls.OfType<TextBox>().ToList();
foreach (var txt in txts)
{
if (txt.Name.StartsWith("textBox"))
{
txt.Visible = true;
}
}
But when I put all of my labels and textboxes into groupbox.My code doesn't work.How can I do this?
Note: My groupbox is also unvisible and when I press button.
groupBox1.visible =true;
This code works and groupbox panel seems, but the code of labels and textboxes doesn't work.
Because you are working with the immediate child of Form here
List<Label> lbls = this.Controls.OfType<Label>().ToList();
Notice this that means your current form. so when you have controls outside in form it works,
But when you put them inside group box it won't be the immediate child anymore.
so use
List<Label> lbls = groupBox1.Controls.OfType<Label>().ToList();
This will give you access to immediate children of the group box.
You're better off creating a recursive method of your own. Try implementing something like this:
private void MakeControlsInvisible(Control container, params Type[] controlTypes)
{
foreach (Control control in container.Controls)
{
if (controlTypes.Contains(control.GetType()))
{
control.Visible = false;
}
if (control.Controls.Count > 0)
{
MakeControlsInvisible(control, controlTypes);
}
}
}
And then using it on whatever container you wish:
MakeControlsInvisible(this, typeof(Label), typeof(TextBox)); // Will make all labels and textboxes inside the entire form invisible.
MakeControlsInvisible(groupBox1, typeof(Label), typeof(TextBox));// Will make all labels and textboxes inside groupBox1 invisible.
I'm new working with C# and I'm asking on here because I didn't find a solution searching in google and other questions on SO, I will explain what my example application does:
When I run it it display a form with a textbox by default, this textbox always will be shown, after type some text and press enter it will generate a new textbox and a new button (all the controls even the default textbox are inside a panel), and the new textboxes have the same functionality as the default textbox, when I click on the button generated next to its textbox it removes the button itself and the textbox but after that if I remove some random textboxes it leaves a space between these controls, how can reorganize this content to dont let space between them?
As you can see in the image, can you tell me how can fix this or give me an advice to achieve this? thank you, by the way this is the method I use to generate the buttons and textboxes
private void GenerarTextBox()
{
panelContenedor.VerticalScroll.Value = panelContenedor.VerticalScroll.Minimum;
TextBox tb = new TextBox();
tb.Text = "Prueba " + id;
tb.Name = "txtBox" + id;
tb.KeyDown += new KeyEventHandler(TextBox_Keydown);
Button bt = new Button();
bt.Cursor = Cursors.Hand;
bt.Text = "X";
bt.Name = "btnPrueba" + id;
bt.Click += new EventHandler(ClickBotones);
Point p = new Point(20, 30 * id);
Point pb = new Point(130, 30 * id);
tb.Location = p;
bt.Location = pb;
panelContenedor.Controls.Add(tb);
panelContenedor.Controls.Add(bt);
tb.Focus();
id++;
}
And this to remove the textboxes and the buttons
private void ClickBotones(object sender, EventArgs e)
{
Button bt = sender as Button;
string nombreBoton = bt.Name;
string idBoton = nombreBoton.Substring(9);
string nombreTextBox = "txtBox" + idBoton;
foreach (Control item in panelContenedor.Controls.OfType<Control>())
{
if (item.Name == nombreTextBox)
{
panelContenedor.Controls.Remove(item);
panelContenedor.Controls.Remove(bt);
}
}
}
You could place your dynamic controls on a FlowLayoutPanel. Either directly or grouped together in a Panel or UserControl.
Set the FlowDirection property of the FlowLayoutPanel to TopDown. The FlowLayoutPanel will then arrange your controls automatically. You can also set the WrapContents property to False and AutoScroll to true to make the scroll bar appear.
Alternatively you can use FlowDirection = LeftToRight, place the text box and the button directly on the FlowLayoutPanel and let the child controls wrap (WrapContents = True). In the child controls, a new property FlowBreak appears. It can be set to True for the last control to appear in a row and let the next one wrap independently of the width of the FlowLayoutPanel.
You can also play with the Margin property of the child controls to control their layout in the FlowLayoutPanel as the Location property becomes useless.
The FlowLayoutPanel (as well as the Panel) is available in the Toolbox in the section "Containers".
When you delete the controls, you need to do a recalc of the positions. So when you have added them in sequence, you can go with:
bool repos = false;
Point p;
foreach (Control item in panelContenedor.Controls.OfType<Control>())
{
if (repos)
{
Point tmp = item.Location;
item.Location = p;
p = tmp;
}
if (item.Name == nombreTextBox)
{
panelContenedor.Controls.Remove(item);
panelContenedor.Controls.Remove(bt);
repos = true;
p = item.Location;
}
}
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 am trying to convert my Windows Form program into a WPF program in order to get images in my NotifyIcon systemtray icon. I am having issues converting it though. My current program has a tabcontrol1 and it uses the function: TabPages and DrawItem. WPF does not have these functions, but instead of TabPages it has Items, but I do not know what to change "DrawItem" to for WPF.
The reason why I am using "DrawItem" in my Windows Form is to change the color of the Tab text.
Changing from:
if (!find<T>(p.Value))
{
T myPage = new T();
myPage.Text = "Ping";
this.FormstabControl.TabPages.Add(myPage);
this.FormstabControl.DrawItem += new DrawItemEventHandler(ListBox1_DrawItem);
myPage.DataGridView.DataSource = p.Result;
}
To:
if (!find<T>(p.Value))
{
T myPage = new T();
myPage.Text = "Ping";
this.WPFtabControl.Items.Add(myPage);
//this.WPFtabControl.DrawItem += new DrawItemEventHandler(ListBox1_DrawItem);
myPage.DataGridView.DataSource = p.Result;
}
I had to comment out DrawItem because I do not know what to use instead. Also, type "T" is of type "TabPages" not Item. Below is the Find function that checks to see if the Tab already exists in the tabcontrol. When I declare myPage of type T, it setsup the DataGridView from within a different class (same as where T is defined.) I tried to fix it by throwing the WPF TabControl1 into a Windows.Form TabControl2, then searching through that tabcontrol's tabpages instead of passing a WPF Tabcontrol Item.
private bool find<T>()
{
bool found = false;
System.Windows.Forms.TabControl FormstabControl= new System.Windows.Forms.TabControl();
FormstabControl.TabPages.Add(this.WPFtabControl.Items.ToString());
foreach (TabPage page in FormstabControl.TabPages)
{
if (page is T)
{
found = true;
break;
}
}
return found;
}
private bool find<T>(string text) where T : TabPage
{
bool found = false;
System.Windows.Forms.TabControl FormstabControl= new System.Windows.Forms.TabControl();
FormstabControl.TabPages.Add(this.WPFtabControl.Items.ToString());
foreach (TabPage page in FormstabControl.TabPages)
{
if (page is T && text.Equals(page.Text))
{
found = true;
break;
}
}
return found;
}
When I run this app and the adjacent function, it runs and completes and adds a BLANK text tab, but returns no data to the datagrid within the TabControl. I do not know what is wrong.
How can I incorporate images in my NotifyIcon? I incorporate it like this:
this.notifyIcon.ContextMenu = new ContextMenu();
this.notifyIcon.ContextMenu.MenuItems.Add(new MenuItem("Hide", new EventHandler(hideApp)));
this.notifyIcon.ContextMenu.MenuItems.Add(new MenuItem("Show", new EventHandler(showApp)));
this.notifyIcon.ContextMenu.MenuItems.Add(new MenuItem("Exit", new EventHandler(exitApp)));
I'm trying to dynamically create objects, and then call from them later. For example...
for(int i=0;i<10;i++)
{
tabControl.TabPages.Add(i, i.ToString());
richTextBox rtb = new richTextBox();
rtb.Parent = tabControl.TabPages[i];
rtb.dock = fill;
}
then later in my coding..
private void onButtonClick_example()
{
var rtb = tabControl.SelectTab.GetChildrenByPoint(new point(1,1));
rtb.WordWrap = true;
}
How can I return that child as a "rich text box" again?
If GetChildrenByPoint returns something other than RichTextBox, then you need to use as and check for null so you don't crash when other controls are encountered.
foreach(var item in tabControl.SelectTab.GetChildrenByPoint(new point(1,1)))
{
RichTextBox rtb= item as RichTextBox;
if(rtb != null) //if we found a RichTextBox
{
rtb.WordWrap = true;
}
}
Add a dynamic ID to your rich text box control when you create it.
Loop through the controls in the selected tab:
foreach(var control in tabControl.SelectTab.Controls)
{
if(control.ID == "NEWCONTROLID")
{
RichTextBox rtb = (RichTextBox) control;
}
}
Did this off the top of my head, so there may be code issues, but hopefully it puts you on the path. Basically you need to search for the control you created in the controls collection of the selected tab, then cast it as a RichTextBox.
You could also use Control.Find() method to find your control and then cast it.