How to refactor duplicated code in Windows Forms? - c#

I'm currently working on refactoring a lot of duplicated code in a couple of UserControls in Windows Forms project.
The architecture leaves much to be desired with a lot of logic implemented inside the UI layer. This will be fixed eventually, I'm now working on improving what I have on my hands now.
The problem is, that a lot of duplicated code relates directly to controls, for instance:
private void InitDestinationPathControls(string path)
{
if (someField)
{
tbOne.Enabled = false;
tbOne.Visible = false;
btnTwo.Enabled = false;
btnTwo.Visible = false;
tbOne.Text = string.Empty;
return;
}
// (...)
}
Don't get too attached to the cited code itself, it is just an example.
I'd like to move this code to a common base class, but it relies directly on specific fields (even though they are exactly the same in all controls too). Those fields, on the other hand, are generated by the designer, so I cannot extract them to the base class.
The only thing that comes to my mind is to pass those fields as parameters to a method in base class, but then if some method uses a lot of them, I'll end up with a monstrous interface part and that won't actually improve the readability too much.
How can I deal with such common parts of user controls in Windows Forms?

Apparently you have a combination of several controls that appears in several forms. In other words: you have for instance some buttons, comboboxes, etc, that you want to show on different forms, and you want them to have the same behaviour.
If not only the behaviour of these controls is the same on all forms, but also the layout, then consider to create a class derived from UserControl.
The UserControl hides from the outside world which controls are shown, how they are visualized and how they behave.
I assume that you already pulled the model out of the visualisation of the data.
If all instances of this user control should all call the same functions of possibly a different object of the same class, then give your special user control a property that represents this class, or at least an interface. During construction you can plug in the actual class that should handle the actions after operator input.
If, on the other hand, the layout differs on each form, but the collection of buttons, comboboxes, etc and their behaviour is similar on all forms that show this collection of controls and they have a lot of common behaviour, consider to create your own ControlCollection.
For instance, if on several forms you have a button to select a (text) file, labels with the name, size and creation date of the selected file, and an edit box that shows the content of the text file, but you want to layout them differently, consider something like this:
class FileDisplayControls : IDisposable
{
public Button ButtonSelectFile {get;} = new Button();
public Label labelFileName {get; } = new Label();
public Label labelFileSize {get; } = new Label();
public TextBox textFileContents {get; } = new FileContents();
private void ButtonSelectFile_Clicked(object sender, ...)
{
// TODO: open file dialog, display result in labels and text box
}
}
Constructor can set initial layout properties of the controls, and subscribe to events, such that the controls will react on user input.
The user of the class (= code, not operator) immediately has a collection of controls that have some standard behaviour, like react on button click. All he has to do is set the location of the items in his own form. If desired change other layout properties (colour, background) and put them on his own form.
If you want to prevent that others change other visual aspects of the controls than the position, don't publish the control themselves, only the position of the control:
public System.Drawing.Point LocationSelectFileButton
{
get => this.buttonSelectFile.Location;
set => this.buttonSelectFile.Location = value;
}
public System.Drawing.Point LocationFileContentTextBox
{
get => this.textBoxFileContent.Location;
set => this.textBoxFileContent.Location = value;
}
etc.
If needed, you can add events for users:
public event EventHandler SelectedFileChanged;
public string FileName => this.labelFileName.Text;
public string FileContents => this.textBoxFileContent.Text;
etc.
Conclusion
The solution that you choose depends on the similarity between the controls on the various forms:
if Behaviour and Layout are all the same: UserControl
If only position and a few properties different: special class with the properties that are different. This way you can force a more similar style: all "Select File" buttons look similar.
If only one or two behaviours are different: add Action<...> properties or events
If you want full control of the layout: expose the Controls.
The behaviour that is common for all you forms that show these controls (in my example: how to select a file and what to do when a file is selected) is inside the class.

repeated code can be extracted to method (possibly in base class, or as static method in helper class)
public void DisableControls(params Control[] controls)
{
foreach(var c in Controls)
{
c.Enabled = false;
c.Visible = false;
if (c is TextBox t)
{
t.Text = string.Empty;
}
}
}
private void InitDestinationPathControls(string path)
{
if (someField)
{
DisableControls(tbOne, btnTwo);
return;
}
// (...)
}

Related

Pass form to another class and extract controls and properties

This is more so a general question than an issue I have. I have a form with 30+ controls that I will use to populate a model, which eventually ends up in a database.
I was wondering, however, if I could just pass the whole form object to another class, and pull the contents out in the other class without setting up a whole bunch of getters and setters.
Let's say I have a form Form1, and I make this call:
OtherClass.Validate(this)
Then, in the OtherClass (which is in a different project in the same solution) I have:
public static void Validate(Form1 myForm)
I have played around with this a little. In the Validate() method, if I put a watch on myForm, I can see all the form controls and properties, but I don't know if there is a way to just pull them out. If I type myForm., intellisense shows me all the standard form methods and properties, but not the controls and properties specific to the form. Has anybody tried this successfully?
You may grab controls from a form object using
myForm.Controls
This gives you a collection of controls within the form. You may iterate through them with a foreach loop.
Example with this form containing two buttons. You may use the following code to get the controls text.
public static void Validate(Form1 myForm)
{
foreach (Control control in myForm.Controls)
{
string text = control.Text;
Console.WriteLine(text);
}
}
Triggering the above function prints the following to the console. (Using this form)
button2
button1
This method works for TextBox and other controls too. However, it may be trickier if you have controls within controls. You may solve that by making a recursive function.
Each control on a form has a property called "Modifiers":
It is "Private" by default (for a reason: UI is the most likely subject for changes, any logic outside the form should not depend on controls. One control can be replaced with another, a group of controls can be replaced with a custom control. When controls are public, such change is not incapsulated and triggers changes in many parts of the system)
Controls which are added on a form, are serialized into C# code in form.designer.cs file. Code for textBox1, when it has modifier Private:
private System.Windows.Forms.TextBox textBox1;
Change it to Public like shown on a screenshot, and it will become
public System.Windows.Forms.TextBox textBox1;
Public controls will be accessible like any other public fields:
public static void Validate(Form1 myForm)
{
if (String.IsNullOrEmpty(myForm.textBox1.text))
{
// do smth about empty field
}
}

Create property of UserControl Instances (not new)

I have a usercontrol where I want a property that can list all the other instances of the same usercontrols in the Windows Form.
Eg. I have a simple usercontrol (sidebarbutton). I drag-drop 2 instances of it in a UserForm. Now I want a property (in the usercontrol itself) that can list both of them.
I have written this property. However, when used in Property Browser Window of Visual Studio, it allows me to add new instances of sidebarButton control.
private List<SidebarButton> _sidebarButtons;
[Browsable(true), EditorBrowsable(EditorBrowsableState.Always), Category("Roshan")]
public List<SidebarButton> SidebarButtons
{
get { return this._sidebarButtons; }
set { this._sidebarButtons = value; }
}
How to add the 2 instances that I drag-droped in the form in this property.
I know the property needs to be modified but don't have a proper direction to take. Please HELP me.
One way of doing this could be that upon addition of the user control to the form, the user control notifies all other user controls of that type that it now exists. Your user control can implement an interface like so:
public interface INotifiable
{
public void AddToList(INotifiable newButton);
}
When the user control is added to the form you iterate through all the controls and check whether they implement this interface. If they do you call the AddToList method and pass the newly added control to it.

Accessing controls from different forms

I have a main form with some buttons, textboxes, labels, etc.
On a second form I would like to copy the text from the main forms textbox onto the second form.
Have tried:
var form = new MainScreen();
TextBox tb= form.Controls["textboxMain"] as TextBox;
textboxSecond.Text = tb.Text;
But it just causes an exception. The main screen textbox is initialised and contains text.
When I hover over form I can see all the controls are there.
What am I doing wrong?
Looking at the original code, there are two potential reasons for the NullReferenceException you are getting. First, tb is not defined in the code you provide so I am not sure what that is.
Secondly, TextBox textbox = form.Controls["textboxMain"] as TextBox can return null if the control is not found or is not a TextBox. Controls, by default, are marked with the private accessor, which leads me to suspect that form.Controls[...] will return null for private members.
While marking the controls as internal will potentially fix this issue, it's really not the best way to tackle this situation and will only lead to poor coding habits in the future. private accessors on controls are perfectly fine.
A better way to share the data between the forms would be with public properties. For example, let's say you have a TextBox on your main screen called usernameTextBox and want to expose it publicly to other forms:
public string Username
{
get { return usernameTextBox.Text; }
set { usernameTextBox.Text = value; }
}
Then all you would have to do in your code is:
var form = new MainForm();
myTextBox.Text = form.Username; // Get the username TextBox value
form.Username = myTextBox.Text; // Set the username TextBox value
The great part about this solution is that you have better control of how data is stored via properties. Your get and set actions can contain logic, set multiple values, perform validation, and various other functionality.
If you are using WPF I would recommend looking up the MVVM pattern as it allows you to do similar with object states.
PhoenixReborn is correct. The problem is that you are creating a new MainScreen, which means that new controls are created, so unless the text in your controls are initialized in the form constructor, they are going to be empty. Usually, the way to handle this is to pass the first form instance to the second form, like this:
SecondForm second = new SecondForm(this);
and in the second form:
public SecondForm (MainForm form)
{
// do something with form, like save it to a property or access it's controls
}
That way, the second form will have access to the first form's controls. You might consider making the properties you need to use public (in the designer properties pane). That way you can just do form.textboxMain.Text.

Move controls from the user control to a panel control

I'm trying to write a form theme class library to adjust the form layout in a simple way for any project I'll be working on.
This is basically an idea of what it should look like:
http://www.beaverdistrict.nl/form_layout.png
In essence, the plugin works as follows:
// form class, which inherits the plugin class
class FormToTheme : ThemedForm
{
public FormToTheme()
{
// some code here
}
}
// plugin class itself
class ThemedForm: Form
{
public ThemedForm()
{
// some code here
}
}
Basically I set the FormBorderStyle to None, and drew the layout by code.
But now, the controls that are added can be placed over the custom titlebar, which isn't possible in a normal form if you keep the default FormBorderStyle.
So I figured that I could work around this by automatically adding the controls to the content panel, instead of the usercontrol.
So what I tried to do was this:
private void ThemedForm_ControlAdded(Object sender, ControlEventArgs e)
{
// some simple code to set the control to the current theme I'm using
e.Control.BackColor = Color.FromArgb(66, 66, 66);
e.Control.ForeColor = Color.White;
// the code where I try to place the control in the contentPanel controls array,
// and remove it from it's parent's controls array.
if (e.Control.Name != contentPanel.Name)
{
e.Control.Parent.Controls.Remove(e.Control);
contentPanel.Controls.Add(e.Control);
}
}
But when I try to add a new control in the main form as well as in the visual editor, i get the following error:
child is not a child control of this parent
So my question is: is there a way to work around this error, and move the controls from the usercontrol to the content panel?
Note that I do want this to be automated in the ThemedForm class, instead of calling methods from the main form.
EDIT:
I tried this:
http://forums.asp.net/t/617980.aspx
But that will only cause visual studio to freeze, and then I need to restart.
I know that it is not really appropriate to answer ones own question, however the solution I came up with will take quite some explaining, which will be too much to add in my question with an edit.
So here we go:
Inside the inherited 'ThemedForm' class, I created a private variable, in order to be able to return the variable when the Controls property would be called:
private Controls controls = null;
I set the variable to null, because I need to pass variables to the class in the 'ThemedForm' class constructor. I will create a new instance of the class later on.
Then I created a class to replace the Controls property:
public class Controls
{
private Control contentPanel = null;
private ThemedForm themedform = null;
public Controls(ThemedForm form, Control panel)
{
contentPanel = panel;
themedform = form;
}
public void Add(Control control)
{
if (control != contentPanel)
{
contentPanel.Controls.Add(control);
}
else
{
themedform.Controls_Add(control);
}
}
public void Remove(Control control)
{
if (control != contentPanel)
{
contentPanel.Controls.Remove(control);
}
else
{
themedform.Controls_Remove(control);
}
}
}
I know this class holds far from all functionality of the original Controls property, but for now this will have to do, and if you like, you can add your own functionality.
As you can see in the Add and Remove methods in the Controls class, I try to determine wether the control that needs to be added is either the content panel I want to add the rest of the controls to, or any other control that needs to be added to the content panel.
If the control actually is the content panel, I add or remove it to or from the Controls property of the base class of the 'ThemedForm' class, which is a 'Form' class. Otherwise, I just add the control to the content panel's Controls property.
Then I added the Controls_Add and Controls_Remove methods to the 'ThemedForm' class, in order to be able to add or remove a control from the Controls property of the 'ThemedForm' base class.
public void Controls_Add(Control control)
{
base.Controls.Add(control);
}
public void Controls_Remove(Control control)
{
base.Controls.Remove(control);
}
They are quite self-explanatory.
In order to call the Controls.Add or the Controls.Remove methods from an external class, I needed to add a public property that hid the current Controls property, and returned the private variable that I assigned to the replacing class.
new public Controls Controls
{
get { return controls; }
}
And finally I created a new instance of the Controls class, passing the current 'ThemedForm' class, and the contentPanel control, in order to get it all to run.
_controls = new Controls(this, contentPanel);
After doing all this, I was able to 'redirect' any controls that were added to the UserControl (even inside the visual editor) to the content panel. This allowed me to use the Dock property of any control, and it would dock inside the content panel, instead of over my entire form.
This is still a little bit buggy, because inside the visual editor the docked controls still seem like they are docked over the entire form, but when running the application the result is as I wanted.
I really hope this helps anyone.

Building C# .NET windows application with multiple views

I'm rewriting an old application and use this as a good opportunity to try out C# and .NET development (I usually do a lot of plug-in stuff in C).
The application is basically a timer collecting data. It has a start view with a button to start the measurement. During the measurement the app has five different views depending on what information the user wants to see.
What is the best practice to switch between the views?
From start to running?
Between the running views?
Ideas:
Use one form and hide and show controls
Use one start form and then a form with a TabControl
Use six separate forms
Creating a bunch of overlaid panels is a design-time nightmare.
I would suggest using a tab control with each "view" on a separate tab, and then picking the correct tab at runtime. You can avoid showing the tab headers by putting something like this in your form's Load event:
tabControl1.Top = tabControl1.Top - tabControl1.ItemSize.Height;
tabControl1.Height = tabControl1.Height + tabControl1.ItemSize.Height;
tabControl1.Region = new Region(new RectangleF(tabPage1.Left, tabPage1.Top, tabPage1.Width, tabPage1.Height + tabControl1.ItemSize.Height));
What I do is to have a Panel where your different views will sit on the main form.
then create user controls for your different views.
Then when I want to switch between a'view' you dock it to Panel on the main form.. code looks a little like this.
i preffer this because you can then reuse your views, like if you want to open up a view in a tab you can dock your user controls inside tab pages.. or even inherit from
tabpage instead of usercontrol to make things a bit more generic
public partial class MainForm : Form
{
public enum FormViews
{
A, B
}
private MyViewA viewA; //user control with view a on it
private MyViewB viewB; //user control with view b on it
private FormViews _formView;
public FormViews FormView
{
get
{
return _formView;
}
set
{
_formView = value;
OnFormViewChanged(_formView);
}
}
protected virtual void OnFormViewChanged(FormViews view)
{
//contentPanel is just a System.Windows.Forms.Panel docked to fill the form
switch (view)
{
case FormViews.A:
if (viewA != null) viewA = new MyViewA();
//extension method, you could use a static function.
this.contentPanel.DockControl(viewA);
break;
case FormViews.B:
if (viewB != null) viewB = new MyViewB();
this.contentPanel.DockControl(viewB);
break;
}
}
public MainForm()
{
InitializeComponent();
FormView = FormViews.A; //simply change views like this
}
}
public static class PanelExtensions
{
public static void DockControl(this Panel thisControl, Control controlToDock)
{
thisControl.Controls.Clear();
thisControl.Controls.Add(controlToDock);
controlToDock.Dock = DockStyle.Fill;
}
}
Tabbed forms are usually good... but only if you want the user to be able to see any view at any time... and it sounds like you might not.
Separate forms definitely works, but you need to make sure that the switch is seemless...if you make sure the new form appears the same exact size and location of the old form, it will look like it thew same for with changing controls.
The method I often use is actually to pre-setup all my controls on individual "Panel" controls and then show and hide these panels as I need them. The "Panel" control is basically a control container... you can move the panel and all controls on it move relative. And if you show or hide the panel, the controls on it do the same. They are great for situations like this.
The method I often use is actually to
pre-setup all my controls on
individual "Panel" controls and then
show and hide these panels as I need
them.
Instead of making each view a panel within a single form you could make each view a UserControl. Then create a single form and write code to create and display the correct UserControl in the Form and to switch from one to the next. This would be easier to maintain because you will have a separate class for each view instead of a single Form class with 6 panels each with their own controls -- that seems difficult and error prone to maintain.
I would also check out Composite Application Guidance for WPF or Smart Client Software Factory

Categories