Using the CurrencyManager to Manage your Data Binding

Now that you've learned how to bind an entire table to a DataGrid and bind a table to a ComboBox for viewing one row at a time, the next step is to learn how to use the CurrencyManager. No, this has nothing to do with money. Currency refers to the current state, including the currently active row and mode (editing, adding, etc.) for a specific data source. Windows Forms has built-in support for keeping track of the current state of bound data on your form, and the CurrencyManager object exposes that state information to you. Using the methods and properties of the CurrencyManager object, you can add navigation support to your form.

For this demo, you are again going to use the Customers table in the Northwind database, so create a new Windows Forms application and set up the data objects as you did before. First things first, you need to lay out the controls the way you want to display the details of a single customer. Also, if you're still a little bit confused by what I mean by adding navigation, Figure 5.8 shows controls that will be used to move forward and backward through the data source.

Figure 5.8. The Access-like interface for navigating through the data in the Customers table.


In this example I have used a standard naming convention for all the controls. All controls that you are going to bind to are the same name as the field you are going to bind to it, but with a control prefix in front of it. The prefixes I used are lbl for Labels and txt for TextBoxes. The buttons and label across the bottom are named btnFirst, btnPrevious, lblPosition, btnNext and btnLast, respectively.

Currency Management in Windows Forms is on a perform basis, so you can get a reference to the currency manager object from the form itself through the BindingContext property.

MyBindingManager = _
    Me.BindingContext(dsNorthwind, "Customers")

The first parameter is the DataSource you want to get the binding manager for. The second and optional parameter is used if you need to specify a specific DataMember. In this case, you're only going to be dealing with the Customers table inside the dsNorthwind DataSet. This will return the instance of the BindingManagerBase class that you will need to keep track of the currency of the data. The BindingContext property represents the collection of all the BindingManager instances for the current Windows Form, one for each datasource that is data bound on that Form. This collection returns a BindingManagerBase type, but you can cast it into a CurrencyManager object as that is its true underlying type. You will be accessing the CurrencyManager object quite a lot in your code, so it is best to store it as a private form level variable so that you can use it as needed. Listing 5.5 demonstrates obtaining the CurrencyManager object and storing it as a form level variable.

Listing 5.5. Saving an Instance of the CurrencyManager Class for our Customers Data Source
Dim cmCustomers as CurrencyManager
Private Sub Form1_Load(ByVal sender As Object, _
        ByVal e As System.EventArgs) _
        Handles MyBase.Load
     SqlDataAdapter1.Fill(dsNorthwind)
     cmCustomers = DirectCast( _
        Me.BindingContext( _
            dsNorthwind, "Customers"), _
        CurrencyManager)
End Sub

Now that you have cmCustomers to use throughout your code, you need to “link” the controls on the form to the binding manager, so it will know what data to show in what control. For this, you will create a new procedure, as shown in Listing 5.6.

Listing 5.6. Set up the bindings on all controls.
Private Sub SetupControlBindings()
     lblID.DataBindings.Add("Text", _
         dsNorthwind, "Customers.CustomerID")
     txtCompanyName.DataBindings.Add("Text", _
         dsNorthwind, "Customers.CompanyName")
     txtContactName.DataBindings.Add("Text", _
         dsNorthwind, "Customers.ContactName")
     txtContactTitle.DataBindings.Add("Text", _
         dsNorthwind, "Customers.ContactTitle")
     txtAddress.DataBindings.Add("Text", _
         dsNorthwind, "Customers.Address")
     txtCity.DataBindings.Add("Text", _
         dsNorthwind, "Customers.City")
     txtRegion.DataBindings.Add("Text", _
         dsNorthwind, "Customers.Region")
     txtPostalCode.DataBindings.Add("Text", _
         dsNorthwind, "Customers.PostalCode")
     txtCountry.DataBindings.Add("Text", _
         dsNorthwind, "Customers.Country")
     txtPhone.DataBindings.Add("Text", _
         dsNorthwind, "Customers.Phone")
     txtFax.DataBindings.Add("Text", _
         dsNorthwind, "Customers.Fax")
End Sub
				

Each control has a DataBindings property, which is a collection of bindings. Each binding is basically a map that links a property of the control to a specific property/field of the data source. So each line in the new SetupControlBindings procedure is setting up a mapping, in this case from the Text property, to whatever field in the table you want. Let's set up the load event for the form to load up the data and set up all of the bindings for the controls. See Listing 5.7.

Listing 5.7. Load the DataSet and set up the Bindings
Dim cmCustomers as CurrencyManager
Private Sub Form1_Load(ByVal sender As Object, _
        ByVal e As System.EventArgs) _
        Handles MyBase.Load
     SqlDataAdapter1.Fill(dsNorthwind)
     cmCustomers = DirectCast( _
        Me.BindingContext( _
            dsNorthwind, "Customers"), _
        CurrencyManager)
     SetupControlBindings()
End Sub

Now that you have the data and all of the bindings, you need to give the user interface some control over the data. You need to link the buttons to some sort of action to the binding manager, to navigate through the data. The Position property is an integer and can be set to any number from zero to the number of records minus one. Like all arrays and collections in .NET, the Rows collection in the DataTable is zero-based, meaning that the first position is zero, instead of one. The basic idea of using the buttons to navigate is simple. If you want to go to the first record, set the position to zero. If you want to go to the next record, you'll just set the position to one number higher than it is already at and so on. Listing 5.8 shows how to use the cmCustomers variable you set up earlier to change the current position that you will be viewing in the data.

Listing 5.8. Change the Position Based on Which Navigation Button was Clicked.
Private Sub btnFirst_Click( _
    ByVal sender As System.Object, _
    ByVal e As System.EventArgs) _
    Handles btnFirst.Click
     cmCustomers.Position = 0
End Sub

Private Sub btnPrevious_Click( _
    ByVal sender As System.Object, _
    ByVal e As System.EventArgs) _
    Handles btnPrevious.Click
     cmCustomers.Position -= 1
End Sub

Private Sub btnNext_Click( _
    ByVal sender As System.Object, _
    ByVal e As System.EventArgs) _
    Handles btnNext.Click
     cmCustomers.Position += 1
End Sub

Private Sub btnLast_Click( _
    ByVal sender As System.Object, _
    ByVal e As System.EventArgs) _
    Handles btnLast.Click
     cmCustomers.Position = cmCustomers.Count – 1
End Sub
				

Now that you have the code to navigate through the data source using the currency manager, you need some visual cues to let the user know where they currently are and also make sure the buttons don't allow them to go past the end or beginning of the data source. Again, you'll use the currency manager, as shown in Listing 5.9, to come up with the text for the label as well as to determine whether each button should be enabled or disabled.

Listing 5.9. Changing the State of the Navigation, so the User Knows Where They Are
Private Sub cmCustomers_PositionChanged( _
      ByVal sender As Object, _
      ByVal e As EventArgs)
     Dim CurrentPosition As Integer = cmCustomers.Position
     Dim CurrentCount As Integer = cmCustomers.Count

     lblPosition.Text = String.Format( _
        "Record {0} of {1}", _
        CurrentPosition + 1, CurrentCount)

     btnFirst.Enabled = CurrentPosition > 0
     btnPrevious.Enabled = CurrentPosition > 0
     btnNext.Enabled = CurrentPosition < CurrentCount – 1
     btnLast.Enabled = CurrentPosition < CurrentCount – 1
End Sub

You'll notice that the code retrieves the Position and Count properties from the binding manager and stores them in variables. This process saves a bit of typing and it's best to store the properties' values and reuse the values instead of accessing them over and over again. It's quite possible that they might have a bit of logic in them to determine their current state, so you're saving a little processing power, which can't hurt.

The code then sets the lblPosition.Text using String.Format, to more easily concatenate what would be four different pieces. Instead of combining the items using the & operator, you provide a format string using placeholders, and then provide the items to be inserted into those positions. The last four lines simply return a Boolean from an evaluation to make sure that the buttons aren't enabled when they shouldn't be; preventing the position to be changed when it would produce an invalid result.

The procedure looks like an event handler (with sender and EventArgs parameters) because it is going to be hooked up to handle an event raised by the CurrencyManager instance.

You will want this code to execute every time you change position in the data source, because you will need to update the label and button states. Luckily for you, there is an event of the CurrencyManager class called PositionChanged. Visual Basic .NET allows you to easily attach to events at runtime, so you can modify the load event (like in Listing 5.10) so the method will be attached to the PositionChanged event of cmCustomers.

Listing 5.10. Adding an Event Handler to the PositionChanged Event of the BindingManagerBase Class
Private Sub Form1_Load(ByVal sender As Object, _
                       ByVal e As System.EventArgs) _
            Handles MyBase.Load
     SqlDataAdapter1.Fill(dsNorthwind)
     cmCustomers = DirectCast( _
        Me.BindingContext( _
            dsNorthwind, "Customers"), _
        CurrencyManager)

     SetupControlBindings()

     AddHandler cmCustomers.PositionChanged, _
         AddressOf cmCustomers_PositionChanged
End Sub
				

The AddHandler clause will set it up so whenever the PositionChanged event of cmCustomers occurs, the method that you wrote to check button states and show what record you're on will also be called. For more information about dynamic event handlers, take a look at Chapter 3. Alternatively, you could declare the cmCustomers variable with the WithEvents keyword and then add an event handler using the left and right hand side combo boxes in the Visual Studio code editor. This would produce slightly different code, as shown in Listing 5.11.

Listing 5.11. Using WithEvents Removes the Need for Dynamic Event Handling in Some Situations
Dim WithEvents cmCustomers As CurrencyManager

Private Sub Form1_Load( _
    ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles MyBase.Load
    SqlDataAdapter1.Fill(dsNorthwind)
    cmCustomers = DirectCast( _
            Me.BindingContext( _
            dsNorthwind, "Customers"), _
        CurrencyManager)
    SetupControlBindings()
End Sub

Private Sub cmCustomers_PositionChanged( _
    ByVal sender As Object, _
    ByVal e As System.EventArgs) _
    Handles cmCustomers.PositionChanged
    Dim CurrentPosition As Integer = cmCustomers.Position
    Dim CurrentCount As Integer = cmCustomers.Count

    lblPosition.Text = String.Format( _
       "Record {0} of {1}", _
       CurrentPosition + 1, CurrentCount)

    btnFirst.Enabled = CurrentPosition > 0
    btnPrevious.Enabled = CurrentPosition > 0
    btnNext.Enabled = CurrentPosition < CurrentCount - 1
    btnLast.Enabled = CurrentPosition < CurrentCount - 1
End Sub
				

With all of this code in place, you can now run the application. You'll notice that immediately, there is data populated in the different controls of the form. You can click any of the buttons also and notice that the appropriate record is shown, along with the button's enabled states. The position label is updated accordingly. All of that code you would have needed to write to keep your UI in sync with your data source isn't necessary. You get that all for free, because it's built right into Windows Forms and ready for you to use. You might also notice that the binding manager even takes care of pushing any changed data from the UI back into the data source for you. There are additional methods on CurrencyManager that you might also want to take a look at. The AddNew method adds a new Row to your data source, as well as moves the position to that row. The RemoveAt method is used to remove a row at a specific position.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.116.87.196