SSW Foursquare

Rules to Better Windows Forms Applications - 47 Rules

  1. 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

  2. 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.

    regenerate
    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.

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

    Use colours on incomplete is so useful in design time:

    • Red = Controls which are incomplete, e.g. An incomplete button
    • Yellow = 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.

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

  4. 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.

    Figure: Bad example - XP themes are not used

    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"

      setbuttonflatstyle
      Figure: How to set the Button's FlatStyle Property

    2. 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
    3. 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.

  5. 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 to come 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)

    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

    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.

  6. 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.

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

  7. 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

    Figure: Font Property Visible

    Figure: Font Property Hidden

  8. 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.

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

    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.

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

    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.

    usercontrolintabform
    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
  9. 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.

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

    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.

  10. 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: 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: 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.

    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).

    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

  11. 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...

    ✅ The pros of the 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.

    ✅ The 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.

    Microsoft's official word on this matter is explained in Designing Data Tier Components and Passing Data Through Tiers.  

    The Case for Business Objects

    At SSW we mostly choose datasets as we believe 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 recently 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.

  12. 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.

    doesntperformsowellwhenrunoveravpn2
    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.

    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));
    }
  13. 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.

  14. 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.

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

    Good example - Only visual elements in the designer

  15. 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.

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

    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: 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: 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: Bad example - Set controls' values in Code

  16. Do you avoid using MDI forms?

    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)

    vs net
    Figure: Bad example - VS.NET with tabs is cool for developers, but not for the average knowledge worker

    Figure: Bad example - Word 2003 in MDI mode

    ::: good

    sdiexample
    Figure: Good example - Word 2003 with Default Settings

    Me.IsMdiContainer = true;
    
    ClientForm frm = new ClientForm();
    frm.MdiParent = this;
    frm.Show();

    Figure: Bad code example - using MDI forms

    ClientForm frm = new ClientForm(); frm.Show();

    Figure: Good example - not using MDI

    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".

  17. 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?.

  18. 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.

    timepronetoptionsconnection 1711074081216
    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.

  19. 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.

    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

  20. 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.

  21. 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.

  22. 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

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

    Figure: Better example - Infragistics UltraGrid is better as you get combos

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

    Figure: The great new Visual Studio 2005 much improved DataGridView

  23. 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.

    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.

  24. 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.

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

    listview good
    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.

  25. 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.

    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:

    Figure: Bad example - Hyperlink

    Figure: Bad example - Hyperlink on a button

    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.

    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

  26. 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.

    acceptbuttonexample good
    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.

  27. 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.

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

    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.

    Figure: Developer Notes properties details

  28. Do you make common controls with consistent widths?

    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.

    Figure: Bad example - Control sizes are not consistent

    Figure: Good example - Control sizes are all standard and consistent

    Figure: Bad example - Non-standard size for Add & Delete buttons

    Figure: Good example - Standard size for Add & Delete buttons

    We have a program called SSW Code Auditor 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)
  29. 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.

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

    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
    2. HKEYCLASSESROOT\AppName\URL Protocol = ""
    3. HKEYCLASSESROOT\AppName\Default Value = "URL:Outlook Folders"
    4. HKEYCLASSESROOT\AppName\shell\Default Value = "open"
    5. HKEYCLASSESROOT\AppName\shell\open\command\Default Value = "Path\AssemblyName.exe /select %1"
    6. 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

  30. 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 ...
  31. 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

    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

    Figure: Good example - Responsive UI in progress

    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

  32. 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.

    textboxheight
    Figure: Text box displaying full file name

  33. 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

  34. 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.

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

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

  35. 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());
    }

    Bad example - Send mail without webservice

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

    Good example - Send mail use webservice

  36. 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

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

    datagridviewgood
    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

  37. 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. MSDN provides an article and an example(C++) on how to calling the Directory Object Picker dialog, and the CodePlex website gives 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);

    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);

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

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

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

  38. Do you set the ScrollBars property if the TextBox is Multiline?

    If a TextBox has Multiline set to true, then the ScrollBars property should be set to "Both" or at least "Vertical".

    Figure: Bad example - Multiline TextBox without "Vertical" scroll bar.

    Figure: Good example - Multiline TextBox with "Vertical" scroll bar

    Figure: Good example - Set the ScrollBars property to "Vertical" if the TextBox is Multiline

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

  39. 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.

    setmanifest
    Figure: Use the newly created App.Manifest

  40. 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.

    autowaitcursor bad
    Figure: Bad example - Cursor set manually

    autowaitcursor good
    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!

  41. 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.

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

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

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

  42. 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.

    Figure: Bad example - FirstDayOfWeek is default

    Figure: Good example - FirstDayOfWeek set to Monday

  43. 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.

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

    Figure: Good example - ShowToday or ShowTodayCircle set to true

  44. 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 "*".

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

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

  45. 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.

    wrongsettings
    Figure: Bad example - Wrong settings in the designer

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

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

    correctanchoringanddocking
    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

  46. 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.

    wrongsettings01
    Figure: Bad example - Wrong settings in the designer

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

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

    correctanchoringanddocking01
    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

  47. 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.

    textalign bad
    Figure: Bad example - Wrong settings in the designer

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

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

    textalignresult good
    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

We open source. Powered by GitHub