Editing Tabular Data in WPF Using the Winforms DataGridView
A pretty common requirement of any business application is to be able to edit data in a "spreadsheet" or tabular style. Usually we use some sort of data grid to do this. When building WPF applications in Visual Studio 2008 you'll notice right away that there is no such control available in the toolbox. Using WPF puts you in complete control of your UI, which is awesome, but in most cases you just need an "out-of-the-box" control for handling data editing like a grid. The WPF team is working on a data grid control which you can check out but in the meantime there are a couple things you can do besides buying a third-party data grid control. In this first post I'll discuss how you can use the Windows Forms DataGridView, in the next post I'll show you how you can "roll your own".
Using WindowsFormsHost
One option is to use the Winforms DataGridView control on your WPF windows. One of the controls available in your WPF toolbox is called a WindowsFormsHost. This control allows you to display any Windows.Forms.Control, including the DataGridView on a WPF window. An easy way to set this up is to create a Winforms UserControl with your data-bound DataGridView on it using the standard technique via the Data Sources window.
For instance, create a new Windows Forms UserControl (Project -> Add New Item -> Under Windows Forms select User Control). Then connect to your data source via the Data Sources window like normal and drag the table you want to edit onto the UserControl. The designer will automatically create the BindingSource and any other components you need. If you are working with DataSets this will include the TableAdapter and TableAdapterManager as well. This process also adds the usual Winforms BindingNavigator and a ToolStrip onto the top of the control. You can remove that if you're planing on creating your own WPF controls for this. I also like setting the BackgroundColor of the DataGridView to the system color "Control" so it looks more WPF-ish ;-)
I named my Winforms UserControl MyGridControl. You'll probably want to add Public methods to Fill and Save the data in the UserControl but for this example we'll interact with the data components on the WPF window directly (TableAdapter, TableAdapterManager and DataSet in this case). Since the default scope is Friend for these components this works for now.
Now we can go back to our WPF window and drag a WindowsFormsHost from the toolbox onto the design surface.
Now we need to set the WindowsFormsHost's Child property to our WinForms UserControl. You cannot do this through the designer so we have to write a couple lines of code (you won't see any of your Winforms components when you have the WPF designer open.) Instead just double-click on the window to add a Window_Loaded event handler. Then just set the Child property of the WindowsFormsHost to a new instance of the UserControl:
Class Window1
Dim WithEvents UC As New MyGridControl
Private Sub Window1_Loaded() Handles MyBase.Loaded
Me.UC.CustomerTableAdapter.Fill(Me.UC.CustomerDataSet.Customer)
Me.WindowsFormsHost1.Child = Me.UC
End Sub
End Class
Notice that I'm simply filling the CustomerDataSet from here by accessing the TableAdapter directly. But typically you would want to encapsulate this in a public Fill method on the UserControl itself. Okay so now when you run the application, you will see the DataGridView filled with data. But what if we want this data to interact with other controls (WPF controls that is) on our Window?
Interacting with Data Across Winforms and WPF Controls
So now we want some WPF buttons on our window for New and Delete as well as Save. The easiest thing to do in this case is interact directly with the UserControl's BindingSource.
Private Sub btnNew_Click() Handles btnNew.Click
With Me.UC.CustomerBindingSource
.AddNew()
'Data must be valid before this call (implement validation on the DataSet)
.EndEdit()
End With
End Sub
Private Sub btnDelete_Click() Handles btnDelete.Click
With Me.UC.CustomerBindingSource
If .Position > -1 Then
.RemoveAt(.Position)
End If
End With
End Sub
Private Sub btnSave_Click() Handles btnSave.Click
With Me.UC
.Validate()
.CustomerBindingSource.EndEdit()
.TableAdapterManager.UpdateAll(.CustomerDataSet)
End With
End Sub
When we run the form again, clicking on the New and Delete buttons puts the DataGridView into the correct row selection and state. But what if you want to also display information from rows here in other WPF controls on the form as well? For instance, what if we also want to data bind some WPF TextBoxes , how do we coordinate what's displayed in the TextBoxes with the selection in the DataGridView?
Like I've discussed before, WPF manages currency through what's called a CollectionView. The BindingListCollectionView gets created when binding WPF controls to BindingList collections or DataTables. We just need to coordinate the BindingSource.Position with the BindingListCollectionView.CurrentPosition. First let's set up a couple data bound TextBoxes in our XAML:
<TextBox
Name="txtFirst"
Text="{Binding Path=FirstName}"
Height="28" Width="150" Margin="2" HorizontalAlignment="Left" />
<TextBox
Name="txtLast"
Text="{Binding Path=LastName}"
Height="28" Width="150" Margin="2" HorizontalAlignment="Left" />
<TextBox
Name="txtCity"
Text="{Binding Path=City}"
Height="28" Width="150" Margin="2" HorizontalAlignment="Left" />
Then in our Window_Loaded method we can set the window's DataContext property to the Customer table and then we can grab a reference to the BindingListCollectionView. So our code now looks like this:
Class Window1
Dim WithEvents UC As New MyGridControl
Dim View As BindingListCollectionView
Private Sub Window1_Loaded() Handles MyBase.Loaded
Me.UC.CustomerTableAdapter.Fill(Me.UC.CustomerDataSet.Customer)
Me.WindowsFormsHost1.Child = Me.UC
Me.DataContext = Me.UC.CustomerDataSet.Customer
Me.View = CType(CollectionViewSource.GetDefaultView(Me.DataContext), BindingListCollectionView)
End Sub
The easiest way to sync the position is to have the UserControl raise an event when the BindingSource current item changes and pass the new position to a handler on our WPF window. So back in the UserControl let's write some code to do this. I'm first going to create an EventArgs class that I can use to store the position called PositionChangedEventArgs:
Public Class PositionChangedEventArgs
Inherits EventArgs
Sub New(ByVal position As Integer)
_position = position
End Sub
Private _position As Integer
Public Property Position() As Integer
Get
Return _position
End Get
Set(ByVal value As Integer)
_position = value
End Set
End Property
End Class
Then in the UserControl we declare a public event and pass this event argument. We're just handling the BindingSource.CurrentChanged event and raising our own with the data that we need to send (the position).
Public Class MyGridControl
Public Event PositionChanged(ByVal sender As Object, ByVal e As PositionChangedEventArgs)
Private Sub OnPositionChanged(ByVal position As Integer)
RaiseEvent PositionChanged(Me, New PositionChangedEventArgs(position))
End Sub
Private Sub CustomerBindingSource_CurrentChanged() Handles CustomerBindingSource.CurrentChanged
Me.OnPositionChanged(Me.CustomerBindingSource.Position)
End Sub
End Class
So now anytime the user clicks on a row in the DataGridView this event will fire. Back in the WPF window you'll notice that I declared the UC variable WithEvents which mean we can add a declarative event handler using the Handles clause (or just drop down the Class Name DropDown at the top of the editor, select the UC variable and then in the Method Name DropDown you'll see our PositionChanged event). In the handler just set the CurrentPosition of the BindingListCollectionView to the Position sent in the PositionEventArgs parameter.
Private Sub UC_PositionChanged(ByVal sender As Object, _
ByVal e As PositionChangedEventArgs) _
Handles UC.PositionChanged
If Me.View IsNot Nothing Then
Me.View.MoveCurrentToPosition(e.Position)
End If
End Sub
You'll first want to make sure that the View reference is not nothing (null) before trying to set it. This is because this event will fire when the data is filled. Now when you run the form you will see the coordination between the Winforms control and the WPF controls.
I uploaded the above sample onto Code Gallery here:https://code.msdn.microsoft.com/wpftabulardata. In the next post I'll show a way to create your own basic grid using the WPF ListView.
Enjoy!
Comments
- Anonymous
September 04, 2008
PingBack from http://www.easycoded.com/editing-tabular-data-in-wpf-using-the-winforms-datagridview/ - Anonymous
September 10, 2008
Hi, I am Gabriela Vega a i need your helpHow can I exoport an sql table in a dataenvironment to excell???Please can you help me???RegardsCan you ansewer me to gvega@uag.mx - Anonymous
September 15, 2008
In my last post on WPF I showed how you could use a Winforms DataGridView on a WPF form in order to edit - Anonymous
September 17, 2008
porbably a dumb question, but what's WPF??? - Anonymous
September 17, 2008
Hi Logan,WPF is Windows Presentation Foundation, a new UI technology that lets you build much richer user experiences. Get start here: http://msdn.microsoft.com/en-us/library/ms754130.aspxHTH,-B - Anonymous
October 20, 2008
In my last post on WPF I showed how you could use a Winforms DataGridView on a WPF form in order to edit - Anonymous
July 17, 2009
Hi Beth, this is Nick. I would need help with passing data from a textbox to a datagridview by push of a button.Using VB2008express edition. I also have other problems with buttons such as next, addnew and commit to add a new record to a accdb database.Would u be able to help?ThanksNick - Anonymous
July 17, 2009
Oh, this is Nick one more time...I think that I forgot to provide u with my email address so u can get back to me. Here it is reinerof94@yahoo.com. I also have ablog on the msdn forum with my username of learningvb8 under the forum Windows Forms Data Controls and Databinding.Need HelpThanksNick - Anonymous
December 08, 2011
Hi everyone, I Want the code in ASP .NET While clicking on the row of datagridview the data will appear in the textbox to edit.