Tutorial: MSSQL- Windows Form Development
Multi-User Application With Custom Record Lockout #4
[]
Dr. Thomas E. Hicks
Computer Science Department
Trinity University
The purpose of this series of tutorials is to show how to
configure a Windows Application Form, using Visual Studio, to process a
multi-form, multi-user network application. This tutorial will briefly review
many of the concepts demonstrated in some of the other tutorials.
Prerequisite Condition: each of our major database tables must have a unique,
auto-incremented, integer field called ID.
This portion of the tutorial will examine and configure a combobox filled by a database table, checkbox controls, password textboxes, and tabcontrol configuration.
This tutorial is fourth in a series of
Windows Form Development Multi-User Application With Custom Record Lockout #1 [Database Configuration, Database Authentication, Generic Database Utilities, Etc.]. The topics in part 1 are as follows:Part I-A:
Part I-B:
Part I-C:
Part I-D:
Part I-E:
Part I-F:
Part I-G:
Part I-H:
This tutorial is fourth in a series of
Windows Form Development Multi-User Application With Custom Record Lockout #2 [Query Set Views & Navigation Buttons & Filters & Order By]. The topics in part 2 are as follows:Part II-A:
Part II-B:
Part II-C:
Part II-D:
Part II-E:
Part II-F:
Part II-G:
Part II-H:
Part II-I:
Part II-J:
For You To DoThis tutorial is fourth in a series of
Windows Form Development Multi-User Application With Custom Record Lockout #3 [Login, RecoordLock DB, Authentication, Configure Dynamic Data Combo, Password Fields, CheckBoxes, & Tab Control]. The topics in part 1 are as follows:Part III-A:
Part III-B
Part III-C
Part III-D:
Part III-F:
Part III-G:
Part III-H: Final Testing
Part III-I: Temporary Main.cs Change For Rapid Development
Part III-J:
Part III-A: Use ComboBoxes To Reduce Data Entry Errors [Database Enables Dynamic ComboBoxes]
Part III-B: Use CheckBoxes To Reduce Data Entry Errors
Part III-C
: Hide The Password DataPart III-D:
Configuring Tab Control For The User FormPart III-E:
LibraryApp AuthenticationPart III-F:
Login ProcessingPart III-G:
Carriage Return/Enter Form ProcessingPart III-H: Final Testing
Part III-I: Temporary Main.cs Change For Rapid Development
Part III-J:
For You To Do
If You Have Difficulty With This Tutorial, You Might Find These Help
Prepare You For This Tutorial
I Recommend That My Students Complete The Tutorials In This Order!
Tutorial: MSSQL-Forms - Windows Application & Basic Form Properties
Tutorial: MSSQL-Forms - Windows Application - Labels & Textboxes
Tutorial: MSSQL-Forms - Windows Application - Buttons
Tutorial: MSSQL-Connection\MSSQL-Connections
Tutorial: MSSQL-Forms - Windows Application - Menu - Multiple Forms
Tutorial: MSSQL-Visual Studio MenuStrip Application With Multiple Windows
1] Copy folder Library-Net-Multi-User-3 to folder Library-Net-Multi-User-4. I always recommend making a backup copy about ever hour or so.
1] When the user sees this form in the Normal Mode, they shall be unable to edit/change the data-related textboxes, comboboxes, checkboxes, radio buttons, etc. Buttons Save and Cancel will have no meaning or activation. All other buttons shall be activated.
2] When the user sees this form in the Edit Mode, they shall be able to edit/change the data-related textboxes, comboboxes, checkboxes, radio buttons, etc. Buttons Save and Cancel will be activated.
3] If users could lock a record and then move to the next record, this would increase the probability that the user would be able to move to the next record and forget to come back and finish editing the previous record. It is important to make sure that the user continues to focus on the business of editing the current record; for this reason we shall disable all of the buttons except Save and Cancel. We shall disable all comboboxes, checkboxes, and radio buttons that do not relate to the data.
4] A change in color should be
present so thatthe user can easily discern between the two states; continue
reading and this shall make more sense as you complete the steps and code below.
1] As mentioned earlier, button btnTesting was added to our form for diagnostic testing during the development cycle; it shall either be removed from or hidden on the final system. We are adding a second diagnostic button, called btnEditMode, to our Users form; it too shall either be removed from or hidden on the final system. (See Below!)

2] Add function EditMode to the Users form. We shall develop the code that goes with this shortly. (See Below!)

3] Double-Click on button btnEditMode to create the event logic which shall enable btnEditMode to execute function EditMode( ); add the function call. (See Below!)

4] We are adding a third diagnostic button, called btnNormalMode, to our Users form; it too shall either be removed from or hidden on the final system. (See Below!)

5] Add function NormalMode to the Users form. We shall develop the code that goes with this shortly. (See Below!)

3] Double-Click on button btnNormalMode to create the event logic which shall enable btnEditMode to execute function NormalMode( ); add the function call. (See Below!)

1] Add the following block of code to the EditMode function. All of the Letter buttons shall be enabled.

2] Add the following block of code to the NormalMode function. Save & Cancel menustrip buttons shall be disabled..

3] Add the following block of code to the NormalMode function. All menustrip buttons, except Save & Cancel, shall be disabled..

4] Add the following block of code to the NormalMode function. All Navigation buttons shall be enabled with their respective images.

5] Add the following block of code to the NormalMode function. All data related textboxes shall be ReadOnly so that their contents may not be altered.

6] Add the following block of code to the NormalMode function. All data related checkboxes and comboboxes shall be disabled so that their contents may not be altered.

7] Add the following block of code to the NormalMode function. All non-data related checkboxes and comboboxes shall be enabled so that their contents may be altered.

8] Execute the program. Push the Normal Mode button. Note that Save and Cancel have been disabled. Note the user can not change the Data (First, Last, Middle Initial, UserName, Password, Major, or Administrator). Note that the Navigation buttons, Letter Buttons, Order By ComboBox, and Select ComboBox are all functional. CloseWindow is the only one of the MenuStrip buttons that are functional; it works! (See Below!)

1] Add the following block of code to the EditMode function. All of the Letter buttons shall be disabled. Once the user has locked a record to edit, we do not want that user to be able to move on to another user.

2] Add the following block of code to the EditMode function. Save & Cancel menustrip buttons shall be enabled..

3] Add the following block of code to the EditMode function. All menustrip buttons, except Save & Cancel, shall be enabled..

4] Add the following block of code to the EditMode function. All Navigation buttons shall be disabled with their respective disabled images.

5] Add the following block of code to the EditMode function. All data related textboxes shall no longer be ReadOnly; the user shall be able to alter the data.

6] Add the following block of code to the EditMode function. All data related checkboxes and comboboxes shall be enabled; the user shall be able to alter the data.

7] Add the following block of code to the EditMode function. All non-data related checkboxes and comboboxes shall be enabled so that their contents may be altered.

8] Execute the program. Push the Edit Mode button. Note that Save and Cancel have been enabled. Note the user can change the Data (First, Last, Middle Initial, UserName, Password, Major, or Administrator). Note that the Navigation buttons, Letter Buttons, Order By ComboBox, and Select ComboBox are no longer functional. None of the MenuStrip items are functional. (See Below!)

1] Change the UserClassDiagnosticLevel to 25. (See Below!)

2] Copy the code from TestModule24 to TestModule25 and make the following changes.
3] Execute the Users Test Module. You should be able to see the application change modes. (See Below!)

1] Many different librarians will have the ability to scroll through our User records with the Users.cs form above. Occasionally one of the librarians will want to make some changes to a given record. It is possible that two or more concurrent users will want to make changes to the same record at the same time. If we allow one user to lock the record - make changes - and then free the record, the potential to avoid concurrent user anomaly problems is optimized.
2] Problem 1: Many databases have the ability to lock a database table, but few have the ability to lock a table record. Locking the entire database greatly limits the functionality of other users!
3] Problem 2: Even the very few databases which offer the ability to lock a record, have no mechanism to determine who has locked the record.
4] Problem 3: Few of the databases have the utilities necessary to easily do timing calculations; we generally have to write some of our own functions.
5] Problem 4: Most computers do not have their clocks synchronized; users may be in different time zones. It is critical that all timings be relative to the SQL Server, as opposed to the user's personal computer.
6] The Record Lock-Out strategy demonstrated in this tutorial can be used in both web-oriented databases and multi-user network databases. The logic can be applied to databases, MySQL, DBase, and other database engines.
1] Now to functionalize this. Change the UserClassDiagnosticLevel to 26. (See Below!)

2] Write function ComputeNoDays which accepts a DateTime Object and explicitly returns the number of days that have passed since Dec. 31st, 1999; thus starting our system timing with the year y2k.

3] Write function ComputeNoSeconds which accepts a DateTime Object and explicitly returns the number of seconds that have passed thus far on the last day.

4] Copy the code from TestModule25 to TestModule26 and make the following changes.

5] Execute the Users Test Module. Examine the test module carefully to make sure that you understand what it is attempting to do. Examine the output carefully to make sure that it matches that below and is consistent with the test code above.

6] The first block of code (1/1/2000) is 1 Second into Day 1 into y2k.
7] The second block of code (1/1/2001) is one minute two seconds [60+2] into is Day 367 [366+1] into y2k.
8] The third block of code (1/3/2002) is one hour [60x60 seconds] into is Day 734 [366 + 365 +3] into y2k
9] Suppose a user were to lock a record [on 1/1/2001 12:01:02] for 120 seconds. The two functions above enable us to get CurrentNoDays = 367 and CurrentNoSeconds = 62. The two functions above enable us to calculate LockExpireDay = 367 and LockExpireSecond = 182; when another user attempts to lock this same record, we shall use these functions, with the current date, to see if a record lock has expired. We shall discuss this later.
1] Now to functionalize this. Change the UserClassDiagnosticLevel to 27. (See Below!)

2] Write function GetCurrentServerDateTime which explicitly returns a C# DateTime object with the current server time.

3] Write function GetCurrentServerDateTimeStr which explicitly returns a string object with the current server time.

4] Copy the code from TestModule26 to TestModule27 and make the following changes.

5] Execute the Users Test Module. Examine the test module carefully to make sure that you understand what it is attempting to do. Examine the output carefully to make sure that it matches that below and is consistent with the test code above. There should be about a five second span between the two sets of data.

1] Now to functionalize this. Change the UserClassDiagnosticLevel to 28. (See Below!)

2] Write function GetDaySecond which converts the date object to the LockDay and LockSecond used by our Record-Lock system.

3] Write function GetDaySecondStr which converts the date, in string format, to the LockDay and LockSecond used by our Record-Lock system.
4] Copy the code from TestModule27 to TestModule28 and make the following changes.

5] Execute the Users Test Module. Examine the test module carefully to make sure that you understand what it is attempting to do. Examine the output carefully to make sure that it matches that below and is consistent with the test code above. I have pasted the equivalent output from TestModule26 for easy comparison.

1] Now to functionalize this. Change the UserClassDiagnosticLevel to 29. (See Below!)

2] Write the three overloads function GetExpiredDaySecond which uses the NoSecondsToLock to compute the LockExpireDay and LockExpireSecond used by our Record-Lock system. The three overloads provide a variety of interfaces by which we may solicit the LockExpireDay and LockExpireSecond.

3] Copy the code from TestModule28 to TestModule29 and make the following changes.

4] Execute the Users Test Module. Examine the test module carefully to make sure that you understand what it is attempting to do. Examine the output carefully to make sure that it matches that below and is consistent with the test code above. The three function overloads provide multiple ways in which to calculate the the LockExpireDay and LockExpireSecond used by our Record-Lock system.
1] Now to functionalize this. Change the UserClassDiagnosticLevel to 29. (See Below!)

2] Write the three overloads function GetExpiredDaySecond which uses the NoSecondsToLock to compute the LockExpireDay and LockExpireSecond used by our Record-Lock system. The three overloads provide a variety of interfaces by which we may solicit the LockExpireDay and LockExpireSecond.

3] Copy the code from TestModule28 to TestModule29 and make the following changes.

4] Execute the Users Test Module. Examine the test module carefully to make sure that you understand what it is attempting to do. Examine the output carefully to make sure that it matches that below and is consistent with the test code above. The three function overloads provide multiple ways in which to calculate the the LockExpireDay and LockExpireSecond used by our Record-Lock system.
1] Several of our generic functions were written with thoughts of one database connection and one datatable for that database. Since our LibraryApp is going to use at least two databases (LibraryApp and RecordLock), we shall modify/update some of our generic functions to make them even more generic.
1] In an attempt to create functions that are generic, we are going to define the DatabaseName and TableName at the top of each and every form that shall use these utilities.
2] Change the declarations, at the top of Users.cs, to be as follows: (include the comments!)

3] Execute LibraryApp; it should continue to bring up the
Users.cs form.
1] As is often the case in computer science, we have to modify functions as an application increases in functionality. Since we are going to use two databases (LibraryApp and RecordLock) we are going to need two connection objects; both Conn and RL_Conn are defined above.
2] To better distinguish between the two connection objects, replace all occurrences of Conn with LA_Conn (LibraryApp Connection); do a global replacement as illustrated below. (See Below!)

3] Our first SQLAuthenticationConnection function is written to connect only to LA_Conn. We could write a similar function to connect to RL_Conn, but this would be really redundant. In reality, our next application might need to connect to three or four database.
4] A better solution is to make function SQLAuthenticationConnection more flexible by passing the Connection object. Change your code to that below!
5] Of course when we make this change, none of our previous calls to this
function will compile. Change each and every occurrence of SQLAuthenticationConnection(
to
SQLAuthenticationConnection("Net1\\SQLExpress", "LibraryApp", "student", "student", LA_Conn);
6] Execute LibraryApp; it should continue to bring up the Users.cs form; test all controls to assure that they still work.
7] Adjust the commenting on any of the documentation boxes in Users.cs.
8] Execute LibraryApp; it should continue to bring up the
Users.cs form; test all controls to assure that they still work.
9] Function SQLAuthenticationConnection has been made more flexible and
more generic by passing the Connection object. It can be used with both of the
databases in our project.
1] Since we are going to use two databases (LibraryApp and RecordLock) we are going to need two datatable objects to perform our queries; both dt and RL_dt are defined above.
2] To better distinguish between the two connection objects, replace all occurrences of dt with LA_dt (LibraryApp DataTable); do a global replacement as illustrated below. (See Below!)

3] Our first FillDataTable function is written to connect only to LA_dt. We could write a similar function to connect to RL_dt, but this would be really redundant. In reality, our next application might need to connect to three or four database.
4] A better solution is to make function FillDataTable more flexible by passing the Connection object. Change your code to that below!
5] Of course when we make this change, none of our previous calls to this
function will compile. Below you will see just one of the many calls to function
FillDataTable. FillDataTable(Query, 
You will have to add LA_Conn to each as illustrated below.
FillDataTable(Query, ref dt, LA_Conn);
6] Execute LibraryApp; it should continue to bring up the Users.cs form; test all controls to assure that they still work.
7] Adjust the commenting on any of the documentation boxes in Users.cs.
8] Execute LibraryApp; it should continue to bring up the
Users.cs form; test all controls to assure that they still work.
9] Function FillDataTable has been made more flexible and more generic by
passing the Connection object. It can be used with both of the databases in our
project.
1] We shall need a connection to database RecordLock. Change function Users_Load to be as follows:

2] Execute LibraryApp; it should continue to bring up the
Users.cs form; test all controls to assure that they still work.
1] In Part 3 of this tutorial set, we created a database called RecordLock.
2] The structure may be seen below:

1] Now to functionalize this. Change the UserClassDiagnosticLevel to 28. (See Below!)

2] Write GetDaySecond which converts a Date object into our LockDay and LockSecond format that will be used in our Record Lock System.

3] Write GetDaySecondStr which converts a String formatted date into our LockDay and LockSecond format that will be used in our Record Lock System.

4] Copy the code from TestModule27 to TestModule28 and make the following changes.

5] Examine the test module carefully to make sure that you understand what it is attempting to do. Examine the output carefully to make sure that it matches that below and is consistent with the test code above.

1] Now to functionalize this. Change the UserClassDiagnosticLevel to 29. (See Below!)

2] Write function GetExpireDaySecond which converts the passed date and NoSecondsToLock into the LockDay and LockSecond format that will be used in our Record Lock System. Note that there are a number of different function overloads to make the using this utility more convenient to utilize.

3] Copy the code from TestModule28 to TestModule29 and make the following changes.

5] Examine the test module carefully to make sure that you understand what it is attempting to do. Examine the output carefully to make sure that it matches that below and is consistent with the test code above.



1] Now to functionalize this. Change the UserClassDiagnosticLevel to 30. (See Below!)

2] Write function LockExpired which explicitly returns true if the lock time has expired; otherwise sets NoSecondsTillLockExpires and returns false.

3] Copy the code from TestModule29 to TestModule30 and make the following changes.

4] Execute the Users Test Module. Examine the test module carefully to make sure that you understand what it is attempting to do. Examine the output carefully to make sure that it matches that below and is consistent with the test code above.

1] Now to functionalize this. Change the UserClassDiagnosticLevel to 31. (See Below!)

2] Write function InsertRecordLockRequest1 which (1) Delete any previous lock requests for this table of this database by this user, (2) Delete Any Expired Lock Requests For This Record, (3) Insert this Record-Lock Request into the Locks Queue.


3] Before you execute the test module, load Microsoft SQL Server Management Studio Express. Delete any and all of the records from table Locks. Minimize Microsoft SQL Server Management Studio Express.

4] Copy the code from TestModule30 to TestModule31 and make the following changes.

5] The first block of code should create eight records. Our Date and Expire times will be different. There are four request LibraryApp->Users->1 and four request for LibraryApp->Books->2. There will then be a 25 second delay in which you can refresh the contents of the Locks database [with SQL Management Studio Express].. (See Below)

6] The next call to InserRecordLockRequest1 should
InsertRecordLockRequest1(
"LibraryApp", "Users", 1, "5", 30);(1) delete all previous lock requests for any record in LibraryApp->Users by User #5 [there are none]
(2) delete all expired lock requests for LibraryApp->Users->1 [records 1 & 2 are expired]
(3) insert a 30 second lock record LibraryApp->Users->1 by User #5 [record 9] (See Below)

7] The next call to InserRecordLockRequest1 should
InsertRecordLockRequest1(
"LibraryApp", "Users", 13, "4", 30);(1) delete all previous lock requests for any record in LibraryApp->Users by User #4 [record 4]
(2) delete all expired lock requests for LibraryApp->Users->1 [there are none]
(3) insert a 30 second lock record LibraryApp->Users->13 by User #4 [record 9] (See Below)

6] Although the program output is hardly profound, it does
help to confirm the LockStartDate, LockEpireDay, and LockExpireSecond for
TestModule31.


1] Now to functionalize this. Change the UserClassDiagnosticLevel to 32. (See Below!)

2] Write function DeleteUserRecordLockRequests which deletes all of the record lock request by this user for this table of this database.

3] Write function DeleteAllRecordLockRequests which deletes all of the record lock requests

4] Before you execute the test module, load Microsoft SQL Server Management Studio Express. If you have just completed the TestModule31, your database should have seven records similar to those below.

5] Copy the code from TestModule31 to TestModule32 and make the following changes.

6] The next call to DeleteUserRecordLockRequests should
DeleteUserRecordLockRequests()
(1) delete all previous lock requests for the default user (UserID=4) on the default table (Users) of the default database (LibraryApp) [Delete record 10] (See Below)

7] The next call to DeleteUserRecordLockRequests should
DeleteUserRecordLockRequests(
"LibraryApp", "Books", "2")(1) delete all previous lock requests for UserID=2 on table (Books) of database (LibraryApp) [Delete record 6] (See Below)

8] The next call to InserRecordLockRequest1 should

9] The program output is hardly profound.


1] Now to functionalize this. Change the UserClassDiagnosticLevel to 33. (See Below!)

2] Write function DeleteExpiredRecordLockRequestsForRecord which deletes all expired record lock requests for this record in this table of this database.

3] Write function DeleteTableLockRequests which deletes all of the record lock requests for this table of this database.

4] Before you execute the test module, load Microsoft SQL Server Management Studio Express. If you have just completed the TestModule32, your database should have no records.

5] Copy the code from TestModule32 to TestModule33 and make the following changes.


6] The code above, should add the the following 11 records and then delay for 425 seconds. Bring up Microsoft SQL Management Studio Express and check out the eleven records. Once again the Date, LockExpireDay, and LockExpireSecond will vary. (See Below)

7] The next call to DeleteExpiredLockRequestsForRecord should
DeleteExpiredLockRequestsForRecord (
"LibraryApp", "Users",1)(1) delete all expired lock requests for LibraryApp->Users->1 [records 11 & 12]

8] The next call to DeleteExpiredLockRequestsForRecord should
DeleteExpiredLockRequestsForRecord(3)
(1) delete all expired lock requests for LibraryApp->Users->3 [records 15 & 16]

9] The next call to DeleteExpiredLockRequestsForRecord should
DeleteTableLockRequests(
"LibraryApp", "Books")(1) delete all lock requests for LibraryApp->Books [records 18, 19, 20, & 21]

10] The program output is hardly profound.


1] Now to functionalize this. Change the UserClassDiagnosticLevel to 34. (See Below!)

2] Make a copy of funciton InsertRecordLockRequest1; call it function InsertRecordLockRequest. By using some of our other functions, we can create a function InsertRecordLockRequest that is shorter and simpler than InsertRecordLockRequest1.

3] The call to DeleteAllLockRequests, at the top of the test module, will leave table Locks with no records.

4] Copy the code from TestModule32 to TestModule33 and make the following changes.

5] The code above, should add the the following 11 records. Once again the Date, LockExpireDay, and LockExpireSecond will vary. (See Below)

10] The program output is hardly profound.


1] Download and add the following three files to folder Library-Net-Multi-User-4
2] You can see the files in the project folder below.

3] Time to add the form to our project. Right mouse click on LibraryApp ==> Select Add ==> Select Existing Item... (See Below!)

4] Select the three CustomMessageBox files. Using the mouse, select/push the Add button. (See Below!)

5] Execute the LibraryApp to make sure that all compiles well.
1] Now to functionalize this. Change the UserClassDiagnosticLevel to 35. (See Below!)

2] Write FillRecordLockDataSet which fills datatable RL_dt with a queue of all of the lock request for the RecordID.

3] Write DisplayRecordLockDataSet which uses the CustomMessageBox to display datatable RL_dt (which represents the queue of all of the lock request for the RecordID).

4] Copy the code from TestModule27 to TestModule28 and make the following changes.

5] Examine the test module carefully to make sure that you understand what it is attempting to do. Examine the output carefully to make sure that it matches that below and is consistent with the test code above.

6] Note the consistency with the five database records from the actual database below.

1] Download and add the following three files to folder Library-Net-Multi-User-4
7] This tutorial contains a number of generic database functions that shall be used in other tables and other applications. We shall make extensive use of this queue of record locks

A
1] This is your opportunity to put into practice what you have learned. Do the Following!
1] Configure a combobox, called cbGenreID to be dynamically filled by the Database Table Genre.

2] Configure the ideal TabOrder to proceed from Title to Author to Price to Genre and back to Title.