Click here to Skip to main content
Click here to Skip to main content

Automatic Dialogs in .NET

By , 13 Apr 2009
 

autodlg.jpg

Introduction

The following tutorial describes how to use the System.Attribute class for building dialogs automatically.

Background

For this tutorial, some knowledge on Reflection and the creation of Windows Forms in .NET is helpful.

Using the code

Assuming you have a class for storing data, e.g., a customer dataset, and you want to provide a dialog for user interaction. The method described in this tutorial gives you a way to provide a dialog simply without programming one, but by defining Attributes for every property of the class that you wish to be edited.

Creating automatic dialogs is simply a matter of identifying the need for specific controls of a class. This is followed by creating and arranging these controls on a Windows form.

As an example, I would like to discuss a class called "Customer", which defines the structure of a Customer dataset. If it has a property name of type string, it is safe to say, you would use a TextBox to display and modify its value. This also works for the DateTime data type and the DateTimePicker control, or bool and CheckBox.

A most simple version of an automatic dialog could use Reflection to identify the properties of a class and then create and arrange the controls depending on the data types of the properties. Reflection would also be used to invoke the get methods for filling the controls initially and the set methods for saving the modified fields. We want to use custom attributes to define the class properties a little more exactly than just through their data types.

Example: The Customer class with the properties:

  • ID (Integer)
  • Name (String)
  • Active (Boolean)
  • DateOfBirth (DateTime)

Now, we describe these properties a little further using an attribute called EditField:

public class Customer
{
    private int id;
    private string name;
    private DateTime dob;
    private bool active;

    [EditField("Custom #")]
    public int ID
    {
        get { return this.id; }
    }

    [EditField("Name")]
    public string Name
    {
        get { return this.name; }
        set { this.name = value; }
    }

    [EditField("Active")]
    public bool Active
    {
        get { return this.active; }
        set { this.active = value; }
    }

    [EditField("DoB")]
    public DateTime DateOfBirth
    {
        get { return this.dob; }
        set { this.dob = value; }
    }
}

In this tutorial, we only consider the possibility of labeling controls created on the dialog with a special name.

So, we use a simple attribute EditField with only one property, Name:

public class EditField : System.Attribute
{
    private string name;
    
    public EditField(string name)

    {
        this.name = name; 
    }
    
    public string Name
    {
        get { return this.name; }
    }
}

Now, we construct an object of a dialog class called DlgEdit. This dialog gets the type or an instance of Customer as the parameter.

It can now loop over the properties of the Customer class with following foreach-loop:

public DlgEdit(Type t)
{
    foreach(PropertyInfo info in t.GetProperties())
    { 
        //...

    }
}

The PropertyInfo object contains information of the data type, and most importantly, a method for reading custom attributes:

We can now loop over all the attributes using:

foreach (EditField editField in 
         info.GetCustomAttributes(typeof(EditField), true) 
{ 
    //...
}

For this simple example, we only need to create a control for the found data type of the property and a label with the text defined in editField.Name, which would be the parameter we delivered by defining the attributes in the Customer class. The PropertyInfo object also contains the information if you can read and write the property; that means, if you have defined get- or set-accessors. You can use this information to set the the readonly properties (or Enabled) of the created controls, if needed.

The control creation depending on the data type would result in a mapping of data types to control types. So, we would create a TextBox for the String type, and a DateTimePicker for the DateTime type. Since there is no control for Integer or Double data types, we would create a TextBox, but the way of reading and writing the content to and from the control would differ. In the last case, we have to call the int.Parse() method before reading from the TextBox and we would have to call the int.ToString() method before writing it as text to the TextBox.

Reading from an instance of Customer is a matter of using the .NET Reflection classes and the methods with the information of the PropertyInfo object.

object data = t.InvokeMember(info.Name, BindingFlags.GetProperty, 
                             null, this.dataset, null); 

TextBox tb = new TextBox();

tb.Text = (string) data;

Writing to an instance of Customer works the other way around:

t.InvokeMember(info.Name, BindingFlags.SetProperty, null, 
               this.dataset, new object[] { tb.Text }); 

We now need to arrange those controls on the DlgEdit object. Since this is only a matter of setting the Top, Left, Width, and Height properties of the controls and labels, and is up to the individual taste of the programmer, I will discuss this in another article.

This method is open for a lot more possibilities when it comes to customizing your dialog. For example, we might want to display a string not in a TextBox but in a RichTextBox because it is a long text, like a description. In the dialog I created, I have the possibility of grouping the controls in GroupBoxes and/or TabPages.

I also a Help attribute to define a short help text for my class and display it on demand in the DlgEdit form.

Points of interest

This way of creating dialogs has the advantage of saving a lot of time and making your application dialogs look familiar for different classes. Providing user interaction is much easier this way.

I will keep you posted on the development of attribute-based architectures here and on my German project page: http://www.eigene-software.de

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

lithium2709
Germany Germany
Member
IT Student

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralMy vote of 5memberHernán Hegykozi29 Oct '12 - 12:56 
Nice Article!
GeneralAttributes aren't localizablememberTiago Freitas Leal24 Feb '10 - 20:53 
Nice example but it's a pity there are no sources included.
 
The problem is Attributes aren't localizable.
GeneralAttributes ARE localizablememberMember 31458349 Jun '10 - 20:38 
Hi there,
 
recently I had to edit an application that was using attributes (esp. CategoryAttribute, DisplayNameAttribute, ... for PropertyGrid) and after searching a while I found a way to localize Attributes.
 
You can read my blog-entry or just have a look at the code I posted there:
http://marioschneider.blogspot.com/2009/10/c-net-35-lokalisiertes-propertygrid.html
 

You can do like that:
namespace LocalizeableDescriptionPropertyGrid
{
    public class Person
    {
        // Properties
        [LocalizedCategoryAttribute("PersonCatData"),
         LocalizedDescriptionAttribute("PersonFirstNameDescription"),
         LocalitedDisplayTextAttribute("PersonFirstNameDisplayText")]
        public String FirstName { get; set; }
        
        [LocalizedCategoryAttribute("PersonCatData"),
         LocalizedDescriptionAttribute("PersonLastNameDescription"),
         LocalitedDisplayTextAttribute("PersonLasttNameDisplayText")]
        public String LastName { get; set; }
        
        // other properties

        // ctor
        public Person(string fName, string lName)
        {
            this.FirstName = fName;
            this.LastName = lName;
        }
        
        // other methods ...
    }
}
 
The trick is to gather the localized data during runtime by gathering it directly from the Resource-Manager. Should also work for that code here.
 

Here is a sample-Code of a localizeable Attribute:
 
public class LocalizedDescriptionAttribute : DescriptionAttribute
{
 /// <summary>
 /// Contains the name of the resource-string
 /// </summary>
 private string rDescription;
 
 /// <summary>
 /// Creates a new LocalizedDescription Attribute instance
 /// giving it the name of the resource-string
 /// </summary>
 /// <param name="description"></param>
 public LocalizedDescriptionAttribute(string description)
 {
  this.rDescription = description;
 }
 
 /// <summary>
 /// (Overridden) Get: fetching the description during runtime
 /// from the Resources (with respect to the current culture)
 /// </summary>
 public override string Description
 {
  get
  {
   return Properties.Resources.ResourceManager.GetString(
    this.rDescription,
    Thread.CurrentThread.CurrentCulture
   );
  }
 }
}
 
Please consider using try-catch when accessing the ResourceManager.
GeneralAnother way to get-set values of propertiesmemberkaagle2 Jul '09 - 22:49 
It seems you can get and set values of proeprties by reflection by more convenient way: PropertyInfo class has methods SetValue and GetValue. So the code will look like:
object data = info.GetValue( this.dataset );
info.SetValue( this.dataset, t.Text ) ;

QuestionWhy a custom EditField attribute?memberSlingBlade21 Apr '09 - 2:33 
Why not just use the existing DisplayName, Description and Category attributes used by the PropertyGrid for your implementation? Good idea though. I've considered implementing something like this for awhile now but have never gotten around to it.
GeneralGreat startmemberChrisVaughan20 Apr '09 - 22:46 
It would be very good to see the full code. An interesting approach.
GeneralRe: Great startmemberStumproot21 Apr '09 - 0:56 
I agree.
A small complete example would be very appreciated.
NewsFilesmemberpablleaf13 Apr '09 - 7:24 
Please post source code and demo
GeneralMy vote of 2memberItay Sagui13 Apr '09 - 4:20 
This solution seems to me as though it's contradicting the single responsability concept - you have UI-related information in your BL objects...
It's also in complete opposite to the trend of separation between BL and UI...
GeneralRe: My vote of 2memberlithium270913 Apr '09 - 5:02 
Since there is no real implementation done by defining attributes in the class, there is no contradiction of the single responsibility principle as in merging different concerns in a class or similar. In fact this solution provides a way to define a common edit-dialog for all "simple" user interactions and so the differring parts of the programm are seperated so far, that you dont even have to worry about implementing the gui part.
 
The UI-information in the bl object is absolutely harmless, the object could be used in a completely different system, that doesnt work with these attributes. Attributes can be fully ignored if not needed. There no single Method, Property or Member in the object that relates to the GUI implementation
GeneralRe: My vote of 2memberEddy Vluggen22 Apr '09 - 0:10 
Itay Sagui wrote:
It's also in complete opposite to the trend of separation between BL and UI..

 
Following a trend is by no means an alternative to thinking. Deducing information from the DAL or BL for reuse in the UI doesn't mean that the two are tightly coupled.
 
--edit--
Good article btw! It would be nice to see a demo-application though Smile | :)
 
I are troll, and damn proud of it Cool | :cool:

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web03 | 2.6.130523.1 | Last Updated 13 Apr 2009
Article Copyright 2009 by lithium2709
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid