Introduction
It is not so straightforward to create a responsive WinForms application. By responsiveness, we do hereby mean usability in different screen resolutions. For WinForms, we need to explicitly take care of resizing and repositioning controls based on resolution. While there are suggestions on using WPF, or using docking and anchoring of controls, using panels, etc., this article provides a different way for applying responsiveness to a WinForm application.
Background
I designed a game which is available here. I designed it in a machine that had resolution of 1920x1080. But when I tried to play it in a laptop, I found that the board fell outside the screen. I felt the need to make it responsive to reach out to people who might have different resolutions. Hence, I made changes in the code to make it responsive. So, I thought it might benefit others just to have an alternate way of applying responsiveness to WinForm applications.
The Technique
The technique is simple. It has two hard-coded constants that preserve the design-time screen resolution. Now whenever the application is run, it obtains a multiplication factor which is actually a scaling factor. It obtains this factor by dividing the current resolution by the design-time resolution. Then all the controls of the form are passed to this class object for scaling and resizing.
Using the Code
The Responsive Class - Responsive.cs
There is a class Responsive.cs which has 5 member variables as below. The purposes of the members are self-explanatory by the names.
float WIDTH_AT_DESIGN_TIME = (float)Convert.ToDouble
(ConfigurationManager.AppSettings["DESIGN_TIME_SCREEN_WIDTH"]);
float HEIGHT_AT_DESIGN_TIME = (float)Convert.ToDouble
(ConfigurationManager.AppSettings["DESIGN_TIME_SCREEN_HEIGHT"]);
Rectangle Resolution;
float WidthMultiplicationFactor;
float HeightMultiplicationFactor;
The design-time screen resolutions are kept in the App.config file.
<add key ="DESIGN_TIME_SCREEN_WIDTH" value="1920"/>
<add key ="DESIGN_TIME_SCREEN_HEIGHT" value="1080"/>
When an instance of the class is created, the current resolution is supplied to the constructor. After that, a call is made to the SetMultiplicationFactor()
method of the class. This method obtains the scaling factor by dividing the current resolution with the design-time resolution.
public Responsive(Rectangle ResolutionParam)
{
Resolution = ResolutionParam;
}
public void SetMultiplicationFactor()
{
WidthMultiplicationFactor = Resolution.Width / WIDTH_AT_DESIGN_TIME;
HeightMultiplicationFactor = Resolution.Height / HEIGHT_AT_DESIGN_TIME;
}
For example, the application was designed in 1920x1080 resolution. If this application is run on a machine with resolution of 1024x768, then the WidthMultiplicationFactor
, and HeightMultiplicationFactor
change as follows:
WidthMultiplicationFactor = 1024/1920 = 0.533
HeightMultiplicationFactor = 768/1080 = 0.711
Finally, there are two overloaded methods which are the ultimate ones that provide responsive solution (optimal size, location and font-size) for the controls of the calling form.
public int GetMetrics(int ComponentValue)
{
return (int)(Math.Floor(ComponentValue * WidthMultiplicationFactor));
}
public int GetMetrics(int ComponentValue, string Direction)
{
if (Direction.Equals("Width") || Direction.Equals("Left"))
return (int)(Math.Floor(ComponentValue * WidthMultiplicationFactor));
else if (Direction.Equals("Height") || Direction.Equals("Top"))
return (int)(Math.Floor(ComponentValue * HeightMultiplicationFactor));
return 1;
}
For example, if there is a control with width = 465, height = 72, Left = 366, Top = 41, and font-size = 40, the method returns the suggested size, position, and font-size for this control as:
Width = 465 * 0.533 = 248
Height = 72 * 0.711= 51
Left = 366 * 0.533= 195
Top = 41 * 0.711= 29
Font-size = 40 * 0.533 = 21
In essence, the methods return the scaled solution for a control with optimum values for size, position and font-size.
Using the Responsive Class in a Form that Requires Responsiveness
What is needed is to simply create an object of this class in any form where responsiveness is intended. The current resolution is provided in the constructor. After that, the required multiplication factors are set up.
Responsive ResponsiveObj;
ResponsiveObj = new Responsive(Screen.PrimaryScreen.Bounds);
ResponsiveObj.SetMultiplicationFactor();
After that, all the controls of the form are passed one by one for resizing/repositioning in the form's load
event. The call is done in the following code. Basically, what it is doing is first position the form to the centre of the screen. Please see there is a calibration constant (30) that is added for optimum vertical position (this would vary from developer to developer - it is a choice). After that, each and every control of the form is repositioned, resized, and re-calibrated for font-sizes.
private void ResponsiveForm_Load(object sender, EventArgs e)
{
Width = ResponsiveObj.GetMetrics(Width, "Width");
Height = ResponsiveObj.GetMetrics(Height, "Height");
Left = Screen.GetBounds(this).Width / 2 - Width / 2;
Top = Screen.GetBounds(this).Height / 2 - Height / 2 - 30;
foreach (Control Ctl in this.Controls)
{
Ctl.Font = new Font(FontFamily.GenericSansSerif,
ResponsiveObj.GetMetrics((int)Ctl.Font.Size), FontStyle.Regular);
Ctl.Width = ResponsiveObj.GetMetrics(Ctl.Width, "Width");
Ctl.Height = ResponsiveObj.GetMetrics(Ctl.Height, "Height");
Ctl.Top = ResponsiveObj.GetMetrics(Ctl.Top, "Top");
Ctl.Left = ResponsiveObj.GetMetrics(Ctl.Left, "Left");
}
}
Example
For demonstration purposes, following is a very simple form that contains a data grid, a label, a text box and a button. The snaps below are taken at three different resolutions. The following snap is taken at 1920x1080 resolution:
The following is a snap taken at 1360x768:
The following snap is taken at 1024x768:
In effect, the form would look the same at different resolutions by contracting/expanding and re-positioning controls to an optimum level.
Modification of Code
Some claibration factors might be required to adjust a little of what we obtain from the technique (like we did for vertical-centre positioning).
Also, it is suggested that the developer should see the outlook of the form at different resolutions to confirm that all the controls are visible and positioned correctly on the screen as intended. If not, some calibration or a little design-time change should be fine.
Also, this is a generic approach for a simple form which assumes that all the controls of the form have these properties - width, height, left, top and font-size. However that is not the case for real scenario. A practical form would contain other controls which might not have all these properties. E.g., a picturebox
doesn't have the font-size property. Hence running the code as is would cause in runtime exceptions if such cases are not handled explicitly. This article is meant to introduce a technique, not to be the ultimate tool for everything; developer needs to calibrate according to the need. A suggested approach is as follows:
private void ResponsiveForm_Load(object sender, EventArgs e)
{
Width = ResponsiveObj.GetMetrics(Width, "Width");
Height = ResponsiveObj.GetMetrics(Height, "Height");
Left = Screen.GetBounds(this).Width / 2 - Width / 2;
Top = Screen.GetBounds(this).Height / 2 - Height / 2 - 30;
foreach (Control Ctl in this.Controls)
{
if (Ctl is PictureBox)
{
Ctl.Width = ResponsiveObj.GetMetrics(Ctl.Width, "Width");
Ctl.Height = ResponsiveObj.GetMetrics(Ctl.Height, "Height");
Ctl.Top = ResponsiveObj.GetMetrics(Ctl.Top, "Top");
Ctl.Left = ResponsiveObj.GetMetrics(Ctl.Left, "Left");
}
else
{
Ctl.Font = new Font(FontFamily.GenericSansSerif,
ResponsiveObj.GetMetrics((int)Ctl.Font.Size), FontStyle.Regular);
Ctl.Width = ResponsiveObj.GetMetrics(Ctl.Width, "Width");
Ctl.Height = ResponsiveObj.GetMetrics(Ctl.Height, "Height");
Ctl.Top = ResponsiveObj.GetMetrics(Ctl.Top, "Top");
Ctl.Left = ResponsiveObj.GetMetrics(Ctl.Left, "Left");
}
}
}
Similar code changes might apply according to need and the nature of controls. Also, there might be need to introduce more overloaded methods for different control types.
Points of Interest
As mentioned earlier, there are other approaches like resorting to WPF, using anchoring/docking, etc. This is one more clever alternative. Loading delay might be experienced if the form has thousands of controls on it. However, with today's faster processors that would still be aired in the blink of an eye. Also to remember, this is one time operation only - at the loading of the form. Hence major performance degradation is not expected.
Conclusion
This is a developer-oriented approach for creating responsive WinForms application that automatically resizes, repositions, and re-calibrate font-sizes according to the runtime resolution of the machine. Just add the class to your project, set the design-time resolution at the App.config file, and add the code for responsiveness in the form's loading event. That would be all - the responsive class will take care of responsiveness.
History
- 18th October, 2016: First release