You are ready to create the user interface for your Territory object. First, add a new, inherited form to the NorthwindTraders project and call it frmTerritoryList.vb. When the Inheritance Picker form displays, select the frmListBase form.
Caution | Visual inheritance has a requirement at design-time. Often one form will inherit from another, but when you try to view the inherited form you get an error saying that the form cannot be loaded in the designer. There are a number of different error messages you can get when this occurs, but what it comes down to is that there is a problem in the base form. This is usually caused by some requirement of the constructor that has not been met. The inherited form must be able to compile at design-time! One way of making sure this requirement is met is to always have the default constructor in the base form and just overload it. |
Before you do anything else, you need to alter the constructor on the frmTerritoryList class so that the call to MyBase.New includes the title of the Territory List. It should look like the following:
MyBase.New(Territories)
This form is fairly similar to the frmRegionList form. There are no major differences between the two forms (except for the properties), so go ahead and add the code in Listing 7-11 to your list form (note that I have not included the Windows Forms Designer-generated code).
Listing 7-11: The frmTerritoryList Code Module and Class
Option Explicit On Option Strict On Imports NorthwindTraders.NorthwindUC Public Class frmTerritoryList Inherits.UserInterface..frmListBase Private mobjTerritoryMgr As TerritoryMgr Private WithEvents mfrmEdit As frmTerritoryEdit Private WithEvents mobjTerritory As Territory Public Sub New() MyBase.New("Territories") 'This call is required by the Windows Form Designer. InitializeComponent() 'Add any initialization after the InitializeComponent() call LoadList() End Sub #Region " Windows Form Designer generated code " 'Form overrides dispose to clean up the component list. Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean) If disposing Then If Not (components Is Nothing) Then components.Dispose() End If End If MyBase.Dispose(disposing) End Sub 'Required by the Windows Form Designer Private components As System.ComponentModel.IContainer 'NOTE: The following procedure is required by the Windows Form Designer 'It can be modified using the Windows Form Designer. 'Do not modify it using the code editor. <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent() components = New System.ComponentModel.Container End Sub #End Region Private Sub LoadList() Dim objTerritory As Territory Dim objDictEnt As DictionaryEntry Try lvwList.BeginUpdate() If lvwList.Columns.Count = 0 Then With lvwList .Columns.Add("Territory ID", CInt(.Size.Width / 1) - 8, _ HorizontalAlignment.Left) .Columns.Add("Territory Description", CInt(.Size.Width _ / 1) - 8, HorizontalAlignment.Left) .Columns.Add("Region", CInt(.Size.Width / 1) - 8, _ HorizontalAlignment.Left) End With End If lvwList.Items.Clear() mobjTerritoryMgr = New TerritoryMgr() For Each objDictEnt In mobjTerritoryMgr objTerritory = CType(objDictEnt.Value, Territory) Dim lst As New ListViewItem(objTerritory.TerritoryID) lst.SubItems.Add(objTerritory.TerritoryDescription) lst.SubItems.Add(objTerritory.Region.RegionDescription) lvwList.Items.Add(lst) Next lvwList.EndUpdate() lblRecordCount.Text = "Record Count: " & lvwList.Items.Count Catch exc As Exception LogException(exc) End Try End Sub Protected Overrides Sub AddButton_Click(ByVal sender As Object, _ ByVal e As System.EventArgs) Try If mfrmEdit Is Nothing Then mobjTerritory = New Territory() mfrmEdit = New frmTerritoryEdit(mobjTerritory) mfrmEdit.MdiParent = Me.MdiParent mfrmEdit.Show() End If Catch exc As Exception LogException(exc) End Try End Sub Private Sub mfrmEdit_Closed(ByVal sender As Object, _ ByVal e As System.EventArgs) Handles mfrmEdit.Closed mfrmEdit = Nothing End Sub Private Sub mobjTerritory_ObjectChanged(ByVal sender As Object, _ ByVal e As ChangedEventArgs) _ Handles mobjTerritory.ObjectChanged Try Dim lst As ListViewItem Dim objTerritory As Territory = CType(sender, Territory) Select Case e.Change Case ChangedEventArgs.eChange.Added mobjTerritoryMgr.Add(objTerritory) lst = New ListViewItem(objTerritory.TerritoryID) lst.SubItems.Add(objTerritory.TerritoryDescription) lst.SubItems.Add(objTerritory.Region.RegionDescription) lvwList.Items.Add(lst) lblRecordCount.Text = "Record Count: " _ & lvwList.Items.Count Case ChangedEventArgs.eChange.Updated For Each lst In lvwList.Items If lst.Text = objTerritory.TerritoryID Then lst.SubItems(1).Text = _ objTerritory.TerritoryDescription lst.SubItems(2).Text = _ objTerritory.Region.RegionDescription Exit For End If Next End Select lvwList.Sort() Catch exc As Exception LogException(exc) End Try End Sub Protected Overrides Sub EditButton_Click(ByVal sender As Object, _ ByVal e As System.EventArgs) Try If mfrmEdit Is Nothing Then If lvwList.SelectedItems.Count > 0 Then mobjTerritory = _ mobjTerritoryMgr.Item(lvwList.SelectedItems(0).Text) mobjTerritory.LoadRecord() mfrmEdit = New frmTerritoryEdit(mobjTerritory) mfrmEdit.MdiParent = Me.MdiParent mfrmEdit.Show() End If End If Catch exc As Exception LogException(exc) End Try End Sub Protected Overrides Sub DeleteButton_Click(ByVal sender As Object, _ ByVal e As System.EventArgs) Try Dim objTerritory As Territory objTerritory = mobjTerritoryMgr.Item(lvwList.SelectedItems(0).Text) objTerritory.Delete() mobjTerritoryMgr.Remove(objTerritory.TerritoryID) lvwList.SelectedItems(0).Remove() lblRecordCount.Text = "Record Count: " & lvwList.Items.Count Catch exc As Exception LogException(exc) End Try End Sub End Class
Caution | The one thing to note about this code is the mobjTerritory_ObjectChanged method. If you look at the updated block, you will notice that you are not updating the TerritoryID even though it is a value the user can edit. This is one reason using a key of this type is a terrible idea. I cannot stress that enough. By changing the primary key, you have no identifier by which you can determine what value was changed, so you cannot update the value in the list (or any other place where it might be used for that matter). I do not address how to take care of this because it can be complicated (especially if you have a composite primary key), but be aware that primary keys should be values that the user does not have access to change. This makes your life a lot easier. |
Next, add an inherited edit form to the NorthwindTraders project and call it frmTerritoryEdit.vb. This form should inherit from your frmEditBase form. There are going to be a couple of errors at this point because you have not added your code to the frmTerritoryEdit form. Let's take care of those errors by adding the necessary controls to the edit form and by adding the code you need. When you are done adding controls, the edit form looks like Figure 7-2. Table 7-1 lists the controls and their properties.
Control | Name | Text | Style |
---|---|---|---|
Form | frmTerritoryEdit | Territory [Detail] | |
Label | lblID | Territory ID: | |
Label | lblTerritory | Territory: | |
Label | lblRegion | Region: | |
Textbox | txtTerritoryID | ||
Textbox | txtTerritory | ||
Combobox | cboRegion | DropDownList |
Figure 7-2: Territory edit form
The other controls have already been added by way of the base edit form. Set the tab order according to Figure 7-2. Remember to set the IconAlignment on erpMain property for all of the edit fields to MiddleLeft. Also, set the text alignment for the labels to the bottom right.
Add the following code to the frmTerritoryEdit.vb code module header:
Option Strict On Option Explicit On Imports NorthwindTraders.NorthwindUC
Declare a private module-level variable for the Territory object:
Private WithEvents mobjTerritory As Territory
Modify the constructor so it reads as follows (note that this is almost identical to the frmRegionEdit form):
Public Sub New(ByRef objTerritory As Territory) MyBase.New() 'This call is required by the Windows Form Designer. InitializeComponent() 'Add any initialization after the InitializeComponent() call mobjTerritory = objTerritory If Not mobjTerritory.IsValid Then btnok.Enabled = False End If End Sub
Next, add the code in Listing 7-12 to handle the form's Load event.
Listing 7-12: The frmTerritoryEdit_Load Method
Private Sub frmTerritoryEdit_Load(ByVal sender As Object, _ ByVal e As System.EventArgs) Handles MyBase.Load Dim DictEnt As DictionaryEntry Dim objRegion As Region Dim objRegionMgr As RegionMgr Try objRegionMgr = objRegionMgr.GetInstance For Each DictEnt In objRegionMgr objRegion = CType(DictEnt.Value, Region) cboRegion.Items.Add(objRegion) Next If mobjTerritory.TerritoryID <> "" Then txtTerritoryID.Text = mobjTerritory.TerritoryID txtTerritory.Text = mobjTerritory.TerritoryDescription cboRegion.SelectedItem = mobjTerritory.Region End If Catch exc As Exception LogException(exc) End Try End Sub
First, this method gets a reference to the RegionMgr object and loops through it to add all of the objects to the combo box. Next, it checks to see if this is a new record because if it is a new record, there is no reason to get anything from the object. If it is an existing record, you set the fields so they contain the proper values. Note the following two lines:
cboRegion.Items.Add(objRegion) . . cboRegion.SelectedItem = mobjTerritory.Region
As mentioned in a previous chapter, the value used to fill combo and list boxes when adding an object to them is the value returned by the ToString method. Once the value has been added to the combo box, it is easily selectable by setting the SelectedItem property to the object that you want to select. You no longer have to loop through the combo or list box looking for a string.
Tip | As a fun test, you can comment out the ToString method in the Region object and look at what fills the combo box. This is not very helpful, but when you do see it, you will remember what you forgot to do! |
At this point, there is just about enough code to be able to bring up the territory list and to be able to open the edit form. Before you can do that, however, you need to modify the frmMain form. In the MainMenu_Click method, add the following Case statement:
Case "&Territories" LoadTerritories()
Next, add a module-level variable for the territory list form as follows:
Private mfrmTerritoryList As frmTerritoryList
Finally, add the LoadTerritories routine as shown in Listing 7-13.
Listing 7-13: The LoadTerritories Method
Private Sub LoadTerritories() Try Cursor = Cursors.WaitCursor mfrmTerritoryList = New frmTerritoryList() mfrmTerritoryList.MdiParent = Me mfrmTerritoryList.Show() Catch exc As Exception LogException(exc) Finally Cursor = Cursors.Default End Try End Sub
Up to this point, there is one thing omitted from the code that loads your list forms, and you have probably discovered what it is by now: You can load up multiple list forms for the same item. To ensure there is only one list form for a particular item that is ever loaded, you need to modify your code a little bit. Let's modify the code in frmMain that loads the territory list form. First, change the module-level form variable to be a WithEvents variable. The reason for this is that you need to capture the Closed event that the form generates. Enter the following code to handle the Closed event:
Private Sub mfrmTerritoryList_Closed(ByVal sender As Object, _ ByVal e As System.EventArgs) Handles mfrmTerritoryList.Closed mfrmTerritoryList = Nothing End Sub
Next, modify the LoadTerritories method to check and see if an instance of your variable already exists. Modify the Try block so that it looks like the following:
If mfrmTerritoryList Is Nothing Then Cursor = Cursors.WaitCursor mfrmTerritoryList = New frmTerritoryList mfrmTerritoryList.MdiParent = Me mfrmTerritoryList.Show() Else mfrmTerritoryList.Focus() End If
Now you are guaranteed that only one instance of your territory list form will ever exist at one time. If a user tries to load the form when it has already loaded, the form will get the focus and be moved to the front of all of the other forms. Now you are ready to run the code.
Before running the code, make sure to rebuild the solution and copy the NorthwindDC and the SharedObjects to the directory that is pointed to by the virtual Internet Information Server (IIS) directory! When you have that done, run the application so that you can examine some of the concepts discussed.
When the application loads, open the Territory screen by selecting Territories from the Maintenance menu. Notice all of the Region entries that say Eastern. Next, close this window and open the Region list screen from the Maintenance menu. Select the Eastern region and edit it so that it reads Eastern1. Close these windows and go back to the Territory list. Notice that everything that did read Eastern now reads Eastern1. All this occurs because you referenced the Region objects instead of instantiating your own.
Note | This is what I meant earlier when I said that even though the property set is ByVal, it is really ByRef in this case! |
It is up to you if you want to go back and change Eastern1 to Eastern. Next, select one of the Territory items for editing. Note the combo box values and the selected value. Close all of this and let's go back to adding code to the edit form.
Now you will add the validated events for the three edit fields. These are identical to the one validated event that you created before in the Region edit form, with only a slight difference for the combo box. Listing 7-14 shows the validated events.
Listing 7-14: The Territory Edit Form Validated Events
#Region " Validate Events" Private Sub txtTerritoryID_Validated(ByVal sender As Object, _ ByVal e As System.EventArgs) Handles txtTerritoryID.Validated Dim txt As TextBox = CType(sender, TextBox) Try mobjTerritory.TerritoryID = txt.Text erpmain.SetError(txt, "") Catch exc As Exception erpmain.SetError(txt, "") erpmain.SetError(txt, exc.Message) End Try End Sub Private Sub txtTerritory_Validated(ByVal sender As Object, _ ByVal e As System.EventArgs) Handles txtTerritory.Validated Dim txt As TextBox = CType(sender, TextBox) Try mobjTerritory.TerritoryDescription = txt.Text erpmain.SetError(txt, "") Catch exc As Exception erpmain.SetError(txt, "") erpmain.SetError(txt, exc.Message) End Try End Sub Private Sub cboRegion_Validated(ByVal sender As Object, ByVal e As _ System.EventArgs) Handles cboRegion.Validated Dim cbo As ComboBox = CType(sender, ComboBox) Try mobjTerritory.Region = CType(cbo.SelectedItem, Region) erpmain.SetError(cbo, "") Catch exc As Exception erpmain.SetError(cbo, "") erpmain.SetError(cbo, exc.Message) End Try End Sub #End Region
The cboRegion_Validated method uses a combo box object, but notice the line of code that assigns the value in the combo box to the Region value in your Territory object. You need to perform a conversion on the SelectedItem so that you can assign it to the Territory object.
Tip | I have enclosed the validate events in a Validate Events region. It is a good idea to put like groups of methods together into a region so that they do not get in your way—especially when the coding is completed and you are 99-percent positive that there are no errors within that block of code. Who wants to keep looking at the same code when it is not important in the context of what you are trying to accomplish at the time? |
Next, add the code for the btnOK.Click method as shown in Listing 7-15. This code is identical to the code in the frmRegionEdit form except that it works on an object.
Listing 7-15: The btnOK_Click Method
Private Sub btnOK_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles btnOK.Click Try If mobjTerritory.IsDirty Then mobjTerritory.Save() End If Close() Catch exc As Exception LogException(exc) End Try End Sub
Finally, add the code in Listing 7-16 to handle the BrokenRules and GetBusinessRules events.
Listing 7-16: Handling the Broken Rules and Business Rules Events
Private Sub mobjRegion_BrokenRule(ByVal IsBroken As Boolean) _ Handles mobjTerritory.BrokenRule If IsBroken Then btnOK.Enabled = False Else btnOK.Enabled = True End If End Sub Private Sub btnRules_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles btnRules.Click Dim frmRules As New frmBusinessRules(mobjTerritory.GetBusinessRules) frmRules.ShowDialog() frmRules = Nothing End Sub
At this point, you can now add, edit, delete, and print the territories.