Any thoughts,recommendations, patterns on a good way to manage an app with multiple forms.
First page is login, that loads a "main form", from there a user could launch a number of other "sub forms" (could grow over time). User should be able to cancel out of whole app at any point.
I know the way I do it right now is not always elegant.
Cody
Consider using the DockPanel Suite. It allows you to create multiple form, and have a complete docking panel solution for you application.
I like an Explorer-style interface: Use a splitter in the main form. Use a list control or tree control in the left side, and add a reference to each sub-form as a tag on an item. When the user clicks on the item, push the sub-form from the tag to the right side of the splitter.
You can monitor the select changed event on the list/tree to do form-level validation.
There's nothing fundamentally wrong with how you've set things up, but I would change the relationship between your login form and your main form so that your main form isn't loaded by your login form.
In the Main method in your Program.cs file, replace this (presumed) line:
Application.Run(new LoginForm());
with something like this:
LoginForm login = new LoginForm();
DialogResult result = login.ShowDialog();
login.Dispose();
if (result != DialogResult.Cancel)
{
Application.Run(new MainForm());
}
Rewrite your LoginForm so that it just returns DialogResult.OK if the login is successful (this.DialogResult = DialogResult.OK), instead of loading and showing an instance of MainForm.
From that point on, there's nothing wrong with loading and showing additional forms from MainForm, provided that a user interface like that makes sense for your program (like, for example, a graphics editing program that incorporates various other floating tool windows as needed).
The user can "cancel out" of your entire application by just closing the main form, which is quite normal behavior for a Windows program.
"to manage an app with multiple forms."
Hi Cody,
Tip : do keep in mind that you have access to a very handy way to know, at all times (in any context where you are using the System.Windows.Forms library) the number of open forms in the Application via :
Application.OpenForms.Count
I think a lot depends on what exactly you mean by "manage" your multiple forms, and by what you mean by the term "sub-forms." I'm assuming you're already familiar with the built-in MDI multiple window facility in .NET, and looking for an alternative to that, or you wouldn't be asking this question. Please correct me if my assumptions are wrong.
If you mean by "sub-forms" : forms created within the scope of your "Main Form," then they will, of course, be disposed when the Main Form is closed.
I personally like the "multiple independent window" model in WinForms sometimes referred to as SDI+ (I think it was Chris Sells that coined this acronym).
I like to start by "hi-jacking" the standard Program.cs file so the Main procedure looks like this :
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Initializer.Initialize();
// it's now your responsibility to shut down the application !
Application.Run();
}
Where 'Intializer is a public static class with one public static method 'Intialize; it might look something like this :
public static class Initializer
{
public static StartFormTemplate StartForm;
public static MainFormTemplate MainForm;
public static void Initialize()
{
MainForm = new MainFormTemplate();
MainForm.FormClosing += new FormClosingEventHandler(FormClosing);
// StartForm will display MainForm in this case
// MainForm.Show();
StartForm = new StartFormTemplate();
StartForm.FormClosing += new FormClosingEventHandler(FormClosing);
StartForm.Show();
}
}
Every form created within the scope of the Initializer class is going to be an "indepedent window" (effectively with : Parent == null).
The interesting "business" in this model is the logic in the FormClosing event (not shown here) which, in this example, both StartForm and MainForm share. You can easily test for the Mainform closing by taking the 'sender parameter of the FormClosing call and comparing to MainForm :
MainForm == sender
And you can use Application.OpenForms.Count to detect when there is only one "independent" window left to close. The tricky part is making sure you detect the cases where you want to keep the MainWindow open and handle those first, cancelling the FormClose event as necessary.
I think that's enough for starters : this may be a "primrose path" you don't really wish to go down :)
best, Bill
I would recommend the Smart Client UI Application Block.
The Block is designed to help you build complex, WinForm–based solutions. It provides a proven architecture and implementation that helps you to build applications using the common patterns found in line-of-business front-end applications.
It allows your application to be
based on the concept of modules or
plug-ins.
Maintainable, reusable code through UI composition.
It facilitates development using
patterns for loose coupling between
modules.
Separation of Model (business logic and data access) from Presentation.
The Model-View-Presenter pattern.
You can use MDI. To do this, set the IsMdiContainer property of the parent from to true, and set the MdiParent property of each child from to the parent from before the child form is shown. You can then use an MdiList menu to automatically list all of the child forms.
Alternatively, you can use tabs; this is easiest to do with a third-party framework.
Finally, you could do it normally by calling the Show method of each child form with the parent form as a parameter.
For a more detailed and specific answer, please provide more details.
I use a navigation-style UI for that sort of thing. Check out this article on MSDN which describes a WinForms framework for "inductive UI" applications.
Related
I have a selection of TextBoxes that a user fills in when they wish to note that they have had contact with another person. Most of the TextBoxes are imply filled in by typing into them. However, for one of them I would like the user to be able to select from a list of People that appears when they click on a button.
This is where I am having problems. So far I have just made a DataGrid appear and handled it's SelectionChanged method to fill in the TextBoxes text property. This has worked fine, however now there is not enough space on the current page to show an entire DataGrid with all the people they can select from.
I've decided to show the People in a separate, smaller Window that appears when the user clicks a Button. The issue I have is that when the user selects the Person they wish to mark the contact for in the new Window, I have no idea how I can notify the original Window that a Person has been selected, close the new smaller Window and fill in the appropriate TextBox on the original Window.
What would be the most intuitive way to fill in the TextBox on the original Window, based on the selection on the Window that opens?
I would use delegates,which call a function of the original window and parse the changed variable with it. So you know when the user clicked something and you can directly react to this "event".
Link:
https://msdn.microsoft.com/en-us/library/ms173171.aspx
If you use a framework like Galasoft's MVVM Light (http://www.galasoft.ch/), they have a messenger system just for this purpose. It allows you to "broadcast" messages that can be "received" by any other part of the application
This is when considering using Domain, Model, Presentation (Winforms/WPF version of MVC formatting) to do your app.
You can have each form as its own class, well they are their own class. Create each form class but add some public members to it if the controls are private. Have them have "get" properties only and to return the values of whatever controls or variables are in that form. Your main form will be the controlling form. All forms will be handled by the main form so when you open it, it is a class the main form can access.
Now, if I remember (been doing more MVC and not any Winforms lately) I believe if you use the ShowDialog() method it will freeze the main thread so when you close out the main form you can continue and read in public members you have in your forms class you opened. Synchronous I believe it runs as. If you use just Show() the thread will keep on trucking, asynchronous. With asynchronous you may then have to use a main form in your startup code so there is always a window there but subscribe to the close event of your forms and have a method that can grab those public members out. Be sure to instantiate the extra forms at the root of the main class so it doesn't fall out of scope when it exists the method that calls it. You may even be able to make the method that calls is a async call and have an await before the command that runs the Show method on the form.
Summary, treat each form as its own class but add public members that can read the values from the controls and/or variables you want. Read that data from the class when it closes via an event or synchronously when the thread closes out from the form closing. The form closing doesn't discard the object, just the visualization of the form.
Oh, if you are passing info from the main form to a child for you are opening, either add a constructor for that form class that takes your input as a model or values to fill in the appropriate variables or forms before showing it or create a public property you can put your values you want to send in before showing the class.
Remember, everything is a class, once you look at it as such and treat it as such, the answer will come. :-)
I should warn, I am a long winded explainer.
At work putting all this down from memory so some errors may exist. Let me know if there are.
I think the problem is to access the controls of the main window, isn`t it?
You can define an event of changing user`s choise and access MainWindow control by using the following construction:
((MainWindow)Application.Current.MainWindow).MyTextBox
I'll try to make this question as uncomplicated as possible. I have three forms frm_Splash which checks for updates; frm_Wizard which completes first-run setup; and frm_Main which is the main program. These forms' relationships are diagrammed below:
Right now in Program.cs I have this code:
public static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new frm_Main());
}
which sets frm_Main as my main form. What is best practice to handle frm_Wizard and frm_Main? I do not want them to load before I have finished updates (if there are any).
After this, if the wizard needs to be shown, the splash screen should disappear and the wizard should appear.
Finally, once all the updates and first-time setup is done, the main form shows (but not before). How do I accomplish all this?
Some things I know:
I know I can set frm_Splash to be the startup form, but then when I close it, the whole program closes.
I could also hide forms, but doesn't that waste memory with forms
sitting in the background?
And finally, I'm looking for some general code or concepts of how to do this. I already am familiar with c# coding, so you don't need to code everything. I'm merely looking for a best practice to handle this. i.e. Should I be coding in the Program.cs, or each form's load event? If there's anything unclear about my question, please let me know before you downvote! Thank you.
One thing to note is that you do not need to use Application.Run to launch a form. So you can just create a 3 step process in your Main function:
public static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
//Show splash form (which checks for updates)
FormSplash splash = new FormSplash();
splash.ShowDialog();//will wait until splash closed
//check if first run and show if needed
if(IsFirstRun())
{
FormWizard wizard = new FormWizard();
wizard.ShowDialog();//will wait until wizard is closed
}
//Finally, run your application as normal
Application.Run(new frm_Main());
}
NOTE: I am not aware of the benefits of using Application.Run to launch you main form, I assume one of them is allowing access to Application state functions (such as Exit), but I can say I have used this technique for a splash screen in the past, and have not had any issues with doing it this way
I'm working on a console application that creates a form to alert users of some given state - at a later stage, the code base will become a class library.
For now, however, I need to show the form (ShowDialog would be the best method here, I guess) THEN call an arbitrary method before the form closes.
As an example, I need to show the form, set the text value of a label control, wait for n number of seconds, then change the value of the label, then close the form. I know that this sounds a little trivial, but I'm trying to proof-of-concept the design.
I've taken a look around and it doesn't look like this is possible, as ShowDialog() requires me to close the form before I can continue through code listing in the calling method/class.
Here's what I have so far:
PopUpForm myForm = new PopUpForm(string messageToDisplay);
myForm.ShowDialog();
//call myForm.someMethod() here, before the form closes
//dispose of the form, now that we've no use for it
myform.Dispose();
//target method in PopUpform class
public void someMethod()
{
lblText.Text = "Waiting for some reason";
//wait n number of seconds
lblText.Text = "Finished waiting. Form will now close";
//it doesn't matter if the form closes before the user can see this.
}
It looks like ShowDialog() doesn't support this sort of behaviour. I'm looking into BackgroundWorker threads, but was wondering if anyone has any advice on this, or have encountered this before.
If you want to show the form, then continue working, then close it - you can do so via Form.Show() instead of Form.ShowDialog():
using (var myForm = new PopUpForm(messageToDisplay))
{
myForm.Show(); // Show the form
DoWork(); // Do your work...
myForm.Close(); // Close it when you're done...
}
However, if this is purely a console application (and doesn't have a message pump), then this will likely not work properly.
Other options would be to provide a timer within your Form to have it close, or pass a delegate into the Form to run your method on Show, after which it could close itself.
at a later stage, the code base will become a class library.
When you do this, you'll likely want to come up with a different mechanism to provide notifications. Coupling your library to a specific UI technology is a bad idea. It would likely be better to have your library just provide events or other notification, and allow the user to provide the UI/notification to the user.
If this code ie destiend to end up in a code library, I recommend against having it display any forms, ever, of its own volition. The library should generate events, or in some cases exceptions, that can be caught by the invoking application to allow it to display the form. If certain details requiring presentation to the user are internal to the library, expose a DisplayEventData() method from the library.
The ShowDialog-method creates a "modal" window, and usually blocks the UI until you close it (either by clicking OK or Cancel). You would need to create a WinForm yourself, can be a simple one though and create a message-pump for it. You can run your own form by calling
Application.Run(YourForm);
You would need to hold your console-thread with a mutex for example, to keep it from continuing, while the form is open.
The form offers all the methods you know from WinForms like Close, where you could tell your console-thread to continue.
See this on MSDN.
I am writing a small class for driving integration testing of a win form application. The test driver class has access to the main Form and looks up the control that needs to be used by name, and uses it to drive the test. To find the control I am traversing the Control.Controls tree. However, I get stuck when I want to get to controls in a dialog window (a custom form shown as a dialog). How can I get hold of it?
You can get a reference to the currently active form by using the static Form.ActiveForm property.
Edit: If no Form has the focus, Form.ActiveForm will return null.
One way to get around this is to use the Application.OpenForms collection and retrieve the last item, witch will be the active Form when it is displayed using ShowDialog:
// using Linq:
var lastOpenedForm = Application.OpenForms.Cast<Form>().Last()
// or (without Linq):
var lastOpenedForm = Application.OpenForms[Application.OpenForms.Count - 1]
I'm not sure if you can access controls on a pre-built dialog box; they seem all packaged together. You may have more luck building a dialog box of your own that does what you want it to do. Then you can access the .Controls inside of it.
Correct me if i'm wrong, though, it sounds as if you are possibly attempting to access the controls on the dialog form when it's not quite possible to.
What I mean is, ShowDialog will "hold up" the thread that the form was created on and will not return control to the application (or, your test class) until ShowDialog has finished processing, in which case your user code would continue on its path.
Try accessing or manipulating the controls from a separate thread (in this case, refactor the test driver class to spawn a separate thread for each new form that must be displayed and tested).
Short version: I want to trigger the Form_Load() event without making the form visible. This doesn't work because Show() ignores the current value of the Visible property:
tasksForm.Visible = false;
tasksForm.Show();
Long version: I have a WinForms application with two forms: main and tasks. The main form is always displayed. The user can either click a button to open the tasks form, or click some buttons that just run a task directly without opening the tasks form.
When a user asks to run a task directly, I'd like to just call some public methods on the tasks form without showing it. Unfortunately, the task logic depends on stuff that happens in the Form_Load() event. The only way I can find to trigger Form_Load() is to call Show(). The best I've been able to do is to show the form in the minimized state:
tasksForm.WindowState = FormWindowState.Minimized;
tasksForm.Show();
I suppose the cleanest solution would be to pull the tasks logic out of the tasks form and into a controller class. Then I can use that class from the main form and from the tasks form, and only load the tasks form when I need it visible for the user. However, if it's an easy thing to load the form without displaying it, that would be a smaller change.
Perhaps it should be noted here that you can cause the form's window to be created without showing the form. I think there could be legitimate situations for wanting to do this.
Anyway, good design or not, you can do that like this:
MyForm f = new MyForm();
IntPtr dummy = f.Handle; // forces the form Control to be created
I don't think this will cause Form_Load() to be called, but you will be able to call f.Invoke() at this point (which is what I was trying to do when I stumbled upon this SO question).
It sounds to me like you need to sit down and re-think your approach here. I cannot imagine a single reason your public methods need to be in a form if you are not going to show it. Just make a new class.
I totally agree with Rich B, you need to look at where you are placing your application logic rather than trying to cludge the WinForms mechanisms. All of those operations and data that your Tasks form is exposing should really be in a separate class say some kind of Application Controller or something held by your main form and then used by your tasks form to read and display data when needed but doesn't need a form to be instantiated to exist.
It probably seems a pain to rework it, but you'll be improving the structure of the app and making it more maintainable etc.
From MSDN:
Form.Load
Occurs before a form is displayed for the first time.
Meaning the only thing that would cause the form to load, is when it is displayed.
Form.Show(); and Form.Visible = true; are the exact same thing. Basically, behind the scenes, Show checks for various conditions, then sets Visible to true. So obviously, setting visible to false (which it already is) before showing the form is meaningless.
But let's forget the technicalities. I completely agree with Rich B and Shaun Austin - the logic shouldn't be in that form anyway.
Sometimes this would be useful without it being bad design. Sometimes it could be the start of a migration from native to managed.
If you were migrating a c++ app to .NET for example, you may simply make yourwhole app a child window of the .NET form or panel, and gradually migrate over to the .NET by getting rid of your c++ app menu, status bar, toolbar and mapping teh .NEt ones to your app using platform invoke etc...
Your C++ app may take a while to load, but the .NET form doesn't..in which you may like to hide the .NEt form until your c++ app has initialised itself.
I'd set opacity=0 and visible=false to false after calling show, then when your c++ app loads, then reverse.
If you make the method public, then you could access it directly.... however, there could be some unexpected side effects when you call it. But making it public and calling it directly will not draw the screen or open the form.
Move mandatory initialization code for the form class out of the Load event handler into the constructor. For a Form class, instantiation of an instance (via the constructor), form loading and form visibility are three different things, and don't need to happen at the same time (although they do obviously need to happen in that order).
None of the answers solved the original question, so, add the below, call .Show() to load the form without showing it, then call .ShowForm() to allow it to be visible if you want to after:
private volatile bool _formVisible;
protected override void SetVisibleCore(bool value)
{
base.SetVisibleCore(_formVisible);
}
public void ShowForm()
{
_formVisible = true;
if (InvokeRequired)
{
Invoke((Action) Show);
}
else
{
Show();
}
}