C# Design Patterns: The Decorator Pattern
The Decorator pattern provides us with a way to modify the behavior of individual objects without having to create a new derived class. Suppose we have a program that uses eight objects, but three of them need an additional feature. You could create a derived class for each of these objects, and in many cases this would be a perfectly acceptable solution. However, if each of these three objects requires different features, this would mean creating three derived classes. Further, if one of the classes has features of both of the other classes, you begin to create complexity that is both confusing and unnecessary.
For example, suppose we wanted to draw a special border around some of the buttons in a toolbar. If we created a new derived Button class, this means that all of the buttons in this new class would always have the same border, even if that isn’t what we want.
Instead, we create a Decorator class that decorates the buttons. Then we derive any number of specific Decorators from the main Decorator class, each of which performs a specific kind of decoration. In order to decorate a button, the Decorator has to be an object derived from the visual environment so it can receive paint method calls and forward calls to other useful graphic methods to the object it is decorating. This is another case where object containment is favored over object inheritance. The Decorator is a graphical object, but it contains the object it is decorating. It may intercept some graphical method calls, perform some additional computation, and pass them on to the underlying object it is decorating.
Decorating a CoolButton
Recent Windows applications such as Internet Explorer and Netscape Navigator have a row of flat, unbordered buttons that highlight themselves with outline borders when you move your mouse over them. Some Windows programmers call this toolbar a CoolBar and the buttons CoolButtons. There is no comparable button behavior in C# controls, but we can obtain that behavior by decorating a Panel and using it to contain a button. In this case, we decorate it by drawing black and white border lines to highlight the button or gray lines to remove the button borders.
Let’s consider how to create this Decorator. Design Patterns suggests that Decorators should be derived from some general visual component class, and then every message for the actual button should be forwarded from the Decorator. This is not all that practical in C#, but if we use containers as Decorators, all of the events are forwarded to the control being contained.
Design Patterns further suggests that classes such as Decorator should be abstract classes and that you should derive all of your actual working (or concrete) Decorators from the Abstract class. In our implementation, we define a Decorator interface that receives the mouse and paint events we need to intercept.
public interface Decorator { void mouseMove(object sender, MouseEventArgs e); void mouseEnter(object sender, EventArgs e); void mouseLeave(object sender, EventArgs e); void paint(object sender, PaintEventArgs e); }
For our actual implementation, we can derive a CoolDecorator from a Panel class and have it become the container that holds the button we are going to decorate.
Now, let’s look at how we could implement a CoolButton. All we really need to do is to draw the white and black lines around the button area when it is highlighted and draw gray lines when it is not. When a MouseMove is detected over the button, the next paint event should draw the highlighted lines, and when the mouse leaves the button area, the next paint event should draw outlines in gray. We do this by setting a mouse_over flag and then forcing a repaint by calling the Refresh method.
public void mouseMove(object sender, MouseEventArgs e){ mouse_over = true; } public void mouseEnter(object sender, EventArgs e){ mouse_over = true; this.Refresh (); } public void mouseLeave(object sender, EventArgs e){ mouse_over = false; this.Refresh (); }
This is the actual paint event.
public virtual void paint(object sender, PaintEventArgs e){ //draw over button to change its outline Graphics g = e.Graphics; const int d = 1; //draw over everything in gray first g.DrawRectangle(gPen, 0, 0, x2 - 1, y2 - 1); //draw black and white boundaries //if the mouse is over if( mouse_over) { g.DrawLine(bPen, 0, 0, x2 - d, 0); g.DrawLine(bPen, 0, 0, 0, y2 - 1); g.DrawLine(wPen, 0, y2 - d, x2 - d, y2 - d); g.DrawLine(wPen, x2 - d, 0, x2 - d, y2 - d); } }