When I read James T. Johnson's article on the
IExtenderProvider, I found what I had been looking for: a way to implement Cascading Style Sheets (CSS), in Windows Forms. Whereas a full CSS implementation matching the HTML version would require lots of details, this article will focus only on the
TextBox controls. It is more of a proof of concept that such a thing can be done than a finished product. At a later date, I hope to release a fully functional Windows Forms CSS implementation. For now, most of the cooler features of CSS (such as the *cascading* and the varied element selectors) are left to the imagination of the coder.
Background and Resources
The reader should be familiar with C# programming and
IExtenderProvider. The latter can be studied from James T. Johnson's excellent article: Getting to know IExtenderProvider. I will be using James' structure in setting up my
IExtenderProvider. You do not need to be familiar with CSS stylesheets as the concepts I'm using are very basic. However, if you would like to extend this small application to aid in your Windows development, you should definitely reference W3's CSS and think carefully about how each concept can be best implemented with an
IExtenderProvider. Since this solution involves code running at Design time in Visual Studio, this may be of interest: Debugging design-time functionality.
Using the code
The Zip file includes the Solution called WindowsStyle. You must do the following to get the solution up and running:
- Extract the Zip called WindowsStyle.zip to c:\projects or your projects directory.
- Double click the solution to open it in Visual Studio.
- Build the solution (Ctrl+Shift+B).
- Find the stylesheet.xml file in the solution directory.
- Open up the form Form1.cs.
- Find the property called
Stylesheet and set it to the full path for stylesheet.xml above.
- If you can not find the
Stylesheet property of
Form1, you may need to add a
IExtenderProvider component to
- Right click on the Toolbox (get this by pressing Ctrl+Alt+X) and select Add/Remove Items...
- Click Browse, go to the bin directory and select WindowsStyle.exe.
- You will now have a
Style component you can drag from the toolbox to
- After doing so, set the
Stylesheet property of
Form1 as mentioned above.
- Click on any of the
TextBox controls or drag new ones to the form. Set their
CssClass property and watch them change.
- Notice that setting a property manually to something else and then running the code keeps the
CssClass specified appearance.
IExtenderProvider is implemented as a component (typical) and it provides the
CssClass property to controls and the
Stylesheet property to forms.
public class Style : System.ComponentModel.Component, IExtenderProvider
private Hashtable properties = new Hashtable();
Hashtable properties member holds pairs of (object) --> (properties). Here the object can be a control (
Form, etc.) and the
properties is an instance of class
private class Properties
public string CssClass;
public string Stylesheet;
CssClass = string.Empty;
Stylesheet = string.Empty;
This class is a wrapper for all the methods we want to provide to all the controls. It holds a
Stylesheet because our controls and forms will need these.
The only thing you have to define when implementing the
IExtenderProvider interface is the
CanExtend method. This method is called by the Designer to allow it to figure out what objects this
IExtenderProvider is providing extensions for. In our method, since a
Form is a child of
System.Windows.Forms.Control, all we need to extend is
public bool CanExtend(object extendee)
return extendee is System.Windows.Forms.Control;
So this has the side effect of giving every control that is on the same form with our
CssClass property. This is what we want, even though for now we will only support
Next, in order to actually provide properties, our
IExtenderProvider must implement
Set[property-name] methods for all properties it provides. These are actually needed to allow reflection to work its property extending magic. In our case, the methods are
[Description("Set this property to apply a class of Style to this Control")]
public string GetCssClass(System.Windows.Forms.Control c)
public void SetCssClass(System.Windows.Forms.Control c, string value)
EnsurePropertiesExists(c).CssClass = value;
if( value.Length < 1 )
switch( c.GetType().FullName )
I'll talk about the
CssClass property and you can check out the
Stylesheet property in the code. The
Get method is ensuring that the
CssClass property exists for the calling control and then returning it. If this property does not exist, the
Ensure method can handle errors. This is a common place for problems and I put a little error handling in here. Problems can occur because of controls trying to get properties they don't have.
Set method is a little more complicated. It starts out by setting the value of the property. If the value is the empty string, it then returns. However, if it is something substantial (i.e. an actual
CssClass), then it goes on. This method figures out what type of control is setting the
CssClass property and it calls an appropriate method to load that control's style from the stylesheet. Here is a straightforward way to handle
TextBox controls. A better algorithm can surely be devised, but since hindsight's 20/20 that's better left to hindsight. Onward then, with the
CssButton method. This actually attempts to load the specific
CssClass from the stylesheet and apply its properties to this
private void CssButton(object sender)
System.Windows.Forms.Button b = (System.Windows.Forms.Button)sender;
Hashtable style = GetStyle(b);
if( style == null ) return;
if( style["Width"] != null )
b.Width = int.Parse((style["Width"]).ToString());
if( style["Height"] != null )
b.Height = int.Parse((style["Height"]).ToString());
if( style["ForeColor"] != null )
b.ForeColor = System.Drawing.Color.FromName(style["ForeColor"].ToString());
if( style["BackColor"] != null )
b.BackColor = System.Drawing.Color.FromName(style["BackColor"].ToString());
if( style["FlatStyle"] != null )
switch( style["FlatStyle"].ToString() )
b.FlatStyle = FlatStyle.Standard;
b.FlatStyle = FlatStyle.Popup;
b.FlatStyle = FlatStyle.Flat;
b.FlatStyle = FlatStyle.System;
GetStyle method is key. We will talk about it below, but first the simple stuff. The returned
Hashtable style may or may not contain some properties. It will contain these properties if they were defined under this
CssClass class inside of the stylesheet. The
Button can then look at the
style Hashtable and ask it a bunch of questions like: Do you have
Width? If so, set the width of the button to the
Width it has. You can see that you can do cool stuff like set the
FlatStyle of the
Button. Some error handling around this code would eliminate design time errors popping up about incorrect formatting in the stylesheet itself (typing 1p for
Width instead of 10 would result in an
Now as promised, let's look at the code that loads the stylesheet itself and returns the properties that a particular control seeks under a particular
public Hashtable GetStyle( System.Windows.Forms.Control c )
System.Windows.Forms.Control parentForm = c.Parent;
while( parentForm != null && !(parentForm is System.Windows.Forms.Form) )
parentForm = parentForm.Parent;
if( parentForm == null ) return null;
string stylesheet = EnsurePropertiesExists(parentForm).Stylesheet;
if( stylesheet.Length < 1 || !File.Exists(stylesheet) ) return null;
XmlDocument x = new XmlDocument();
catch( IOException ex )
System.Diagnostics.Debug.Write("Error opening" +
" stylesheet document for "+c.Name+": "+ex.ToString());
string cssClass = EnsurePropertiesExists(c).CssClass;
XmlNodeList nodes = x.SelectNodes(string.Format("/stylesheet" +
if( nodes.Count < 1 )
EnsurePropertiesExists(c).CssClass = string.Empty;
throw new Exception(string.Format(
This style class does not exist or
does not have any properties",stylesheet,cssClass));
Hashtable style = new Hashtable();
foreach( XmlNode node in nodes )
style[node.Name] = node.InnerText.TrimEnd('\n','\r','\t',' ');
This works in a few steps. First it finds the
Form parent of the control. This may be a few levels up as a control can be in
Tabs or other sorts of containers. Then, it loads the stylesheet of the
Form parent. Notice that there's room here to load the stylesheets of all the parents and merge them together. This would allow for the cool "Cascading" feature of CSS, but is beyond the scope of this first article. If a stylesheet is successfully loaded as an XML document, get the
CssClass that this control is looking for. This is done using an XPath query. XPath is a wonderful query language and it is summarized nicely here: .NET and XML: Part 1—XPath Queries. Suffice it to say that this particular XPath query returns all the child nodes of the class with name
CssClass of the control in question. The
GetStyle method then packages all the properties defined in this class into a
Hashtable and returns this to whichever control was interested in asking in the first place.
CssButton method above uses this
GetStyle method and looks through the
Hashtable setting whatever properties it is compatible with. This happens at design time when a
CssClass property is set. The designer will call
SetCssClass and go through everything explained above. However, what happens at runtime? The important part of this is to have each control load its properties at runtime. While it is nice to see the controls the way they will look when you run the form, if you change something in the stylesheet, the control should not have to have its
CssClass property reset in order to propagate the changes. That would defeat the whole purpose of a centrally defined stylesheet. The way I made sure that controls load their
CssClass at runtime is to hook into the
Load event. This can be done inside the
SetStylesheet method. The
Form can iterate through its components and call the
CssWhatever methods to set the style at runtime:
public void SetStylesheet(System.Windows.Forms.Form f, string value)
f.Load += new EventHandler(CssFormLoad);
private void CssFormLoad(object sender, EventArgs e)
foreach( Control c in ((Form)sender).Controls )
if( EnsurePropertiesExists(c).CssClass.Length < 1 ) continue;
switch( c.GetType().FullName )
This replicates everything explained above but at runtime. Again, a better data structure certainly exists to take the place of the straightforward
switch and separate methods I made use of.
In closing, let's look at the format I picked for the stylesheet. Nothing fancy, stylesheet.xml file looks like this. I used it for the example illustrated at the top of this article. The image on the left is before I set any of the
CssClass properties. The image on the right is afterwards.
That's it. If any of that was confusing at all, please don't hesitate to drop me a line. This is my first try at this and I appreciate your patience for any unclear explanation.
Points of Interest
This was an eye opening project for me. With a more thorough implementation, you can save many man hours spent aligning, resizing, coloring and standardizing all sorts of controls on Windows Forms. With careful data structure organization, this can be extended to allow for layout manipulation and many other uses. Currently, I think XML may be a better format for the stylesheet itself. On the other hand, one could take advantage of Firefox's open source CSS engine to parse out a typical .css file for the stylesheet.
First submission. A basic proof of concept for applying style to Windows Forms from XML formatted stylesheets.