2009-08-12

Creating design-time compatible properties and events, part 1

We all create custom or user controls and classes all day and, of course, custom properties and events. Well, whenever you create you own control, it’s properties are automatically show in the property window at design time. But our properties don’t look the same as the built in ones for objects, right? Those have got categories, descriptions, lists with custom values, and so on. And for custom events, they don’t even show up!

In this two part article, I’ll be showing you how to make your properties and events more “developer friendly” (nice term, right?). In part 1, I’ll be talking about simple properties, and in part 2 we’ll get to events and custom lists.

This is what any built in property for, let’s say, a TextBox looks like:

 

We got:

  1. A category
  2. A custom list, which provides from an enumeration
  3. A description of what the property does

We can get that for our properties and events as well, so first, to continue with the example, let’s create or custom TextBox, which will basically be a class that inherits from the TextBox class, and add a couple of properties:

  1: public partial class NiceTextBox : TextBox
  2: {
  3:     public string SomeString { get; set; }
  4: 
  5:     public int SomeInt { get; set; }
  6: }

Nice. Compile and add a NiceTextBox control to the form. Our two very clever properties will show in the Properties Window, but they’ll be under the “Misc” category (meaning they don’t have a category at all), and they will have no comment:

  

In order to make our properties “look” that way, we need to use a concept called Attributes. Attributes provide a powerful method of associating declarative information with C# code (types, methods, properties, and so forth). In our case, these attributes will “kinda” represent the metadata used by the designer in the properties window.

So, to create a category for our couple of properties, will use the “Category” attribute, like this:

  1: [Category("Custom properties")]
  2: public string SomeString { get; set; }
  3: 
  4: [Category("Custom properties")]
  5: public int SomeInt { get; set; }

Here’s what we’ll get:

Of course, the category name, which is the one in double quotes, can be anything, and it doesn’t have to be the same for all properties.

Just as the Category attribute, we can get a lot of things by using more of them, here’s a few:

  • DefaultValue – Value to set for the property by default
  • Description – Description of what he property does
  • ReadOnly – Indicates if the property can be changed at design time in the properties window. It can still be changed via code though. By default, this attribute is false.
  • DisplayName – The name to show in the properties window
  • Browsable – Indicates if the property should be shown at all at design time in the properties window. When set to false, it won’t be visible, although you can still access it at runtime, in your code. By default, this attribute is true, except for events, which is by default false.

So, let’s elaborate our properties a bit more:

  1: [Category("Custom properties"), Description("Some string used")]
  2: public string SomeString { get; set; }
  3: 
  4: [Category("Custom properties")]
  5: [Description("Some number used in the NiceTextBox")]
  6: [ReadOnly(true)]
  7: [DisplayName("SomeNumber")]
  8: public int SomeInt { get; set; }

Notice that, in the first property, SomeString, we set all attributes in the same line, separating them by commas, and in the second property, SomeInt, we did it in different lines. Both approaches are valid. So, this is what we get:

As put in our attributes, we included a description for both properties. In addition, SomeInt property will be read-only at design time, and will be shown as SomeNumber.

Of course, there are a lot more attributes than the ones I listed, but I hope you get the general idea. In part 2 of this article, I’ll be showing you some more advanced features. See you then.

2009-08-05

The String.Format method

Believe it or not, one of the most powerful classes in the .NET Framework is the String class. Yep, you read it right, the String class. It has about ten millions uses, and every other class can be converted to one.

The String class also has some pretty cool methods: String.Contains and String.IsNullOrEmpty are two of my favorites, no to mention a whole other bunch of methods that every programmer uses on a a daily basis: Substring, CompareTo, IndexOf, Replace, and so on. The list is pretty huge, and they all have great uses. Specially the String.Format method, which is what I’ll talk about in this post.

Many .NET developers use String.Fomat, but from my experience, most of them only use it to concatenate strings in a more fancy, readable way. So, instead of writing this:

  1: string s = "Your name is, '" + name + "' and your age is " + age;

They’ll write this:

  1: string s = string.Format("Your name is '{0}' and your age is {1}", name, age);

What tat line says is “replace the {0} symbol with the first variable and the {1} symbol with the second variable” (and so on). Even though that’s a good start, and it’s a better solution performance-wise, there’s really a lot more we can get from the String.Format method. Take a look at this example (the dots at the beginning and end of the string are just for reference):

  1: string.Format(".{0,10}.", "right") //".     right."

Here we also have the {0} symbol, which will be replaced by the first variable, “right” in this case, but we’re also telling the method to fill with blank spaces the rest of the string until it reaches 10 characters long. The positive 10 indicates that the variable text should be placed to the right of those spaces. So if used a negative number, we’ll get the variable value to the left:

  1: string.Format(".{0,-10}.", "left"); // ".left      ."

We could also format numbers and dates, like in this examples:

  1: string.Format("{0:g}", DateTime.Now); //5/08/2009 04:02 PM
  2: string.Format("{0,-9:g5}", 3.14159); // "3.1416   "

In line 1, what we said was “give me the current date and time with a general (g) format”. In line 2, it was something like “give me the number 3.14159, formatted to deliver 5 decimal places (which include the decimal point, so ‘g5’ will give only 4 decimal numbers), and complete result, aligned to the right, with spaces to reach 9 characters”.

Is the above examples, the ‘g’ character is called a Formatting Specifier. with a number to the right of a formatting specifier, we specify the number of decimal places. Here are all the others:

Numeric formatting specifiers
  1: int x = 12345;
  2: string.Format("{0:g}", x); // 12345 (general)
  3: string.Format("{0:c}", x); // $12,345 (currency)
  4: string.Format("{0:e}", x); // 1.234500e+004 (scientific notation)
  5: string.Format("{0:f}", x); // 12345.00 (fixed-point)
  6: string.Format("{0:p}", x); // 12,345% (percent)
  7: string.Format("{0:r}", x); // 12345 (rounded)
  8: string.Format("{0:n}", x); // 12,345.00 (number with commas)
  9: string.Format("{0:d}", x); // 12345 (decimal)
 10: string.Format("{0:x}", x); // 3039 (hexadecimal)
Date and time formatting specifiers
  1: string a = DateTime.Now;
  2: string.Format("{0:d}", a); // 8/5/2009 (short date)
  3: string.Format("{0:D}", a); // Wednesday, 5 August 2009 (long date)
  4: string.Format("{0:f}", a); // Wednesday, 5 August 2009 04:35 PM (full short)
  5: string.Format("{0:F}", a); // Wednesday, 5 August 2009 04:35:17 PM (full long)
  6: string.Format("{0:g}", a); // 5/08/2009 04:35 PM (general)
  7: string.Format("{0:G}", a); // 5/08/2009 04:35:17 PM (general long)
  8: string.Format("{0:m}", a); // 5 August (month day)
  9: string.Format("{0:o}", a); // 2009-08-05T16:35:17.4687500-05:00 (round trip)
 10: string.Format("{0:R}", a); // Wed, 5 August 2009 16:35:17 GMT (RFC1123 pattern)
 11: string.Format("{0:s}", a); // 2009-08-5T16:35:17 (sortable)
 12: string.Format("{0:t}", a); // 04:35 PM (short time)
 13: string.Format("{0:T}", a); // 04:35:17 PM (long time)
 14: string.Format("{0:u}", a); // 2009-08-5 16:35:17Z (universal)
 15: string.Format("{0:U}", a); // Wednesday, 5 August 2009 4:35 PM (Universal GMT)
 16: string.Format("{0:Y}", a); // August 2009 (Year month)

Besides, you can always use custom formats for both numbers and dates:

  1: //Numbers
  2: "{0:0.0}" // 1234.6 (decimal point)
  3: "{0:0,0}" // 1,235 (thousands)
  4: "{0:0,.}" // 1 (number scaling)
  5: "{0:0%}" // 123456% (percent)
  6: "{0:00e+0}" // 12e+2 (scientific notation)
  7: //Dates an times
  8: "{0:MMM}" // Aug (month abbr)
  9: "{0:MMMM}" // August (month full)
 10: "{0:ss}" // 40 (seconds)
 11: "{0:tt}" // PM (am/pm)
 12: "{0:yy}" // 09 (year)
 13: "{0:yyyy}" // 2009 (year full)

A complete document about custom formats can de found here.

The final thing I want to talk about in the String.Format method is the conditional formatting. Let’s see:

  1: int x = 1;
  2: string.Format("{0:positive;negative;zero}", x);

The output of the String.Format depends on the value of the variable x. If the value is > 0, the output will be the word “positive”, which takes the first position after the “0:”. Obviously, if it’s < 0, it will display the word “negative”, and if it’s == 0, then it’ll show “zero”. Another example is:

  1: string.Format("{0:$#,##0.00;($#,##0.00);-}", x);

In this case, we’re not only formatting the number as a currency value, but also using parenthesis for negative values and a dash for zeroes.

So, getting to really know the String class, and in this particular case the String.Format method, is something that will open up a world of possibilities. Remember, most of the times the simplest things are the most important ones.