-
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!
-
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!
-
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!
-
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!
-
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!
-
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!
-
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!
-
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!
-
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!
-
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!
-
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!
-
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