Steve C. Orr

Software Engineer, Web Developer, Database Designer
 
  

 


























































































































































LANGUAGES: VB.NET | C#

ASP.NET VERSIONS: 1.x

Get the Most Out of Windows Forms Controls

Create Rich User Interfaces by Injecting Windows Forms Controls into Your ASP.NET Web Pages

By Steve C. Orr

Developers love Web applications because they are easier to deploy than Windows applications. But developers love Windows applications because functionally rich user interfaces can be created rapidly. Wouldn’t it be nice to have the best of both worlds? In this article I’ll show you how you can have your cake and eat it too by supercharging your ASP.NET Web pages with Windows Forms controls.

Why Use Windows Forms Controls?

There are many examples of things that can be done easily with Windows Forms controls that would be difficult or impossible with Web controls alone. For example, there is no Web control that provides the rich functionality of the Windows Forms ComboBox control. So what’s to stop a developer from using the ComboBox control on their Web forms? Not much, as will be demonstrated shortly. The custom ComboBox control defined in this article can even fetch its own data from a Web service.

How about interactive images that can constantly redraw and keep themselves up do date? The Bar Graph custom Windows control detailed later in this article will demonstrate a mesmerizing animated bar chart that can redraw itself based on user input without having to call back to the server.

Browser and .NET Framework Detection

Every user will need two things to successfully consume Windows Forms controls in a Web page: Internet Explorer and the .NET Framework. These requirements shouldn’t be hard to meet these days, but there will sometimes be a minority of users who don’t meet the requirements. Luckily, as a developer, you can help them find their way. A few lines of code are all it takes to verify the user has Internet Explorer and the .NET Framework installed; if they don’t, you can redirect them to an alternate page or guide them toward a solution:

Dim msg As String

If Request.Browser.Browser.ToUpper().IndexOf("IE") >= 0 Then

  If Request.Browser.ClrVersion.Major > 0 Then

    Msg ="Your browser is good and powerful."

  Else

    msg ="Get the .NET Framework from www.WindowsUpdate.com"

  End If

Else

  Response.Write("This page requires Internet Explorer.")

End If

Response.Write(msg)

The Request.Browser property contains information about the user’s Web browser. In this example, searching for the string “IE” confirms the user is running Internet Explorer.

The Request.Browser.ClrVersion property contains detailed version information for the most up-to-date version of the .NET Framework that’s installed on the user’s machine. The Major property will be zero if the user doesn’t have any version of the .NET Framework installed. The GetClrVersions method (not shown) will return an array of all the versions of the .NET Framework that are installed on the user’s machine (in case you need such detailed information). In the vast majority of cases, that level of detail shouldn’t be necessary, but it’s nice to know it’s there.

The Basics

To embed a custom Windows control in a Web page, you’ll need an HTML snippet such as this:

<OBJECT id="MyControl1" height="20" width="192"

classid="http:MyCustom.dll#MyNamespace.MyClassName">

<PARAM NAME="BackColor" VALUE="Yellow">

You have an unsupported browser.

</OBJECT>

A special technique is used to overload the ClassID attribute so all the necessary information can be specified to launch the .NET component. The ClassID must start with “http:”, which must be followed by the name of the DLL containing the .NET component. This DLL must reside in the Web application’s root directory. After the DLL name there must be a pound sign (#), followed by the full name of the component (namespace included).

<PARAM> tags can be used to declaratively set properties of the control. In the previous example, the BackColor property of the control is set to yellow. Client-side script can also be used to call properties and methods of the custom .NET Windows control, as will be demonstrated later in the article.

The “unsupported browser” text will only appear in cases where the message is true.

A Basic ComboBox

Using the technique just described, it’s not possible to refer to any components that reside in the GAC, and, therefore, it’s impossible to refer directly to a standard Windows control. It is possible to use standard Windows controls in your Web pages, but they must be encapsulated within a custom assembly that can be referred to with the precise HTML syntax previously listed. It’s not difficult.

For example, compile this simple class into a new Windows Control Library using Visual Studio:

Public Class MyComboBox

    Inherits System.Windows.Forms.ComboBox

End Class

Add the resulting control to a Web page using the earlier HTML snippet syntax and you’re good to go. Don’t forget to place the control’s resulting DLL in the Web application’s root directory, or else it won’t be downloaded to the user’s computer.

(advertisement)

An Enhanced ComboBox

A ComboBox isn’t very useful unless you can get data into and out of it. The above ComboBox control will be enhanced to retrieve its own data from a Web service.

After setting a reference to a local Web service that returns a DataSet, this improved ComboBox control calls the Web service back on its home server and fills itself with the resulting data:

Public Class ComboBox

  Inherits System.Windows.Forms.ComboBox

 

  Public Sub New()

    MyBase.New()

 

    'Fetch data from a web service and fill the ComboBox

    Dim ws As New MyWebService.MyDataSource

    For Each dr As DataRow In ws.GetDataSet.Tables(0).Rows

       Items.Add(dr(0).ToString)

    Next

  End Sub

End Class

This control inherits from the standard Windows Forms ComboBox control, thereby getting all its functionality. It then extends the standard functionality by calling a Web service from within the constructor. Finally, it loops through the records and adds them to the Items collection of the ComboBox. Figure 1 shows it in action.


Figure 1: Don’t be discouraged by the fact that there’s no ComboBox Web control in ASP.NET. The Windows Forms ComboBox control can be used in Web applications.

To retrieve the value the user selects in the ComboBox, an OnSubmit attribute is added to the HTML Form tag. Using a line of JavaScript, the ComboBox value is placed into an HTML HiddenTextBox control just before the page is submitted back to the server:

onsubmit="document.forms[0].HiddenTextBox1.value =

 document.forms[0].ComboBox1.Text;"

Because that HiddenTextBox is marked with the runat=“Server” attribute, the value can be easily retrieved from server-side code:

Response.Write("You chose " + HiddenTextBox1.Value

An Animated Bar Graph Control

The bar graph shown in Figure 2 is another example of a custom Windows Forms control that doesn’t need to post back to the server. In fact, there is no server-side code needed at all for this page, as you can see by the plain HTML page listed in Figure 3.


Figure 2: It would be difficult — if not impossible — to implement such a rich, animated bar graph as this with HTML and JavaScript alone. Windows Forms controls are simply better at many tasks than Web controls.

<html>

<head>

      <title>BarTest</title>

      <meta content="JavaScript" name="vs_defaultClientScript">

</head>

<body MS_POSITIONING="FlowLayout">

  <OBJECT id="WinWebBarGraph1" height="300" width="400"

    classid="http:ControlFreak.dll#ControlFreak.BarGraph"

    name="WinWebBarGraph1" VIEWASTEXT>

      <PARAM NAME="Backcolor" VALUE="lightblue">

  </OBJECT>

            <br><br>

September: <INPUT id="txtSep" type="text" value="40"

name="txtSep"><br>

October: <INPUT id="txtOct" type="text" value="50"

name="txtOct"><br>

November: <INPUT id="txtNov" type="text" value="60"

name="txtNov"><br>

December: <INPUT id="txtDec" type="text" value="70"

name="txtDec"><br><br>

<BUTTON id="graphbutton" onclick="graphbutton_onclick(1);"

name="graphbutton" type="button"

      value="Button">Static

</BUTTON>&nbsp;

<BUTTON id="Button1" onclick="graphbutton_onclick(2);"

name="graphbutton" type="button"

      value="Button">Random Animation

</BUTTON>&nbsp;

<BUTTON id="Button2" onclick="graphbutton_onclick(3);"

name="graphbutton" type="button"

      value="Button">Smooth Animation

</BUTTON>

 

<script language="javascript">

           

WinWebBarGraph1.AddBar("September",40);

WinWebBarGraph1.AddBar("October",50);

WinWebBarGraph1.AddBar("November",60);

WinWebBarGraph1.AddBar("December",70);

WinWebBarGraph1.DrawBarsAnimatedSmooth()

           

function graphbutton_onclick(AnimStyle)

{

      WinWebBarGraph1.ClearBars();

      WinWebBarGraph1.AddBar("September",txtSep.value);

      WinWebBarGraph1.AddBar("October",txtOct.value);

      WinWebBarGraph1.AddBar("November",txtNov.value);

      WinWebBarGraph1.AddBar("December",txtDec.value);

      if (AnimStyle==1) WinWebBarGraph1.DrawBars();

      if (AnimStyle==2) WinWebBarGraph1.DrawBarsAnimatedRandom();

      if (AnimStyle==3) WinWebBarGraph1.DrawBarsAnimatedSmooth()

}

</script>

</body>

</html>

Figure 3: Windows controls can be embedded in plain HTML pages, with no server-side required. In this example, client-side JavaScript is used to call methods of the Windows control that are hosted within the browser.

Just after the beginning <body> tag you can see the <OBJECT> tag, with syntax matching the description at the beginning of this article. The Backcolor property is set to light blue via a <PARAM> tag. After that, a few textboxes are followed by a few buttons. Finally there is a block of JavaScript that sets initial values for the bar graph control and tells the control to begin its colorful animation cycle.

The final graphbutton_onclick JavaScript function operates similarly when one of the buttons is clicked, setting the bar graph values to match the user’s input. The bar graph control is capable of two different kinds of animation, as well as a static (non-animated) layout. There is an HTML button on the form matching each animation style. All three buttons call this client-side JavaScript function, which clears the old bar values, adds new bars with sizes that match the user-entered values, and sets the animation style according to the user’s command.

Internally, the BarGraph control contains a collection of Bar objects. The Bar class is nothing very fancy; basically, it’s just a container to hold a few values. The Bar class is defined in Figure 4.

Public Class cBar

    Public Sub New(ByVal Text As String, _

        ByVal Value As Double)

        _text = Text

        _Value = Value

    End Sub

 

    Dim _text As String = String.Empty

    Public Property Text() As String

        Get

            Return _text

        End Get

 

        Set(ByVal Value As String)

            _text = Value

        End Set

    End Property

 

    Dim _Value As Double = 0

    Public Property Value() As Double

        Get

            Return _Value

        End Get

        Set(ByVal Val As Double)

            _Value = Val

        End Set

    End Property

End Class

Figure 4: A bar object is instantiated for each bar in the BarGraph control.

What’s more interesting is the code for the actual BarGraph control. The basic drawing method for this control is listed in Figure 5. This function is for drawing the static (non-animated) version of the BarGraph control.

Public Sub DrawBars()

  Timer1.Enabled = False

 

  'initialize a drawing surface

  Dim objBitmap As Bitmap = New Bitmap(Me.Width, Me.Height)

  Dim objGraphics As Graphics = _

     Graphics.FromImage(objBitmap)

  objGraphics.FillRectangle(New SolidBrush(Me.BackColor), _

      0, 0, Me.Width, Me.Height)

 

  'loop through and draw each bar

  Dim iCount As Integer = 0

  For Each Bar As cBar In _Bars

      'determine the bar dimensions

      Dim Width As Single = _

          Convert.ToSingle(Me.Width / _Bars.Count - 20)

      Dim BarHeightPercent As Integer = _

          Convert.ToInt32(Bar.Value / _Bars.MaxValue * 100)

      Dim BarHeight As Single = _

          Convert.ToSingle(BarHeightPercent _

          / 100 * Me.Height - 20)

      Dim rect As New RectangleF(iCount * Width + 20 _

          * iCount + 10, (Me.Height - 10) - BarHeight, _

          Width, BarHeight - 10)

 

      'specify the bar colors

      Dim Color1 As Color = Color.Green

      Dim Color2 As Color = Color.Blue

 

      'draw the bar

      objGraphics.FillRectangle(New _

        Drawing2D.LinearGradientBrush(rect, Color1, Color2, _

        Drawing2D.LinearGradientMode.ForwardDiagonal), rect)

 

      'Draw the text

      Dim drawFont As New Font("Arial Narrow", 10)

      Dim drawPoint As New PointF(rect.X, rect.Y + _

  rect.Height + 1)

      Dim drawBrush As New SolidBrush(Color.Black)

      objGraphics.DrawString(Bar.Text, drawFont, _

  drawBrush, drawPoint)

 

      'Draw the value

      drawPoint = New PointF(rect.X, rect.Y - 14)

      objGraphics.DrawString(Bar.Value.ToString, _

          drawFont, drawBrush, drawPoint)

 

      iCount += 1

  Next

 

  Me.BackgroundImage = objBitmap

  _DisplayStyle = DisplayStyleEnum.NonAnimated

End Sub

Figure 5: The DrawBars method of the BarGraph control loops through each bar and draws them along with their associated text.

First, the internal timer is disabled because it’s used only for the animation functions. Then the drawing surface is initialized and is filled with the specified background color. The code then loops through each Bar object in the Bars collection. The bar dimensions are then calculated and the two bar colors are chosen before each rectangular bar is drawn with a gradient brush. The bar text is drawn just below the bar, and the bar value is drawn just above the bar.

Finally, the completed in-memory image is assigned to be the background image of the control, which causes it to appear. Keep in mind that all this code is being executed by the .NET Framework on the user’s machine and the output is displayed in the user’s browser.

A similar function is shown in Figure 6. This function is called each time the encapsulated timer ticks, rapidly redrawing the bars with random colors and gradient modes.

Public Sub DrawBarsAnimatedRandom()

  Dim rdm As Random = New Random

 

  'initialize a drawing surface

  Dim objBitmap As Bitmap = New Bitmap(Me.Width, Me.Height)

  Dim objGraphics As Graphics = _

    Graphics.FromImage(objBitmap)

    objGraphics.FillRectangle(New SolidBrush(Me.BackColor), _

    0, 0, Me.Width, Me.Height)

 

  'loop through and draw each bar

  Dim iCount As Integer = 0

  For Each Bar As cBar In _Bars

    'determine the bar dimensions

    Dim Width As Single = Convert.ToSingle(Me.Width / _

      _Bars.Count - 20)

    Dim BarHeightPercent As Integer = _

      Convert.ToInt32(Bar.Value / _Bars.MaxValue * 100)

    Dim BarHeight As Single = _

      Convert.ToSingle(BarHeightPercent / 100 _

      * Me.Height - 20)

    Dim rect As New RectangleF(iCount * Width + 20 _

      * iCount + 10, (Me.Height - 10) - BarHeight, _

      Width, BarHeight - 10)

 

    'determine the bar’s colors randomly

    Dim Color1 As Color = Color.FromArgb(rdm.Next(0, 255), _

      rdm.Next(0, 255), rdm.Next(0, 255))

    Dim Color2 As Color = Color.FromArgb(rdm.Next(0, 255), _

      rdm.Next(0, 255), rdm.Next(0, 255))

 

    'random gradient style

    Dim ldb As Drawing2D.LinearGradientMode

    Select Case rdm.Next(0, 3)

      Case 0

        ldb =Drawing2D.LinearGradientMode.BackwardDiagonal

      Case 1

        ldb = Drawing2D.LinearGradientMode.ForwardDiagonal

      Case 2

        ldb = Drawing2D.LinearGradientMode.Horizontal

      Case 3

        ldb = Drawing2D.LinearGradientMode.Vertical

    End Select

 

    'draw the bar

    objGraphics.FillRectangle(New _

        Drawing2D.LinearGradientBrush(rect, _

        Color1, Color2, ldb), rect)

 

    'Draw the text

    Dim drawFont As New Font("Arial Narrow", 10)

    Dim drawPoint As New PointF(rect.X, rect.Y + _

        rect.Height + 1)

    Dim drawBrush As New SolidBrush(Color.Black)

    objGraphics.DrawString(Bar.Text, drawFont, _

        drawBrush, drawPoint)

 

    'Draw the value

    drawPoint = New PointF(rect.X, rect.Y - 14)

    objGraphics.DrawString(Bar.Value.ToString, _

        drawFont, drawBrush, drawPoint)

 

    iCount += 1

  Next

 

  Me.BackgroundImage = objBitmap

  _DisplayStyle = DisplayStyleEnum.AnimatedRandom

  Timer1.Enabled = True

End Sub

Figure 6: The System.Random class is used extensively in this alternate version of the DrawBars method to create an alluring animation of random, pulsating colors.

The general structure of this method is essentially the same as the DrawBars method. It starts by initializing a drawing surface and filling it with the specified background color. Looping through each bar, the same calculations are used to determine the bar dimensions. Then things start to diverge a bit. A random gradient mode is chosen; the gradient colors are chosen randomly, as well. Then, as in the DrawBars function, the text values are drawn and the function finishes by assigning the image to be the background image of the control. A client-side timer is enabled, ensuring that this function will be called again shortly so that the next frame of the animation can be drawn.

The full source code for the BarGraph control contains another function named DrawBarsAnimatedSmooth (the full source code is available; see end of article for download details). This function is similar, but uses slightly different randomization algorithms for the drawing to create an alternate (but equally alluring) animation.

(advertisement)

Limitations and Security Issues

Windows Forms controls are better than ActiveX controls from a security standpoint. Every Web-based ActiveX control comes with a security warning that users must accept or deny no matter what the control does. The control might be entirely harmless — or it might maliciously reformat their hard drive. It’s an all-or-nothing scenario, and if an uneducated user makes the wrong choice, there is no limit to the potential negative outcomes. Today, ActiveX controls are one of the primary sources of Spyware and Adware infections.

The .NET Framework provides a much more robust security system, improving upon Java’s “Sandbox” methodology. To make decisions about whether the control is harmless or potentially dangerous it examines the kinds of functions that the control wants to use, evidence about the user, and application context. Potentially dangerous functions are blocked from execution, although educated users can make exceptions with the .NET Framework Wizard located under Control Panel | Administrative Tools.

The controls in this article contain no potentially dangerous function calls; therefore, they are automatically downloaded and run on the user’s computer without any kind of warning or dialog box needing to appear at all.

What kinds of functions are considered potentially dangerous? Internet-based applications have the most restrictions, while intranet applications have somewhat more lenient boundaries. For example, all network calls are blocked for Internet applications except calls back to the originating application. The ComboBox control listed earlier in this article calls back to the same server from which the containing Web page is hosted. Calls to a different server would fail unless the user (or a custom installation program) explicitly grants the assembly the necessary permissions. This security is based on the URL, so http://localhost is considered different than http://192.168.0.1 — even if both those URLs ultimately point to the same server. If you tinker with the ComboBox control from this article, make sure the browser’s URL root matches the URL root of the Web service it is calling.

Access to the user’s hard drive is also heavily restricted. Although isolated storage is available for temporary files, reading from other locations is strictly prohibited unless the user explicitly chooses a file through the permitted OpenFileDialog class.

Reflection and Serialization are also entirely prohibited. No XSL transformations are permitted, and ADO.NET access is limited. Calls to unmanaged code are prohibited, such as Windows API functions.

Another issue is that communication between client-side script and Windows controls is currently one-way only. That is, JavaScript functions within the page can call methods of the hosted Windows control, but the Windows control cannot raise events back to the page. This (hopefully temporarily) broken functionality is related to the current mix of patches for Windows, Internet Explorer, and the .NET Framework. Although this limitation is annoying, you should be familiar with such one-way communication because it is essentially a metaphor for HTTP communication. That is, Web browsers can make requests to Web servers, but Web servers cannot call directly to a Web browser without the Web browser initiating the conversation.

Unfortunately, all of these limitations rule out a lot of potentially useful applications, but most of these limitations are there for good reason — so users can sleep more soundly at night (and so Microsoft won’t catch so much heat about security problems!).

Despite the limitations, clearly there are many good uses for Windows Forms controls in Web applications, just a few of which have been demonstrated in this article.

The sample code in this article is available for download.

This article was originally published in ASP.NET Pro Magazine.


 

(advertisement)