WPF Forms over Data: 2 More Videos!
24 July 08 06:39 PM

I couldn't wait for the content pages to prop onto MSDN to tell you that I just uploaded two more WPF Forms over Data videos and code samples onto Code Gallery: http://code.msdn.microsoft.com/wpfdatavideos, videos 3 and 4.

Video #3 shows how to create a dropdown combobox (pick-list) that pulls data from a lookup table in a database. #4 shows a technique for customizing the display of validation messages on controls (for more details on that you can read this post too.)

Enjoy!

Postedby Beth Massi | 2 Comments    
Channel9 Interview: Visual Basic Language Design Meeting
23 July 08 06:02 PM

I sat down with the VB Language design team and asked them about their design process, favorite features, their thoughts on other languages, as well as what the Visual Basic language strategy really is. It was a fun and enlightening interview with a group of really smart people lead by Paul Vick. You can find most of the team members writing on the Visual Basic Team Blog.

Stay tuned for more interviews like these... 

Enjoy!

Postedby Beth Massi | 2 Comments    
Keeping the Dev Centers Fresh with Social Bookmarking
22 July 08 12:03 PM

Last week Chris Slemp from MSDN/TechNet interviewed me and 3 other top bookmarkers about how we use the Social Bookmarking Preview and how we'd like to see it evolve. In this post he also has some interesting stats and analysis on how poeple are using social bookmarking (scroll down for the interviews).

To be honest, I don't really think about social bookmarking all that hard, it's become part of my routine (and I'm hoping others in the community start to feel that way as well). I use it for organizing all the great content I produce and manage so it's pretty easy for me to be in the habit of tagging things.

It's also made the Dev Centers a lot easier to maintain and keep fresh so I'm loving it. Give it a try, the more you tag things "Visual Basic" the more great content will show up on the Visual Basic Dev Center Community Page through this feed. This allows everyone in the community to see what Visual Basic content is popular anywhere on the web. Pretty cool. Of course there are still features to add and bugs to fix but I think it's a great preview so far.

Enjoy!

Postedby Beth Massi | 6 Comments    
Community Article: Using Windows Communication Foundation with Windows Workflow Foundation – Part 1
17 July 08 02:16 PM

This week on the VB Dev Center we're featuring another community submitted article by Maurice de Beijer (VB MVP) on Using Windows Communication Foundation with Windows Workflow Foundation. Maurice has a great wiki as well that you should check out if you're doing Windows Workflow development.

In this article he shows you how to use SendActivity which enables you to call WCF as well as standard web services in your workflows. Stay tuned for the next article which will show you how to use ReceiveActivity, which enables a you to publish a workflow as a WCF service.

Enjoy!

Postedby Beth Massi | 1 Comments    
Silicon Valley Code Camp Registration Open
15 July 08 10:45 AM

Peter Kellner (ASP.NET MVP) and crew are at it again this year organizing the 3rd annual Silicon Valley Code Camp.
Once again, Foothill College has stepped up and is providing free use of their facility on Saturday and Sunday November 8th and 9th this year.  The event is again free and ready for registration at the following URL:

http://www.siliconvalley-codecamp.com/

I'll be speaking on LINQ of course but I'll also be showing some Office development and Open XML as well. There were 800 people registered last year and there is a huge breath of technology that it covers, not just Microsoft or .NET technologies, so it's a great way to get exposure to a lot of cool stuff. We are fortunate to live in the Bay Area where there is a lot of cool technology and great speakers. This is also your chance to share your knowledge with other developers. Registration is FREE and anyone can submit talks so if you think you have someting interesting to teach people, go for it!

Hope to see you there!

Postedby Beth Massi | 2 Comments    
Filed under: ,
Auto-Reply: Out of Office on Vacation
07 July 08 08:00 AM

Yes, even Beth Massi gets a vacation. This year I have the pleasure of attending Nick Landry's (Mobile MVP) wedding in Jamaica. It's my first time in the Caribbean. This is what I call community baby! ;-)

I'm actually writing this before I leave and queuing it up to publish later. Yes that's right I'm probably on the beach right now drinking Red Stripe. Ya mon. Maybe I'll work on my dreadlocks too.

I'll be back to blogging when I get back next week. Don't miss me too much. :-)

Later!

Postedby Beth Massi | 2 Comments    
Filed under:
NINE Questions
05 July 08 11:30 AM

Chris Williams (VB MVP) is running around the net asking people nine questions. I'm one of the people he interviewed and he's got a good gang of other people up on his blog as well so check them out. I was bummed to hear that Rocky doesn't have any tattoos, but D'Arcy made up for it with his answer to that question. ;-)

Enjoy!

Postedby Beth Massi | 1 Comments    
Filed under:
TechEd Panel: VB XML Literals for C# Developers
03 July 08 10:00 AM

This week TechEd Online posted the TechEd panel I was on with Erick Thompson and Shyam Pather of the SQL Data team. I thought it was a pretty funny panel description they came up with, ala Dr. Strangelove:

VB XML Literals for C# developers or: How I learned to stop worrying and love Dim
You dream in curly braces and end your sentences with semicolons. With Visual Studio 2008, you've seen the power that LINQ brings to your relational data. However, not all of your data is relational; a lot of it is XML. VB 9 introduced XML Literals, which drastically improve the LINQ to XML experience. However, if you are a C# developer, you may not be familiar with all the capabilities of XML Literals as they are only available in VB. This panel explores the uses of XML Literals and how you can leverage this powerful technology in your applications, even the curly braced ones. With Erick Thompson, Shyam Pather, and Beth Massi. Hosted by Richard Campbell and Carl Franklin.

Also available on .NET Rocks! 

Enjoy!

Postedby Beth Massi | 1 Comments    
WPF Forms over Data Video Series
02 July 08 08:11 AM

Yesterday I created a Code Gallery project with a couple videos on WPF data-based app development in Visual Studio 2008. I’m starting a series on WPF directed at LOB developers building data-oriented applications. These videos show how to work with data, they don't show you how to create fancy UIs.

MSDN hasn’t propped the page content yet but you can check out the Code Gallery resource page that has the links to the videos and the code downloads now!

http://code.msdn.microsoft.com/wpfdatavideos

Look for more videos when I get back from vacation in mid-July.

Enjoy!

Postedby Beth Massi | 3 Comments    
Filed under: , , , ,
Article: Sharpening Your Axis with Visual Basic 9
30 June 08 02:12 PM

In the July/August issue of CoDe Magazine there's an article I wrote with Avner Aharoni (the Program Manager on XML literals) called Sharpening Your Axis with Visual Basic 9. (They just opened up online access to even non-subscribers today!) I highly recommend this article which shows you from the beginning how XML literals, embedded expressions and axis properties work in Visual Basic.

I start with the basic syntax and definitions and then walk through the XML to Schema tool (which has been added to VS2008 SP1) to enable XML IntelliSense, as well as show some practical examples of using these features with LINQ. Avner describes how IntelliSense works and discusses advanced scenarios with XML namespaces. We end with tips-and-tricks which get you thinking about using XML literals in other text generation/manipulation scenarios.

If you like this topic, so do I! Check out the XML topic feed from this blog for more resources.

Enjoy!

Postedby Beth Massi | 2 Comments    
Displaying Data Validation Messages in WPF
27 June 08 03:57 PM

As you can probably tell from my last couple posts I've been working with WPF in different data scenarios. Yesterday I was playing with data validation in WPF and .NET 3.5 which is pretty slick. In this article I'll walk through how to hook up validation in your data objects using the IDataErrorInfo interface and then I'll go through a couple Validation ErrorTemplates you can use to display the validation error messages and cues to the user.

Performing Validation on Data Objects

If you're using custom business objects or LINQ to SQL classes you first need to implement the IDataErrorInfo interface in order to collect validation messages on your objects. If you are using DataSets on your WPF or Windows Forms, the DataRowView already implements this interface so you can just add validation to your DataTable partial classes and you're good to go. Just open the DataSet designer, right-click on the DataTable and select "View Code" and party on. For instance, if we have a customer DataTable we can write validation for the LastName field like so:

Partial Class CustomerDataSet
    Partial Class CustomerDataTable

        Private Sub CheckLastName(ByVal row As CustomerRow)
            If row.IsNull("LastName") OrElse row.LastName = "" Then
                row.SetColumnError(Me.LastNameColumn, "Please enter a last name")
            Else
                row.SetColumnError(Me.LastNameColumn, "")
            End If
        End Sub

        Private Sub CustomerDataTable_ColumnChanged(ByVal sender As Object, _
                                                    ByVal e As System.Data.DataColumnChangeEventArgs) _
                                                    Handles Me.ColumnChanged
            If e.Column Is Me.LastNameColumn Then
                Me.CheckLastName(CType(e.Row, CustomerRow))
            End If
        End Sub


        Private Sub CustomerDataTable_TableNewRow(ByVal sender As Object, _
                                                  ByVal e As System.Data.DataTableNewRowEventArgs) _
                                                  Handles Me.TableNewRow
            Dim row As CustomerRow = CType(e.Row, CustomerRow)
            'This will fire the ColumnChanged event which will give 
            'immediate feedback to user when a row is added.
            '(Stick other default values here too.)
            row.LastName = ""
        End Sub
    End Class

End Class

If you're building our own custom business objects or are using LINQ to SQL classes then it's up to you to implement the IDataErrorInfo interface yourself. I showed how to do this with LINQ to SQL classes in this post where I set it up in a base business class. Here's a "short version" example implementation for a customer LINQ to SQL class that performs the same validation on the LastName field:

Partial Class Customer
    Implements System.ComponentModel.IDataErrorInfo

    'This dictionary contains a list of our validation errors for each field
    Private validationErrors As New Dictionary(Of String, String)

    Protected Sub AddError(ByVal columnName As String, ByVal msg As String)
        If Not validationErrors.ContainsKey(columnName) Then
            validationErrors.Add(columnName, msg)
        End If
    End Sub

    Protected Sub RemoveError(ByVal columnName As String)
        If validationErrors.ContainsKey(columnName) Then
            validationErrors.Remove(columnName)
        End If
    End Sub

    Public Overridable ReadOnly Property HasErrors() As Boolean
        Get
            Return (validationErrors.Count > 0)
        End Get
    End Property

    Public ReadOnly Property [Error]() As String _
        Implements System.ComponentModel.IDataErrorInfo.Error
        Get
            If validationErrors.Count > 0 Then
                Return String.Format("{0} data is invalid.", TypeName(Me))
            Else
                Return Nothing
            End If
        End Get
    End Property

    Default Public ReadOnly Property Item(ByVal columnName As String) As String _
        Implements System.ComponentModel.IDataErrorInfo.Item
        Get
            If validationErrors.ContainsKey(columnName) Then
                Return validationErrors(columnName).ToString
            Else
                Return Nothing
            End If
        End Get
    End Property

    Private Sub OnValidate(ByVal action As System.Data.Linq.ChangeAction)
        Me.CheckLastName(Me.LastName)

        If Me.HasErrors Then
            Throw New Exception(Me.Error)
        End If
    End Sub

    Private Sub OnLastNameChanging(ByVal value As String)
        Me.CheckLastName(value)
    End Sub

    Private Sub CheckLastName(ByVal value As String)
        If value = "" Then
            Me.AddError("LastName", "Please enter a last name")
        Else
            Me.RemoveError("LastName")
        End If
    End Sub
    
End Class

Data Binding in WPF

Now that our data objects are validating themselves we can data bind them to a form. Setting up a simple WPF Window with some TextBoxes and binding them is easy in XAML once you get the knack for remembering the syntax ;-). The key is to make sure you specify the ValidatesOnDataErrors attribute on the Binding and set it to True. Take a look at the TextBoxes in the XAML below:

<Window x:Class="Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Customers" Height="253" Width="300" Name="Window1">
    <Grid Margin="6">
        <Grid.RowDefinitions>
            <RowDefinition Height="222*" />
            <RowDefinition Height="40*" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="112*" />
            <ColumnDefinition Width="166*" />
        </Grid.ColumnDefinitions>
        <StackPanel Name="StackPanel1">
            <Label Name="Label1" 
                   Width="Auto" 
                   HorizontalContentAlignment="Right" 
                   Margin="3">
                Last Name:</Label>
            <Label Name="Label2" 
                   Width="Auto" 
                   HorizontalContentAlignment="Right" 
                   Margin="3">
                First Name:</Label>
            <Label Name="Label3" 
                   Width="Auto" 
                   HorizontalContentAlignment="Right" 
                   Margin="3">
                City:</Label>
            <Label Name="Label4" 
                   Width="Auto" 
                   HorizontalContentAlignment="Right" 
                   Margin="3">
                State:</Label>
            <Label Name="Label5" 
                   Width="Auto" 
                   HorizontalContentAlignment="Right" 
                   Margin="3">
                ZIP:</Label>
        </StackPanel>
        <StackPanel Grid.Column="1" Name="StackPanel2">
            <TextBox 
                Text="{Binding Path=LastName, ValidatesOnDataErrors=True}"
                Name="TextBox1" 
                Height="28" 
                Width="Auto" 
                HorizontalContentAlignment="Left" 
                Margin="3" />
            <TextBox 
                Text="{Binding Path=FirstName, ValidatesOnDataErrors=True}"
                Name="TextBox2" 
                Height="28" 
                Width="Auto" 
                HorizontalContentAlignment="Left" 
                Margin="3" />
            <TextBox 
                Text="{Binding Path=City, ValidatesOnDataErrors=True}"
                Name="TextBox3" 
                Height="28" 
                Width="Auto" 
                HorizontalContentAlignment="Left" 
                Margin="3"/>
            <TextBox 
                Text="{Binding Path=State, ValidatesOnDataErrors=True}"
                Name="TextBox4" 
                Height="28" 
                Width="Auto" 
                HorizontalContentAlignment="Left" 
                Margin="3"/>
            <TextBox 
                Text="{Binding Path=ZIP, ValidatesOnDataErrors=True}"
                Name="TextBox5" 
                Height="28" 
                Width="Auto" 
                HorizontalContentAlignment="Left" 
                Margin="3" />
        </StackPanel>
        <Button Name="btnAdd" 
                Grid.Column="1" Grid.Row="1" 
                Margin="0,0,79,6" 
                Height="24" Width="75"
                VerticalAlignment="Bottom" 
                HorizontalAlignment="Right" >Add</Button>
        <Button Name="btnSave" 
                Grid.Column="1" Grid.Row="1" 
                HorizontalAlignment="Right" 
                Margin="0,0,0,6" 
                Width="75" Height="24" 
                VerticalAlignment="Bottom">Save</Button>
    </Grid>
</Window>

Now we can load our data and set it to the Window.DataContext in the Window_Loaded event handler. If you're using DataSets, then set up your TableAdapter query like normal and Fill the DataSet. Then set the Window's DataContext to the customer DataTable:

Me.CustomerTableAdapter.Fill(Me.MyCustomerData.Customer)
Me.DataContext = Me.MyCustomerData.Customer

If you're using LINQ to SQL classes then just call upon the SQLDataContext to load your list of customers:

Dim db As New MyDatabaseDataContext
Me.DataContext = From Customer In db.Customers _
                 Where Customer.LastName = "Massi"

WPF's Default Validation ErrorTemplate

So if we were to run this as-is WPF would give us a default visual cue when our validation fails. The control is drawn with a red border indicating there is a problem, however no message is displayed. Oh yea, that's helpful! Prepare for your tech support phone lines to light up if you release this baby.

Specifying a Custom Validation Style

We obviously want to let the user know what needs fixing here. Let's just do something simple and display the message in a ToolTip. For now, we can just create a Style in our Window.Resources section that applies to the Textboxes on this form. The Style sets up a Trigger that sets the ToolTip property to the validation message when the Validation.HasError changes to True:

<Window.Resources>
    <Style TargetType="TextBox">
        <Style.Triggers>
            <Trigger Property="Validation.HasError" Value="true">
                <Setter Property="ToolTip"
                        Value="{Binding RelativeSource={x:Static RelativeSource.Self},
                        Path=(Validation.Errors)[0].ErrorContent}"/>
            </Trigger>
        </Style.Triggers>
    </Style>
</Window.Resources>

Now when we run this again, you'll see that when you hover over the TextBox the validation message is displayed in a ToolTip. Better! But this solution only displays for TextBoxes. What about the rest of our controls like CheckBoxes, ComboBoxes, etc.? And we really want to declare all this in one place for our entire application. No problem, we can stick this Style into the Application.Resources instead. We can also specify that the TargetType="Control" and then we can declare additional styles for the rest of our controls and base them on this one. Open up your Application.xaml and add this XAML to your Resources section:

<Application.Resources>
    <Style TargetType="Control" x:Key="myErrorTemplate">
        <Style.Triggers>
            <Trigger Property="Validation.HasError" Value="true">
                <Setter Property="ToolTip"
                    Value="{Binding RelativeSource={x:Static RelativeSource.Self},
                    Path=(Validation.Errors)[0].ErrorContent}"/>
            </Trigger>
        </Style.Triggers>
    </Style>
    <Style TargetType="TextBox" BasedOn="{StaticResource myErrorTemplate}" />
    <Style TargetType="CheckBox" BasedOn="{StaticResource myErrorTemplate}" />
    <Style TargetType="ComboBox" BasedOn="{StaticResource myErrorTemplate}" />
</Application.Resources>

We just need to specify a x:Key for the Template and then we can set the BasedOn attribute on the inherited Styles. Now all the controls in the entire application can pick up this Style.

Replacing the Entire ErrorTemplate

So far all we've done is specify a Style Trigger. The default WPF ErrorTemplate is still being utilized as we're still seeing the red border around the control. We can completely change the ErrorTemplate that is used by defining a new one here in the Application.Resources. Let's take a simple example by setting up our ErrorTemplate to display a generic message over the control. In the Style above the Trigger section (we'll leave the ToolTip mesage there) we set the Validation.ErrorTemplate property and its Value to our very own ControlTemplate.

<Style TargetType="Control" x:Key="myErrorTemplate">
        <Setter Property="Validation.ErrorTemplate">
            <Setter.Value>
                <ControlTemplate>
                    <TextBlock Foreground="Red" Text="DOH! Thank you for trying."/>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
        <Style.Triggers>
        <Trigger Property="Validation.HasError" Value="true">
            <Setter Property="ToolTip"
                Value="{Binding RelativeSource={x:Static RelativeSource.Self},
                Path=(Validation.Errors)[0].ErrorContent}"/>
        </Trigger>
    </Style.Triggers>
</Style>

Now when we run this again, we still get our ToolTip when we hover over the control, but now we're also overlaying the control with TextBlock we defined in our ControlTemplate. Notice that there's no more red border:

Okay a pretty lame example, I admit. The problem (besides being a sarcastic message) is that the TextBlock is really covering the control and you have to hover over the edge to get the ToolTip to display. The other problem of course is that if we start typing into the field again the message won't disappear until we tab off if it so that's pretty annoying.

Instead you can stick a DockPanel into the ControlTemplate and Dock the TextBlock to the right in order to display the text after the control (and this time let's just display an asterisk). Say you want to still have that red border around the control. We can do this by specifying a special element called AdornedElementPlaceholder in our XAML for the ErrorTemplate Setter.Value:

<Setter Property="Validation.ErrorTemplate">
    <Setter.Value>
        <ControlTemplate>
            <DockPanel LastChildFill="True">
                <TextBlock DockPanel.Dock="Right" 
                        Foreground="Red"
                        FontSize="11pt" 
                        FontWeight="Bold">*
                </TextBlock>
                <Border BorderBrush="Red" BorderThickness="1">
                    <AdornedElementPlaceholder Name="myControl"/>
                </Border>
            </DockPanel>
        </ControlTemplate>
    </Setter.Value>
</Setter>
 

Much better! Of course you can use your imagination to create any kind of visual cue appropriate for your application. That's the cool thing about WPF.

Duplicating the Winforms ErrorProvider Look and Feel

For those Winforms developers out there, what if you want to duplicate the look and feel of the ErrorProvider which displays a blinking error icon? I always liked to place the red error icon inside the right-hand side of the control so I didn't have to worry about spacing issues between the controls when I was designing forms. And I actually liked how the icon would flash a few times and then stop. It's relatively easy to do this type of animation in WPF using Storyboards (and it's REALLY easy to create animations in Expression Blend so I highly recommend you have a look at that product if you're making the transition to WPF).

This time we'll create an Ellipse and set up an EventTrigger for the Loaded event to begin our animation which will simply toggle the Visibility property of the Ellipse a few times. We also want to place a TextBlock over the Ellipse whose Text is an exclamation point (the animation will run on this as well). And since I want to place these inside the right-hand side of the control by setting a negative left margin, I'm going to want to also set the ToolTips of the Ellipse and the TextBlock so that if the user hovers over the error glyph it will display the ToolTip as well.

Here's the complete XAML to enable this look and feel contained in the Application.Resources:

<Application.Resources>
    <Storyboard x:Key="FlashErrorIcon">
        <ObjectAnimationUsingKeyFrames BeginTime="00:00:00" 
                                       Storyboard.TargetProperty="(UIElement.Visibility)">
            <DiscreteObjectKeyFrame KeyTime="00:00:00" Value="{x:Static Visibility.Hidden}"/>
            <DiscreteObjectKeyFrame KeyTime="00:00:00.2000000" Value="{x:Static Visibility.Visible}"/>
            <DiscreteObjectKeyFrame KeyTime="00:00:00.4000000" Value="{x:Static Visibility.Hidden}"/>
            <DiscreteObjectKeyFrame KeyTime="00:00:00.6000000" Value="{x:Static Visibility.Visible}"/>
            <DiscreteObjectKeyFrame KeyTime="00:00:00.8000000" Value="{x:Static Visibility.Hidden}"/>
            <DiscreteObjectKeyFrame KeyTime="00:00:01" Value="{x:Static Visibility.Visible}"/>
        </ObjectAnimationUsingKeyFrames>
    </Storyboard>
    <Style x:Key="myErrorTemplate" TargetType="Control">
        <Setter Property="Validation.ErrorTemplate">
            <Setter.Value>
                <ControlTemplate>
                    <DockPanel LastChildFill="True">
                        <Ellipse DockPanel.Dock="Right" 
                                 ToolTip="{Binding ElementName=myTextbox, 
                                     Path=AdornedElement.(Validation.Errors)[0].ErrorContent}"
                                 Width="15" Height="15" 
                                 Margin="-25,0,0,0"
                                 StrokeThickness="1" Fill="Red" >
                            <Ellipse.Stroke>
                                <LinearGradientBrush EndPoint="1,0.5" StartPoint="0,0.5">
                                    <GradientStop Color="#FFFA0404" Offset="0"/>
                                    <GradientStop Color="#FFC9C7C7" Offset="1"/>
                                </LinearGradientBrush>
                            </Ellipse.Stroke>
                            <Ellipse.Triggers>
                                <EventTrigger RoutedEvent="FrameworkElement.Loaded">
                                    <BeginStoryboard Storyboard="{StaticResource FlashErrorIcon}"/>
                                </EventTrigger>
                            </Ellipse.Triggers>
                        </Ellipse>
                        <TextBlock DockPanel.Dock="Right" 
                                ToolTip="{Binding ElementName=myControl, 
                                     Path=AdornedElement.(Validation.Errors)[0].ErrorContent}"
                                Foreground="White"
                                FontSize="11pt" 
                                Margin="-15,5,0,0" FontWeight="Bold">!
                            <TextBlock.Triggers>
                                <EventTrigger RoutedEvent="FrameworkElement.Loaded">
                                    <BeginStoryboard Storyboard="{StaticResource FlashErrorIcon}"/>
                                </EventTrigger>
                            </TextBlock.Triggers>
                        </TextBlock>
                        <Border BorderBrush="Red" BorderThickness="1">
                            <AdornedElementPlaceholder Name="myControl"/>
                        </Border>
                    </DockPanel>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
        <Style.Triggers>
            <Trigger Property="Validation.HasError" Value="true">
                <Setter Property="ToolTip"
                        Value="{Binding RelativeSource={x:Static RelativeSource.Self},
                        Path=(Validation.Errors)[0].ErrorContent}"/>
            </Trigger>
        </Style.Triggers>
    </Style>
    <Style TargetType="TextBox" BasedOn="{StaticResource myErrorTemplate}" />
    <Style TargetType="CheckBox" BasedOn="{StaticResource myErrorTemplate}" />
    <Style TargetType="ComboBox" BasedOn="{StaticResource myErrorTemplate}" />
</Application.Resources>

Now when we run our application and trigger the validation error we see an error icon that flashes 3 times (it looks a lot smoother than this image ;-))

Validating your data objects in WPF with the .NET Framework 3.5 is the same as before with WinForms using the IDataErrorInfo interface. However, WPF styles and control templates make displaying visual cues to the user extremely flexible. If you can imagine it, you can probably do it with WPF.

Enjoy!

Postedby Beth Massi | 4 Comments    
Dynamic Data Entry with WPF and LINQ
24 June 08 03:29 PM

In my last post on this subject I explored creating WPF UI's dynamically using XML literals. The one part that bugged me a bit was that even though the UI was dynamic, we were using a fixed object model of our customer (using LINQ to SQL classes). I wanted to augment this code a bit more so that we could not only dynamically generate the WPF UI but also dynamically edit any maintenance table in our database -- not just customer. What I really wanted to enable is if we modify the database schema of any of our maintenance tables then we don't have to update our object model and recompile our code.

To achieve this I decided to explore loading and editing a simple untyped DataTable at runtime. This worked well using XML literals again -- this time to generate the SELECT and UPDATE statements for the SqlDataAdapter. One thing to note, this technique relies on the database to enforce any validation rules. This is why I would only use this type of dynamic form to edit very simple tables (aka. maintenance tables).

For this example I decided to create a typed DataTable for my TableSchema instead as well, eliminating the need for LINQ to SQL classes in the project. If you recall this is used to hold the column metadata (ColumnName, DataType, etc.) for the table we want to edit. I just right-clicked on my project to add a new item and selected DataSet. I named it TableSchemaDataSet and then just simply dragged the GetTableSchema stored procedure (which we added to the Northwind database in the last post) onto the design surface. This automatically creates a typed DataTable for us with no fuss. I renamed the DataTable to TableSchema and saved it.

Now for the fun part. We need to load an untyped DataTable into our dynamically generated WPF form because we don't want to make any assumptions about the schema of the table we're editing (except that there is a primary key of some sort). DataTables and DataSets work well with WPF but there are a couple things we need to set up manually since we're loading this all at runtime. (Writing the code to load and save the DataTable makes me REALLY appreciate the DataSet designer and the code that it generates for you).

First I set up a Public property on the form to hold the name of the table we want to edit called TableName and set the default to "Shippers" this time. Then I created some private class level variables to reference the ADO.NET objects we'll need. (Take a look at the last post for the XAML markup of the Window, it's exactly the same for this example.)

Imports <xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
Imports <xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
Imports System.Windows.Markup
Imports System.Data.SqlClient
Imports System.Data

Partial Public Class Window2
    'This is the metadata table we created in the DataSet Designer
    Private TableSchema As New TableSchemaDataSet.TableSchemaDataTable
    'ADO.NET objects used to load and save the table we're editing
    Private TableDataAdapter As New SqlDataAdapter
    Private TableConnection As New SqlConnection(My.Settings.NorthwindConnectionString)
    Private Table As DataTable
    'This is the key field used in searching for a row in this example
    Private PKField As TableSchemaDataSet.TableSchemaRow

    'This property can be set before the Form.Show() to edit any table
    Private m_tableName As String = "Shippers"
    Public Property TableName() As String
        Get
            Return m_tableName
        End Get
        Set(ByVal value As String)
            m_tableName = value
        End Set
    End Property

In the Loaded event handler we can now load the metadata, create and load our XAML to display our UI just like before, and then set our UpdateCommand on the TableDataAdapter.

Private Sub Window1_Loaded() Handles MyBase.Loaded
    Try
        'Get the schema of the database table we want to edit
        Dim taSchema As New TableSchemaDataSetTableAdapters.TableSchemaTableAdapter
        taSchema.Fill(Me.TableSchema, Me.TableName)

        'Create the DataTable that will hold the record we're editing
        Me.Table = New DataTable(Me.TableName)
        Me.Title = Me.TableName
        Me.LoadUI() 
Me.SetPrimaryKey() Me.SetUpdateCommand()
Catch ex As Exception MsgBox(ex.ToString) Me.Close() End Try End Sub Private Sub LoadUI() Dim UI = <Grid xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" Name="Grid1"> <Grid.ColumnDefinitions> <ColumnDefinition Width="100*"/> <ColumnDefinition Width="200*"/> </Grid.ColumnDefinitions> <StackPanel Name="StackLabels" Margin="3"> <%= From column In Me.TableSchema _ Where column.IsPrimaryKey = 0 AndAlso column.DataType <> "timestamp" _ Select <Label Height="28" Name=<%= column.ColumnName & "Label" %> HorizontalContentAlignment="Right"> <%= column.ColumnName %>:</Label> %> </StackPanel> <StackPanel Grid.Column="1" Name="StackFields" Margin="3"> <%= From column In Me.TableSchema _ Where column.IsPrimaryKey = 0 AndAlso column.DataType <> "timestamp" _ Select GetUIElement(column) %> </StackPanel> </Grid>
Me.DynamicContent.Content = XamlReader.Load(UI.CreateReader()) End Sub Private Function GetUIElement(ByVal columnInfo As TableSchemaDataSet.TableSchemaRow) As XElement Select Case columnInfo.DataType.ToLower Case "datetime", "int", "smallint", "money" Return <TextBox Height="28" Name=<%= "txt" & columnInfo.ColumnName %> Text=<%= "{Binding Path=" & columnInfo.ColumnName & "}" %>/> Case "bit" Return <CheckBox HorizontalContentAlignment="Left" Name=<%= "chk" & columnInfo.ColumnName %> IsChecked=<%= "{Binding Path=" & columnInfo.ColumnName & "}" %>> <%= columnInfo.ColumnName %> </CheckBox> Case Else Return <TextBox Height="28" Name=<%= "txt" & columnInfo.ColumnName %> MaxLength=<%= columnInfo.MaxLength %> Text=<%= "{Binding Path=" & columnInfo.ColumnName & "}" %>/> End Select End Function

Now that we've got the UI defined I'll set the primary key field (which is a TableSchemaDataRow object) so that we can use this in our UPDATE statement as well as in the SELECT query when the user clicks the Find button on the form. Generally primary keys are surrogate keys (like auto-incrementing integers) and mean nothing to the user, so instead you may want to create another Public property that captures the search field name. Since there is only one primary key field on a table, I use the FirstOrDefault() extension method which returns the first of the sequence.

Private Sub SetPrimaryKey()
    'Grab the Primary Key column of the table we want to edit so we can use it in the search
    Me.PKField = (From column In Me.TableSchema Where column.IsPrimaryKey = 1).FirstOrDefault()
End Sub

To create the SELECT statement, notice that I'm once again using XML literals but this time I'm not creating XML. Instead I'm creating a string by calling the XElement's .Value property. We then can create the SqlCommand and fill our untyped DataTable with the results. Setting the WPF form's DataContext sets up the data binding to the fields we specified when we generated the XAML above.

Private Sub btnFind_Click() Handles btnFind.Click