I'm building a small tabbed c# Form and I'd like each tab page to have some common features, notably, an OK button and an error message and to have a space for the specific form fields.
Has anyone else done something similar and how did you approach it?
This is easy to do without extending either TabControl/TabPage.
Define one UserControl, and put the common elements on it you want on every TabPage.
On the Form: go ahead and design the TabPage specific controls you want for each TabPage : make sure they are not going to visually overlap with the common controls once the UserControl has been added.
In the Form Load Event of your main Form do something like this :
// form scoped variable to hold a referece to the current UserControl
private UserControl1 currentUserControl;
private void Form1_Load(object sender, EventArgs e)
{
foreach(TabPage theTabPage in tabControl1.TabPages)
{
currentUserControl = new UserControl1();
theTabPage.Margin = new Padding(0);
theTabPage.Padding = new Padding(0);
theTabPage.Controls.Add(currentUserControl);
currentUserControl.Location = new Point(0,0);
currentUserControl.Dock = DockStyle.Fill;
currentUserControl.SendToBack();
}
}
Even though the 'SendToBack isn't really required here it is "insurance" that your UserControl with the 'Okay button and TextBox for an error message are placed behind the individual controls you have assigned to each TabPage.
Several ideas:
Keep the common controls outside the tabpanel;
Extend the TabPage/TabControl
Create a base UserControl with the common buttons and make usercontrols that inherit from it. Then place one inherited usercontrol per TabPage.
Related
I have a form with a splitcontainer. One of the panels in the splitcontainer then loads a user control. This user control contains a combobox with the following properties changed from default:
DrowdownStyle=DrowDownList
FlatSyle=flat
Anchor=top,left,right
When the form and user control load, the combobox looks as expected:
However, if I resize the splitcontrol, it starts looking weird. If I increase the size of the panel that holds the control, this happens:
If i decrease the size of the splitter, it looks less weird, but still not how it is supposed to:
In either case, the combobox starts looking normal once I mouse over it.
Here is my code:
Main Form:
//Main form. Has a split container created from the toolbox
public partial class SampleForm:Form
{
public SampleForm()
{
InitializeComponent();
SampleControl cntrl = new SampleControl();
splitContainer1.Panel1.Controls.Add(cntrl);
splitContainer1.BorderStyle = BorderStyle.FixedSingle;
cntrl.Dock = DockStyle.Fill;
}
}
User Control:
//User control. Has a combobox created from toolbox. Options as described above
public partial class SampleControl : UserControl
{
public SampleControl()
{
InitializeComponent();
}
}
How can I fix this problem?
May be try to set dock to None . Also try to fix the margin for control
I know this is old, but was having the same problem and just in case someone finds themselves in the same predicament, answer below. Basically, create a new Class:
Imports System.Windows.Forms
Public Class MyComboBox
Inherits ComboBox
Protected Overrides Sub OnResize(e As EventArgs)
Me.Refresh()
End Sub
End Class
You can either drag this from your toolbox or add it in code. It will then auto redraw itself on resize of any parent controls.
Winforms is a bit like a sibling - flawed and deeply annoying sometimes - but you just gotta love it.
This might be kind of beginner question but I searched and didn't find any clear answer!
The main question is: How to inherit properties of a control (specially FlatStyle) from a base form which doesn't have that control in C#?
Details: I have Form1 inherited from baseForm. baseForm has a Panel and a Label control but no Button. In Form1 I added a button named Button1. How can I change the style of that Button through the baseFrom?
I don't want to create a custom control or redesign the button using rectangles or similar ways, but only change that property for all buttons in my application.
UPDATE: I want all of the buttons to be affected, whether they already exist or just added. Not matter in which -if any- container they are.
In baseForm, you could hook the ControlAdded event on the Panel where the Button is to be added, and style appropiately via code. This will work for every form inherited from baseForm.
For example (in baseForm)
public partial class BaseForm : Form
{
public BaseForm()
{
InitializeComponent();
// "myPanel" is the panel where the button will be added in inherited forms
myPanel.ControlAdded += myPanel_ControlAdded;
}
private void myPanel_ControlAdded(object sender, ControlEventArgs e)
{
var button = e.Control as Button;
if (button != null)
{
button.FlatStyle = FlatStyle.Flat;
button.ForeColor = Color.Red;
}
}
}
Just made a really quick test... it works even in design mode:
As an alternative, if you are going to use heavily styled buttons everywhere in your application, you may consider creating a custom control inheriting from Button, and assign the properties there, like:
public class FlatButton : System.Windows.Forms.Button
{
public FlatButton()
{
FlatStyle = FlatStyle.Flat;
}
}
After building, you will find it in the Toolbox (under "[Your Project's] components" tab), or you can cram it on your own control library (in a different solution) and add it permanently to the Toolbox in Visual Studio.
You would need to make use of Reflection
You can use a LINQ query to do this. This will query everything on the form that is type Button
var c = from controls in this.Controls.OfType<Button>()
select controls;
foreach(var control in c)
control.FlatStyle = FlatStyle.Flat;
Let's say that I have a panel with like... 3 controls in it. I may end up adding more controls to it or changing the positioning within that panel. When the program starts, I will programmatically HIDE the control. Eventually, the user can click a button that will create a duplicate of the original panel to populate an area on the form. The button should have the option for another click eventually, meaning that multiple instances of these can come about to populate this area. Remember that these controls may have text labels within them that can be individually set or altered later on, programmatically. I am assuming that to do this program, I need to make a List of controls, maybe a List of panels? I'm not exactly sure how to do this considering the fact that I need multiple controls duplicated multiple times.
Is there a nice, simple way to do this? I really don't want to do the duplication with any kind of 3rd-party package.
You will have to do it in code and therefore it'll be as nice as you can code ;-)
Seriously the course most often taken is to
create a UserControl which is a class related to a form, with the layout you want..
..and add more and more instances of it..
..often to a FlowLayoutPanel, often with AutoScroll
This is pretty nice and simple imo.
Here is a short walk-though..:
first we start, as usual, by picking a nice name for the UserObject class, maybe 'DataPanel' or 'BookItem'..
Next we create it: Go to the project explorer and right-click, choosing Add-New UserControl and give it the class name you chose. I'll use 'BookItem'.
Now you can see the Designer showing you a small empty control.
Look closer: You can also see that in the project explorer ther is now not only the new 'BookItem.cs' file but also the complementary 'BookItem.Designer.cs' and even a 'BookItem.resx' file; so this works very much like creating a new Form..
Let's add a few controls from the toolbox, I chose to add a PictureBox, four Labels and a NumericUpDown.
Have a look at the BookItem.Designer.cs file: Here you can see the very things you see in a Form.Desginer.cs file: All settings and all declarations for all controls you add to the layout. Note especially the declarations (at the bottom of the file): Just like for a Form, all controls by default are declared as private!
We can now work on the layout and script the controls. We also can add functions and properties to the UC, just like a Form.
Please note: Anything you need to access from outside, read from your form or its methods must be public! So if you want to access the NUpDown, let call it 'nud_quantity' you have a choice
You can change its declaration in the BookItem.Designer.cs from private to public or in the Designer by changing the Modifiers property
Or you can write a public function in the UC to get/set its value
Chosing between those two ways is a matter of taste; if other developers will work with the UC class, it will probably be better to put close control over what you expose by writing access methods.
After you have compiled the project you can see the new UC in the Toolbox.
You can now either add it from the Toolbox or
you can add it in code like any control you create dynamically.
Let's look at an example:
Imagine a simple order system in a bookstore: The customer has done a search on the books in our store and is presented with a list of books in a DataGridView 'dgv_bookList', readonly, multiselect. To the right there is a FlowLayoutPanel 'flp_cart' represeting a shopping cart. And we have a command button 'cb_addItems' to add selected books to the cart.
The Button might be scripted like this:
private void cb_addItems_Click(object sender, EventArgs e)
{
if (dgv_bookList.SelectedRows.Count <= 0) return;
foreach (DataGridViewRow row in dgv_bookList.SelectedRows)
{
BookItem book = new BookItem (row);
book.label1.Text = "#00" + book.label1.Text;
book.Name = book.label1.Text;
flp_cart.Controls.Add(book);
}
}
This will add one BookItem for each selected row in the DGV.
A few things to note on the above code:
I pass a DataGridViewRow into the constructor of the UC so it can directly set its labels! This means that, in addition to the parameterless contructor the desginer has built for us, we need to write a second contructor, maybe like this:
public bookItem()
{
InitializeComponent();
}
public bookItem(DataGridViewRow bookData)
{
InitializeComponent();
label1.Text = bookData.Cells[0].FormattedValue.ToString();
label2.Text = bookData.Cells[1].FormattedValue.ToString();
label3.Text = bookData.Cells[2].FormattedValue.ToString();
label4.Text = bookData.Cells[3].FormattedValue.ToString();
}
Instead you could write a public setData(DataGridViewRow bookData) function.
Also note how stupid my labels are named! You can do better than that, I hope!
Also note how I access 'label1' and modify its Text from a Button in the Form; to do that I had to change its declaration in the Desginer.cs file:
private System.Windows.Forms.PictureBox pb_cover;
public System.Windows.Forms.Label label1; // <<----expose this label !
private System.Windows.Forms.Label label2;
private System.Windows.Forms.Label label3;
private System.Windows.Forms.Label label4;
private System.Windows.Forms.NumericUpDown numericUpDown1;
Often preferrable: An access function, maybe like this:
public int quantity() { return (int) numericUpDown1.Value; }
Or, of course a Property:
public int quantity { get { return (int)numericUpDown1.Value; } }
Also note, that I set the Name of the BookData item to some variant of the 1st data item, my book id. This might as well, or better, happen in the constructor; and there should be a check to prevent adding the same item twice..
All in all one can say, that using UserControls is very much like working with Forms, including all the usual ways or tricks for inter-form communication: keep references, expose members, create properties and functions..
One final Note: Like with forms or subclassed controls there is one catch: By placing them in the designer, you assign the designer the responsiblity to display your UC during design time.
This is normally just fine; however it is also possible to introduce subtle mistakes which make it impossible for the designer to display the control. You need to correct these problems before the designer will be able to show a control or any form that contains it. Let have a look at a simple example of such a problem:
Let's script the Paint event of the PictureBox 'pb_cover' in the UC:
public Brush myBrush = null;
private void pb_cover_Paint(object sender, PaintEventArgs e)
{
if (pb_cover.Image == null)
{
Size s = pb_cover.ClientSize;
e.Graphics.FillRectangle(myBrush, 0, 0, s.Width, s.Height);
e.Graphics.DrawLine(Pens.Red, 0, 0, s.Width, s.Height);
e.Graphics.DrawLine(Pens.Red, s.Height, 0, 0, s.Width);
}
}
And let's modify the code in the Add button:
BookItem book = new BookItem (row);
book.label1.Text = "#00" + book.label1.Text;
book.myBrush = Brushes.OliveDrab;
flp_cart.Controls.Add(book);
Now, if you run the program all will be fine. Even if you try to look at the UC in the designer there may or may not be problems. But once you try to open a Form on which the UC was placed, the Desginer will crash and tell you that it can't work, since the Brush is null. Here the remedy is simple: add a default value to the Brush declaration and all is well. Other situations may need a little more thinking..
I don't even run into the problem btw, since I have not placed an instance of BookItem on the Form; they are only created in the Add Button..
I hope that gets you started!
hi all and sorry for the confusing title... can't find the right words yet.
just out of curiosity, I am playing with c# user control, I created a control that is built from a panel, a textbox that is being used as a filter, and some labels / buttons/ etc... that are being filtered.
when ever you change the text of the textbox, all the controls on the panel are visible / invisible depending if their Text property contains the Text of the textbox. very simple.
but I want this user control to be such that the user that uses it can drop more labels or controls to it and they will behave the same, I can't figure out how to do that..
when I am editing the control (adding controls to it), it works as expected and the new controls behave as the old ones without code modifications, but only when I am editing the user control and not when using it.
when I am dragging the user control to a form, I can not add controls to it... when I try to add a label to the control - it is just added to the form and not to the control and therefore the text box is not influencing the added label. what should I do if I want to be able to add the control to a form and then add some controls to the control?
I will be happy for some pointers.
here is the relevant code:
private void textBox1_TextChanged(object sender, EventArgs e)
{
foreach (Control c in panel1.Controls)
{
if (c.Text.Contains(textBox1.Text))
{
c.Visible = true;
}
else
{
c.Visible = false;
}
}
}
edit - pictures added.
as you can see - i typed 1 in the filter text box and all the controls except button1 are now invisible - and of course the bad behaving label.
Thanks,
Jim.
This problem can be solved easily by following the guidelines in
https://support.microsoft.com/en-us/kb/813450 which decribes step by step How to make a UserControl object acts as a control container design-time by using Visual C#
In order to modify the user control as a design time control container
add the following code to the Declarations section:
using System.ComponentModel.Design;
Apply the System.ComponentModel.DesignerAttribute attribute to the control as follows:
[Designer("System.Windows.Forms.Design.ParentControlDesigner, System.Design", typeof(IDesigner))]
public class UserControl1 : System.Windows.Forms.UserControl
{
...
}
Then build the solution.
the control will appear as usual in the Toolbox and can be added to forms. Additional controls such as buttone , text boxes etc.. can be added to the control as required.
You describe one of the reasons why I almost never use UserControls. Anything that isn't done to the original UC must be done in code..
You can instead make it a class that is not a UserControl, ie make it a simple subclass of Panel (or FlowLayoutPanel as I do here merely for convenience, while dropping stuff on it during my tests).
class FilterPanel : FlowLayoutPanel
{
TextBox tb_filterBox { get; set; }
Label st_filterLabel { get; set; }
public FilterPanel()
{
st_filterLabel = new Label();
st_filterLabel.Text = "Filter:";
this.Controls.Add(st_filterLabel);
tb_filterBox = new TextBox();
this.Controls.Add(tb_filterBox);
// st_filterLabel.Location = new Point(10, 10); // not needed for a FLP
// tb_filterBox.Location = new Point(100, 10); // use it for a Panel!
tb_filterBox.TextChanged += tb_filterBox_TextChanged;
}
void tb_filterBox_TextChanged(object sender, EventArgs e)
{
foreach(Control ctl in this.Controls)
{
if (ctl != tb_filterBox && ctl != st_filterLabel)
ctl.Visible = ctl.Text.Contains(tb_filterBox.Text);
}
}
}
Now after placing it on a form (or whatever) you (or whoever) can drop Controls onto it in the designer and they'll be part of its Controls collection, just like you want it and will behave as expected..
Two notes on subclassing Controls:
If you break one during developement, the Form(s) using it will be broken, too, until you fix the problem. So take a little extra care!
For the Designer to display the Control it always needs to have one parameterless constructor, like the one above. Even if you prefer to have the ability to hand in parameters, one parameterless constructor must still be there or the designer will get into trouble!
I've been playing with Windows Form Application for a half year, and now I found a very wired thing: The control created in Form Designer cannot be set with another instance in code.
Here is what I've done:
I create a Windows Form Application, the main form is Form1 class. And I drag a GroupBox control to Form1, then I add this code in Form_Load event:
private void Form1_Load(object sender, EventArgs e)
{
GroupBox gb = new GroupBox();
gb.Text = "gb in code";
groupBox1 = gb;
//groupBox1.Text = "set in code";
}
Normally, I thought it will give instance groupBox1 the new value of 'gb', which is another instance of GroupBox class, the the Text property is "gb in code". However, when I press F5, compile and run, the groupBox1 Text is still "groupBox1". And even if I uncomment the last line, the groupBox1 Text is not changed to "set in code".
I feel it's so wired. Is it because I created the groupBox1 instance in Form Designer? So I cannot revalue it in another partial of class?
I kinda need to revalue the control created in Form Designer, for my user controls.
Any help will be much appreciated. Thanks guys :)
--------------------EDIT---------------------
What I want to achieve is: I have two user controls: UC1 and UC2, and Now I'm creating another user control UC3, which contains UC1 and UC2. When I initiate UC3, I want to pass the instance of UC1 and UC2 to UC3, otherwise I have to pass all the property values of UC1 and UC2 to UC3. And I kinda need to use Designer for UC3, to re-size UC1 and UC2.
So in UC3, I drag my UC1 and UC2 to UC3 Designer, and I make a public property point to UC1 and UC2, like:
public UserControl param_UC1
{
get
{
return UC1;
}
set
{
UC1 = value;
}
}
But unfortunately, the UC1 is not set by param_UC1. Now I think I should not use designer, just add UC1 during the Load event.
Thanks a lot for your help :)
Your thinking is completely incorrect.
What you're doing here.. is creating a reference to an in-memory GroupBox instance. Then you're pointing your current reference at this new instance. That means both references are pointing at your in-memory GroupBox.. your GroupBox on your form is now completely unreferenced.
If you need to set properties on the control.. do so through the initial reference. Currently you're changing properties of your in-memory GroupBox. Your thinking here is completely wrong.. maybe you would benefit from telling us what you're trying to achieve so that we can help you achieve that goal.