Forms and Dialogs in a Windows DLL with C Sharp
This article covers the programming of Windows forms and dialogs so that they are located in a Windows DLL (dynamic link library). This can help with an apps design, maintenance, and testing, and when adding new features, by breaking up a large project into more manageable chunks. This tutorial assumes you can create and start a WinForms project in Visual Studio, if not see the article Hello World in C#, A Starting WinForms Example
Introduction to Windows Forms in a DLL
When it comes to programming an app, try and keep the apps functions contained into small separate units, it'll help with future development and help to avoid spaghetti code. For Windows apps there is the main executable, or .exe, that starts the program. Then, especially for large programs with lots of functionality, there are library files that contain other code the main .exe can use. A library file is .dll file, dll for dynamic link library. Lots of developers add Windows forms (WinForms) to the .exe for the user interface. However, WinForms can be added to dlls to help break up a bigger program into more manageable chunks.
Start with a Test Program and Class Library
For this tutorial a Windows .exe is going to load a WinForm from a DLL. The WinForm is located in a Class Library project, which compiles into a DLL. In Visual Studio create a test app for the .exe, here it is called DLLFormTest, then add a new Class Library project called DLLForm to the new solution (using Add and New Project).
Put a WinForm in the DLL and Add References
Next, use the context menu on the .dll project (here called DLLForm) to add a Windows form, using Add then New Item (or add a new form via the Project menu). Rename the form, e.g. to MyForm from Form1, so that you do not get confused with the form in the .exe app, also called Form1. Accept the Visual Studio option to rename all references to the renamed form. Change the new renamed DLL form's text property as well, e.g. from Form1 to MyForm. Delete the Class1 file from the DLLForm project, it won't be used. In the DLLFormTest project add a reference to the DLLForm Class Library.
Load the DLL Form from the EXE
Drop a button onto the main EXE's form (in this tutorial Form1 in the DLLFormTest project). Reference the form in the DLL, firstly with using DLLForm, and code to load the DLL form from a button. The code will look similar to this:
using System;
using System.Windows.Forms;
using DLLForm;
namespace DLLFormTest
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
MyForm myForm = new MyForm();
myForm.Show();
}
}
}
Run the code and press the button to load the form from the DLL. When running it will look something like that shown at the beginning of this article. If there are errors check that the form renaming was correct, e.g. the MyForm constructor is correct:
using System.Windows.Forms;
namespace DLLForm
{
public partial class MyForm : Form
{
public MyForm()
{
InitializeComponent();
}
}
}
In the binary output directories for the solution, under the bin folders there will be DLLFormTest.exe and DLLForm.dll, with MyForm in the .dll file.
Showing a DLL WinForm Modally
WinForms can be displayed so that no other form in the app can be used until the new form is closed. The MessageBox is often used by developers to ask the user for a response before carrying on. The enumeration (enum) called DialogResult is returned by a MessageBox. DialogResult is used to test for the MessageBox return value:
private void button2_Click(object sender, EventArgs e)
{
if (MessageBox.Show("Exit app?", "Exiting", MessageBoxButtons.YesNo) == DialogResult.Yes)
Application.Exit();
}
This type of form is called a Modal form, or sometimes a dialog box. To do the same with a normal WinForm use the ShowDialog() method:
using System;
using System.Windows.Forms;
using DLLForm;
namespace DLLFormTest
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
Form myForm = new MyForm();
myForm.ShowDialog(); //Show modally
}
}
}
Returning DialogResult from a WinForm
A normal WinForm that is shown modally can also return DialogResult values. To do it add a button to the form and set the button's DialogResult property.
Try it with the MyForm example. Add two buttons, one with the text Yes and with the DialogResult property set to Yes, and one with the text No and with the DialogResult property set to No. Support for DialogResult allows for fancy custom dialog boxes to be built and stored in a separate dll.
private void button1_Click(object sender, EventArgs e)
{
MyForm myForm = new MyForm();
if( myForm.ShowDialog() == DialogResult.Yes ) //Show modally
Application.Exit();
}
Default Escape and Return Actions
WinForms support default button actions on presses of the return/enter or escape key. For MyForm set the AcceptButton property to the name of the Yes button. For the CancelButton property of MyForm set it to the name of the No button. (Because the DialogResult value for the buttons have been set they override the default values which are DialogResult.OK for a button assigned to the AcceptButton property, and DialogResult.Cancel for a button assigned to the CancelButton property.)
Setting and Returning Values from a WinForm
For data that is more complex than a DialogResult add extra properties to the DLL form. This allows data to be passed to and from the DLL form. Here a FirstNameField is being used to read and write from a TextBox:
using System.Windows.Forms;
namespace DLLForm
{
public partial class MyForm : Form
{
public MyForm()
{
InitializeComponent();
}
//Read and update a TextBox for first name
public string FirstNameField
{
get
{
return textBox1.Text;
}
set
{
textBox1.Text = value;
}
}
}
}
The property is then used to access the forms data fields:
private void button1_Click(object sender, EventArgs e)
{
MyForm myForm = new MyForm();
myForm.ShowDialog();
label1.Text = "The first name is " + myForm.FirstNameField;
}
However, if not using the form modally another event is required to control reading of the data from the form. Here button1 creates the form and button2 reads the data. Notice how the form variable is now at the module level so it can be used by both buttons. There is also extra logic to see if the form is loaded:
using System;
using System.Windows.Forms;
using DLLForm;
namespace DLLFormTest
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
MyForm myForm; //Keep a module reference to a form
private void button1_Click(object sender, EventArgs e)
{
//See if we need to create the form
if (myForm == null || myForm.IsDisposed)
{
myForm = new MyForm();
myForm.Show();
}
}
private void button2_Click(object sender, EventArgs e)
{
//Check for form available
if (myForm != null && !myForm.IsDisposed)
label1.Text = "The first name is " + myForm.FirstNameField;
}
}
}
Form management can become an issue in large programs, though the Application.OpenForms property and FormCollection class can help. If passing lots of data to and from a form use an object or structure that is common to both forms. Pass a reference to that common object or structure to the form being loaded, then use it to write and read from the forms fields (see also Managing Objects in C Sharp).
See Also
- Managing Objects in C Sharp, a Life Cycle Template
- For a full list of the articles on Tek Eye see the website's index.
Comments
Jim Butler on 18th April 2021 at 11:31 said: Hi. Do you have any articles that take this a step or two further, in the sense of handling data in/out of the form with a SQL database? I am struggling to decide the best way handle changes to database records from a Save button in a WinForm in a DLL.
Should I pass a dataset, tableadapter and bindingsource to the form from the main EXE? Should the form already contain a binding source, and just set the datasource on it from the main EXE? Or should I just pass a 'datarow' to the form from the main EXE? What do you suggest as the best way?
Thank you.
Tek Eye on 18th April at 22:48 said: Hi Jim. Thanks for your interest in the article. There is no article on Tek Eye covering WinForm data binding. It is a huge topic as there are many ways to connect a WinForm to data from an SQL database. Efficient data binding varies depending upon the type of app (client/server, client only, data over a web service, etc.) and the amount of data being handled. For performance reasons I would not pass around too many objects, especially if they contain large datasets. This makes your 2nd option preferred.
Jim Butler on 19th April 2021 at 09:16 said: Thanks for your reply.
I have a large WinForms application that I need to split into DLLs to make it easier to manage etc. What troubles me is where to put and how to reference datasets with tableadapters etc. Should I put them all in one DLL and reference this DLL from each DLL with some WinForms in them? Or should I have smaller datasets to store with the WinForms in that DLL?
I'm trying to avoid having to rebuild all DLLs when a small change happens in a dataset, i.e. one column added to one table. (We currently have over 100 tables in our application.) Similarly I only want to distribute a single DLL if a WinForm in it changes in some way. So if you have any other suggestions as to best practice, or articles, or books you could recommend it would be useful. MS docs not very good in this respect!
Many thanks and sorry to trouble you again.
Tek Eye on 19th April at 16:02 said: Hi Jim, without knowing about the app and how its distribution and maintenance are performed I can only give a short general answer. When redesigning an app into library DLLs, too much granularity is almost as bad as too little. It is best to organise into functional areas. I would suggest putting DataSets into groups of DLLs, and the UI and its logic into other groups of DLLs. This gives a three tier UI, data, database architecture. Furthermore, improving DevOps by optimising build, testing and distribution processes are non-code areas that often benefit a project.
I agree, MS docs can be quite poor. The examples given are often lacking detail and do not cater for those that like to use the Visual Studio design tools and wizards. I haven't seen any decent books recently. Most books and articles are either to old and cover the old ADO way of doing things, or the new books seem to concentrate on enterprise and web architectures using Entity Framework and/or poorly explained LINQ. Those wanting a simpler UI to BindingSource to DataSet to Database architecture are not well served.
I would certainly recommend an evolution rather than revolution approach to your endeavour. Identify a straighforward section of app functionality to develop the new granular approach. This allows seeing if the new approach works for your app. As you breakup the app into a more granular architecture the object management becomes important. I hope the diagram below gives an idea for a C# database app architecture. Having the UI controls BindingSources and/or BindingNavigators linked to publically exposed DataSets in data layer DLLs is probably what you need. If engineered correctly there will be no direct connection from the UI to the database, everything going via the data layer DLLs.
Jim Butler on 21st April 2021 at 07:10 said: Thank you! In a few paragraphs and a diagram, you have given me something concrete to work with, which is far more than I could find in loads of books! Invaluable - very much appreciated - thank you!
Jeffrey on 14th May 2021 at 16:30 said: Using Visual Studio 2017 I have created several class libraries using VB but I can't get the Tab key to change the focus from one command button to another on a form. Just setting TabStop True is not enough. What am I missing?
Tek Eye on 16th May 2021 at 12:35 said: Hi Jeffrey, not aware of that issue when using the visual designer to build UIs. You could try a new project using the visual designer and compare the new code to your problematic code. You didn't mention which version of .NET you use. I do know that WinForms in its .NET Core implementation was not perfect when it first came out. Maybe try the latest Visual Studio 2019 and latest .NET versions. A good place to get help is at Stack Overflow, you will need to be specific with which version of .NET you are using, and upload an example of the code where the issue exists.
Author:Daniel S. Fowler Published: