Correct architecture to extend WinForm UserControl base classes? - c#

I have large number of UserControls that are very similar. They share a lot of common behavior. I been using a base class that has the common stuff and then specializing the class as needed.
class BaseControl : UserControl
{
// common
}
class RedControl : BaseControl
{
// specialized
}
class BlueControl : BaseControl
{
// specialized
}
etc ...
This works fairly well until I needed to start inserting or changing the layout of the child controls contained in BaseControl. For example RedControl needs to add a Button to a particular panel of the base control. In other case I need to change the size or layout of other base child controls.
When I tried the following code, I did not see any button at run time...
public partial class RedControl : BaseControl
{
public RedControl()
{
InitializeComponent();
addButtonToBase(); // no button shows up
this.PerformLayout();
}
void addButtonToBase()
{
Button button = new Button();
button.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)));
button.Location = new System.Drawing.Point(3, 3);
button.Size = new System.Drawing.Size(23, 23);
button.Text = "My Button";
baseSplitContainer.Panel1.Controls.Add(button); // protected child control in base
}
// ...
}
I can make the button display as a child of baseSplitContainer if I make addButtonToBase() virtual and manually add it to the generated code in InitalizeComponent() of BaseControl.
The layout of BaseControl was still going on and you are permitted to call virtual functions in constructors in C#.Net....
So even though it works, its not a nice solution. For one thing, when I edit BaseControl in the VS designer, the call to addBaseControl() in IntializeComponent gets removed,
For another calling virtual functions in constructors feels dangerous.
I think I need to get the layout of the base control to happen again in the derived control...
I tried that but either did it wrong or it wont work...
btw yes I know WPF is good at this. Cant use it because of constraints on other systems.

It turns out the correct way to modify the layout of a base control is to override the layout call from Control.OnLayout()
so something like
public RedControl()
{
//....
protected override void OnLayout(LayoutEventArgs e)
{
addButtonToBase(); // modify base layout
base.OnLayout(e);
}
}

I think you just miss something to call the base init logic to create the controls and your changes are then overriden. Try to call it like
public RedControl()
: base()
{ ... }
OR
public RedControl()
{
base.InitializeComponent();
InitializeComponent();
addButtonToBase(); // no button shows up
this.PerformLayout();
}

Related

Using other classes to modify panel within Windows Forms

The program i am trying to make involves one main form, which should be able to switch between 5 different menus. Programming all the functionality within the Form.cs file would make this an extremely long class, so what i want to do is call the Panel from another class to add control elements and load all the data from a MySQL database, depending on the menu chosen.
More specifically I have my ParentInterface.cs, where I want to show a ChoreUI within a dynamic Panel which will be modified in a new class called ChoreUI.cs.
I have tried making ChoreUI inherit from the ParentInterface, as well as making it the target. Though my lack of knowledge of Windows Forms is in the way.
ParentInterface.cs
namespace ChoreApplication.UI{
public partial class ParentInterface : Form
{
private ChoreUI ChoreUI;
private ParentInterface PUI;
public ParentInterface()
{
ChoreUI = new ChoreUI(PUI);
InitializeComponent();
}
private void ChoreNavButton_Click(object sender, EventArgs e)
{
var ChoreUI = new ChoreUI(PUI);
ChoreUI.DillerDaller();
}
}
ChoreUI.cs
namespace ChoreApplication.UI
{
class ChoreUI
{
public ParentInterface PUI;
public ChoreUI(ParentInterface PUI)
{
this.PUI = PUI;
}
public void DillerDaller()
{
PUI.dynamicPanel.Controls.Add(new Label { Location = new Point(10, 10), Name="textName", Text = "hello"});
}
}
I want to be able to add new control elements to the Panel, from the ChoreUI class instead of in the ParentInterface class. But as of now I am not succeeding in doing so.
What you have at the moment is not that bad, your child component has a reference to the parent and that's ok.
This, however, is the issue
public void DillerDaller()
{
PUI.dynamicPanel.Controls.Add(new Label { Location = new Point(10, 10), Name="textName", Text = "hello"});
}
At the basic level, you violate the encapsulation principle, where the dynamicPanel is protected inside the form so that it's not accessible from outside. Inheriting the child component from the main form is not the right solution.
At somewhat higher level, you violate here the so called Law of Demeter where the inner implementation details of a component should not be that misused. Changing the dynamicPanel visibility to public will not help. Rather, the rule says you should wrap such implementation details with a stable interface
public partial class ParentInterface : Form
{
...
public void AddDynamicPanelControl( Control control ) {
this.dynamicPanel.Controls.Add( control );
}
public void RemoveDynamicPanelControl( Control control ) {
this.dynamicPanel.Controls.Remove( control );
}
}
and use the newly introduced interface
public void DillerDaller()
{
var label = new Label { Location = new Point(10, 10), Name="textName", Text = "hello"};
this.PUI.AddDynamicPanelControl( label );
// if you store the reference to the label somewhere,
// you'll be able to call `Remove....` to remove this control from
// the form
}

Multiple exactly the same forms

I have multiple forms who look exactly the same (invoice, priceoffer, order,...) Now I created the first form. But after thinking about this, all the other forms will look exactly the same.
Is there a technique, or some other way that prevents me of creating all the forms over and over again?
Just create a single form class that you'd call OrderForm or something like that
public class OrderForm : Form {
}
Then use this to create all your controls on it, and create an instance of this class every time you need to show it to a user. Best part of this is if one of your forms changes (let's say invoice) you could just inherit from your class and only change the part that is different:
public class InvoiceForm : OrderForm {
}
And then just create an instance of InvoiceForm when you need that.
EDIT answering OPs question in the comment.
Let's say you create a bunch of controls in the constructor of OrderForm :
public class OrderForm : Form {
public OrderForm {
var button = new Button() { ... };
var label = new Label () { ... };
this.Controls.Add(button);
this.Controls.Add(label);
}
}
Now if you'd create an instance of OrderForm, these controls would be added to the form, no matter how many instances you'd create of these:
//each of these instances have the same controls
var form1 = new OrderForm();
var form2 = new OrderForm();
var form3 = new OrderForm();
Now if you'd define a subclass of OrderForm namely InvoiceForm, you'd be able to use the same form controls as OrderForm, with some changes to it:
public class InvoiceForm : OrderForm {
public InvoiceForm() : base(){ // : base() executes the constructor of the superclass
//all controls in the `OrderForm` class are added because we called base().
var invoiceControl = new Label() { ... };
this.Controls.Add(invoiceControl);
//now in total your form will count 4 controls
}
}
Now you can just create an instance of InvoiceControl to get the changed form.
//first 2 forms have 3 controls, last 2 forms have 4 controls.
var form1 = new OrderForm();
var form2 = new OrderForm();
var form3 = new InvoiceForm();
var form4 = new InvoiceForm();
Create a UserControl, and use it in multiple forms.
A good place to start with Visual inheritance.
https://msdn.microsoft.com/en-us/library/bx1155fz(v=vs.110).aspx
But as GuidoG say above you could have some strange behavior in Visual Studio and in the designer.
You could handle the different save button behavior by making the click handler calling an other overridable Sub and then override this Sub in the inherited Form (instead of coding your saving process directly in the event handler as most people do:-)).
Exemple
public class BaseForm
{
private void SaveButton_Click(System.Object sender, System.EventArgs e)
{
OnSaveButtonClick();
}
protected virtual void OnSaveButtonClick()
{
//Saving process for base form
}
}
public class InheritedForm : BaseForm
{
protected override void OnSaveButtonClick()
{
//Saving process for inherited form
}
}
Keep in mind that further modification in the "base" form would also apply to the inherited one without a word. This could be an issue if you are not well organized. Sometimes it's better to do a good old copy/paste.

A global function to be executed before opening a form

I have around 15 forms and every form includes some similar piece of codes.
What i want to know is that there is any way to automatically call a function containing that particular piece of code when a form is opening?
Like, lets say i want to show Hello World message every time when any form of a project is loaded.
So what i can do is i can create a module or class file and i can add a piece of code their and i can call it in every form.
But this i don't want, what i want is that, is there any way where i can add this piece of code and automatically it gets populated/triggered when a form gets loaded.
Maybe we can call it something like - auto calling function for forms
Like, whenever a form is opening automatically a class or function gets called without defining it in the particular form. Maybe a library kind of thing which will be called anyways when a form is loaded and i can add my piece of code there and it gets executed.
Create your own base class inheriting from form:
public abstract class FormBase : Form { /*...*/ }
Then every form you are using may inherit from this base class:
public class MyForm : FormBase { /*...*/ }
You can add an event handler to the form, and put whatever code needs to be run in there.
For Example:
this.Load += new System.EventHandler(this.FormName_Load);
public class frm_Base : Form
{
public void frm_Base()
{
this.Load += new System.EventHandler(this.frm_Base_Load);
}
public void frm_Base_Load(object sender, EventArgs e)
{
OnFormLoad();
}
public virtual OnFormLoad()
{
MessageBox.Show("Hello World");
}
}
public class frm_Derived : frm_Base
{
public override OnFormLoad()
{
base.OnFormLoad();
MessageBox.Show("Another Hello From Derived");
}
}
You can now inherit the functionality that happens on load in all of your other forms as well as do other things too by making your load method virtual.

In a Windows Forms (.Net 4) application, how can I define a base form style for all forms?

For example, I'd like all of my forms to have the same Icon and StartPosition. However I also need to be able to define things in each form how you normally would, dragging and dropping controls, etc.
Is this possible?
Create a form and set the Icon and StartPosition properties the way you want them. Compile. This will be your base form. Now use Project + Add New Item, Windows Forms node and pick the Inherited Form item template. The IDE will prompt you to select the base form.
Antoher way to go, is to make an extension method where you set all the parameters:
public static class FormExtentsions
{
public static void SetDefault(this Form form)
{
form.Icon = new Icon("path");
form.StartPosition = FormStartPosition.CenterScreen;
}
}
The use it like this:
public partial class Form1 : Form
{
public Form1()
{
// Put it here if you want to be able to override everything
this.SetDefault();
InitializeComponent();
// Put it here if you want the defualt to override "local" settings
this.SetDefault();
}
}
Simply create your own base Form class:
class FormBase : Form
{
public FormBase()
{
Icon = SomeIcon;
StartPosition = StartPosition.Whatever;
}
}
You could have a static Icon and Position, initialize it with a static constructor, and then make a constructor where you initialize the instance's Icon and Position properties with the static Icon and position:
Fx.
class Foo : Form {
static Bitmap sIcon { get; private set; }
static Point sPosition { get; private set; }
static Foo() {
sIcon = /* Load from external source */
sPosition = new Point( x, y ); //Insert x and y
}
public Foo()
: base() {
Icon = Foo.sIcon;
Position = Foo.sPosition;
}
}
Then use "Foo" as your base form when creating your forms.
I didn't check references for the "Icon" and "position" so I don't know if they exists, but you get the idea :)
Yes, but bear in mind that forms inheritance is somewhat flaky in terms of designer support. Just keep in mind that any controls that need to be accessible to child forms must have their modifier changed to Protected (Internal will work for forms in the same assembly, but will also expose the control to ANY class in the same assembly; Public should be avoided). This includes things like panels or other containers, which you'll likely want to use if your base form has to define some basic presentation elements, so you'll want to contain the child form to a particular area.
To this, just create your base form as you would any other form, then when you go to create new forms, choose "Inherited Form" instead of "Form", and select your base form.

How to call an EventHandler in a parent class

I have added an EventHandler for the Click-event to a picturebox but on runtime this handler is never called (the debugger shows me that it is added to the control directly but when I click on the picturebox nothing happens).
I assume it has something to do with my inheritance. I have a usercontrol called AbstractPage (its not really abstract since the designer doesnt like that) which only consists of a heading and this picturebox but it provides quite some functions the actual pages rely on.
#region Constructor
public AbstractPage()
{
InitializeComponent();
lblHeading.Text = PageName;
picLock.Click += new EventHandler(picLock_Click);
}
#endregion
#region Events
void picLock_Click(object sender, EventArgs e)
{
...do some stuff
}
#endregion
The page implementations just inherit this class and add their controls and behavior. We recently figured out that subclassing UserControl is not performant and we lose some performance there, but its the best way to do it (I dont want to c&p function for 25 pages and maintain them).
My pageA looks like this
public partial class PageA : AbstractPage
{
#region Constructor
public PageA()
{
// I dont call the base explicitely since it is the
// standard constructor and this always calls the base
InitializeComponent();
}
#endregion
public override string PageName
{
get { return "A"; }
}
public override void BindData(BindingSource dataToBind)
{
...
}
Anyway, the picLock_Click is never called and I dont know why?
The pages are all put into a PageControl which consists of a TreeView and a TabContainer where the pages are put once I call addPage(IPage)
public partial class PageControl {
...
protected virtual void AddPages()
{
AddPage(new PageA());
AddPage(new PageD());
AddPage(new PageC());
...
}
protected void AddPage(IPage page)
{
put pagename to treeview and enable selection handling
add page to the tabcontainer
}
Thanks in advance
If I understand your problem correctly, this worked for me out of the box (using VS2k8). My code:
public partial class BaseUserControl : UserControl
{
public BaseUserControl()
{
InitializeComponent(); //event hooked here
}
private void showMsgBox_Click(object sender, EventArgs e)
{
MessageBox.Show("Button clicked");
}
}
public partial class TestUserControl : BaseUserControl
{
public TestUserControl()
{
InitializeComponent();
}
}
I moved the TestUserControl to a form, clicked the button and got the message box as expected. Can you paste some more code, e.g. how do you use your AbstractPage?
I found the problem. We are using the Infragistics WinForms but in that case I used the standard picturebox. I replaced it with the UltraPictureBox and now it works.

Categories