|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
* Two of the DLLs in the demo project's BIN folder are 2gs_datetextbox.dll and 2gs_extendedtextbox.dll. Click here to read about these user controls.
Demo project main form
Demo project New dialog box
Demo project Change dialog box (same form as New dialog box)
Demo project Remove dialog box Contents
IntroductionThe Access Table class library (2gs_accesstable.dll) houses one class.
The demo project comes with an Access database (demo.mdb) that contains a table named Products. The main demo form lists all of the records from the Products table, and provides buttons for adding, changing, and deleting products. A second form is used for both the New and Change functions. Communication of data and states between the main and the second form is done primarily via PO box variables in a common module. BackgroundMany moons ago, I discovered that "DLL Hell" was only one of the mine fields we programmers had to traverse. What I refer to as "MDAC Attack" is a similar pit of horrors with which I became acquainted the first time I prepared an application for distribution that included an Access database. To my chagrin, the MDAC added more than 10 MB to my download file which, without the database interface, was less than 1 MB. Then, I was introduced to the potential version conflicts possible with an MDAC. This experience convinced me to stay away from Access databases for my future applications. In desperation, I began using what I affectionately call normalized text files. The MDAC quirks, along with DLL Hell, have apparently been obviated by the .NET Framework. I am again returning to Access databases as my preferred data storage medium for standalone applications. With my return to the Access fold, I began thinking about a way to simplify the extraction and updating of Access tables in situations such as table maintenance, where the SQL requirements are minimal. I am presently working on three applications, and I can foresee having to write very similar code for many, many table maintenance forms where the database connection string is always the same and the SQL statements differ only in table and column names. It occurred to me that there might be some way to encapsulate all that consistency. I played with the The Using the Class LibraryFollow these steps to make the
At the top of each class and module in which the Imports _2gs_accesstable
The Access Table ClassThere are two functions performed by the Access Table object in its role as an interface between an Access table and the form or forms that maintain the data stored in the table.
Extracting Table DataAfter instantiating a With the connection string and SQL query in place, invoking the Updating Table DataWhat you do with the ConstructorsThe
MethodsThe
PropertiesThe
Sample CodeThese code samples are all from the demo project. Not all possible uses of the Defining the Access TableThe first item of business when setting up to use the In the demo project, the Access database is located in the project's BIN folder. Only the database name is needed for the path. '** The Access database path and table name
Public Const gtACCESS_TABLE_NAME As String = "Products"
Public Const gtDATABASE_PATH As String = "demo.mdb"
'** the Access table column names
Public Const gtTBL_COL_ADDED As String = "Added"
Public Const gtTBL_COL_PRODNUMBER As String = "ProdNumber"
Public Const gtTBL_COL_DESCRIPTION As String = "Description"
Public Const gtTBL_COL_UOM As String = "UOM"
Public Const gtTBL_COL_PRICE As String = "Price"
Public Const gtTBL_COL_ONHAND As String = "OnHand"
Public Const gtTBL_COL_ONORDER As String = "OnOrder"
With the above information about the target Access table in the code, you are ready to begin extracting data from, and updating that table via the Populating the DataTableAs I stated previously, I avoid bound controls like the plague. (Please don't ask why. Let's just leave it as a personal preference forged by experience.) In the demo project, a #Region " Members "
'** A datatable to contain the database table rows being maintained
Dim theDataTable As New DataTable
#End Region#Region " ... PopulateDataTable "
Private Sub PopulateDataTable(Optional ByVal _
targetItem As Integer = Nothing)
'-----------------------------------------------------
' Populate the datatable from the database
' and then the listview from the datatable
' Pass: targetItem When passed, the item number
' to be selected in the
' listview after
' the listview is populated
' Return: N/A
'-----------------------------------------------------
' Date Developer Code Change
' ---------- -------------------- -------------------
' 05/18/2006 G Gilbert Original code
'-----------------------------------------------------
Dim at As New C_AccessTable(gtDATABASE_PATH)
With at
SetDataTableParms(at)
'** Attempt to populate the datatable from the database
If .PopulateDataTable() Then
'** Grab a copy of the populated datatable
theDataTable = .FilledDataTable
'** Populate the listview from the datatable
PopulateListview(targetItem)
Else
'** Populating of the datatable failed. Inform the user
'** and turn off all buttons except Exit.
DisplayOops("Reading of Products from the database failed")
btnNew.Enabled = False
btnChange.Enabled = False
btnRemove.Enabled = False
End If
End With
at = Nothing
End Sub
#End Region
#Region " ... PopulateListview "
Private Sub PopulateListview(Optional ByVal _
targetItem As Integer = Nothing)
'---------------------------------------------------
' Display all of the rows in the datatable
' in the listview
' Pass: targetItem When passed, the number
' of the item to be selected
' in the listview after
' the listview is populated
' Return: N/A
'---------------------------------------------------
' Date Developer Code Change
' ---------- -------------------- -----------------
' 05/11/2006 G Gilbert Original code
'---------------------------------------------------
'---------------------------------------------------
' Populate the listview control
'---------------------------------------------------
With lvwProducts
.BeginUpdate()
.Items.Clear()
For i As Integer = 0 To theDataTable.Rows.Count - 1
Dim li As New ListViewItem
With theDataTable.Rows(i)
Dim dw As New _
C_DateWizard(CType(.Item(gtTBL_COL_ADDED), Date))
li.SubItems(0).Text = dw.BaseDate.ToShortDateString
dw = Nothing
li.SubItems.Add(.Item(gtTBL_COL_PRODNUMBER).ToString)
li.SubItems.Add(.Item(gtTBL_COL_DESCRIPTION).ToString)
li.SubItems.Add(.Item(gtTBL_COL_UOM).ToString)
'** Display two decimal places
' with no thousands separators since
'** the _ThousandsSeparator property
' in the Price Extended Textbox
'** in the New/Change form is set to False
li.SubItems.Add(FormatNumber(.Item(gtTBL_COL_PRICE), _
2, , , _
TriState.False))
li.SubItems.Add(.Item(gtTBL_COL_ONHAND).ToString)
Select Case .Item(gtTBL_COL_ONORDER)
Case True : li.SubItems.Add("Yes")
End Select
End With
.Items.Add(li)
li = Nothing
Next
.EndUpdate()
End With
'--------------------------------------------------
' If a specific item is to be selected, do so;
' otherwise, select the first item
' listed
'--------------------------------------------------
With lvwProducts
If .Items.Count > 0 Then
If targetItem = Nothing Then
'** Select the first item
.Items(0).Selected = True
Else
'** Select the targeted item
For i As Integer = 0 To .Items.Count - 1
If CType(.Items(i).SubItems(1).Text, _
Integer) = targetItem Then
.Items(i).Selected = True
Exit For
End If
Next i
End If
.Select()
End If
End With
End Sub
#End Region
#Region " ... SetDataTableParms "
Private Sub SetDataTableParms(ByRef at As C_AccessTable)
'----------------------------------------------------
' Set the C_AccessTable object parameters
' in preparation for populating the datatable
' Pass: at The C_AccessTable object
' being configured
' Return: N/A
'----------------------------------------------------
' Date Developer Code Change
' ---------- -------------------- ------------------
' 05/20/2006 G Gilbert Original code
'----------------------------------------------------
'** Add an ORDER BY clause to the SQL SELECT
' query to sort on the product number
at.SQLOrderBy = gtTBL_COL_PRODNUMBER
'** Set the Access table name.
' Use the default SQL SELECT command
'** (built in the C_AccessTable class)
' instead of passing a complete
'** SQL string.
at.DatabaseTableName(True) = gtACCESS_TABLE_NAME
End Sub
#End Region
The
Back in the Adding a RecordThe steps for adding a new Product to the demo database are as follows:
Can you find the two lines of code below that accomplish step 3? #Region " ... New "
Private Sub btnNew_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) _
Handles btnNew.Click
'--------------------------------------------------
' Date Developer Code Change
' ---------- -------------------- ----------------
' 05/11/2006 G Gilbert Original code
'--------------------------------------------------
'--------------------------------------------------
' Tell the add/change form to be in add mode
'--------------------------------------------------
gePO_Box_01 = FormMode.Add
'--------------------------------------------------
' Display the form
'--------------------------------------------------
Dim DialogForm As New F_New_Change
With DialogForm
If .ShowDialog = DialogResult.OK Then
'** Retrieve the new product information from the PO box
TurnOnHourglass(Me)
Dim productFields() As String
productFields = Split(gtPO_Box_01, Chr(12))
gtPO_Box_01 = ""
'** Add the product to the datatable
Dim newRow As DataRow = theDataTable.NewRow
newRow(gtTBL_COL_ADDED) = Now().ToShortDateString
newRow(gtTBL_COL_PRODNUMBER) = CType(productFields(0), Integer)
newRow(gtTBL_COL_DESCRIPTION) = productFields(1)
newRow(gtTBL_COL_UOM) = productFields(2)
newRow(gtTBL_COL_PRICE) = CType(productFields(3), Single)
newRow(gtTBL_COL_ONHAND) = CType(productFields(4), Integer)
newRow(gtTBL_COL_ONORDER) = CType(productFields(5), Boolean)
theDataTable.Rows.Add(newRow)
newRow = Nothing
'** Update the database with the new product
Dim insertedOK As Boolean = True
Dim at As New C_AccessTable(gtDATABASE_PATH)
With at
If Not .InsertRow(theDataTable) Then
TurnOnArrow(Me)
DisplayOops("Adding of New Product failed")
insertedOK = False
End If
End With
at = Nothing
'** Refresh the datatable and update the listview if
'** the insert went OK
If insertedOK Then
PopulateDataTable(CType(productFields(0), Integer))
TurnOnArrow(Me)
End If
End If
.Dispose()
End With
'--------------------------------------------------
' Shift the focus back to the listview
'--------------------------------------------------
lvwProducts.Focus()
End Sub
#End Region
Changing a RecordChanging a Product in the demo program is, of course, a little more complex than adding a new Product. The user has to be presented with the existing Product information to which changes can be made.
As stated earlier, for illustration purposes, the #Region " ... Change "
Private Sub btnChange_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) _
Handles btnChange.Click
'-------------------------------------------------
' Date Developer Code Change
' ---------- -------------------- ---------------
' 05/11/2006 G Gilbert Original code
'-------------------------------------------------
'-------------------------------------------------
' Bail if there are no products in the listview
'-------------------------------------------------
If lvwProducts.Items.Count <= 0 Then
Beep()
lvwProducts.Focus()
Exit Sub
End If
'-------------------------------------------------
' Tell the add/change form to be in change mode
'-------------------------------------------------
gePO_Box_01 = FormMode.Change
'-------------------------------------------------
' Build a string to pass the selected
' product info to the dialog box. The date
' added is not passed since that field
' is not entered by the user. Separate the
' product fields with the
' unprintable character Chr(12).
'-------------------------------------------------
Dim sb As New StringBuilder("")
With lvwProducts.SelectedItems(0)
sb.Append(.SubItems(1).Text)
sb.Append(Chr(12))
sb.Append(.SubItems(2).Text)
sb.Append(Chr(12))
sb.Append(.SubItems(3).Text)
sb.Append(Chr(12))
sb.Append(.SubItems(4).Text)
sb.Append(Chr(12))
sb.Append(.SubItems(5).Text)
sb.Append(Chr(12))
If .SubItems.Count > 6 Then
sb.Append("True")
Else
sb.Append("False")
End If
gtPO_Box_01 = sb.ToString
End With
sb = Nothing
'-------------------------------------------------
' Display the form
'-------------------------------------------------
Dim DialogForm As New F_New_Change
With DialogForm
If .ShowDialog = DialogResult.OK Then
'** Retrieve the changed product information from the PO box
TurnOnHourglass(Me)
Dim productFields() As String
productFields = Split(gtPO_Box_01, Chr(12))
gtPO_Box_01 = ""
'** Update the product in the datatable
With theDataTable.Rows(lvwProducts.SelectedItems(0).Index)
.Item(gtTBL_COL_DESCRIPTION) = productFields(1)
.Item(gtTBL_COL_UOM) = productFields(2)
.Item(gtTBL_COL_PRICE) = CType(productFields(3), Single)
.Item(gtTBL_COL_ONHAND) = CType(productFields(4), Integer)
.Item(gtTBL_COL_ONORDER) = CType(productFields(5), Boolean)
End With
'** Update the database with the changed datatable row
Dim updatedOK As Boolean = True
Dim at As New C_AccessTable(gtDATABASE_PATH)
With at
'** Update the database using the C_AccessTable object UpdateRow
'** method
'If Not .UpdateRow(theDataTable, _
' lvwProducts.SelectedItems(0).Index, _
' gtTBL_COL_PRODNUMBER & _
' " = " & _
' productFields(0)) Then
' TurnOnArrow(Me)
' DisplayOops("Changing of the Product failed")
' updatedOK = False
'End If
'** When the CommandBuilder object is used to update the database,
'** care must be taken to ensure the SQL statement used by the
'** DataAdapter (in C_AccessTable) is identical to the SQL
'** statement used to originally populate the datatable.
SetDataTableParms(at)
'** Update the database using CommandBuilder
.FilledDataTable = theDataTable
If .UseCommandBuilder < 0 Then
TurnOnArrow(Me)
DisplayOops("Changing of the Product failed")
updatedOK = False
End If
End With
at = Nothing
'** Refresh the datatable and update the listview if
'** the update went OK
If updatedOK Then
PopulateDataTable(CType(productFields(0), Integer))
TurnOnArrow(Me)
End If
End If
.Dispose()
End With
'-------------------------------------------------
' Shift the focus back to the listview
'-------------------------------------------------
lvwProducts.Focus()
End Sub
#End Region
Deleting a RecordRemoving a Product from the demo database is the simplest maintenance task. Instead of displaying the New/Change form, a message is displayed to the user to ensure that they want to delete the Product selected in the
Note that, in this case, the product is not deleted from the #Region " ... Remove "
Private Sub btnRemove_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) _
Handles btnRemove.Click
'---------------------------------------------------
' Date Developer Code Change
' ---------- -------------------- -----------------
' 05/15/2006 G Gilbert Original code
'---------------------------------------------------
'---------------------------------------------------
' Bail if there are no products in the listview
'---------------------------------------------------
If lvwProducts.Items.Count <= 0 Then
Beep()
lvwProducts.Focus()
Exit Sub
End If
'---------------------------------------------------
' Ensure the user is serious
'---------------------------------------------------
Dim removeMsg As String
Dim sb As New StringBuilder("")
With sb
.Append(lvwProducts.SelectedItems(0).SubItems(1).Text)
.Append(" ")
.Append(lvwProducts.SelectedItems(0).SubItems(2).Text)
.Append(vbCrLf)
.Append(vbCrLf)
.Append("Delete this product?")
removeMsg = .ToString
End With
sb = Nothing
If MessageBox.Show(removeMsg, _
"Remove Product", _
MessageBoxButtons.OKCancel, _
MessageBoxIcon.Question, _
MessageBoxDefaultButton.Button2) = DialogResult.OK Then
'** Update the database to remove the deleted row
TurnOnHourglass(Me)
Dim deletedOK As Boolean = True
Dim at As New C_AccessTable(gtDATABASE_PATH)
With at
If Not .DeleteRow(theDataTable, _
gtTBL_COL_PRODNUMBER & _
" = " & _
lvwProducts.SelectedItems(0).SubItems(1).Text) Then
TurnOnArrow(Me)
DisplayOops("Removal of the Product failed")
deletedOK = False
End If
End With
at = Nothing
'** Refresh the datatable and update the listview if
'** the delete went OK
If deletedOK Then
PopulateDataTable()
TurnOnArrow(Me)
End If
End If
'---------------------------------------------------
' Shift the focus back to the listview
'---------------------------------------------------
lvwProducts.Focus()
End Sub
#End Region
A Distinct QueryThe #Region " ... ListUOMs "
Private Sub ListUOMs()
'---------------------------------------------------
' List all existing Units of Measure as a reference
'---------------------------------------------------
' Date Developer Code Change
' ---------- -------------------- -----------------
' 05/20/2006 G Gilbert Original code
'---------------------------------------------------
Dim at As New C_AccessTable(gtDATABASE_PATH)
With at
'** List each UOM only once
.SQLDistinct = True
'** Override the default * in the SELECT
' query to select only the UOM
'** column
.SQLSelectList = gtTBL_COL_UOM
'** Sort the UOMs (ascending sequence is default)
.SQLOrderBy = gtTBL_COL_UOM
'** Build the SQL SELECT query
.DatabaseTableName(True) = gtACCESS_TABLE_NAME
'** Populate the datatable and, if there are UOMs on file, list
'** same
If .PopulateDataTable Then
For i As Integer = 0 To .FilledDataTable.Rows.Count - 1
lstUOM.Items.Add(.FilledDataTable.Rows(i).Item(0))
Next
End If
End With
at = Nothing
End Sub
#End Region
Reading a Specific RecordIn the demo project, part of the editing of the user's input for a new Product is checking that the Product number is not already in use. This requires building and executing a SQL '** Check if the product number is already assigned
Dim duplicateRecords As Integer = 0
Dim at As New C_AccessTable(gtDATABASE_PATH)
With at
'** Add a WHERE clause to the default SQL command
.SQLWhere = gtTBL_COL_PRODNUMBER & " = " & txtNumber.Text
'** Set the Access table name. Use the default SQL SELECT command
'** (built in the C_AccessTable class) instead of passing a complete
'** SQL string.
.DatabaseTableName(True) = gtACCESS_TABLE_NAME
'** Attempt to populate the datatable
If .PopulateDataTable() Then
duplicateRecords = .FilledDataTable.Rows.Count
End If
End With
at = Nothing
If duplicateRecords > 0 Then
DisplayOops("That Number is already in use")
txtNumber.Focus()
Exit Function
End If
Points of InterestThere are no points of interest that come to mind for this article. It was interesting doing the needed research, but nothing worth mentioning. ConclusionWith three fairly large VB.NET projects (one of which includes some ASP.NET) staring me in the face, I feel more confident in my timeline with the Licensing and Limitation of LiabilityYou may use all code offered in this article any way you choose without restriction. Under no circumstances, and under no legal theory, tort, contract or otherwise, will George Gilbert (hereafter referred to as "software author") or his licensors, be liable to the user of the Double Text library and all code offered in this article (hereafter referred to collectively as "article code") for any damages, including any lost profits, lost data, or other indirect, special, incidental or consequential damages, arising out of the use or inability to use the article code, and data or information supplied, even if the software author, his licensors or authorized dealer have been advised of the possibility of such damages, or for any claim by any other party. History
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||