Rules to Better Windows Forms Applications

  • Do you know why you choose Windows Forms?

    Almost everyone assumes today to use web forms for broad reach because of easy installation and cross platform compatibility. That is correct.

    In the old days (1995-2000) companies used Windows Forms, later (2000-2007) they rolled their own ASP.NET solution, however since then (2007+) SharePoint has become the default choice for an intranet. When you need something richer and you can control the environment.

    1. Bandwidth - Presentation Layer Only the data is transferred from the server, not the presentation code. Web forms must download the data and the rendered UI taking up large bandwidth.
    2. Bandwidth - Compression Data transfer can be compressed and uncompressed to use less bandwidth. For example, using a Pkzip scale (1-9) of 6, we used the Open source algorithm 'Blowfish' to compress/encrypt 240K of data to 30K. i.e. 87% compression.
    3. Caching If you are going to the same record within a certain time period, Windows forms will retrieve the data from cache instead of calling the data service again. For example, when you click search on a Windows form, you don't have to do a request again if the search was done recently.
    4. Faster Server Because of the bandwidth advantages above, the server will make less requests and hence runs faster. The client has become thicker, using more processing power and capable of more complex business logic.
    5. Richer Interface The application's interface can be richer as you can design your own custom controls and do not need complicated resource-intensive and complex DHTML and JavaScript.
    6. More Responsive The interface will respond quicker to your clicks, no need to post a request for an interface response. i.e. no 10 second latency.
    7. Better Development Development is much easier with quick feedback. There are no compliance issues to follow as in web development with browsers.
    8. More people are happy! By choosing windows forms you are making the developer, end user and accounts groups happier. The only group which may rather a Web solution is the network admins.
    GroupBrowser BasedRich Client
    Network Admins
    Developers
    End Users
    Accounts

    Figure: Table of who benefits from Windows Forms, and Web Forms

  • Do you use code generators?

    Code generators can be used to generate whole Windows and Web interfaces, as well as data access layers and frameworks for business layers, making them an excellent time saver. It's not crucial which one you use as long as you invest the time and find one you are happy with. The one important thing is they must have command line support and the files they generate should be recognizable as code generated by prefix or a comment like "Don't touch" as this was automatically generated code. Make it easy to run by putting all the command line operations in a file called '_Regenerate.bat'.

    A Regenerate.bat file must exist under the solution items to recreate data access layer and stored procs.

    Image

    Figure: The _Regenerate.bat file under solution items

    The built in Data Form Wizard in Visual Studio .NET is not any good. We prefer other code generators like CodeSmith - Good for generating strongly-typed collections.

    Note: It also includes templates for Rocky Lhotka's CSLA architecture from a SQL Server database.

  • Do you use red and yellow colours to distinguish elements in the designer?

    Use colours on incomplete is so useful in design time:

    • <span style="background-color:red">Red</span> = Controls which are incomplete, e.g. An incomplete button
    • <span style="background-color:yellow">Yellow</span> = Controls which are deliberately invisible that are used by developers e.g. Test buttons

    Usually these controls are always yellow. However sometimes new areas on forms are made red and visible, so you can get UI feedback on your prototypes. Since they are red, the testers know not to report this unfinished work as a bug.

    Image

    Figure: Invisible controls highlighted in yellow, and incomplete items highlighted in red

  • Do your applications support XP themes?

    All applications should be compatible with the Windows XP user interface and should be fully themed. Applications that do not use XP themes look like they were designed only for an earlier version of Windows. Mixing themed and non-themed controls looks equally unprofessional.

    Image

    ❌ Figure: Bad example - XP themes are not used

    Image

    ✅ Figure: Good example - XP themes are used

    Implementing XP Themes

    We recommend using manifest file to support XP Themes in .NET. Follow this to use the manifest file.

    1. Set the FlatStyle Property in all our controls to "System"
    Image

    Figure: How to set the Button's FlatStyle Property

    1. Copy XPThemes.manifest file to your bin folder By default, you can get it from C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\XPThemes.manifest
    2. Rename "XpThemes.manifest" to "ApplicationName.exe.manifest"

    Note: In .NET 1.1 you can use Application.EnableVisualStyles to support XP Themes. This approach is not recommended because it can cause an 'SEHException' to be thrown and some common controls could disappear.

  • Do you use inherited forms for consistent behaviour?

    If you ask a new .NET developer (from the Access or VB6 world) what is the best thing about .NET Windows Forms, most of your answers will be "Form Inheritance" that allows them to keep a nice consistent look for all forms. If you ask them a couple of months later, they will probably tell you the worst thing about .NET Windows Forms is "Form Inheritance". This is because they have had too many problems with the bugs in the form designer regarding this feature. Many abandon them altogether and jump on the user control band wagon. Please don't... we have a solution to this...

    If you can keep the level of form inheritance to a minimum, then you may not see the problem or at least you will experience the problem less. Anyway even if you do, stop whinging and just close down Visual Studio.NET and restart. You don't change the base form that often anyway.

    Well how do you keep it to a minimum? Well make the first base form without any controls, only code (to make it as flexible as possible and avoid having a multitude of base forms).

    We try to keep the number of controls on inherited forms, and the levels of inheritance to a minimum, because it reduces the risk of problems with the Visual Studio Designer (you know when the controls start jumping around, or disappearing from the Designer, or properties getting reset on inherited copies or even the tab order getting corrupted). Designer errors can also occur in the task list if the InitializeComponent method fails.

    Every form in your application should inherit from a base form which has code common to every form, for example:

    • Company Icon
    • Remembering its size and location - Code sample <span style="background-color: red">to come</span> in the SSW .NET Toolkit
    • Adding itself to a global forms collection if SDI (to find forms that are already open, or to close all open forms)
    • Logging usage frequency and performance of forms (load time)
    Image

    Figure: Base Form for all SSW applications with SSW icon

    a) Sorting out the StartPosition:

    1. CentreParent only for modal dialogs (to prevent multi-monitor confusion)
    2. CentreScreen only for the main form (MainForm), or a splash screen
    3. WindowsDefaultLocation for everything else (99% of forms) - prevents windows from appearing on top of one another

    b) Sorting out FormBorderStyle:

    1. FixedDialog only for modal dialog boxes
    2. FixedSingle only for the the main form (MainForm) - FixedSingle has an icon whereas FixedDialog doesn't
    3. None for splash screen
    4. Sizable for everything else (99% of forms) - almost all forms in an app should be resizable

    We have a program called SSW CodeAuditor to check for this rule.

    c) Sorting out a base data entry form:

    1. Inherited from the original base form
    2. OK, Apply and Cancel buttons
    3. Menu control
    4. Toolbar with New, Search and Delete
    Image

    Figure: Base data entry form with menu, toolbar and OK, Cancel & Apply buttons

    Note: The data entry base form has no heading - we simply use the Title Bar.

    We have a program called SSW .NET Toolkit that implements inherited forms.

  • Do you display consistent information?

    When you have a link in your application, use the same text layout as below and a "More" hyperlink to the same page with the same description. The resulting effect is when the user clicks on the "More" hyperlink, the page will begin with exactly the same information again. This ensures the user is never confused when navigating from your application to a link.

    Image

    Figure: See how the text in the application is reflected in the link

  • Do you encapsulate (aka lock) values of forms?

    One useful feature of inherited forms is the ability to lock the value of certain properties on the inherited copy. E.g.:

    • Font - we want to maintain a consistent font across all forms
    • BackColor - changing the background color prevents the form from being themed
    • Icon - we want all of our forms to have the company Icon

    This can be achieved with the following code, which works by hiding the existing property from the designer using the Browsable attribute. The Browsable attribute set to False means "don't show in the the designer". There is also an attribute called EditorBrowsable, which hides the property from intellisense.

    C#:

    using System.ComponentModel;
    [Browsable(false)] // Browsable = show property in the Designer
    public new Font Font
    {
    get
    {
    return base.Font;
    }
    set
    {
    //base.Font = value; //normal property syntax
    base.Font = new Font("Tahoma", 8.25);
    // Must be hard coded - cannot use Me.
    }
    }

    VB.NET:

    Imports System.ComponentModel
    <Browsable(False)> _
    Public Shadows Property Font() As Font
    Get
    Return MyBase.Font
    End Get
    Set(ByVal Value As Font)
    'MyBase.Font = Value 'normal property syntax
    MyBase.Font = Me.Font
    End Set
    End Property
    Image

    Figure: Font Property Visible

    Image

    Figure: Font Property Hidden

  • Do you know when to use User Controls?

    User controls allow you to have groups of elements which can be placed on forms.

    Bad: User controls can be really misused and placed in forms where they shouldn't be. An example of that is shown below, under the components directory the user controls placed and used only once at a time during the application flow. There is much more coding responsibility on the developer to load those controls correctly one at a time inside the main form.

    Image

    ❌ Figure: Bad example - All the forms in the application are user controls

    Image

    ❌ Figure: Bad example - All of the controls on this form are on a user control, but are only used once

    Good: User Controls are best used for recurring or shared logic either on the same form or throughout the application. This encourages code reuse, resulting in less overall development time (especially in maintenance). Example, the figure below shows the good use of User Controls, the address control is repeated three times but coded once.

    Image

    ✅ Figure: Good example - User controls are only used for shared controls

    Image

    ✅ Figure: Good example - The Address User Control is repeated

    Exception: User controls can be made for tab pages (e.g Tools | Options) and search pages. This allows the breakdown of complex forms, and development by different developers.

    Image

    😐 Figure: User controls are OK in tab pages (exception)

    Summary

    ✅ The pros of User Controls are:

    • You can use a user control more than once on the same form eg. Mailing Address, Billing Address
    • You can reuse logic in the code behind the controls e.g. Search control
    • User controls are less prone to visual inheritance errors
    • When used in a form with multiple tab pages - and each tab page potentially having a lot of controls, it is possible to put each tabpage into a separate user control
    • Reduce lines of generated code in the designer by splitting it into multiple files
    • Allow multiple persons to work on different complex tabpages

    ❌ However the cons are:

    • You lose the AcceptButton and CancelButton properties from the Designer eg. OK, Cancel, Apply. Therefore the OK, Cancel and Apply buttons cannot be on User Controls
  • Do you know how to design a user friendly search system?

    Designing a user-friendly search system is crucial in today’s information-driven world, as it significantly enhances the user experience by enabling efficient and effective access to relevant data. A well-structured search interface not only simplifies the process of locating specific information amidst vast datasets but also caters to a variety of user needs, from basic inquiries to complex queries.

    By prioritizing clarity, simplicity, and adaptability in search design, we can ensure that users can navigate and utilize applications more intuitively, leading to increased productivity, satisfaction, and overall success of the software.

    Image

    ❌ Figure: Bad example - Search fields are on the same form as the data entry controls

    Image

    ✅ Figure: Good example - Search functionality on a dedicated form with a recently updated records and standard search

    Therefore, I believe search system should:

    1. Importatnt - Separate it from the data entry fields (on a different form) - this avoids confusion
    2. Have a "Simple" tab this shows minimum fields, that is just one like Google. E.g. A customer calls, they said they were from Winkleton, but I'm not sure what that is. Do I put it in the Region, City or Address fields? so you need to simply search in all fields with one single text box.
    3. Have a "Recent" tab this shows the most recent records opened/updated
    4. Have a "Common" tab this shows the common fields Note: Preferred over customers needing to learn prefixes like Google (for example, "city:winkleton").
    5. Have an "Advanced" tab only for power users for building up a WHERE clause

    We have a program called SSW .NET Toolkit that implements this cool Search Control.

  • Do you use Validator controls?

    Validation is extremely important on a data entry form. There are two ways to do validation:

    1. ErrorProvider control The ErrorProvider control is code intensive. You must manually handle the Validating event of each control you want to validate, in addition to manually running the validation methods when the OK or Apply button is clicked.
    Private Sub productNameTextBox_Validating(ByVal sender As Object, _
    ByVal e As System.ComponentModel.CancelEventArgs) Handles _
    productNameTextBox.Validating
    ValidateProductName(False)
    End Sub
    Private Function ValidateProductName(ByVal force As Boolean) _
    As Boolean
    If Me.productNameTextBox.Text.Length = 0 Then
    Me.errorProvider.SetError(Me.productNameTextBox,
    "You must enter the Product Name.")
    If force Then
    MessageBox.Show("You must enter the Product Name.", _
    Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Warning)
    End If
    Return False
    Else
    Me.errorProvider.SetError(Me.productNameTextBox, _
    String.Empty)
    Return True
    End If
    End Function
    Private Function ValidateInput() As Boolean
    Dim force As Boolean = True
    Dim isValid As Boolean = ValidateProductID(force)
    If Not isValid Then
    force = False
    End If
    isValid = ValidateProductName(force)
    If Not isValid Then
    force = False
    End If
    isValid = ValidateCategory(force)
    Return isValid
    End Function
    Private Sub okButton_Click(ByVal sender As Object, _
    ByVal e As System.EventArgs)
    If Me.ValidateInput() Then
    'Test
    End If
    End Sub

    ❌ Figure: Figure: Bad example - lots of code but no balloon tooltips

    Private Sub productNameTextBox_Validating(ByVal sender As Object, _
    ByVal e As System.ComponentModel.CancelEventArgs) _
    Handles productNameTextBox.Validating
    ValidateProductName(False)
    End Sub
    Private Function ValidateProductName(ByVal force As Boolean) _
    As Boolean
    If Me.productNameTextBox.Text.Length = 0 Then
    Me.errorProvider.SetError(Me.productNameTextBox, _
    "You must enter the Product Name.")
    If force Then
    If Me.balloonToolTip.IsSupported Then
    Me.balloonToolTip.SetToolTip(Me.productNameTextBox, _
    "You must enter the Product Name.")
    Else
    MessageBox.Show("You must enter the Product Name.", _
    Me.Text, MessageBoxButtons.OK,
    MessageBoxIcon.Warning)
    End If
    End If
    Return False
    Else
    Me.errorProvider.SetError(Me.productNameTextBox, _
    String.Empty)
    Return True
    End If
    End Function
    Private Function ValidateInput() As Boolean
    Dim force As Boolean = True
    Dim isValid As Boolean = ValidateProductID(force)
    If Not isValid Then
    force = False
    End If
    isValid = ValidateProductName(force)
    If Not isValid Then
    force = False
    End If
    isValid = ValidateCategory(force)
    Return isValid
    End Function
    Private Sub okButton_Click(ByVal sender As Object, _
    ByVal e As System.EventArgs)
    If Me.ValidateInput() Then
    'Test
    End If
    End Sub

    ✅ Figure: Figure: Good example - lots of code but balloon tooltips are used

    Note: The component for balloon tooltips can be found in the SSW .NET Toolkit.

    The error provider has the advantage over the extended provider that it can be used with balloon tooltips. If you are not using balloon tooltips, however, the error provider should not be used.

    Image

    Figure: .NET ErrorProvider Control with a custom balloon tooltip

    1. SSW Extended ProviderThe SSW Extended Provider integrates with the ErrorProvider control to provide the same functionality, but requires no code to implement (everything can be done in the Designer).
    Image

    Figure: SSW Extended Provider controls and properties on a TextBox

    We have a program called SSW .NET Toolkit that implements this cool Error Provider Control

  • Do you use DataSets or create your own business objects?

    In .NET, there are 2 ways to pass data through the layers of your application. You can:

    • Use DataSet objects, OR
    • Write your own custom business objects

    There are 2 very different opinions on this matter amongst .NET developers:

    ✅ Pros of DataSet object:

    • Code Generation - Strongly typed DataSet objects can be created automatically in Visual Studio. Custom business objects must be laboriously coded by hand.
    • CRUD functionality DataSets - When used with data adapters, can provide CRUD (Create, Read, Update, Delete) support. You must manually implement this functionality with custom business objects.
    • Concurrency - Support for concurrency is part of the DataSet object. Again, you must implement this yourself in a custom business object.
    • Data binding - It is difficult and time-consuming to write custom business objects that are compatible with data binding. The DataSet object is designed for data binding.

    ✅ Pros of Custom Business objects:

    • Better performance - The DataSet object is a very heavy object and is memory-intensive. In contrast custom business objects are always much more efficient. Business objects are usually faster when manipulating data, or when custom sorting is required.
    • Business objects allow you to combine data storage (NOT data access) and business logic (e.g. validation) in the one class. If you use DataSet objects, these must be in separate classes.

    The Case for Business Objects

    Usually, it is recommended to choose datasets as it is believed you get more for free. However, all the the features you get in the dataset can be manually coded up in business objects.

    E.g. For business objects you must manually code up the bindings, with datasets however you may use the designer for binding straight after designing the dataset. This layer should be code generated - so it doesn't matter much.

    In Visual Studio, binding to business objects is supported in which case we might be swayed to use business objects.

    Exception: Real complex forms say 500,000 lines of C# code

    Datasets are a tool for representing relational data in an object oriented world. They are also slower across networks. Datasets are fantastic for maintenance forms (an editable grid with a couple of checkboxes and text boxes and a save button), but terrible for real complex forms. In a complicated scenario you might have a Customer object. An Order form has a reference to this customer object that it uses to display. When a process is run on the Customer invoked from the Order, you can simply pass a reference to the customer, and if something changes, fire an event back to the Order. If datasets were used, you would be either passing datasets around (which some may say is not very safe, or good OO) or pass an ID around and have the process load the data again.

    Also it appears.NET 2.0's BindingList makes binding extremely easy along with IEditableObject. But in most cases, you don't even need to implement these.

    Rocky Lhotka appeared on a .NET Rocks! episode and they had a big discussion of Business Objects versus DataSets. The use of either must change on a case by case basis. Datasets do allow you to get more for free, but if one day management decide you need to do something a little out of the ordinary, there will be problems. In contrast, business objects take longer to write (this can be minimized with a good code generator and custom templates), but stand the test of time much better than Datasets.

  • Do your Windows Forms have a StatusBar that shows the time to load?

    Every form should have a StatusBar that shows the time taken to load the form.

    Developers can't catch and reproduce every performance issue in the testing environment, but when users complain about performance they can send a screenshot (which would including the time to load). Users themselves also would want to monitor the performance of the application. This is one of Microsoft Internet Explorer's most appalling missing feature, the status bar only says 'Done.' when the page is loaded - 'Done: Load Time 14 seconds'.

    In the figure below, the time taken to load the form over a dialup connection is 61.1 seconds, this proves to the developer that the form is not useable over a dialup connection. In this particular case, the developer has called a 'select * from Employees' where it was not needed, only the name, password and ID is needed for this form.

    Note: Once the form is loaded and load time is shown, the status bar can be used to show anything useful as the form is being used.

    Image

    ✅ Figure: Good example - Another form with the StatusBar that shows the time to load - very slow on dialup.

    Add a StatusBar to the form, and add a StatusBarPanel to the StatusBar, then set the properties like below.

    Image

    Figure: Add StatusBarPanel to StatusBar

    private DateTime StartLoadTime = System.DateTime.Now;
    private void Form1_Load(object sender, System.EventArgs e)
    {
    TimeSpan elapsedLoadTime = DateTime.Now.Subtract(StartLoadTime);
    this.statusBarPanel1.Text = string.Format(
    "Load time: {0} seconds",
    Math.Round(elapsedLoadTime.TotalSeconds, 1));
    }
  • Do you not cache lookup data in your Windows Forms application?

    To avoid unnecessary database look-ups, many developers cache lookup tables when creating a windows application. There are issue  that can arise as a result, mainly to do with the synching of the lookup data. If the database administrator decides to change the lookup tables, there is bound to be a user online using a static old version of the lookup data. This may result in sql exception, and data corruption.

    Exception #1: If the application can be taken offline where the users will not access the database for a finite time, then it is recommended that you cache lookup data. However, we do not recommend caching of non-lookup data, i.e. products, clients or invoices.

    Note: This is a different scenario to complete offline caching; offline caching is recommended and should be implemented (e.g outlook & IE - [Work Offline]. However, this rule is about combo boxes and list views which contain less than 100 records. There is not much benefit to caching lookup data as there is much more coding involved.

    Exception #2: If the application contains minor non-critical data. (eg. If you allow the user to customize the text displayed on forms (some people prefer 'Customer' while some prefer 'Client') and this is stored in a database)

    Depending on the frequency of this data being changed (and if the change is user dependant), you may want to:

    • Low frequency: Place an option to change this data in the application's installation process
    • High frequency: Cache the data and provide an option to refresh all cached data or disable caching all together. (e.g menu items View->'Refresh All' and Tools->'Options'->'Disable Caching').

    We would love to be proved wrong on this rule. We have 1000s of users on some of our applications, we have tried caching lookup data and we ended up with a lot more code containing exception handling and table refreshing than its benefit.

  • Do you use the designer for all visual elements?

    The designer should be used for all GUI design. Controls will be dragged and dropped onto the form and all properties should be set in the designer, e.g.

    • Labels, TextBoxes and other visual elements
    • ErrorProviders
    • DataSets (to allow data binding in the designer)

    Things that do not belong in the designer:

    • Connections
    • Commands
    • DataAdapters

    However, and DataAdapter objects should not be dragged onto forms, as they belong in the business tier. Strongly typed DataSet objects should be in the designer as they are simply passed to the business layer. Avoid writing code for properties that can be set in the designer.

    Image

    ❌ Figure: Bad example - Connection and Command objects in the Designer

    ✅ Figure: ![Good example - Only visual elements in the designer](/uploads/rules/do-you-use-the-designer-for-all-visual-elements/GoodDesigner.gif)

  • Do you always use the Visual Studio designer for data binding where possible?

    Basic data binding should always be done in the designer because the syntax for data binding is complex, and confusing for other developers reading the code.

    Image

    Figure: Simple data binding (binding to a single property) in the designer

    Image

    Figure: Complex data binding (binding to a list) in the designer

    When you need to handle the Format or binding events, you can still use designer data binding, as long as you hook in your events prior to filling data.

    private void Form1_Load(object sender, System.EventArgs e)
    {
    Binding currencyBinding = this.textBox1.DataBindings("Text");
    currencyBinding.Format += new
    ConvertEventHandler(currencyBinding_Format);
    currencyBinding.Parse +=
    new ConvertEventHandler(currencyBinding_Parse);
    OrderDetailsService.Instance.GetAll(Me.OrderDetailsDataSet1);
    }
    private void currencyBinding_Format(object sender, ConvertEventArgs e)
    {
    if(e.DesiredType == typeof(string))
    {
    e.Value = ((decimal)e.Value).ToString("c");
    }
    }
    private void currencyBinding_Parse(object sender, ConvertEventArgs e)
    {
    if(e.DesiredType == typeof(decimal))
    {
    e.Value = Decimal.Parse(e.Value.ToString(),
    System.Globalization.NumberStyles.Currency);
    }
    }
    //
    // Designer auto generated code.
    //
    private void InitializeComponent()
    {
    this.cmbTumorQuad = new System.Windows.Forms.ComboBox();
    //
    // cmbTumorQuad
    //
    this.requiredValidator1.SetCustomValidationEnabled(this.cmbTumorQuad, true);
    this.cmbTumorQuad.DataBindings.Add(new System.Windows.Forms.Binding("SelectedValue", this.dvOccMain, "TumorQuadrant"));
    this.cmbTumorQuad.DataSource = this.dvTumorQuad;
    this.cmbTumorQuad.DisplayMember = "Description";
    this.requiredValidator1.SetDisplayName(this.cmbTumorQuad, "");
    }

    ✅ Figure: Figure: Good example - DataBinding in Designer

    private void DataBind()
    {
    ChangeBinding(txtRuleName.DataBindings, "Text", jobRules, "RuleData.RuleName");
    ChangeBinding(cmbFileFilter.DataBindings, "Text", jobRules, "RuleData.FileFilter");
    ChangeBinding(txtSearchString.DataBindings, "Text", jobRules, "RuleData.SearchString");
    ChangeBinding(txtCreatedBy.DataBindings, "Text" , jobRules, "RuleData.EmpCreated");
    }
    protected Binding ChangeBinding(ControlBindingsCollection bindings, string propertyName,
    object dataSource, string dataMember, ConvertEventHandler eFormat, ConvertEventHandler eParse)
    {
    Binding b = bindings[propertyName];
    if ( b != null )
    bindings.Remove(b);
    b = new Binding(propertyName, dataSource, dataMember);
    bindings.Add(b);
    return b;
    }

    ❌ Figure: Figure: Bad example - DataBinding in Code

    private void DataBind()
    {
    //Header
    picRuleType.Image = Core.GetRuleTypeImage((RuleType)rule.RuleType, 48);
    ruleNameTextBox.Text = rule.RuleName;
    //General Tab
    notesTextBox.Text = rule.RuleDescription;
    ruleUrlTextBox.Text = rule.RuleURL;
    //Search Tab
    cboRuleType.SelectedValue = (RuleType)rule.RuleType;
    searchForTextBox.Text = rule.SearchString;
    shouldExistComboBox.SelectedIndex = (rule.ShouldExist == true ? 0 : 1);
    //Change History Tab
    createdByTextBox.Text = rule.EmpCreated;
    dateCreatedTextBox.Text = rule.DateCreated.ToString();
    lastUpdatedByTextBox.Text = rule.EmpUpdated;
    dateLastUpdatedTextBox.Text = rule.DateUpdated.ToString();
    }

    ❌ Figure: Figure: Bad example - Set controls' values in Code

  • Do you avoid using MDI forms?

    
    <introEmbed
      body={<>
    MDI (Multiple Document Interface) forms should be avoided in most modern data-centric applications because they:
    
    - Are a hangover from the days of Windows 3.1 and Access 2.0
    - Constrained within a smaller window
    - Only show as one window on the taskbar
    - Have no multiple monitor support (the killer reason)
      </>}
    />
    <imageEmbed
      alt="Image"
      size="large"
      showBorder={false}
      figureEmbed={{
        preset: "badExample",
        figure: 'Bad example - VS.NET with tabs is cool for developers, but not for the average knowledge worker',
        shouldDisplay: true
      }}
      src="/uploads/rules/do-you-avoid-using-mdi-forms/vs.net.jpg"
    />
    
    <imageEmbed
      alt="Image"
      size="large"
      showBorder={false}
      figureEmbed={{
        preset: "badExample",
        figure: 'Bad example - Word 2003 in MDI mode',
        shouldDisplay: true
      }}
      src="/uploads/rules/do-you-avoid-using-mdi-forms/wordmdibad.gif"
    />
    
    <figureEmbed figureEmbed={{
      preset: "goodExample",
      figure: '<imageEmbed
      alt="Image"
      size="large"
      showBorder={false}
      figureEmbed={{
        preset: "default",
        figure: \'Good example - Word 2003 with Default Settings\',
        shouldDisplay: true
      }}
      src="/uploads/rules/do-you-avoid-using-mdi-forms/sdiexample.jpg"
    />
    
    ```cs
    Me.IsMdiContainer = true;
    
    ClientForm frm = new ClientForm();
    frm.MdiParent = this;
    frm.Show();
    ```',
      shouldDisplay: true
    } } />
     bad
    Figure: Bad code example - using MDI forms
    :::
    
    ```cs
    ClientForm frm = new ClientForm(); frm.Show();
    ```
    
    <figureEmbed figureEmbed={{
      preset: "goodExample",
      figure: 'Figure: Good example - not using MDI',
      shouldDisplay: true
    } } />
    
    
    MDI forms have the advantage that the MDI parent form will have a collection **MdiChildren** which contains all of its child forms. This makes it very easy to find out which forms are already open, and to give these forms focus. Accomplishing this with an SDI application requires you to:
    
    - A global collection of forms
    - A line of code on the load and closed events of each form which adds / removes the form from the global collection
    
    **But what about tabs?**
    As developers, we love to use tabs similar Visual Studio.NET (figure below) and  browsers such as Mozilla and CrazyBrowser. Tabs are great for developers, but standard business applications (e.g Sales Order System) should be developed as SDI (Single Document Interface). This is because users are used to Outlook and other office applications, which don't use MDIs at all. If the users want to group windows, Windows XP lets you "Group Similar Taskbar Icons".
    
  • Do you have a correctly structured common code assembly?

    Your common code assembly should be divided into the following sections:

    • Common (e.g. SSW.Framework.Common)
      • Code which is not UI specific
      • Example: Code to convert a date into different formats
    • CommonWindows (e.g. SSW.Framework.WindowsUI)
      • Example: Base forms which are the same for all products, wizard frameworks
    • CommonWeb (e.g. SSW.Framework.WebUI)
      • Example: Generic XML-based navigation components

    For more information see Do you have a consistent .NET Solution Structure?.

  • Are your Data Access Layers compatible with Web Services?

    Data Access Layers should support not only direct connections to SQL Server but also connections through web services.

    Many applications are designed for use with a database connection only. As users decide to take the application some where else away from the database, the need for web services arises.

    Image

    ✅ Figure: Good example - Options form showing choice of connection

    There are 3 ways to implement this:

    1. Lots of if statements (really messy - most people try this first)
    2. Interfaces (implements statement in VB)
    3. Factory pattern (✅ best - most flexible and extensible approach)

    All database applications should be web services ready as the future direction is to use web services only, because even locally a web service connection is not much slower than direct connection. The performance difference shouldn't be substantial enough to require a double code base.

  • Do you log all errors (with SSW Exception Manager)?

    All unhandled exceptions should be logged to provide developers with sufficient information to fix bugs when they occur. There are two options we for logging exceptions:

    The Microsoft Exception Management Application BlockMicrosoft provides full source code for the EMAB, which is fully extensible with custom logging target extensions. We decided to customize the EMAB to produce the SSW Exception Management Block, which logs exceptions to a database using a web service, allowing us to keep a history of all exceptions.

    Image

    Figure: Exception Reporting Web Service

    Your code should not contain any empty catch blocks as this can hamper exception handling and debugging.

    We have a program called SSW Code Auditor to check for this rule.

    We have a program called SSW .NET Toolkit that implements Exception Logging and Handling

  • Do you implement trace logging (with Log4Net)?

    By using logging, the developer has access to more information when a particular error occurs like which functions were called, what state is the application currently in and what certain variables are. This is important as a simple stack trace will only tell you where the error occurred but not how it occurred.

    Log4Net is an open-source logging library for .NET based on the Log4J library. It provides a simple to use library to enable logging in your application. It provides several logging options such as:

    • XML File (Recommended)
    • Text File
    • Database
    • Rolling log file
    • Console

    Log4Net also provides different levels of tracing - from INFO to DEBUG to ERROR - and allows you to easily change the logging level (through the config file)

    We have a program called SSW CodeAuditor to check for this rule.

  • Do you make a strongly-typed wrapper for app.config?

    If your application accesses properties in app.config, you should provide a strongly typed wrapper for the app.config file. The following code shows you how to build a simple wrapper for app.config in an AssemblyConfiguration class:

    using System;
    using System.Configuration;
    namespace SSW.Northwind.WindowsUI
    {
    public sealed class AssemblyConfiguration
    {
    // Prevent the class from being constructed
    private AssemblyConfiguration() { }
    public static string ConnectionString
    {
    get
    {
    return
    ConfigurationSettings.AppSettings["ConnectionString"].
    ToString();
    }
    }
    }
    }

    Unfortunately, the Configuration Block does not automatically provide this wrapper.

  • Do you keep the standard .NET DataGrid?

    In Visual Studio 2003 the standard DataGrid has some limitations. It was ugly compared to a ListView and did not support combo box or button columns, making it useless for many applications.

    In Visual Studio 2005 we have this great new DataGridView control which solves these problems.

    If you still want more then you need a 3rd party control. We recommend these (in this order):

    1. Janus GridEx
    2. Developer Express XtraGrid
    3. Infragistics Wingrid
    4. ComponentOne TrueDBGrid

    For more Details have a look at our Best 3rd Party Controls for Windows Forms

    Image

    ❌ Figure: Bad example - The standard .NET DataGrid in 2003 was ugly and missing combos

    ✅ Figure: ![Figure: Better example - Infragistics UltraGrid is better as you get combos](/uploads/rules/do-you-keep-the-standard-net-datagrid/infragisticsultragrid.gif)

    Image

    ✅ Figure: Good example - Janus Grid is even better. A great datagrid has easy grouping, just like Outlook

    Image

    Figure: The great new Visual Studio 2005 much improved DataGridView

  • Do you replace the standard .NET Date Time Picker?

    A good replacement for the standard Date Time picker is the UltraDatePicker by Infragistics.

    The main reason for the use of the UltraDatePicker over the standard .NET one is because the .NET one does not take null for a date value.

    This is a lot of hassle for DataBinding. The Windows Form DataBinding will try to put null into the bound field, when:

    1. The bound data is DBNull

    2. The current row is removed (i.e., there is no more data in the DataTable)

    If you set the property "Nullable" to false in UltraDatePicker, the same issues appears again.

    Image

    Figure: Set "Nullable" to true to allow DBNull values from bound DataRows

    So the solution is to allow null, but where the field is required, make sure the validation picks it up and asks the user to enter a value when saving the form.

  • Do you avoid 3rd party menus & toolbars?

    The menu & toolbar controls in Visual Studio .NET 2003 do not allow you to have icons in your menus or have alpha-blended toolbar icons. They also do not provide an Office 2003 like look. However, we have tried several third party menu and toolbar controls and all of them had serious bugs. E.g.:

    • DotNetMagic
      • Docking panels didn't implement enough events and it is unclear what the events are doing
      • Menu control is OK
    • DotNetBar
    • Janus Systems

    We love 3rd party controls, a lot of developers spend a lot of time implementing these tools to make their applications sweeter, but we found that there is not enough benefit in implementing these controls.

    I am very keen on 3rd party controls, but only where they add real value. Knowing about Visual Studio 2005 which provides Office 2003 style menus and toolbars with the new ToolStrip control mean I will wait in this case....Another worry is upgrading from these 3rd party controls will be difficult)

    Image

    Figure: Visual Studio 2005's new controls

    However, it would be better if VS 2005 stored the details of menus and toolbars in an XML file.

  • Do your List Views support multiple selection and copying?

    List Views such as in SSW Diagnostics can present a wealth of information to the user. But too often, users are unable to copy this information to paste into a support email because the list view doesn't support copying. Instead, the user has to frustratingly retype the information with the risk of introducing errors.

    Image

    ❌ Figure: Bad example - List view with only single selection and no copying

    Image

    ✅ Figure: Good example - List view with multiple selection and copying

    Make it easier for the user by enabling the "MultiSelection" property of a ListView and providing a right click menu with a "Copy" item that copies to the clipboard.

  • Do you use an image button for opening a web page taking action?

    Opening a specific web page (that the user is aware of) from a windows application should always be in the form of a hyperlink. Below is a simple example of a hyperlink simply opening a web page containing just more information or help.

    Image

    Figure: Simple hyperlink not taking action

    However if you are taking action then opening the page (e.g concatenating the URL, etc) then you must have an image button to illustrate the action which will be taken.

    Here is a compilation of a few bad examples for this:

    Image

    ❌ Figure: Bad example - Hyperlink

    Image

    ❌ Figure: Bad example - Hyperlink on a button

    Image

    ❌ Figure: Bad example - Normal button

    But when it requires some form of action (e.g. generating reports, passing and processing values), use a button with an image.

    Image

    ✅ Figure: Good example - XP button with image

    Note: Screenshot contains XP button because the .Net 1.1 button does not support images, however the default button in .NET 2.0 supports images. E.g. EdwardForgacs.Components.WindowsUI.dll

  • Do you use "OK" instead of "Ok"?

    Make sure you use correct casing and wording for "OK" in buttons and content.

    Image

    ❌ Figure: Bad example - Button text "Ok"

    Image

    ✅ Figure: Good example - Button text "OK"

    We have a program called SSW Code Auditor to check for this rule.

  • Do your forms have Accept and Cancel buttons?

    If you have a button in a form you must have an accept or a cancel button. As a result user can use "Enter" and "Esc" to control the form.

    Image

    ✅ Figure: Good example - Next button is set as the accept button

    We have a program called SSW CodeAuditor to check for this rule.

    Note: The CodeAuditor Rule will just test the buttons on the Base form and ignore all the inherit forms, because for more reusable code, the Accept and Cancel buttons should be in the base form.

  • Do you label buttons to be an action, like "Save, "Open", etc?

    If your form has an "OK" button it should be renamed to be as an action verb. For example: Save, Open, etc.

    Image

    ❌ Figure: Bad example - "OK" button which is not an action

    Image

    ✅ Figure: Good example - "Save" button which is an action

    We have a program called SSW Code Auditor to check for this rule.

  • Do you make "Enter" go to the next line when you have a multi-line textbox rather than hit the OK button?

    If you have a multi-line textbox in a form, you should make the "Enter" key go to the next line in the text box, rather than cause it to hit the OK button.

    Image

    ❌ Figure: Bad example - "Enter" button causes OK button to be pressed instead of going to next line in the multi-line text box

    Image

    ✅ Figure: Good example - "Enter" button goes to the next line in the text box

    It can be done by assigning "True" value to AcceptsReturn and Multiline options in properties bar.

    Image

    Figure: Developer Notes properties details

  • Do you make common controls with consistent widths?

    
    <introEmbed
      body={<>
    There are a few common controls we always use in our products. For example, DateTime and Ellipsis Button. We need a standard for the width so the controls should be more consistent.
      </>}
    />
    **Note:** Controls on base forms will be made to be 'protected' rather than 'private', especially so that inherited forms of different sizes don't mess up.
    
    <imageEmbed
      alt="Image"
      size="large"
      showBorder={false}
      figureEmbed={{
        preset: "badExample",
        figure: 'Bad example - Control sizes are not consistent',
        shouldDisplay: true
      }}
      src="/uploads/rules/make-common-controls-with-consistent-widths/commoncontrolbad.gif"
    />
    
    <imageEmbed
      alt="Image"
      size="large"
      showBorder={false}
      figureEmbed={{
        preset: "goodExample",
        figure: 'Good example - Control sizes are all standard and consistent',
        shouldDisplay: true
      }}
      src="/uploads/rules/make-common-controls-with-consistent-widths/commoncontrolgood.gif"
    />
    
    <imageEmbed
      alt="Image"
      size="large"
      showBorder={false}
      figureEmbed={{
        preset: "badExample",
        figure: 'Bad example - Non-standard size for Add & Delete buttons',
        shouldDisplay: true
      }}
      src="/uploads/rules/make-common-controls-with-consistent-widths/adddeletebad.gif"
    />
    
    <imageEmbed
      alt="Image"
      size="large"
      showBorder={false}
      figureEmbed={{
        preset: "goodExample",
        figure: 'Good example - Standard size for Add & Delete buttons',
        shouldDisplay: true
      }}
      src="/uploads/rules/make-common-controls-with-consistent-widths/adddeletegood.gif"
    />
    
    We have a program called [SSW Code Auditor](https://ssw.com.au/ssw/CodeAuditor/Rules.aspx#CommonControl) to check for the following two rules:
    
    **Rule - C#/VB.NET UI- Button Height and Width - for Standard Button (75 x 23 pixels)**
    
    - **Level 2:** All buttons \< 6 characters:\*\* Check the standard size (75 X 23 pixels) for buttons with the word length less than or equal to six characters, except the following buttons.
    - **Level 1:** The action buttons:\*\* Check the standard size (75 X 23 pixels) for the following action buttons:
    
      - Add
      - Delete
      - Edit
      - OK
      - Close
      - Cancel
      - Save
      - Browse
      - Select
      - Test<
      - Next
      - Back
      - Remove
      - Refresh (Exception to the rule as it has 7 letters)
    
  • Do you support URLs on Windows Forms applications?

    Aside from ease of installation, what is the one thing a web browsers has over a Windows Forms application? - a URL!

    With a Windows Forms application, you typically have to wade through layers of menus and options to find a particular record or "page". However, Outlook has a unique feature which allows you to jump to a folder or item directly from the command line.

    Image

    Figure: Outlook can automatically jump to a specified folder or item from a command line

    Image

    Figure: Outlook address bar (Web toolbar) shows you the URL for every folder

    We believe that all applications should have this capability. You can add it to a Windows Application using the following procedure:

    1. Add the necessary registry keys for the application
    • HKEY_CLASSES_ROOT\AppName\URL Protocol = ""
    • HKEY_CLASSES_ROOT\AppName\Default Value = "URL:Outlook Folders"
    • HKEY_CLASSES_ROOT\AppName\shell\Default Value = "open"
    • HKEY_CLASSES_ROOT\AppName\shell\open\command\Default Value = "Path\AssemblyName.exe /select %1"
    1. Add code into your main method to handle the extra parameters

    C#:

    public static void Main(string[] args)
    {
    ...
    if(args.Length > 0)
    {
    string commandData = args[1].Substring(args[1].IndexOf(":") +
    1).Replace("\"", String.Empty);
    Form requestedForm = null;
    switch(commandData)
    {
    case "Client":
    {
    requestedForm = new ClientForm();
    break;
    }
    // Handle other values
    default: // Command line parameter is invalid
    {
    MessageBox.Show("The command line parameter specified" +
    " was invalid.", "SSW Demo App",
    MessageBoxButtons.OK, MessageBoxIcon.Error);
    // Exit the application
    return;
    }
    }
    requestedForm.Show();
    // Show the main form as well
    MainForm mainForm = new MainForm();
    mainForm.Show();
    // Give the requested form focus
    requestedForm.Focus();
    Application.Run(mainForm);
    }
    else // No command line parameters
    {
    // Just show the main form
    Application.Run(new MainForm());
    }
    }

    VB.NET:

    Public Shared Sub Main()
    ...
    Dim args As String = Microsoft.VisualBasic.Command()
    If args.Length > 0
    Dim commandData As String = _
    args.Substring(args.IndexOf(":") + 1).Replace("""", "")
    Dim requestedForm As Form = Nothing
    Select Case commandData
    Case "Client"
    requestedForm = New ClientForm()
    ' Handle other values
    Case Else ' Command line parameter is invalid
    MessageBox.Show("The command line parameter specified " &_
    "was invalid.", "SSW Demo App", MessageBoxButtons.OK, &_
    MessageBoxIcon.Error);
    ' Exit the application
    Exit Sub
    End Select
    requestedForm.Show()
    ' Show the main form as well
    Dim mainForm As MainForm = New MainForm()
    mainForm.Show()
    ' Give the requested form focus
    requestedForm.Focus()
    Application.Run(mainForm);
    Else ' No command line parameters, just show the main form
    Application.Run(new MainForm())
    End If
    End Sub

    Sample code implementation in the SSW .NET Toolkit

  • Do you include "Back" and "Undo" buttons on every form?

    Following on from including a URL, almost every form should have a Back and an Undo button which takes you back to the previous screen, or reverses the last action. This is just like Outlook (see figure below), it has a Back button to take you to the previous folder and an Undo button.

    Image

    ✅ Figure: Good example - Back & Undo buttons in Outlook Advanced toolbar

    Notes:

    • "Back" button should only be implemented if different views can be shown in the same window
    • Don't put "Undo" buttons on non data entry forms such as a Print Preview form

    The list of forms/URLs and the order in which they have been accessed should be stored in a DataSet held in memory (like IE) - not saved to disk.

    For example:

    MenuActionUndoBack
    CutRemember: Remember Text and Cursor Position Cut To ClipboardReturn to Remembern/a
    Save RecordRemember old values Execute procCustomerSave Close FormReturn to Old valuesReopen form

    Sample code implementation in the SSW .NET Toolkit.

  • Do you use NUnit to write Unit Tests?

    When anyone sends you a bug that happen in their environment do a unit test. Just because the code runs on your machine it doesnt mean it will work on the users machine. E.g permissions issues - you are an admin while the user is only a simple user, registry & io reads might fail, NUnit will test for this and let you know.

    Unit testing is a valuable tool when maintaining code, particularly in a team environment where you may have to fix a bug in someone else's code. Unit Tests ensure that you do not introduce new bugs in someone else's code, or code that you have not looked at for a while. We like NUnit because it is free, we have found that it is easy for developers to learn and it integrates well with Visual Studio. Visual Studio .NET 2005 integrates Unit Testing with Visual Studio Team System.

    You should always try to write unit tests for:

    1. Code that is a core component to your application
    2. Any regular expressions (as these change it is easy to change functionality and cause errors)
    3. Any external factors that your program relies on like hyperlinks to websites

    One important test that should be implemented (if your setup package or build script doesn't pick it up) is to validate that your application installs all required DLLs. .NET loads DLLs just in time (JIT) - which means that a missing DLL is will not generate an error unless it is required.

    Example: You may have a shared project that your application uses. Another developer adds a reference to that project - unbeknownst to you. You build the application with no errors, and the application passes basic user testing. Problem is that the user did not run the tutorial component - which is missing from the setup package. Users who run the tutorial report runtime errors. You can resolve this issue by creating a unit test to check that all DLLs are included in the setup.

    Image

    Figure: Unit Tests accessible from the help menu

    Unit tests should also be accessible from the Help menu to assist in troubleshooting when your users call up tech support.

    Note: Unit testing also works with Web projects.

  • Do you save user settings and reuse them by default?

    There should always be default values in your application if you allow users to change the settings. This will help your users to have a better first time experience and insure the application work as expected.

    However when the users change settings for their own preference, it is better to save these settings and give user has a better return experience, your application looks smarter in this way.

    Image

    Figure: Save user setting

  • Do you have a ResetDefault() function to handle messed up user settings?

    In development life cycle, developers always have different settings to the user's settings. Because of this, debug settings won't always work on the remote machine.

    In order to have settings.config, we also have a defaults.config. This is good because this gives a chance for the user to roll back bad settings without reinstalling the application. The application can also roll back the settings it automatically. Below is the code that what we do.

    VB.NET

    Public Sub RuneXtremeEmail(ByVal state As Object)
    If Environment.MachineName <> Configuration.MachineName Then
    resetSettings()
    Else
    End

    We have a program called SSW Code Auditor to check for this rule.

    We have a program called SSW .NET Toolkit that implements this rule.

    Note: in Access we do like this

    Private Sub Form_Load()
    If Nz(DLookup("CurrentComputerName", "ControlLocal", "ID=1"), "") <> CurrentComputerName
    Then
    Me.ctlCurrentComputerName.Value = CurrentComputerName
    Else ...
  • Do you use Threading to make your user interfaces more responsive?

    Threading is not only used to allow a server to process multiple client requests - it could make your user interfaces responsive when your application is performing a long-running process, allowing the user to keep interactive.

    :: bad

    Image

    Figure: Bad example - Unresponsive UI because no threading code

    :::

    private void Form1_Load(object sender, EventArgs e)
    {
    this.ValidateSQLAndCheckVersion();// a long task
    }

    Code: No threading code for long task

    Image

    ✅ Figure: Good example - Responsive UI in progress

    Image

    ✅ Figure: Good example - Responsive UI completed

    private void Page05StorageMechanism_Load(object sender, EventArgs e)
    {
    this.rsSetupOk = false;
    this.databaseSetupOk = false;
    this.NextButtonState.Enabled = false;
    CheckDatabase();
    CheckReports();
    }
    public void CheckDatabase()
    {
    if(sqlConnectionString == null)
    {
    OnValidationFinished(false, false);
    }
    if(upgradeScriptPath ==null && createScriptPath == null)
    {
    OnValidationFinished(false, false);
    }
    Thread thread = new Thread(new ThreadStart(this.ValidateSQLAndCheckVersion) ) ;
    thread.Name = "DBCheckingThread";
    thread.Start();
    }

    Code: Threading code for long task

  • Do you display file name in the text box in full?

    The height of a text box may need to be twice of the font height to display file name in full.

    Image

    Figure: Text box displaying full file name

  • Do you use Status Control?

    SSW Status Control provides graphical UI for status checking result.

    Image

    Figure: Warning message in status control

    Image

    Figure: Successful message in status control

    Image

    Figure: Failed message in status control

    We have a program called SSW .NET Toolkit that uses status control.

  • Do you prevent users from running two instances of your application?

    In some cases, running two instances of an application at the same time may cause unexpected result. See this issue is solved via the code below on SSW Exchange Reporter:

    try
    {
    Process current = Process.GetCurrentProcess();
    Process[] processes = Process.GetProcessesByName( current.ProcessName);
    if ( processes.Length>1 )
    {
    DialogResult userOption = MessageBox.Show(Application.ProductName + " is already running on this machine. " + Environment.NewLine+Environment.NewLine + "Please click: "+Environment.NewLine+
    " - 'Try again' to exit the other instance and try again, or "+Environment.NewLine+
    " - 'Cancel' to exit now."+Environment.NewLine,
    Application.ProductName+" "+(new Version(Application.ProductVersion)).ToString(2),
    MessageBoxButtons.RetryCancel, MessageBoxIcon.Warning);
    switch(userOption)
    {
    case DialogResult.Cancel: return;
    case DialogResult.Retry:
    foreach(Process currProcess in processes)
    {
    if ( currProcess.Id != current.Id)
    {
    currProcess.Kill();
    }
    }
    break;
    }
    }
    }
    catch (Exception ex)
    {
    TracingHelper.Trace(null, Loggers.WindowsUILogger, TracingLevels.DEBUG, "Cannot get process information, Excpetion occured.", ex) ;
    DialogResult result = MessageBox.Show("Exchange Reporter cannot detect process information. This may be caused by disabled 'Performance Counter' on your machine. "+Environment.NewLine+
    "In such case, Exchange Reporter cannot ensure there is only one instance running. "+
    Environment.NewLine+
    "You may continue to run Exchange Reporter, however, please make sure you have only one instance of Exchange Reporter running. "+
    Environment.NewLine+
    "Multiple instances will cause unexpected behaviour. "+
    Environment.NewLine+Environment.NewLine+
    "Please click 'OK' to continue, or click 'Cancel' to quit."
    , Application.ProductName+" "+(new Version(Application.ProductVersion)).ToString(2),
    MessageBoxButtons.OKCancel,
    MessageBoxIcon.Warning);
    if ( result == DialogResult.Cancel)
    {
    return;
    }
    }

    Code: Avoid running two instances of an application

  • Do you add a "(customized)" column in grid if there are default values?

    Add a column and show "(customized)" in grid - that is an easier way to know if you have changed from the defaults.

    Image

    ❌ Figure: Bad example - You need to compare with the default values to know whether it is modified

    Image

    ✅ Figure: Good example - A "(customized)" column is an easier way to know if you have changed from the defaults

  • Do you have a standard menu item "Check for Updates"?

    A standard menu item "Check for Updates" should be available in the Help menu. Its function is running SSW Diagnostics to check updates and keep the system up to date easily. More on Do you allow users to check for a new version easily?

    Image

    Figure: "Check for Updates" menu item

    Here's the code to run Diagnostics:

    System.Diagnostics.Process.Start("http://us.ssw.com.au/ssw/diagnostics/download/SSWDiagnostics.application#SSWDiagnostics.application");
  • Do you use Web Service to send emails?

    In a Windows application, if you need to send mail, please use a WebService to do this, because using WebService to send emails is safer.You don't need to store the email server configuration in your application.config file, which can be installed on the client and be exposed to someone who could take advantage of it.

    SmtpClient client = new SmtpClient();
    try
    {
    client.Host = "****.***.com.au";
    client.Port = 25;
    client.UseDefaultCredentials = true;
    MailAddress from = new MailAddress("test@ssw.com.au");
    string unrecAddy = "test@ssw.com.au";
    MailAddress to = new MailAddress(unrecAddy);
    MailMessage mailMessage = new MailMessage(from, to);
    mailMessage.Subject = "aaa";
    string strVer = "aaa";
    mailMessage.Body = "aaa";
    client.Send(mailMessage);
    }
    catch(Exception ex)
    {
    client.SendAsyncCancel();
    MessageBox.Show(ex.ToString());
    }

    ❌ Figure: Bad example - Send mail without webservice

    Emailer.PubicFunction pf = new EmailWebServices.Emailer.Publiction();
    pf.SendMail("Test", textBox3.Text, textBox1.Text, textBox2.Text, false, "", null);

    ✅ Figure: Good example - Send mail use webservice

  • Do you have colons beside labels on input controls (Textbox, Combobox, Datetime, etc.)?

    Use colon with your labels.

  • Do you always use GridView instead of ListBox?

    Always choose a GridView (over a ListBox) because it can have:

    1. Multiple columns
    2. Checkboxes in the header of the control, which enables users to easily check or uncheck all items
    3. Add sub-controls added such as buttons, links, charts, and even customized controls to the Gridview. This means you get unlimited flexibility with the GridView
    Image

    ❌ Figure: Bad example - No header rows and no checkbox to check or uncheck all items. None of this can be done with the ListView

    Image

    ✅ Figure: Good example - A header row and a checkbox to control all items, and multiple columns give users a richer experience. This can all be done using a GridView

  • Do you know how to make .NET wrapper work on both x64 and x86 platforms?

    Sometimes, we need to use .NET wrapper to call Windows built-in forms for implementing special functionalities. For example, calling the Directory Object Picker dialog enables a user to select objects from the Active Directory. Microsoft provides an article and an C++ example on how to calling the Directory Object Picker dialog, and the CodePlex website used to give a .NET version of implementation(C#).

    However, all of this implementations only work on x86 platform, and will crash on x64 platform, regarding to this problem, the keynote is to understand the difference of IntPtr in between x64 and x86 platforms.

    • In x86 platform, IntPtr = Int32
    • In x64 platform, IntPtr = Int64

    So, To fix the crash, we should re-write the code below:

    DSOP_SCOPE_INIT_INFO[] scopeInitInfo = new DSOP_SCOPE_INIT_INFO[2];
    IntPtr refScopeInitInfo = Marshal.AllocHGlobal(Marshal.SizeOf (typeof (DSOP_SCOPE_INIT_INFO)) * 2);
    Marshal.StructureToPtr (scopeInitInfo[0], refScopeInitInfo,true);
    Marshal.StructureToPtr(scopeInitInfo[1], (IntPtr)((int)refScopeInitInfo + Marshal.SizeOf(typeof(DSOP_SCOPE_INIT_INFO))), true);

    ❌ Figure: Bad example - The code above always gets crash in x64 platform, because of an integer overflow and result in a segmentation fault in 64 bits.

    IntPtr refScopeInitInfo = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(DSOP_SCOPE_INIT_INFO)) * 2);
    int scopeInitInfoSize = Marshal.SizeOf (typeof (DSOP_SCOPE_INIT_INFO));
    int offset = scopeInitInfoSize;
    IntPtr scopeInitInfo = (IntPtr)(refScopeInitInfo.ToInt64() + offset);

    ✅ Figure: Good example - The Directory Object Picker dialog works on both x64 and x86 platforms well when using the good code above.

    Image

    ❌ Figure: Bad example - Calling the Directory Object Picker dialog causes crash on x64 platform when using the bad code above

    Image

    ✅ Figure: Good example - The Directory Object Picker dialog works on both x64 and x86 platforms well when using the good code above

  • Do you set the ScrollBars property if the TextBox is Multiline?

    
    <introEmbed
      body={<>
    If a TextBox has Multiline set to true, then the ScrollBars property should be set to "Both" or at least "Vertical".
      </>}
    />
    <imageEmbed
      alt="Image"
      size="large"
      showBorder={false}
      figureEmbed={{
        preset: "badExample",
        figure: 'Bad example - Multiline TextBox without "Vertical" scroll bar.',
        shouldDisplay: true
      }}
      src="/uploads/rules/set-the-scrollbars-property-if-the-textbox-is-multiline/multiline_bad.gif"
    />
    
    <figureEmbed figureEmbed={{
      preset: "goodExample",
      figure: '<imageEmbed
      alt="Image"
      size="large"
      showBorder={false}
      figureEmbed={{
        preset: "default",
        figure: \'Good example - Multiline TextBox with "Vertical" scroll bar\',
        shouldDisplay: true
      }}
      src="/uploads/rules/set-the-scrollbars-property-if-the-textbox-is-multiline/multiline_good.gif"
    />',
      shouldDisplay: true
    } } />
    
    
    <figureEmbed figureEmbed={{
      preset: "goodExample",
      figure: '<imageEmbed
      alt="Image"
      size="large"
      showBorder={false}
      figureEmbed={{
        preset: "default",
        figure: \'Good example - Set the ScrollBars property to "Vertical" if the TextBox is Multiline\',
        shouldDisplay: true
      }}
      src="/uploads/rules/set-the-scrollbars-property-if-the-textbox-is-multiline/multilinetextbox.gif"
    />',
      shouldDisplay: true
    } } />
    
    
    We have a program called [SSW Code Auditor](https://ssw.com.au/ssw/CodeAuditor/) to check for this rule.
    
  • Do you know how to run write application to run with UAC turn on?

    Some applications may need to have administrator right for running the application, e.g. create a file, access system library, etc. It will be an issue for the application to run if UAC is turned on. Below is the step to solve the issue:

    1. Add App.Manifest into WindowsUI project. It should contain the below code:
    <?xml version="1.0" encoding="utf-8"?>
    <asmv1:assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv1="urn:schemas-microsoft-com:asm.v1"
    xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
    <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
    <security>
    <requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
    <requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
    </requestedPrivileges>
    </security>
    </trustInfo>
    </asmv1:assembly>

    App.Manifest

    1. Change the project settings for WindowsUI to use the newly created App.Manifest.
    Image

    Figure: Use the newly created App.Manifest

  • Do you use AutoWaitCursor on Windows applications?

    It can be extremely tiresome to have to continually remember to set and unset the wait cursor for an application. If an exception occurs you have to remember to add a try finally block to restore the cursor, or if you popup a message box you must remember to change the cursor first otherwise the user will just sit there thinking the application is busy.

    Image

    ❌ Figure: Bad example - Cursor set manually

    Image

    ✅ Figure: Good example - Implemented AutoWaitCursor

    AutoWaitCursor Class automatically monitors the state of an application and sets and restores the cursor according to whether the application is busy or not. All that required are a few lines of setup code and you are done. See this great blog on how to use AutoWaitCursor. If you have a multithreaded application, it won't change the cursor unless the main input thread is blocked. In fact, you can remove all of your cursor setting code everywhere!

  • Do you make your add/delete buttons crystal clear?

    You don't want someone hitting a delete button by accident. You don't want a use clicking delete expecting a record to be deleted and 10 are deleted.

    Aim to make your delete button red and add the count into button text, so the user will be empowered before hitting that fateful delete button.

    Image

    ❌ Figure: Bad example - The user wants to click 'OK' but there is not a 2nd check

    Image

    ✅ Figure: Good example - Apple got the delete button perfecton the iPhone. It is red + the count of the selected items is clear

    Image

    ✅ Figure: Good example - When adding multiple records, do a quick count of the selected items in DataGridView

  • Do you always set FirstDayOfWeek to Monday on a MonthCalendar?

    It is always good idea to set FirstDayOfWeek property to Monday to initialize it instead of leave it with the dafault value.

    Image

    ❌ Figure: Bad example - FirstDayOfWeek is default

    Image

    ✅ Figure: Good example - FirstDayOfWeek set to Monday

  • Do you always set ShowToday or ShowTodayCircle to true on a MonthCalendar?

    It is always good idea to set ShowToday or ShowTodayCircle to true to increase the user experience on MonthCalendar control.

    Image

    ❌ Figure: Bad example - ShowToday or ShowTodayCircle not set to true

    Image

    ✅ Figure: Good example - ShowToday or ShowTodayCircle set to true

  • Do you set PasswordChar to "*" on a TextBox on sensitive data?

    If you want to work with sensitive data on textboxes is always good practice to set PasswordChar to "*".

    Image

    ❌ Figure: Bad example - The user doesn't set PasswordChar to "*"

    Image

    ✅ Figure: Good example - The user set PasswordChar to "*"

  • Do you use Anchoring and Docking (full) for multiline textboxes?

    If you add a multiline textbox in a form, you should add anchoring and/or docking properties to allow it to expand when the form is resized.

    Image

    ❌ Figure: Bad example - Wrong settings in the designer

    Image

    ✅ Figure: Good example - Set Anchor property to Top, Bottom, Left, Right in the designer

    Image

    ❌ Figure: Bad example - Multiline textbox with the wrong anchoring and/or docking properties

    Image

    ✅ Figure: Good example - Multiline textbox with the correct anchoring and/or docking properties

    We have a program called SSW Code Auditor to check for this rule

  • Anchoring and Docking - Do you use Anchoring and Docking (horizontal only) with single line textboxes?

    If you add a text box in a form you should add anchoring and/or docking properties to allow it to grow as the form widens, but not as it increases in height.

    Image

    ❌ Figure: Bad example - Wrong settings in the designer

    Image

    ✅ Figure: Good example - Set Anchor property to Top, Bottom, Left, Right in the designer

    Image

    ❌ Figure: Bad example - Textbox with the wrong anchoring and/or docking properties

    Image

    ✅ Figure: Good example - Textbox with the correct anchoring and/or docking properties

    We have a program called SSW Code Auditor to check for this rule

  • Do you know TextAlign should be TopLeft or MiddleLeft?

    If you add a text box in a form you should add anchoring and/or docking properties to allow it to grow as the form widens, but not as it increases in height.

    Image

    ❌ Figure: Bad example - Wrong settings in the designer

    Image

    ❌ Figure: Bad example - Set Anchor property to Top, Bottom, Left, Right in the designer

    Image

    ✅ Figure: Good example - Textbox with the wrong anchoring and/or docking properties

    Image

    ✅ Figure: Good example - Textbox with the correct anchoring and/or docking properties

    We have a program called SSW Code Auditor to check for this rule