• Recent Comments

    Implementing Renderers Into Your Custom Controls

    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.

    Leave a Comment