XTab's Blog

Ged Mead's Blog at vbCity

vbCity Blogs moved to:
http://cs.vbcity.com/blogs
  Home :: Syndication  :: Login

JanFebruary 2010Mar
SMTWTFS
31123456
78910111213
14151617181920
21222324252627
28123456
78910111213

Archives

Topics

Ramblings

VB.NET

Introduction     
A recent forum post on VBCity asked for a way to highlight an important TextBox in a Windows Form. Assuming that the plan is to highlight the TextBox if the user is about to leave the Form without entering any text into it, there are of course several ways this can be done using Windows Forms techniques. But I thought it might be interesting to try something different and see how easy it would be to create a WPF UserControl that incorporated a TextBox, a smack-you-in-the-eye gradient and a border with rounded corners*. I picked those last two features because they are not particularly easy to create in Windows Forms. The WPF UserControl would then have to be integrated seamlessly into the Windows Forms environment.

(*If this isn't cutting-edge enough for you, I will be looking at adding animation to the control in a follow up blog post).

So here's our demo Windows Form that will contain two TextBoxes. The WinForms TextBox is already in place. We will create the WPF one shortly.

  

The WPF UserControl
The first step is to add a WPF UserControl to the Windows Forms project. This is as simple as hitting Ctrl+Shift+A and selecting "WPF" from the Add New Item menu which appears. The only choice you will then see is "User Control (WPF)", so select this and change the default name to 'HiliteTextBox.xaml'. Then click the Add button.

  

The WPF UserControl will be added to your Windows Forms project and behind the scenes all the required References will be added also. The UserControl will be on display as the selected item in Visual Studio and you will see that all the WPF tools are in the Toolbox, not the WinForms ones.

Delete the default Grid from the UserControl and then drag a Border element from the Toolbox and drop it on the XAML pane, on one of the empty lines where the Grid used to be. You might think it strange that you drag a visual element and drop it into the markup area. There's nothing to stop you from dropping it in the Design pane instead. If you do this, you will find that several properties are automatically added for you and, as it happens, we don't want those particular properties set and you would therefore have to delete them.

Assign the Name 'GradBorder' to the Border. Create an empty line between the opening and closing tags of the Border. Drag a TextBox from the Toolbox and drop it on the empty line. Name the TextBox 'InputTextBox'.

Here's the Markup as at this point:

<UserControl x:Class="HiliteTextBox"

   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Width="300" Height="300">

  <Border x:Name="GradBorder">

    <TextBox x:Name="InputTextBox"></TextBox>

  </Border>

</UserControl>

Don't worry that nothing seems to be happening in the Design pane. We'll soon change that. With the Border selected in the XAML pane, move over to the Properties window and change the BorderBrush to LightGray, the BorderThickness and CornerRadius properties to 4. (Still no change in the Design pane; no worries).

The plan is to have a plain gray border round the TextBox by default and have this change to a bright gradient if the user tries to continue without entering any text in the TextBox. To set this up, first create a new empty line between the opening tag of the Window and the opening tag of the Border. Copy and paste the following markup, which creates a LinearGradientBrush that will be stored as a Resource.

  <UserControl.Resources>

    <LinearGradientBrush x:Key="BrightGradient" EndPoint="0.056,0.993" StartPoint="0.634,0.342" SpreadMethod="Repeat">

      <GradientStop Color="#FFB50D0D"/>

      <GradientStop Color="#FFE80C2C" Offset="1"/>

      <GradientStop Color="#FFF4DF5A" Offset="0.17399999499320984"/>

      <GradientStop Color="#FFCA0C1F" Offset="0.357"/>

      <GradientStop Color="#FFDCD762" Offset="0.522"/>

      <GradientStop Color="#FFB70D36" Offset="0.716"/>

      <GradientStop Color="#FFDDEE49" Offset="0.823"/>

    </LinearGradientBrush>

  </UserControl.Resources>

There is a very good reason for storing the gradient brush separately and naming it with the 'BrightGradient' Key, as you will see soon.

We will set a few properties on the TextBox. Select the TextBox in the XAML pane and then move to the Properties Window and set the TextWrapping property to 'Wrap'.

In the XAML pane, create a new empty line inside the opening tag of the TextBox. Then type in: 'TextChanged='.    As soon as you do this, an Intellisense popup will appear, inviting you to create a new event handler for the TextChanged event.



Press the TAB key and the rest of the line will be completed for you. It will be assigned a default name and this event handler will have been added to the code-behind for you.

Your markup for the Border and TextBox should now look like this:

  <Border x:Name="GradBorder"

           BorderBrush="LightGray"

           BorderThickness="1"

           CornerRadius="4,4,4,4">

 

    <TextBox x:Name="InputTextBox"

            TextWrapping="Wrap"

            TextChanged="InputTextBox_TextChanged">

    </TextBox>

  </Border>

Open up the vb file for the UserControl - HiliteTextBox.xaml.vb - in the project files in Solution Explorer. The InputTextBox_TextChanged event handler will be there for you.
Before we code the event handler though, first create a Sub that will check if the TextBox contains any Text. If it does, the Border will be set to the non-highlighted LightGray; if it doesn't, the Border will be the bright gradient. Enter the following code:

Imports System.Windows

Imports System.Windows.Media

    Public Sub HighlightEmpty()

        If InputTextBox.Text.Length > 0 Then

            GradBorder.BorderBrush = New SolidColorBrush(Colors.Black)

            GradBorder.BorderThickness = New Thickness(1)

        Else

            GradBorder.BorderBrush = FindResource("BrightGradient")

            GradBorder.BorderThickness = New Thickness(4)

        End If

    End Sub

The only code that may be new to you might be the FindResource method. This is a really useful feature in WPF, which allows you to ferret through the project and pull out any specific resource that you want to use. The 'BrightGradient', you will recall, is the Key that I assigned to the LinearGradientBrush that I stored as a UserControl Resource earlier.

The Imports statements at the top of the file ensure that you are accessing elements in the System.Windows namespace.

Hosting the UserControl    
Now the UserControl is built, it has to be hosted in the Windows Form. Rebuild the project and then select the Form1.vb file. The control that WinForms uses to host WPF UserControls is the ElementHost. This probably won't be in your Toolbox by default, so if it isn't listed, right-click on the Toolbox and select Choose Items...   Select the ElementHost from the .NET Framework Components Tab.

Drag an instance of the ElementHost from the Toolbox on to the surface of the Form. It will be too large by default, but before you change its size, click on small down arrow next to 'Select Hosted Content' and select HiliteTextBox from the list:

   

The Smart Tag window will close and you can then adjust the size of the ElementHost. As you can see from the screenshot, an error message appears inside the ElementHost. You can safely ignore this as it is only tells you that the Windows Form can't display the visuals of the UserControl at Design time.

Next we need to add code to the Continue button. For our demo purposes, this will be sufficient:

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

        HiliteTextBox1.HighlightEmpty()

    End Sub

If you look back at the earlier code, you will know that the HighlightEmpty method checks if the TextBox has Text entered and sets the Border accordingly. Although the Button on the Windows Form can't access the graphical elements of the UserControl directly, the HighlightEmpty procedure allows for this interchange between the two.

Try running the application now. Don't enter any text into the UserControl, then press the Continue button. You should see the highlight gradient border appear around the TextBox.

  

If you then insert some text and press the Continue button again, the highlight border will be replaced with the gray one.

Adding Some Refinements
That pretty much fulfills the spec, but there are a couple of things I would definitely add. The first feature is that if the user is warned about not entering text, then I would like to give some positive feedback as soon as they do so. That is, before they press the Continue button for a second time. This is easily achieved by adding a line of code to the TextChanged event handler of the InputTextBox.

Go back to the WPF UserControl code-behind file and add a call to the HighlightEmpty procedure:

    Private Sub InputTextBox_TextChanged(ByVal sender As System.Object, ByVal e As System.Windows.Controls.TextChangedEventArgs)

        HighlightEmpty()

    End Sub

When you run the application again and press the Continue button with no text in the TextBox, the highlight border appears as before. Now though, when you enter a character, the highlight Border disappears. As a small bonus, if the user then deletes all the text in the TextBox, the highlight will reappear, just to make certain they know about it.

The second thing I would like to add is a way of giving the developer a choice of actions on the Continue Button Click event. What I mean by this is that at the moment, the call goes to the HighlightEmpty procedure of the UserControl. This approach makes it difficult to build choices into the Button Click event, along the lines of "Depending whether the TextBox is empty, do something here in the Form's logic".

There are several easy ways of doing this, including adding a Boolean Property to the UserControl or creating a simple Function. This tests if the InputTextBox is empty and if it is, it Returns True. Based on the returned value, the Button event handler can follow one course of action or another.

Here's a Function that can be added to the UserControl:

    Public Function IsTBEmpty() As Boolean

        If InputTextBox.Text.Length = 0 Then

            Return True

        Else

            Return False

        End If

    End Function

You can then put whatever code you want in the Button Click event handler. Maybe something like:

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

        If HiliteTextBox1.IsTBEmpty = True Then

            HiliteTextBox1.HighlightEmpty()

            Me.ElementHost1.Focus()

        Else

            MessageBox.Show("You are clear to continue")

        End If

    End Sub

You can apply this approach of using a WPF UserControl in a Windows Form to get access to lots of those enhanced visual goodies that WPF offers, including Animation. As you have seen, creating a WPF UserControl hosted in an ElementHost is a very easy process. As an alternative to fiddling with individual controls on a Windows Form, you can of course include complete WPF Windows in Windows Forms projects.

posted on Wednesday, July 01, 2009 4:49 PM

Post Feedback

Title:
Name:
Url:
Comments: 
Protected by Clearscreen.SharpHIPEnter the code you see: