Managing EventHandlers within a large Form - c#

I'm developing a WinForm application and I've done a pretty bad job thus far of managing the size and contents. I was hoping someone could give me an example of how to break out some of the logic that I have within the main form cs file.
Here is an example of an EventHandler function that I have within my MainWindow.cs:
private void GroupBoxRequestTypeCheckedChanged(object pSender, EventArgs pEventArgs)
{
RadioButton vRadioButton = pSender as RadioButton;
if (vRadioButton != null)
{
this.fSelectedButton = vRadioButton.Checked ? vRadioButton : null;
if (vRadioButton.Equals(this.RadioButton_Copy) || vRadioButton.Equals(this.RadioButton_Delete) || vRadioButton.Equals(this.RadioButton_Download)
|| vRadioButton.Equals(this.RadioButton_Move) || vRadioButton.Equals(this.RadioButton_Upload))
{
this.GroupBox_Files.Enabled = true;
this.GroupBox_Variables.Enabled = false;
}
else
{
this.GroupBox_Files.Enabled = false;
this.GroupBox_Variables.Enabled = true;
}
if (this.fSelectedButton != null)
{
if (this.fSelectedButton.Equals(this.RadioButton_Delete))
{
this.TextBox_DestinationFile.Enabled = false;
this.Button_DestinationBrowse.Enabled = false;
}
else
{
this.TextBox_DestinationFile.Enabled = true;
this.Button_DestinationBrowse.Enabled = true;
}
}
}
}
So this is simply one of many EventHandler's that I have within a Form. I created a MainForm which has a Tabbed Pane and has a collection of Tabs which have buttons, textboxes, checkboxes etc in each tab. All of the events that I handle go into the MainForm.cs file and now I've got close to 1,000 lines in this one file.
Can someone give me a simple example (or a article/document) detailing good structure? Can I define my EventHandler functions in a separate class (if so, how would this work...) Do I create some sort of static Helper class where I simply pass the instance of the objects i need to manipulate? I.E.
private void GroupBoxRequestTypeCheckedChange(object pSender, EventArgs pEventArgs)
{
HelperClass.HandleGroupBoxRequestTypeCheckedChanged(pSender, pEventArgs, this);
}
Where 'this' is the Form itself which has all the references to the objects I need to manipulate?
It's probably worth noting that I've learned a good bit about the Cross-Thread calls and I've started making Extension methods for many instances that I need which are simplistic.
Another question - I notice that the Visual Designer automatically makes all Components created with it private by default, is it in general a bad idea to make these internal and use the form object to reference these components as needed from outside the class? If it is not a good idea, what is better?

First I would suggest to separate independent user-interface parts into UserControls or Components. Then - if needed - wire them using Events (eg. your own specialized events and properties.
For example you can place your main content (the TabControl / Container) in a UserControl and place that user control in the main form. All tab-/page-switching logic/UI etc. then belongs to that user control. In that UserControl you can define for example your own Event that gets fired when the user switches a tab. The main form then can register to this event - just like it can for other Winforms-control-events - and do its stuff (eg. change the window title to represent the currently active tab).
Then next you can move the content of each tab to its own user-control and use these user-controls within your new tabs-usercontrol. Move the logic down to the UserControl which is responsible for the given task.
A form/controls hierarchy from some typical application could look like this:
MainForm (Form)
MainTabContainerControl (UserControl)
Page1Control (UserControl)
Page2Control (UserControl)
MyImprovedDbRowGridControl (UserControl or Component)
Page3Control (UserControl)
SidebarControl (UserControl)
SearchControl (UserControl)
MyImprovedDbRowGridControl (UserControl or Component)
QuickHelpControl (UserControl)
Next thing is so keep all the UI-eventhandlers as small as possible and doing only UI stuff. Move other logic like business- or dataaccess-logic to other classes outside of the user-interface.
If you have combinations of the controls that are needed more then once in the application: move them to a re-usable UserControl. (eg. breadcrum).
Regarding your sample code you can make it more compact and therefore maintainable by simplyfing its logic:
if (this.fSelectedButton.Equals(this.RadioButton_Delete))
{
this.TextBox_DestinationFile.Enabled = false;
this.Button_DestinationBrowse.Enabled = false;
}
else
{
this.TextBox_DestinationFile.Enabled = true;
this.Button_DestinationBrowse.Enabled = true;
}
...could be:
var delete = fSelectedButton == RadioButton_Delete;
this.TextBox_DestinationFile.Enabled = !delete;
this.Button_DestinationBrowse.Enabled = !delete;
Update:
When it comes to refactoring and code-cleanup a very usefull tool is Resharper (R#). I can highly recommend it.
Hope this gives you some ideas where to start.

Related

get control of another window instance

In my WPF program, there is a mask window called CoverP3D needs to be operated (to be maximized or minimized) by different UserControls. The way I call it is to instantiate it in corresponding UserControl back-end code - this causes redundancy because I only need one instance.
CoverP3D cover = new CoverP3D();
...
if (cover.WindowState == WindowState.Minimized)
{
cover.WindowState = WindowState.Maximized;
}
...
I need to create many instances like "cover" in the above code which is redundant. How do I get control of the window that has been already instantiated? Is
Window.GetWindow()
possible? If possible, what dependency object should be its parameter?
Instead of creating a CoverP3D window every time, you need to keep a reference of the created CoverP3D instance and reuse it. like:
public class MainWindow : Window {
private CoverP3D _cover;
CoverP3D Cover {
get {
if (null==_cover)
_cover = new CoverP3D();
return _cover;
}
}
...
}
Then update your code to use the Cover property wherever you want to access the window. like:
if (Cover.WindowState == WindowState.Minimized)
{
Cover.WindowState = WindowState.Maximized;
}
CoverP3D cover = new CoverP3D();
This is a local variable. If you want to share it, you need to put it in a place where it's not lost after the scope ends. As you have not shown as your program there is no way to give you a direct place, but you should put it as a class field somewhere it makes sense. Maybe in your parent window? You say you aren't doing MVVM yet, so it's hard to tell.
Summary: make this variable a field somewhere, so you can share it and not have to recreate it every time.
get control of another window instance
Try this:
CoverP3D coverP3D = (CoverP3D)Application.Current.Windows.OfType<CoverP3D>().FirstOrDefault();
if(coverP3D.Count() > 0)
{
// Do something with existing window
}
else
{
//create new instance
}

Create a new instance of a control dynamically

Ok so I have a form I'm building that's going to change it's interface by using custom controls. What I'm trying to do is make several checks before a new control is created, like checking if one is already up. I have everything working just fine but I can't seem to create the new control dynamically, without creating it before running the checks which defeats the purpose.
The controls all implement an interface called ICustomControl and inherit from a MasterControl. Then I have a custom control called JobForm, and a button on the main form that calls the method like so: Check_Controls(newJobForm)
JobForm newJobForm;
private void Check_Controls(Control control) // Checks current controls to see if a new one can be opened
{
bool currentControl = false;
foreach (Control c in this.Controls)
{
if (c is ICustomControl && c != masterControl)
currentControl = true;
}
if (currentControl)
{
TimedMessageBox timedMessage = new TimedMessageBox("There is currently an open form. Please close the current control before opening another.");
timedMessage.ShowDialog();
}
else
{
Control c = (Control)Activator.CreateInstance(control.GetType());
this.Controls.Add(c);
Position_Control(c);
c.Show();
}
}
I dont't want to create a new instance of the custom control like: JobForm newJobForm = new JobForm(); before running the check method, I want to pass the reference to the check method and then have it create the new instance after it's checks are complete. In this way no matter how many new custom controls I wind up adding to the application, all I have to do to set one up is create the reference variable, then for the button call the check method and pass it the reference.
Anyone know how I can do this?
I think you are thinking about the problem backwards. Instead of saying "I have control X, is it valid?" think "would control X be valid, if so create". You are wanting to make checks to see if a control is valid, yet you want to send in a reference to that control.
Your code doesn't check for specific types of control, but rather just that at least one control belonging to the current form implements your interface. If this is the intended behavior, just have a function that does your initial check to see if any ICustomControl exist on your form. If that function return false, then go along with your creation.
You can accomplish this with a single function by using a constrained generic. This also gets you away from the less-than-ideal practice of using Activator and other reflection methods for dynamic type generation:
private void CheckAndAddControl<ControlType>()
where ControlType : MasterControl, new()
{
bool currentControl = false;
foreach (Control c in this.Controls)
{
if (c is ControlType)
{
currentControl = true;
break;
}
}
if (currentControl)
{
TimedMessageBox timedMessage = new TimedMessageBox("There is currently an open form. Please close the current control before opening another.");
timedMessage.ShowDialog();
}
else
{
var c = new ControlType();
this.Controls.Add(c);
Position_Control(c);
c.Show();
}
}
You would then use this function as follows:
CheckAndAddControl<JobForm>();

Single reusable function to open a single instance of form

I am trying to create a reusable function that can open a single instance of form. Means if a form is not already open it should create and show the new form and if already open it should bring the existing form to front.
I was using the following function,
if (Application.OpenForms["FPSStorageDemo"] == null)
{
FPSStorageDemo fp = new FPSStorageDemo();
fp.Name = "FPSStorageDemo";
fp.Show();
}
else
{
((FPSStorageDemo)Application.OpenForms["FPSStorageDemo"]).BringToFront();
}
But I have to write this code again and again whereever I have to open a form. But I need a single reusable function that can do this job.
I wrote a function like,
void OpenSingleInstanceForm(Type TypeOfControlToOpen)
{
bool IsFormOpen = false;
foreach (Form fm in Application.OpenForms)
{
if (fm.GetType() == TypeOfControlToOpen)
{
IsFormOpen = true;
fm.BringToFront();
break;
}
}
if (!IsFormOpen)
{
Object obj = Activator.CreateInstance(TypeOfControlToOpen);
//obj.Show(); //Here is the problem
}
}
But at the end I don't know how to show the new form instance. Can anybody suggest how to do it? Is this wrong or there is another way to do this?
Thanks in advance.
public static class FormUtility
{
public static FormType GetInstance<FormType>() where FormType : Form, new()
{
FormType output = Application.OpenForms.OfType<FormType>().FirstOrDefault();
if(output == null)
{
output = new FormType();
}
//you could add the show/bring to front here if you wanted to, or have the more general method
//that just gives a form that you can do whatever you want with (or have one of each).
return output;
}
}
//elsewhere
FormUtility.GetInstance<Form1>.BringToFront();
I'd also like to take the time to mention that while having methods like this are quick and easy to use, in most cases they are not good design. It leads you to the practice of just accessing forms globally rather than ensuring that when forms need to communicate with each other they do so by exposing the appropriate information through the appropriate scope. It makes programs easier to maintain, understand, extend, increases reusability, etc. If you have trouble determining how best for two or more forms to communicate without resorting to public static references to your forms (which is exactly what Application.OpenForms is) then you should feel free to post that question here for us to help you solve.
You are looking for Singleton
Check this Implementing Singleton in C#

Are typecasts a sign of poorly designed code?

I'll be the first person to tell someone that my code design could use improvement. I can't help but feel that when I have typecasts in my code that its a sign that something needs to be redesigned to remove the typecasts. This question sort of has two parts to it, the first being simply the one posted: Are typecasts a sign of poorly designed code?
The second question is based on the first, if typecasts are as evil as I feel they are, then how could the below situation be avoided?
class InputPanel : Control
{
public event EventHandler InputEvent;
}
class OutputPanel : Control
{
}
class MainWindow : Form
{
public MainWindow()
{
var loadButton = new Button();
loadButton.Click += new EventHandler(HandleButtonClick);
var inputPanel = new InputPanel();
inputPanel.InputEvent += new EventHandler(HandleInputEvent);
bodyControl = inputPanel;
}
private void HandleButtonClick(object sender, EventArgs args)
{
OpenFileDialog dialog = new OpenFileDialog();
if (dialog.ShowDialog(this) == DialogResult.OK)
{
var data = LoadDataFromFile(dialog.FileName);
var inputPanel = bodyControl as InputPanel; // Ugly typecast...
if (inputPanel != null)
{
inputPanel.PopulateFromData(data);
}
}
}
private void HandleInputEvent(object sender, EventArgs args)
{
var outputPanel = new OutputPanel();
bodyControl = outputPanel;
}
Control BodyControl;
}
The reasoning behind the above code is that the MainWindow form contains a MenuStrip (simplified to a button in this example) and a single control (the BodyControl). As the displayed control needs to be changed from the input panel to the output panel, via the button click, you can simply reassign the BodyControl field (adjust parents and such). This means only one panel is loaded at a time, layout logic becomes simplified because there is only one panel to position within the MainWindow (two if you include the MenuStrip) as opposed to conditionally laying out multiply 'body' controls based on which state the program is in (input vs. output).
You can make your code much cleaner (and avoid the typecast) by using an interface or a common base class for the two controls (inputPanel and outputPanel). Simply store BodyControl as the base class or interface (instead of Control). Assuming the interface or base class implements the PopulateFromData method, you wouldn't need the cast at all.
Also be sure you're aware of the where clause in C#. It can come in handy when dealing with similar scenarios.
Typecasts are not a sign of poorly designed code. Lots of typecasts, however, are somewhat indicative of poorly designed code.
then how could the below situation be avoided?
You could store an InputPanel and OutputPanel variable and use a flag to indicate what "mode" your class is in (input mode or output mode).
Also, I don't understand the point of:
var outputPanel = new OutputPanel();
bodyControl = outputPanel();
I think it should read bodyControl = outputPanel; the same way bodyControl = inputPanel;

Does data binding work on invisible control?

This is a .net problem with winforms, not asp.net.
I have a windows form with several tabs. I set data bindings of all controls when the form is loaded. But I have noticed that the data bindings of controls on the second tab do not work. Those bindings work only when the form is loaded and when I select the second tab. This brings the suspicion to me: data bindings work only when bound controls become visible.
Anyone can tell me whether this is true or not? It is not hard to test this but I would like to know some confirmation.
Thanks
You are correct. A data-bound control are not updated until the control is made visible.
The only reference I can find for this at the moment is this MSDN thread.
Your issue has to do with the behavior of the TabControl. See Microsoft bug report. I posted a workaround for that problem which subclasses the TabControl and 'Iniatalizes' all the tab pages when the control is created or the handle is created. Below is the code for the workaround.
public partial class TabControl : System.Windows.Forms.TabControl
{
protected override void OnHandleCreated(EventArgs e_)
{
base.OnHandleCreated(e_);
foreach (System.Windows.Forms.TabPage tabPage in TabPages)
{
InitializeTabPage(tabPage, true, Created);
}
}
protected override void OnControlAdded(ControlEventArgs e_)
{
base.OnControlAdded(e_);
System.Windows.Forms.TabPage page = e_.Control as System.Windows.Forms.TabPage;
if ((page != null) && (page.Parent == this) && (IsHandleCreated || Created))
{
InitializeTabPage(page, IsHandleCreated, Created);
}
}
protected override void OnCreateControl()
{
base.OnCreateControl();
foreach (System.Windows.Forms.TabPage tabPage in TabPages)
{
InitializeTabPage(tabPage, IsHandleCreated, true);
}
}
//PRB: Exception thrown during Windows Forms data binding if bound control is on a tab page with uncreated handle
//FIX: Make sure all tab pages are created when the tabcontrol is created.
//https://connect.microsoft.com/VisualStudio/feedback/details/351177
private void InitializeTabPage(System.Windows.Forms.TabPage page_, bool createHandle_, bool createControl_)
{
if (!createControl_ && !createHandle_)
{
return;
}
if (createHandle_ && !page_.IsHandleCreated)
{
IntPtr handle = page_.Handle;
}
if (!page_.Created && createControl_)
{
return;
}
bool visible = page_.Visible;
if (!visible)
{
page_.Visible = true;
}
page_.CreateControl();
if (!visible)
{
page_.Visible = false;
}
}
}
We've encountered a similar problem. We're trying to write to 2 bound, invisible fields so that we can change the format that we write to our dataset. This works fine when the objects are visible, but stops working when the visible property was changed to false.
To get round it, I added the following code:
// Stop our screen flickering.
chSplitContainer.Panel2.SuspendLayout();
// Make the bound fields visible or the binding doesn't work.
tbxValueCr.Visible = true;
tbxValueDb.Visible = true;
// Update the fields here.
<DO STUFF>
// Restore settings to how they were, so you don't know we're here.
tbxValueCr.Visible = false;
tbxValueDb.Visible = false;
chSplitContainer.Panel2.ResumeLayout();
I've struggled with this myself and concluded that the only workaround, besides subclassing apparently (see hjb417's answer), was to make the other tab visible. Switching to the other tab and going back to the previous immediately before the form is visible doesn't work. If you do not want to have the second tab visible, I've used the following code as a workaround:
this.tabControl.SelectedTab = this.tabPageB;
this.tabPageB.BindingContextChanged += (object sender, EventArgs e) => {
this.tabContainerMain.SelectedTab = this.tabPageA;
};
Assuming tabPageA is the visible tab, and tabPageB is the invisible one you want to initialize. This switches to pageB, and switches back once the data binding is complete. This is invisible to the user in the Form.
Still an ugly hack, but at least this works. Off course, he code gets even uglier when you have multiple tabs.
Sorry for necromancing this thread, but it is easy to force the invisible controls' databinding/handles to be ready using this method:
https://social.msdn.microsoft.com/Forums/vstudio/en-US/190296c5-c3b1-4d67-a4a7-ad3cdc55da06/problem-with-binding-and-tabcontrol?forum=winforms
Simply, let's say if your controls are in tab page tpg_Second (or tabCtl.TabPages[1]), before you do anything with their data, call this first:
tpg_Second.Show()
This will not activate any of the tab pages, but viola, the databinding of the controls should work now.
This is not something I've come across directly. However, you might be experiencing a problem with the BindingContext. Without more details it's hard to say, but if I were you I'd set a breakpoint and make sure the controls are all bound in the same context.
Based on the answers, I made this method that works for me:
public partial class Form1: Form
{
private void Form1_Load(object sender, EventArgs e)
{
...
forceBindTabs(tabControl1);
}
private void forceBindTabs(TabControl ctl)
{
ctl.SuspendLayout();
foreach (TabPage tab in ctl.TabPages)
tab.Visible = true;
ctl.ResumeLayout();
}
}
In addition to solving the problem, the tabs are loaded at the beginning and are displayed faster when the user clicks on them.

Categories

Resources