When I first played around with the new .net 2.0 controls, I was fascinated by the ToolStrip-Control and its endless possibilities to render it to your will by using the new Renderer property.
Having this kind of approach on any user-defined control would give the developer a powerful way of designing his application without forcing him to play around in the inner workings of the source.
So I decided to plunge into this topic and out came the little example you will see here.
(I’m also planning to recode most of the XPCommonControls to use this new Rendering approach)
How To
So I did some research on the topics of Renderers, RenderModes and other properties of the ToolStrip control and put together a little „recipe“ to cook up my own Rendering support for basically any user control.
Class Structure
If you want to support Rendering in your control you will have to build a lot of sattelite classes to get it working. In the following table I list all the classes I added for this little example (simply exchange the [Control] with your control’s name)
[Control]
Your UserControl that should be rendered.
You should split the control’s layout in multiple items so that the user must not alter the whole rendering but specific parts of it.
You will need a RenderMode property and a Renderer property of type [Control]Renderer that is set to the default rendering class (e.g. [Control]SystemRenderer.
You will then have to override the OnPaint method and call every Draw[Item] of the base renderer.
Protected Overrides Sub OnPaint( ByVal e As System.Windows.Forms.PaintEventArgs)
MyBase.OnPaint(e)
e.Graphics.SmoothingMode = Drawing2D.SmoothingMode.HighQuality
_renderer.DrawBackground( New BoxControlRenderEventArgs(e.Graphics, Me))
_renderer.DrawBorder( New BoxControlRenderEventArgs(e.Graphics, Me))
End Sub
[Control]RenderMode
An Enumeration with all Renderers your control will support „out of the box“. This should usually include „System“, „Professional“ and „Custom“, whereas the „Custom“ mode should not be set manually
[Control]Renderer
The base class for all Renderers of your control. You should set it to „MustInherit“ because it usually does not render anything. It does include all methods and events that are called from your Paint method and can be overwritten by inherited classes.
For each item of your control that should be rendered individually (like background, borders, texts, ...) you will have to include the following methods and events:
Public Delegate Sub Render[Item]Handler( ByVal sender As Object, ByVal e As [Control]RenderEventArgs)
Public Event RenderBorder As RenderBorderHandler
Public Overridable Sub Draw[Item]( ByVal e As [Control]RenderEventArgs)
Call OnRender [Item] (e)
End Sub
Protected Overridable Sub OnRender[Item]( ByVal e As [Control]RenderEventArgs)
RaiseEvent Render[Item]( Me, e)
End Sub
By doing so, you or any other developer will be able to alter the rendering behaviour by simply overriding the Draw[Item] method.
[Control]SystemRenderer
This class inherits from the [Control]Renderer and overrides every Draw[Item] method to implement a XP-like rendering of the control.
[Control]ProfessionalRenderer
This class inherits from the [Control]Renderer and overrides every Draw[Item] method to implement a Office-like rendering of the control.
[Control]RenderEventArgs
This class holds all the information that the renderer classes need to render the control, most noteably the following properties:
AffectedBounds
a rectangle that defines the space where the renderer can draw into.
Graphics
the graphics reference from the controls OnPaint method
[Control]
a reference to the instance of the control itself
It is possible that you will have to implement multiple [Control]RenderEventArgs to cover all the specific needs of the rendering method of the item (e.g. a text to be drawn...)
An Example
I put together a simple example to show you how it can be done. I chose a simple rectangle control that has a border and a linear gradient as background.
It will support a system renderer that uses the new theming capacities of .net 2.0, a ProfessionalRenderer, that uses the Office style and a userdefined renderer, just to show how you can override the standard behaviour.
The BoxControlClass
This is the user control itself. A very simple control that displays a border and a background. Different to a regular approach, the BoxControlClass has no idea how this border and background will look like. This is the work of the Renderers.
The BoxControl has two items, the border and the background:
The BoxControlRenderMode Enumeration
This enumeration holds all the standard renderers that are provided with this control. Currently this is System and Professional. The Custom value is needed only for referring to a derived renderer class.
The BoxControlRenderer Class
This class is declared as MustInherit and is the base class for all renderers that can be attached to the BoxControl class. It implements the drawing methods for any part and its events. Here is an example of the background part declarations:
Public Delegate Sub RenderBackgroundHandler(ByVal sender As Object, ByVal e As BoxControlRenderEventArgs)
Public Event RenderBackground As RenderBackgroundHandler
''' <summary>
''' the entry point for the BoxControl painting calls.
''' </summary>
''' <param name="e">an EventArgs class that holds all the information
''' needed for drawing the class</param>
''' <remarks></remarks>
Public Overridable Sub DrawBackground(ByVal e As BoxControlRenderEventArgs)
Call OnRenderBackground(e)
End Sub
Protected Overridable Sub OnRenderBackground(ByVal e As BoxControlRenderEventArgs)
RaiseEvent RenderBackground(Me, e)
End Sub
The BoxControlSystemRenderer Class
This is the derived class of the standard renderer that provides system rendering services (it draws with the XP theming info). As you see, I did not overwrite the OnRenderBorder method because this renderer will not use any borders
Imports System.Windows.Forms.VisualStyles
Public Class BoxControlSystemRenderer
Inherits BoxControlRenderer
Private renderer As VisualStyleRenderer = Nothing
Private element As VisualStyleElement
''' <summary>
''' overriding this method to draw a custom background.
''' </summary>
''' <param name="e"></param>
''' <remarks></remarks>
Protected Overrides Sub OnRenderBackground(ByVal e As BoxControlRenderEventArgs)
MyBase.OnRenderBackground(e)
element = VisualStyleElement.Tab.Pane.Normal
If Application.RenderWithVisualStyles AndAlso _
VisualStyleRenderer.IsElementDefined(element) Then
renderer = New VisualStyleRenderer(element)
renderer.DrawBackground(e.Graphics, e.AffectedBounds)
Else
e.Graphics.FillRectangle(System.Drawing.SystemBrushes.Window, e.AffectedBounds)
End If
End Sub
End Class
The BoxControlProfessionalRenderer Class
This is the derived class of the standard renderer that provides professional rendering services (it tries to immitate the current MS Office styling). This class overrides both rendering methods to draw a custom background and a custom border
Imports System.Drawing
Imports System.Drawing.Drawing2D
Public Class BoxControlProfessionalRenderer
Inherits BoxControlRenderer
Private rect As Rectangle
Private bgBrush As LinearGradientBrush
Private linePen As Pen
''' <summary>
''' overriding the background rendering to draw a gradient filled rectangle
''' </summary>
''' <param name="e"></param>
''' <remarks></remarks>
Protected Overrides Sub OnRenderBackground(ByVal e As BoxControlRenderEventArgs)
MyBase.OnRenderBackground(e)
rect = e.AffectedBounds
rect.Inflate(-1, -1)
bgBrush = New LinearGradientBrush(rect, SystemColors.GradientInactiveCaption, SystemColors.InactiveCaption, LinearGradientMode.Vertical)
e.Graphics.FillRectangle(bgBrush, rect)
bgBrush.Dispose()
End Sub
''' <summary>
''' overriding the border rendering to draw a simple line
''' </summary>
''' <param name="e"></param>
''' <remarks></remarks>
Protected Overrides Sub OnRenderBorder(ByVal e As BoxControlRenderEventArgs)
MyBase.OnRenderBorder(e)
rect = e.AffectedBounds
rect.Inflate(-1, -1)
linePen = New Pen(SystemColors.ActiveCaption)
linePen.Width = 1
e.Graphics.DrawRectangle(linePen, rect)
linePen.Dispose()
End Sub
End Class
The BoxControlUserRenderer
This class also inherits from the baser BoxControlRenderer. But since it’s not included as a RenderMode you can only use it with setting the BoxControl’s Renderer property. This class hase some special features that allows you to set the background colors, border widths and so on at runtime to alter it’s rendering behavior.
Imports System.Drawing
Imports System.Drawing.Drawing2D
''' <summary>
''' this class provides a special rendering to the BoxControl
''' where you can set colors and properties at runtime
''' </summary>
''' <remarks></remarks>
Public Class BoxControlUserRenderer
Inherits BoxControlRenderer
Private bgBrush As Brush = SystemBrushes.ControlLight
Private linePen As Pen = SystemPens.ControlLightLight
Private rndCorners As Corner = Corner.All
Private radius As Integer = 5
Private rect As Rectangle
''' <summary>
''' the background brush with which the renderer draws the background part
''' you can set this property to a simple Brush or a gradient brush
''' </summary>
''' <value></value>
''' <returns></returns>
''' <remarks></remarks>
Public Property BackgroundBrush() As Brush
Get
Return bgBrush
End Get
Set(ByVal value As Brush)
bgBrush = value
End Set
End Property
''' <summary>
''' the pen with which the border around the control is drawn.
''' Using a pen will allow you to set the border's width, dotted lines
''' and other properties
''' </summary>
''' <value></value>
''' <returns></returns>
''' <remarks></remarks>
Public Property BorderPen() As Pen
Get
Return linePen
End Get
Set(ByVal value As Pen)
linePen = value
End Set
End Property
''' <summary>
''' this property allows you to specify all or some corners that will be
''' drawn with a rounded effect
''' </summary>
''' <value></value>
''' <returns></returns>
''' <remarks></remarks>
Public Property RoundedCorners() As Corner
Get
Return rndCorners
End Get
Set(ByVal value As Corner)
rndCorners = value
End Set
End Property
''' <summary>
''' the corner radius (in points) of the rounded edges.
''' </summary>
''' <value></value>
''' <returns></returns>
''' <remarks></remarks>
Public Property CornerRadius() As Integer
Get
Return radius
End Get
Set(ByVal value As Integer)
radius = value
End Set
End Property
Protected Overrides Sub OnRenderBackground(ByVal e As BoxControlRenderEventArgs)
MyBase.OnRenderBackground(e)
rect = e.AffectedBounds
rect.Inflate(linePen.Width * (-1), linePen.Width * (-1))
e.Graphics.FillPath(bgBrush, DrawingHelperClass.RoundedRect(rect, radius, rndCorners))
End Sub
Protected Overrides Sub OnRenderBorder(ByVal e As BoxControlRenderEventArgs)
MyBase.OnRenderBorder(e)
rect = e.AffectedBounds
rect.Inflate(linePen.Width * (-1), linePen.Width * (-1))
e.Graphics.DrawPath(linePen, DrawingHelperClass.RoundedRect(rect, radius, rndCorners))
End Sub
End Class
BoxControlRenderEventArgs
This class holds all the info needed for drawing the border and the background in the renderer classes. The EventArgs are pushed to the renderer in the OnPaint method of the BoxControl class
Public Class BoxControlRenderEventArgs
Inherits System.EventArgs
Private _affectedBounds As Rectangle
Private _graphics As Graphics
Private _box As BoxControl
Public Sub New(ByVal g As Graphics, ByVal box As BoxControl)
_graphics = g
_box = box
End Sub
Public ReadOnly Property AffectedBounds() As Rectangle
Get
Return New Rectangle(_box.Padding.Left, _
_box.Padding.Top, _
_box.Width - _box.Padding.Left - _box.Padding.Right, _
_box.Height - _box.Padding.Top - _box.Padding.Bottom)
End Get
End Property
Public ReadOnly Property Graphics() As Graphics
Get
Return _graphics
End Get
End Property
End Class
The Test Application
The Test Application allows you to apply multiple renderers to the BoxControl on the left. If you choose the Userdefined Renderer, you will be allowed to customize the rendering of the BoxControl further. Change the colors by double-clicking on the color-fields
Conclusion
The Renderer-Approach is a very nice but labour-intensive way of allowing your controls to be rendered by custom code. I will certainly implement this for most of the XPCommonControls.
Because I don’t want to break compatibility with the current release, I’m planning to create a new library with rendered controls only.
Currently rated 4.0 by 1 people
- Currently 4/5 Stars.
- 1
- 2
- 3
- 4
- 5