Alessandro Del Sole's Blog

/* A programming space about Microsoft® .NET® */
posts - 159, comments - 0, trackbacks - 0

My Links

News

Your host

This is me! This space is about Microsoft® .NET® and Microsoft® Visual Basic development. Enjoy! :-)

These postings are provided 'AS IS' for entertainment purposes only with absolutely no warranty expressed or implied and confer no rights.

Microsoft MVP

My MVP Profile

I'm a VB!

Watch my interview in Seattle

My new book on VB 2015!

Pre-order VB 2015 Unleashed Pre-order my new book "Visual Basic 2015 Unleashed". Click for more info!

My new book on LightSwitch!

Visual Studio LightSwitch Unleashed My book "Visual Studio LightSwitch Unleashed" is available. Click the cover!

Your visits

Follow me on Twitter!

CodePlex download Download my open-source projects from CodePlex!

Article Categories

Archives

Post Categories

.NET Framework

Blogroll

Help Authoring

Microsoft & MSDN

Setup & Deployment

Visual Basic 2005/2008/2010

WPF: the drag'n'drop data-binding in Visual Basic 2010 Beta 1 - part one (DataSet)

One of the most important new features that is available in Microsoft Visual Studio 2010 Beta 1 is the drag'n'drop data-binding for Windows Presentation Foundation applications which works very similarly to what we already knew about Win Forms.

 

We discussed about this new features in the interview I made to Milind Lele from Microsoft during the MVP Global Summit 2009, so my goal is showing something more about this topic. Obviously, this kind of data-binding could need some additional code or additional manual steps to improve the result. In real life scenarios you will probably implement techniques such as the Model-View-ViewModel. By the way, I personally appreciate this new feature because it gives some advantages. First of all, you can use it with different data sources: e.g. with DataSets and Entity Data Models. Second, and perhaps this is the most important aspect, this feature is useful for those developers who are beginners with the WPF data-binding because they can easily understand what are the primary objects they need to build data-centric applications with WPF. Last but not least, nothing is immutable: we can modify every object or line of code generated by Visual Studio according to our needs.

 

So I'd like to begin writing a small blog posts series about the drag'n'drop data-binding for WPF in Dev10, dividing the series into 4 blog posts:

 

1.    In this first part we'll learn how to use the data-binding versus a DataSet object which is based on an Access database. Our objective here is building a representation for tabular data implementing the new DataGrid control;

 

2.  In the next blog post we'll create the same kind of application, for representing tabular data but this time we'll refer to an Entity Data Model which is based on a SQL Server database;

 

3.  In the third part we'll retake the DataSet - Access topic for building a master-detail WPF form, implementing UI controls for navigating between items;

 

4.    In the last part we'll do the same as the third post, but this time we'll use an Entity Data Model and ADO.NET Entity Framework.

 

Each demo will require the Northwind database, both the Access version and the SQL Server one. Ok, now it's time to get our hands dirty on the Visual Studio 2010 Beta 1.

 

Creating the project and connecting to the database

 

Once opened the IDE, let's create a new WPF project with Visual Basic 2010, ensuring that .NET Framework 4.0 is selected as the target framework:

Once the project is created, let's add a new data source using the Data|Add new data source command. In the first step of the wizard, let's select Database:

In the next step, we must select the DataSet:

The next step will allow us to specify the database, choosing also the connection settings:

In the last step we have to choose what tables must be represented by the new DataSet; we're going to select Customers and Orders, which are useful to see in action the new DataGrid:

At this point the new DataSet is generated as we would usually expect, so we have to do nothing more on the new object while it's very important to compile the project, so that all references to data can be updated.

 

So let's open the Data Sources window via the command called Data|Show Data Sources. As we can see, in the Data Source windo there are two objects that represent the tables in the database:

Now let's imagine we want to create a window where we can select a customer from a customers list stored inside a ComboBox and, when selected, all the orders related to that customers are shown. First of all, from the Data Source window let's expand the Customers item then let's select the CompanyName property changing the view into ComboBox, as shown in the following figure:

 

Now let's drag with the mouse the CompanyName onto the Window and release. When released, we should get something like the result shown below (you can of course rearrange UI items):

You'll also notice how the IDE generated some XAML code which is the markup representation of our drag'n'drop action:

 

<Window x:Class="Window1"

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

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

    Title="Window1" Height="300" Width="600" xmlns:my="clr-namespace:TabularDataDemoWithDataSet">

    <Window.Resources>

        <my:NorthwindDataSet x:Key="NorthwindDataSet" />

        <CollectionViewSource x:Key="CustomersViewSource" Source="{Binding Path=Customers, Source={StaticResource NorthwindDataSet}}" />

    </Window.Resources>

    <Grid>

        <Grid DataContext="{StaticResource CustomersViewSource}" Height="31.96" Margin="0,0,348,230" Name="Grid1"

              Width="230.136666666667">

            <Grid.ColumnDefinitions>

                <ColumnDefinition Width="Auto" />

                <ColumnDefinition Width="Auto" />

            </Grid.ColumnDefinitions>

            <Grid.RowDefinitions>

                <RowDefinition Height="Auto" />

            </Grid.RowDefinitions>

            <Label Content="Company Name:" Grid.Column="0" Grid.Row="0" HorizontalAlignment="Left" Margin="3"

                   VerticalAlignment="Center" />

            <ComboBox DisplayMemberPath="CompanyName" Grid.Column="1" Grid.Row="0" Height="23"

                      HorizontalAlignment="Left" IsSynchronizedWithCurrentItem="True"

                      ItemsSource="{Binding}" Margin="3" Name="CompanyNameComboBox" SelectedIndex="0"

                      VerticalAlignment="Center" Width="120">

                <ComboBox.ItemsPanel>

                    <ItemsPanelTemplate>

                        <VirtualizingStackPanel />

                    </ItemsPanelTemplate>

                </ComboBox.ItemsPanel>

            </ComboBox>

        </Grid>

    </Grid>

</Window>

 

It's worth mentioning that the IDE generated a new CollectionViewSource object that basically is the XAML counterpart of the Win Forms BindingSource, so a kind of "bridge" between the data and the UI.

It has been declared inside the Window's resources and its source is defined via binding, pointing to the collection named Customers (Binding Path=Customers) exposed by the DataSet (Source={StaticResource NorthwindDataSet}).

 

Also notice how there is a Grid containing a Label and the ComboBox for choosing the customer. In such Grid the IDE assigned the DataContext property, so that nested controls will get populated right by this property which is pointing to the above declared CollectionViewSource. The ComboBox is also populated via data-binding and retrieves a list of Customers. For each Customer, the Combo will show the value of the CompanyName property (see DisplayMemberPath).

 

Let's switch to the Data Sources window and select the Ordes object (the one nested inside Customers), ensuring that the DataGrid view is selected, as shown in the following figure:

Let's drag the object onto the Window and release, then we should get the following result:

Cool! Visual Studio generated a DataGrid specifying for us also the columns' names. But that's not all. We don't need to manually generated columns because it also excluded the ones referring to navigation properties (which instead we would get setting the automatic columns generation to True).

 

On the XAML side, this is the code that the IDE generated in this moment:

 

        <DataGrid AutoGenerateColumns="False" EnableRowVirtualization="True" Height="200"

                  HorizontalAlignment="Left" IsSynchronizedWithCurrentItem="True"

                  ItemsSource="{Binding}" Margin="0,34,0,0" Name="OrdersDataGrid"

                  RowDetailsVisibilityMode="VisibleWhenSelected" SelectedIndex="0" VerticalAlignment="Top" Width="400">

 

            <DataGrid.Columns>

                <DataGridTextColumn Binding="{Binding Path=OrderID}" Header="Order ID" Width="SizeToHeader" />

                <DataGridTextColumn Binding="{Binding Path=CustomerID}" Header="Customer ID" Width="SizeToHeader" />

                <DataGridTextColumn Binding="{Binding Path=EmployeeID}" Header="Employee ID" Width="SizeToHeader" />

                <DataGridTemplateColumn Header="Order Date" Width="SizeToHeader">

                    <DataGridTemplateColumn.CellTemplate>

                        <DataTemplate>

                            <DatePicker SelectedDate="{Binding Path=OrderDate}" />

                        </DataTemplate>

                    </DataGridTemplateColumn.CellTemplate>

                </DataGridTemplateColumn>

                <DataGridTemplateColumn Header="Required Date" Width="SizeToHeader">

                    <DataGridTemplateColumn.CellTemplate>

                        <DataTemplate>

                            <DatePicker SelectedDate="{Binding Path=RequiredDate}" />

                        </DataTemplate>

                    </DataGridTemplateColumn.CellTemplate>

                </DataGridTemplateColumn>

                <DataGridTemplateColumn Header="Shipped Date" Width="SizeToHeader">

                    <DataGridTemplateColumn.CellTemplate>

                        <DataTemplate>

                            <DatePicker SelectedDate="{Binding Path=ShippedDate}" />

                        </DataTemplate>

                    </DataGridTemplateColumn.CellTemplate>

                </DataGridTemplateColumn>

                <DataGridTextColumn Binding="{Binding Path=ShipVia}" Header="Ship Via" Width="SizeToHeader" />

                <DataGridTextColumn Binding="{Binding Path=Freight}" Header="Freight" Width="SizeToHeader" />

                <DataGridTextColumn Binding="{Binding Path=ShipName}" Header="Ship Name" Width="SizeToHeader" />

                <DataGridTextColumn Binding="{Binding Path=ShipAddress}" Header="Ship Address" Width="SizeToHeader" />

                <DataGridTextColumn Binding="{Binding Path=ShipCity}" Header="Ship City" Width="SizeToHeader" />

                <DataGridTextColumn Binding="{Binding Path=ShipRegion}" Header="Ship Region" Width="SizeToHeader" />

                <DataGridTextColumn Binding="{Binding Path=ShipPostalCode}" Header="Ship Postal Code" Width="SizeToHeader" />

                <DataGridTextColumn Binding="{Binding Path=ShipCountry}" Header="Ship Country" Width="SizeToHeader" />

            </DataGrid.Columns>

        </DataGrid>

 

Of course the code is quite long (so we should appreciate even more the IDE's work), but we can also notice how the automatic generation of columns in the DataGrid is set to False, so the IDE generated several DataGridTextColumn. Instead, for fields referring to DateTime objects, Visual Studio automatically defined a custom data template (DataGridTemplateColumn) so that the application can use the new DatePicker control instead of the TextBlock or TextBox, which is much more appropriate. The DataGrid is data-bound and its columns are populated via the properties of the associated collection. 

 

But the question is: how is the Grid effectively populated? The answer is that Dev10 generated a second CollectionViewSource, which is still declared inside the resources:

 

        <CollectionViewSource x:Key="CustomersOrdersViewSource" Source="{Binding Path=CustomersOrders, Source={StaticResource

                              CustomersViewSource}}" />

 

Then, the IDE assigned the DataContext property of the Grid:

 

    <Grid DataContext="{StaticResource CustomersOrdersViewSource}">

 

Now it's time to go and see what happened in the Visual Basic code-behind file. The tasks we performed until now caused the IDE to generate the following Visual Basic code:

 

    Private Sub Window_Loaded(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles MyBase.Loaded

 

        Dim NorthwindDataSet As TabularDataDemoWithDataSet.NorthwindDataSet = CType(Me.FindResource("NorthwindDataSet"),

                                                                              TabularDataDemoWithDataSet.NorthwindDataSet)

        'Load data into the table Customers. You can modify this code as needed.

        Dim NorthwindDataSetCustomersTableAdapter As TabularDataDemoWithDataSet.NorthwindDataSetTableAdapters.CustomersTableAdapter = _

            New TabularDataDemoWithDataSet.NorthwindDataSetTableAdapters.CustomersTableAdapter()

        NorthwindDataSetCustomersTableAdapter.Fill(NorthwindDataSet.Customers)

        Dim CustomersViewSource As System.Windows.Data.CollectionViewSource = CType(Me.FindResource("CustomersViewSource"),

                                                                              System.Windows.Data.CollectionViewSource)

        CustomersViewSource.View.MoveCurrentToFirst()

        'Load data into the table Orders. You can modify this code as needed.

        Dim NorthwindDataSetOrdersTableAdapter As TabularDataDemoWithDataSet.NorthwindDataSetTableAdapters.OrdersTableAdapter = _

            New TabularDataDemoWithDataSet.NorthwindDataSetTableAdapters.OrdersTableAdapter()

        NorthwindDataSetOrdersTableAdapter.Fill(NorthwindDataSet.Orders)

        Dim CustomersOrdersViewSource As System.Windows.Data.CollectionViewSource = CType(Me.FindResource("CustomersOrdersViewSource"),

                                                                                          System.Windows.Data.CollectionViewSource)

        CustomersOrdersViewSource.View.MoveCurrentToFirst()

    End Sub

 

As you can see, it's basically code that instantiates and populates the DataSet. Moreover, the code retrieves the instance of the CollectionViewSources so that we can have a managed reference to the data. Among other things, such behavior can realize the two-ways data-binding, so we can consequently save to the database all the changes we make via the DataGrid in a simple way, as we'll see in a few moments.

 

This is basically all the source code generated by default. As you can easily understand, you might want some modifications to fine-tune the application's behavior. For example you could modify the query that fetches the data instead of retrieving all the data from a table.

 

Let's go back to the XAML code editor, so that we can give a better aspect to the UI implementing also a button for saving changes. First, let's divide the main Grid into three rows:

 

    <Grid DataContext="{StaticResource CustomersOrdersViewSource}">

        <Grid.RowDefinitions>

            <RowDefinition Height="40" />

            <RowDefinition />

            <RowDefinition Height="40" />

        </Grid.RowDefinitions>

 

Then let's move the controls for selecting the customer to the first row, removing Width, Height and Margin properties:

 

        <Grid DataContext="{StaticResource CustomersViewSource}" Name="Grid1" Grid.Row="0">

 

Let's do something similar to the DataGrid, moving it to the central row:

 

        <DataGrid AutoGenerateColumns="False" EnableRowVirtualization="True" IsSynchronizedWithCurrentItem="True"

                  ItemsSource="{Binding}" Name="OrdersDataGrid"

                  RowDetailsVisibilityMode="VisibleWhenSelected" SelectedIndex="0" Grid.Row="1">

 

At last, let's add a new button to the last row, represented by the following:

 

        <Button Grid.Row="2" Width="100" Height="30" Content="Save changes" Name="SaveButton"/>

 

Our Window should now look like this:

 

Let's go back again to the Visual Basic code, where we need to do some edits. First, let's move to class level the declarations for the DataSet and the orders' TableAdapter, that are now defined in the Window1_Loaded event handler:

 

    Private NorthwindDataSet As NorthwindDataSet

    Private NorthwindDataSetOrdersTableAdapter As TabularDataDemoWithDataSet.NorthwindDataSetTableAdapters.OrdersTableAdapter

 

let's change the lines of code that previously instantiated the above objects as follows:

 

        Me.NorthwindDataSet = CType(Me.FindResource("NorthwindDataSet"), TabularDataDemoWithDataSet.NorthwindDataSet)

 

        Me.NorthwindDataSetOrdersTableAdapter = New TabularDataDemoWithDataSet.NorthwindDataSetTableAdapters.OrdersTableAdapter()

 

Now we can handle the Click event for our new Button:

 

    Private Sub SaveButton_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles SaveButton.Click

        Try

            Me.NorthwindDataSetOrdersTableAdapter.Update(Me.NorthwindDataSet.Orders)

            MessageBox.Show("Done!")

        Catch ex As Exception

            MessageBox.Show("An error occurred while attempting to save changes")

        End Try

    End Sub

 

It's worth mentioning that a good practice would be refactoring the code, for example it would be a much better approach encapsulating actions outside the event handlers (or, better, separating the logics from the UI), but what we do need now is understanding how the data-binding works.

 

Now try to run the application and see the magic happen:

Our DataGrid is offering a tabular representation of the data and also allows modifying the data themselves. About dates, we can use the DatePicker. If we try to apply some changes, they will be correctly persisted to the underlying database.

 

So this is the end of the first blog post about the WPF drag'n'drop data-binding in .NET 4.0. As I said before, the manual work which follows the automated one done by the IDE can be significant but I think that this feature is really and really useful to begin getting started with the WPF data-binding techniques. 

 

In the next post we'll see how things change using the ADO.NET Entity Framework in the same kind of application. The source code will be available at the end of the series via the MSDN Code Gallery.

 

Stay tuned! :-)

 

Alessandro

Print | posted on lunedì 15 giugno 2009 22:36 | Filed Under [ Visual Studio 2010 Visual Basic Windows Presentation Foundation ]

Feedback

No comments posted yet.

Post Comment

Title  
Name  
Email
Url
Comment   
Please add 1 and 7 and type the answer here:

Powered by:
Powered By Subtext Powered By ASP.NET