Click here to Skip to main content
15,860,943 members
Articles / Web Development / ASP.NET
Article

VML Web Controls

Rate me:
Please Sign up or sign in to vote.
4.61/5 (14 votes)
15 Dec 20055 min read 71K   1.9K   46   11
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
<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.

VB
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.

VB
<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.

VB
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.

VB
<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:

VB
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.

VB
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

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.

VB
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.

VB
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


Written By
Web Developer
United States United States
University of Florida graduate in Geography. Currently pursuing a Master of Science in Management Information Systems.


Go Gators!

Comments and Discussions

 
Generalvml huge data Pin
nghiadh200027-Dec-07 16:02
nghiadh200027-Dec-07 16:02 
Hello!
I'm very thanks for your writting. I have a problem when using your control to daw a map about 15000 objects. So how can i optimize this drawing.
I look forward to hearing from you.
Your Trully,
Nghia Dinh
GeneralRe: vml huge data Pin
Justin Carasick28-Dec-07 4:41
Justin Carasick28-Dec-07 4:41 
QuestionControl can't find Image control ID in user controls? Pin
Bunce3-Nov-07 16:35
Bunce3-Nov-07 16:35 
AnswerRe: Control can't find Image control ID in user controls? Pin
Justin Carasick20-Feb-08 13:22
Justin Carasick20-Feb-08 13:22 
GeneralAtlas Multiple Browsers Pin
fpatton6-Sep-06 11:07
fpatton6-Sep-06 11:07 
GeneralVML server interaction Pin
codeviktor28-Dec-05 1:14
codeviktor28-Dec-05 1:14 
GeneralRe: VML server interaction Pin
Justin Carasick29-Dec-05 3:49
Justin Carasick29-Dec-05 3:49 
QuestionNice Pin
Dylan Thomas15-Dec-05 14:09
Dylan Thomas15-Dec-05 14:09 
AnswerRe: Nice Pin
jcarasick115-Dec-05 16:47
jcarasick115-Dec-05 16:47 
GeneralRe: Nice Pin
Dylan Thomas15-Dec-05 17:48
Dylan Thomas15-Dec-05 17:48 
GeneralRe: Nice Pin
jcarasick116-Dec-05 1:54
jcarasick116-Dec-05 1:54 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.