Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

VML Web Controls

0.00/5 (No votes)
15 Dec 2005 1  
VML drawing controls for ASP.NET web forms.

VML Drawing Controls

Introduction

My background as a Geographer/GIS guru often involves developing web sites for interactive mapping on a variety of topics. One of our requirements include abilities to create vector graphics on the client side for functions such as zoom boxes, measurement tools, shapes (points, lines, polygons), and other things common to desktop Geographical Information Systems. Many of our clients have strict guidelines that include developing applications which do not require plug-ins and/or downloads. I have found VML to be a perfect solution for this requirement and have developed VML drawing controls to enable drag and drop tools for the creation of client side line, polyline, polygon, rectangle and round rectangle graphics to automate this task. In this article, we will take a look at the VMLControl base class and then the VMLPolygon web control as an example of these VML drawing controls.

* Please note, although there are no downloads or plug-ins required, an Internet Explorer web browser is.

Background

For more background information about VML, please see Simon Stewart's article Introduction to VML and Microsoft's VML Reference. I would also like to extend a special thanks to Sreenivas Vemulapalli whose articles on Using the Property Grid helped these controls take shape.

VML namespace reference: the use of VML requires the following tag usage to be included in the HTML document:

<HTML xmlns:v="urn:schemas-microsoft-com:vml">
    <HEAD>
        <style>v\:* { BEHAVIOR: url(#default#VML) }
        </style>
    </HEAD>
    ....

Using the code

These controls should be used like any other .NET web control and can be added to your project through the Add/Remove Toolbox Items... under the Tools menu in the Visual Studio IDE.

The VMLDrawingControls comprises of five shape control classes that are derived from the VMLControl base class.

  1. VMLLine
  2. VMLPolyline
  3. VMLPolygon
  4. VMLRectangle
  5. VMLRoundRectangle (inherits from VMLRectangle)

VML Designer Controls

VMLControl

The MustInherit VMLControl base class provides the framework for the controls and includes properties for:

  • ID
  • ButtonType [Button | Image]
  • ButtonText
  • ImageSrc, ImageSrcMouseDown, ImageSourceMouseOver
  • CursorStyle, CustomCursor, CustomCursorEnabled
  • BoundingDrawCanvas

The BoundingDrawCanvas is the required property which holds the ID of the image control that will be used as the bounding drawing canvas for the vector graphics. I used the AddAttributesToRender overrides of the System.Web.UI.WebControls.WebControl to warn the user of this requirement. Once the requirement has been met in the control's properties, the warning will go away.

Protected Overrides Sub AddAttributesToRender(ByVal _
                    output As System.Web.UI.HtmlTextWriter)

    'Warn User Of Control Requirements

    If Me.BoundingDrawCanvas = "" Or _
          Me.BoundingDrawCanvas = "(none)" Then
        output.Write("<BR>")
        output.Write("<Font color='Red'>" & _ 
               "<li>BoundingDrawCanvas Property Is Required</Font>")
    Else
        If Page.FindControl(Me.BoundingDrawCanvas) Is Nothing Then
            output.Write("<BR>")
            output.Write("<Font color='Red'>" & _ 
                   "<li>BoundingDrawCanvas Control ID" & _ 
                   " Cannot Be Found</Font>")
        End If
    End If

    MyBase.AddAttributesToRender(output)

End Sub

* Code Source: VMLControl.vb

BoundingDrawCanvas Warning

The following is the BoundingDrawCanvas property from the VMLControl base class that is used in all of the derived VML shape classes.

<Category("Appearance"), _
Description("The Control ID Of The Image Control" & _ 
            " Which Defines The Bounding Draw Canvas"), _
TypeConverter(GetType(BoundingControlsConverter)), _
PersistenceMode(PersistenceMode.Attribute)> _
Public Property BoundingDrawCanvas() As String
    Get
        _BoundingDrawCanvas = _
           CType(ViewState("VMLBoundingDrawCanvas"), String)
        If _BoundingDrawCanvas Is Nothing Then
            Return "(none)"
        Else
            Return _BoundingDrawCanvas
        End If
    End Get
    Set(ByVal Value As String)
        ViewState("VMLBoundingDrawCanvas") = Value
        _BoundingDrawCanvas = Value
    End Set
End Property

*Code Source: VMLControl.vb

I created a BoundingControlsConverter for this property to list out the current image controls on the web form. Due to the nature of my work, I use the image web control (which contains my map image) as my drawing canvas. The GetStandardValues override from the base class System.ComponentModel.TypeConverter allows me to retrieve the IDs of the image controls for the property dropdown list. Using the Context.Container.Components passed in, allows me to loop the components of the page to identify image controls to use for the canvas. A future modification will be to accommodate table cells, divs, and other container tags.

Public Overloads Overrides Function GetStandardValues(ByVal context As _
       System.ComponentModel.ITypeDescriptorContext) As _
       System.ComponentModel.TypeConverter.StandardValuesCollection

    Dim supportedCtrls As New ArrayList
    Dim Component As IComponent

    'Supported Control Types

    Dim wcImg As WebControls.Image

    For Each Component In context.Container.Components
        If TypeOf Component Is WebControls.Image Then
            wcImg = CType(Component, WebControls.Image)
            supportedCtrls.Add(wcImg.ID)
        End If
    Next

    'Sort the list

    supportedCtrls.Sort()

    'return values to be listed in the property browser's dropdown list

    Dim svc As New _
      StandardValuesCollection(CType(supportedCtrls.ToArray(GetType(String)), _
      String()))

    Return svc

End Function

*Code Source: BoundingControlsConverter.vb

VML Styles

I created three VML style classes that were used as properties for the drawing control classes to define things like color, width, style, fill patterns, etc.

  • VMLLineStyle
  • VMLFillStyle
  • LineEndPointProperties (provides From and To point styles)

VML Style Properties

For example, the VMLPolygon control has the FillStyle property of type VMLFillStyle which defines the color, pattern, etc. for the polygon to be rendered. The ExpandableObjectConverter was used to make the property expandable in the property grid similar to the Font property of other controls.

<Category("VML Symbolization"), _
Description("Defines The VML Fill Style Properties To Be Used"), _
DesignerSerializationVisibility(DesignerSerializationVisibility.Content), _
TypeConverter(GetType(ExpandableObjectConverter))> _
Public Property FillStyle() As VMLFillStyle
    Get
        Return _FillStyle
    End Get
    Set(ByVal Value As VMLFillStyle)
        _FillStyle = Value
    End Set
End Property

*Code Source: VMLPolygon.vb

This is the VMLFillStyle class:

Imports System.ComponentModel
Imports System.Web.UI
Imports System.Drawing.Design
<Serializable(), PersistenceMode(PersistenceMode.Attribute)> _
Public Class VMLFillStyle
    Enum FillTypeEnum
        Solid
        Gradient
        GradientRadial
        Tile
        Pattern
        Frame
    End Enum
    Private _FillType As FillTypeEnum = FillTypeEnum.Solid
    Private _FillColor As System.Drawing.Color = Nothing
    Private _FillColor2 As System.Drawing.Color = Nothing
    Private _FillOpacity As Int16 = 100
    Private _FillOpacity2 As Int16 = 100
    Sub New()
        'Class Constructor

    End Sub
    <Category("Rectangle Fill Appearance"), _
    Description("Defines The Type Of Fill To Apply"), _
    NotifyParentProperty(True)> _
    Public Property FillType() As FillTypeEnum
        Get
            Return _FillType
        End Get
        Set(ByVal Value As FillTypeEnum)
            _FillType = Value
        End Set
    End Property
    <Category("Rectangle Fill Appearance"), _
    Description("Defines The Primary Fill Color"), _
    NotifyParentProperty(True)> _
    Property FillColor() As System.Drawing.Color
        Get
            Return _FillColor
        End Get
        Set(ByVal Value As System.Drawing.Color)
            _FillColor = Value
        End Set
    End Property
    <Category("Rectangle Fill Appearance"), _
    Description("Defines The Secondary Fill Color"), _
    NotifyParentProperty(True)> _
    Property FillColor2() As System.Drawing.Color
        Get
            Return _FillColor2
        End Get
        Set(ByVal Value As System.Drawing.Color)
            _FillColor2 = Value
        End Set
    End Property
    <Category("Rectangle Fill Appearance"), _
    Description("Defines The Opacity Of The Primary Color"), _
    NotifyParentProperty(True)> _
    Property FillOpacity() As Int16
        Get
            Return _FillOpacity
        End Get
        Set(ByVal Value As Int16)
            If ((Value < 0 Or Value > 100)) Then
                Throw New ArgumentException("The Opacity" & _ 
                              " Must Be Beteen 0 and 100")
            End If
            _FillOpacity = Value
        End Set
    End Property
    <Category("Rectangle Fill Appearance"), _
    Description("Defines The Opacity Of The Secondary Color"), _
    NotifyParentProperty(True)> _
    Property FillOpacity2() As Int16
        Get
            Return _FillOpacity2
        End Get
        Set(ByVal Value As Int16)
            If ((Value < 0 Or Value > 100)) Then
                Throw New ArgumentException("The Opacity2" & _ 
                               " Must Be Beteen 0 and 100")
            End If
            _FillOpacity2 = Value
        End Set
    End Property
    <DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)> _
    Public ReadOnly Property FillTag() As String
        Get
            Dim retFillTag As New System.Text.StringBuilder
            retFillTag.Append("<v:fill ")
            retFillTag.Append(" Type=" & Quote(Me.FillType.ToString))
            retFillTag.Append(" Color=" & _
               Quote(HelperFunctions.BuildRGBString(Me.FillColor)))
            retFillTag.Append(" Color2=" & _
               Quote(HelperFunctions.BuildRGBString(Me.FillColor2)))
            retFillTag.Append(" Opacity=" & _
               Quote(FillOpacity.ToString & "%"))
            retFillTag.Append(" Opacity2=" & _
               Quote(Me.FillOpacity2.ToString & "%"))
            retFillTag.Append(" />")

            Return retFillTag.ToString
        End Get
    End Property
    <Description("Define The VML Fill Properties")> _
    Overrides Function ToString() As String
        Return "(VML Fill Style)"
    End Function
End Class

*Code Source: VMLFillStyle.vb

A module named HelperFunctions was created to extend these controls. The ReadJavascript procedure was created to read the JavaScript source file to manipulate the VML graphics on the client side as a resource of the assembly and the contents written to the out-stream. Using this method, I can keep all of the JavaScript in a single .js file and modify it as needed and then simply rebuild the assembly. There is also a property in the VMLControl base class which I added to reference an external .js file but never thoroughly implemented it.

Public Function ReadJavasript() As String

    Dim jsStream As IO.Stream
    jsStream = System.Reflection.Assembly.GetExecutingAssembly()._
               GetManifestResourceStream("VMLDrawingControls" & _ 
                                         ".VMLClientJavascript.js")

    Dim jsStreamReader As New StreamReader(jsStream)

    Return jsStreamReader.ReadToEnd

End Function

*Code Source: HelperFunctions.vb

Protected Overrides Sub Render(ByVal output As _
                    System.Web.UI.HtmlTextWriter)

    'Stream the Javascript

    Page.RegisterStartupScript("VMLScript", _
         "<script language=""javascript""> " & _
         HelperFunctions.ReadJavasript & " </script>")

    MyBase.Render(output)
End Sub

*Code Source: VMLPolygon.vb

VMLPolygon

The VMLPolygon is an example of the VMLDrawingControls. This controls inherits from the VMLControl base class and has methods and properties to define the look and feel of the rendered polygon. As shown above, the control uses the VMLFillStyle to define the fill properties and it all uses the VMLLineStyle to define the outline properties of the polygon.

Imports System.web.UI.HtmlTextWriter
Imports System.Web.UI
Imports System.Web.UI.Design
Imports System.ComponentModel
Imports System.Drawing.Design
Imports System.Drawing
Imports System.Design
Imports System.Windows.Forms
<Designer(GetType(VMLControlDesigner)), _
ToolboxData("<{0}:VMLPolygon runat="server"></{0}:VMLPolygon>")> _
Public Class VMLPolygon
    Inherits VMLControl
    Implements INamingContainer
    Sub New()
        MyBase.ButtonText = "Draw Polygon"
    End Sub
    Private _LineStyle As New VMLLineStyle
    Private _FillStyleEnabled As Boolean = False
    Private _FillStyle As New VMLFillStyle
    <Category("Appearance"), _
    Description("Defines The Line Stroke Style"), _
    DesignerSerializationVisibility(DesignerSerializationVisibility.Content), _
    TypeConverter(GetType(ExpandableObjectConverter))> _
    Public Property LineStyle() As VMLLineStyle
        Get
            Return _LineStyle
        End Get
        Set(ByVal Value As VMLLineStyle)
            _LineStyle = Value
        End Set
    End Property
    <Category("VML Symbolization"), _
    Description("Enables The Use Of A FillStyle")> _
    Public Property FillStyleEnabled() As Boolean
        Get
            Return _FillStyleEnabled
        End Get
        Set(ByVal Value As Boolean)
            _FillStyleEnabled = Value
        End Set
    End Property
    <Category("VML Symbolization"), _
    Description("Defines The VML Fill Style Properties To Be Used"), _
    DesignerSerializationVisibility(DesignerSerializationVisibility.Content), _
    TypeConverter(GetType(ExpandableObjectConverter))> _
    Public Property FillStyle() As VMLFillStyle
        Get
            Return _FillStyle
        End Get
        Set(ByVal Value As VMLFillStyle)
            _FillStyle = Value
        End Set
    End Property
    Protected Overrides Sub Render(ByVal output As _
                        System.Web.UI.HtmlTextWriter)

        'Stream the Javascript

        Page.RegisterStartupScript("VMLScript", _
             "<script language=""javascript""> " & _
             HelperFunctions.ReadJavasript & " </script>")

        MyBase.Render(output)
    End Sub
    Protected Overrides Sub AddAttributesToRender(ByVal output _
                                As System.Web.UI.HtmlTextWriter)

        Dim cursorString As String
        If Me.CustomCursorEnabled Then
            cursorString = "url(" & Me.CustomCursor.ToString.ToLower & ")"
        Else
            cursorString = Me.CursorStyle.ToString
        End If

        Select Case Me.ButtonType
            Case ButtonTypeEnum.Button
                output.AddAttribute(HtmlTextWriterAttribute.Type, "button")
                output.AddAttribute(HtmlTextWriterAttribute.Onclick, _
                       "activate('" & Me.ID & "','" & Me.BoundingDrawCanvas & _
                       "','POLYGON','" & cursorString & "')")
                output.AddAttribute(HtmlTextWriterAttribute.Value, Me.ButtonText)
            Case ButtonTypeEnum.Image
                output.AddAttribute(HtmlTextWriterAttribute.Type, "image")
                output.AddAttribute(HtmlTextWriterAttribute.Onclick, _
                       "activate('" & Me.ID & "','" & Me.BoundingDrawCanvas & _
                       "','POLYGON','" & cursorString & "'); return false;")
                output.AddAttribute(HtmlTextWriterAttribute.Src, _
                                   Me.ImageSrc.Replace("\", "/"))
                output.AddAttribute("onmouseout", "this.src='" & _

                       Me.ImageSrc.Replace("\", "/") & "';")


                'Add Mouseover & MouseDown Images

                If Not Me.ImageSrcMouseOver.Equals(String.Empty) Then
                    output.AddAttribute("onmouseover", "this.src='" & _
                           Me.ImageSrcMouseOver.Replace("\", "/") & "';")

                End If

                If Not Me.ImageSrcMouseDown.Equals(String.Empty) Then
                    output.AddAttribute("onmousedown", _
                           "return false; this.src='" & _
                           Me.ImageSrcMouseDown.Replace("\", "/") & "';")

                End If
        End Select

        MyBase.AddAttributesToRender(output)
    End Sub
    Protected Overrides Sub RenderChildren(ByVal output As _
                            System.Web.UI.HtmlTextWriter)

        'Create The VML Tag

        Dim outTags As New System.Text.StringBuilder
        outTags.Append("<v:polyline ID=" & Quote(Me.ID) & " ")

        Dim cursorString As String
        If Me.CustomCursorEnabled Then
            cursorString = Me.CustomCursor
            outTags.Append("style=" & DoubleQuoteChar & _
                           "z-index: 1002;cursor: url('" & _
                           cursorString & "');" & DoubleQuoteChar & " >")
        Else
            cursorString = Me.CursorStyle.ToString
            outTags.Append("style='cursor: " & cursorString & ";z-index: 1002;'>")
        End If

        outTags.Append(Me.LineStyle.StrokeTag)

        If Me.FillStyleEnabled Then
            outTags.Append(Me.FillStyle.FillTag)
        End If

        outTags.Append("</v:polyline>")

        output.Write(outTags.ToString)

    End Sub
End Class

*Code Source: VMLPolygon.vb

Putting It All Together

With the properties assigned, we can use the RenderChildren of our shape classes to write out the VML tags to the browser. The ID assigned in the control properties is used by the client-side script to dynamically modify the VML shape properties.

Protected Overrides Sub RenderChildren(ByVal output As System.Web.UI.HtmlTextWriter)

    'Create The VML Tag

    Dim outTags As New System.Text.StringBuilder
    outTags.Append("<v:polyline ID=" & Quote(Me.ID) & " ")

    Dim cursorString As String
    If Me.CustomCursorEnabled Then
        cursorString = Me.CustomCursor
        outTags.Append("style=" & DoubleQuoteChar & _
                "z-index: 1002;cursor: url('" & _
                cursorString & "');" & DoubleQuoteChar & " >")
    Else
        cursorString = Me.CursorStyle.ToString
        outTags.Append("style='cursor: " & _
                cursorString & ";z-index: 1002;'>")
    End If

    outTags.Append(Me.LineStyle.StrokeTag)

    If Me.FillStyleEnabled Then
        outTags.Append(Me.FillStyle.FillTag)
    End If

    outTags.Append("</v:polyline>")

    output.Write(outTags.ToString)

End Sub

*Code Source: VMLPolygon.vb

Points of Interest

One of the issues I ran into while creating these controls is the ability or inability to persist the control properties to the designer. I found the Visual Studio IDE to sometime not update or persist these properties during the building phases of these controls and seemed to resolve itself upon a restart of the IDE. I believe these problems to be resolved with newer releases of the IDE.

Future Enhancements

I hope to enhance these controls in the future to have a VML control for general shape, text, arc, oval, image and other VML elements. I would also like to implement a method for returning the coordinates of the shapes to the server for special processing needs.

History

  • Original posting -- Dec 15, 2005

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here