Virtual Developer Workshop: Containerized Development with Docker
Today, I would like to talk about how to become a great VB developer. Obviously, it takes time, a ton of hard work, and more patience to become a great developer; my aim with this article is to assist and guide you toward things to look for when starting out as a VB developer.
1. Realize That VB Has Had Quite a Metamorphosis Over the Last Decade
As you may have gathered, VB is definitely not the same it was pre twenty first century. It is no longer living in the shadow of C++. You can do just about anything with VB today.
2. Know About Exception Handling
- Compile time exceptions
- Logic exceptions
- Run time exceptions
At the end of the day, the area in which you need to spend the most time on would be run time exceptions. Why do I say so? Well, as a developer, you need to cater to any possible error in your application. It may seem obvious, but there are numerous errors that can occur at any given point in your app. With proper planning, you should be able to eradicate most of them; but, it is only when you have properly tested your application that you encounter many a unforeseen error. Let me not get ahead of myself here; let me start at the top of the list.
Compile Time Exceptions
Compile time exceptions happen when you compile your application and the compiler informs you about certain errors. These errors may be any of the following:
- Wrongly spelt objects. These are usually "spelling" errors. For example, if you have an object named objValidate and you have referred to it in code as objValodate.
- Wrongly referenced Properties and methods. Again, these may be due to a spelling error, or when you have wrongfully referred to an object when you meant a different object. For example: Let me say you have two objects, objA and objB. Now, let me presume further that objA has a Property called Alphabetic and objB has a Property named Numeric. Now, it can get pretty confusing sometimes when you have hundreds of objects in your class(es) and somebody will eventually mix them up by referring to the Alphabetic Property of objB or Numeric Property of objA.
- Creating objects incorrectly. This is quite common. Many a developer forgets to initialize their objects with the New keyword. It happens to the best of us.
- Referencing obsolete objects. This happens when you refer to an object that was already disposed.
The list goes on, and I could probably go on forever as well, but you get the idea...
Truth be told, this is not an exception where the compiler will tell you what went wrong. In fact, you won't know until the particular segment of code has been properly tested. How will you know? You will get the wrong results. Let me use a very simple example. Suppose you had a segment of code that is supposed to turn the Form's BackColor to blue (this is a piece of code where you have to physically see the results for yourself), but your code kept on turning the colour to Red. This is a logic error. This means that you have to change the logic of your code to get the desired result.
Run Time Exceptions
Bad news. Users aren't perfect and neither is your program. The sooner you accept these two facts, the sooner your apps will be built better. In the planning phase of your app, you must cater to most of the errors the user may encounter. These include, but are not limited to, the following:
- Attempting to read a file at the wrong location or a file that doesn't exist
- Network problems
- Drive problems
- Expecting a numeric value when an alphabetic value has been entered
- Division by 0
- Having insufficient privileges to complete a certain task
- Normal validation problems
The preceding list may seem obvious to most experienced developers, but inexperienced developers tend not to plan properly.
This brings me to the question: How do I handle exceptions properly?
Try and Catch Blocks
Try, Catch, and Finally Blocks
'Try/Catch/Finally' statements are used for structured exception handling. Structured Exception Handling is a term used to describe the process that can be followed whilst coding. The structure may be broken down as follows:
- Try. You put all your code that you need to run inside the Try Block. The compiler will then attempt to execute the code inside the Try block until an exception occurs, if ever.
- Catch. In the event of an exception, the Catch Block will catch the error or errors and attempt to handle them successfully. This is where you will handle your exceptions. Do not think that you can handle all the errors with one Catch statement, as you cannot. The trick is that you have to handle each exception individually, because each exception is ultimately different from the others.
- Finally. A Finally block assists in cleaning up your resources and disposing of the necessary objects.
An example of a proper Try/Catch/Finally statement looks as follows:
Private Sub GenDWMCalc() 'Check If Duration Expired ' Try Dim DateGen As Date DateGen = CType(DateReg, Date) 'Date From Registry ' Select Case GenPeriodReg Case "Day(s)" 'Day(s) ' 'If Today's Date > Than Registry Date ' If DateDiff(DateInterval.Day, DateGen, Now) >= GenDurReg Then RandomAllGenie() 'Randomise ' End If Case "Week(s)" 'Week(s) ' '* 7 For Week ' If DateDiff(DateInterval.Day, DateGen, Now) >= _ (GenDurReg * 7) Then RandomAllGenie() End If Case "Month(s)" 'Month(s) ' If DateDiff(DateInterval.Month, DateGen, Now) >= GenDurReg Then RandomAllGenie() End If End Select Catch df As DateFormatException MessageBox.Show("Date In Wrong Format!") Catch ae As ArgumentException MessageBox.Show("Missing Arguments!") Catch ua As UnAuthorizedAccessException MessageBox.Show("You Do Not Have Sufficient Privileges _ For This Task!") End Try End Sub
Here, there might be a potential of three different exceptions, even more. Each exception has been handled individually—although not super duper classy, but the point still remains that you should not handle all exceptions in the same way. Why? Well, look at the type of exceptions handled. There was an exception for the wrong date format, an exception for the wrong number of arguments received, as well as an exception for not having appropriate privileges for this given task. Each of these exceptions vary not only by type, but in the way the particular problem should be handled, from each other
3. Managed Code Versus Unmanaged Code
In short: Managed code is code that is written in the .NET Framework with C# or VB.NET, for example. Unmanaged code is code written in any language that is not .NET. This is most commonly languages such as C or C++ because they are known as low-level languages that can call the Operating System's methods directly. This brings me to my next topic: APIs.
A delegate is simply a type that represents a reference to physical methods with the same parameter list and return type. Okay, what does that mean in simple terms? A delegate is basically a substitute for a method with the same return type and with the same signature. We make use of a delegate when we cannot access certain threads directly, or when we need to ensure that managed and unmanaged code can get executed properly.
Add the necessary Imports:
Imports System.Text 'For StringBuilder Imports System.Runtime.InteropServices 'For API
These Imports enable us to make use of a StringBuilder object as well as to make calls to some system APIs.
Add the necessary modular variables and APIs:
'Delegate CallBack For EnumWindows Unmanaged API Public Delegate Function DelegateCallBack(ByVal hWnd As IntPtr, _ ByVal lParam As Int32) As Boolean 'Delegate For Managed Code ' Public Delegate Sub StringSubDelegate(ByVal aString As String) Private Const BufferSize As Int32 = 100 'Limit String Builder Size ' 'API to List Open Windows & Processes ' <DllImport("user32.dll")> _ Public shared Function EnumWindows(ByVal callback As _ DelegateCallBack, ByVal param As Int32) As Int32 End Function 'API To Get External Applications'' Text ' <DllImport("user32.dll")> _ Public shared Function GetWindowText(ByVal hWnd As IntPtr, _ ByVal lpString As StringBuilder, ByVal nMaxCount As Int32) _ As Int32 End Function
These APIs enables us to list all the active system processes and windows. Quite powerful. Although APIs are not the topic of discussion today, you may want to delve into this more, as you will be amazed what you can achieve with them. These APIs will be called via a delegate to do their work. I also created a Delegate to be used with Managed code a bit later.
Add the Function to handle the Unmanaged code's execution:
'Function To CAll APIs Public Function DisplayRunningApps(ByVal hWnd As IntPtr, _ ByVal lParam As Int32) As Boolean 'To Host App Names Dim sbStrings As New StringBuilder(BufferSize) 'Get Text Of Open Window If GetWindowText(hWnd, sbStrings, BufferSize) _ <> 0 Then 'Add To ListBox lstDelegates.Items.Add(sbStrings.ToString()) End If Return True End Function
The DisplayRunningApps function displays the captions of all active windows and adds each to a StringBuilder object, which ultimately gets added to the ListBox. Add the following code behind the button labeled "Unmanaged Delegate":
Private Sub btnUnmanaged_Click(sender As Object, e As EventArgs) _ Handles btnUnmanaged.Click 'Unmanaged Code From API ' EnumWindows(AddressOf DisplayRunningApps, 0) End Sub
Voilà! You have now successfully called unmanaged code through a delegate.
Add a class inside Form 1 and give it a method to be called from Form 1:
Public Class ClassForStringSubDelegate Public Sub ShowMessage(ByVal strTemp As String) MessageBox.Show(strTemp ) 'Show MessageBox ' End Sub End Class
This small example will just show how to use a delegate to call managed code from a separate class. Now, add the following code inside the button labeled "Managed Delegate":
Private Sub btnManaged_Click(sender As Object, e As EventArgs) _ Handles btnManaged.Click 'Managed ' 'Create Instance Of Separate Class ' Dim clsStringDelegate As New ClassForStringSubDelegate Dim delManaged As StringSubDelegate 'Specify Delegate 'Give It Something To Do ' delManaged = AddressOf clsStringDelegate.ShowMessage 'Do It ' End Sub
Here, I instantiate the class we created previously, and make use of the Invoke method of our Delegate to execute the appropriate code. This code simply produces a MessageBox that says 'Hello'.
A class is a template from which objects are created. Look at a television set, for example. You may have a Hisense 32 inch flat screen TV, while your friend may have an old Blaupunkt TV set from the early 80's. Each of these televisions look, behave, and are different from one another but are still classified as a TV. Any object in real life can be related to the above test: cars, cell phones, trees, modems, plates, and cups.
What makes each object different?
Properties and methods make each object different. Properties make each object look and feel differently, whereas methods make each object behave differently.
Properties describe objects. A colour is a property. Height, weight, and size are all properties. In Visual Basic, this simple formula is functionally the same. You can have various properties set up in your class, but provide different settings for each different object.
Methods determine how an object behaves. Some cars are lower on fuel consumption than others, but still have the same capabilities as other cars. In Visual Basic, you will have to write the method for the object; but how that method is ultimately implemented may be different than for a different object.
6. Configuration Files
As the name implies, a Configuration file allows you to store configuration settings. These configuration settings could be anything such as a database connection, common strings, or objects that will be used throughout your entire application. The benefit of using a config file is that it is automatically part of your application. This eliminates the need to create separate files to store your settings.
Configuration files are in XML format. This means that every setting stored inside a config file can easily be read if you understand the very basics of XML. Every Windows Forms application includes a file called app.config; I will talk about that now. As said, any Windows Forms application includes this file as part of the solution, so you can create a VB.NET Windows Forms application quickly. You can store all your database connection strings, resource locations (et al.) inside it. If you were to open the App.Config file inside Visual Studio, it will look like the following:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" /> </startup> </configuration>
Doesn't look like much. This simply tells the application that it expects .NET Framework 4.5 to run. Now, where do the settings I mentioned come in? You have to edit the App.Config file to include your desired settings. Edit your App.Config to look like the next code listing:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" /> </startup> <appSettings> <add key="TestKey" value="whatever"/> </appSettings> </configuration>
All I included here was the appSettings section. Here, I created a sub key named TestKey and supplied a senseless value (because this is just an example). Now, you have added a section to your config file, and now you can manipulate it through VB.NET Code.
Storing a Value Inside a Configuration File
Now that everything is set up, you can finally store a value inside the appSettings key you created earlier. Add the following code behind the button:
Private Sub Button1_Click(sender As Object, e As EventArgs) _ 'Save Original Value cAppConfig.Save (ConfigurationSaveMode.Modified) Handles Button1.Click asSettings.Settings.Item _ ("TestKey").Value = 5 End Sub
Here you opened the TestKey inside appSettings and give it a value. The next line simply saves the Configuration into the config file reading from a Configuration file.
Add the next code behind the button labeled 'Show':
Private Sub Button3_Click(sender As Object, e As EventArgs) _ Handles Button3.Click Try 'Read Stored Value ' Dim appSettings = ConfigurationManager.AppSettings Dim result As String = appSettings("TestKey") If IsNothing(result) Then result = "Not found" End If MessageBox.Show(result) Catch ec As ConfigurationErrorsException MessageBox.Show("Error reading app settings") End Try End Sub
You created an AppSettings object to read through the config file's AppSettings section. Then, you read from the specified key; in this case, it is TestKey. If there is data present, it will show the data inside a MessageBox; otherwise, it will inform you that nothing has been stored.
Editing Config File Values
Add the next code behind another button:
Private Sub Button2_Click(sender As Object, e As EventArgs) _ Handles Button2.Click 'Re-Open file and Store a new value ' cAppConfig = ConfigurationManager.OpenExeConfiguration(Application.StartupPath _ & "\Config_File_Ex.exe") asSettings = cAppConfig.AppSettings asSettings.Settings.Item("TestKey").Value = 15 'Save Modified Value ' cAppConfig.Save(ConfigurationSaveMode.Modified) End Sub
7. Parameterized Queries
Well, they are basically the same as an ordinary query, but, they allow you to make use of parameters inside your queries. Okay, so what is a parameter? A parameter is additional information you can supply to an SQL Query. This information usually works in conjunction with a condition inside the SQL query. We all know by now (or should) that, with a Condition, you can narrow down your search better.
A simple example of an SQL Query containing a parameter looks like this:
"Select * From StudentInfo where StudentName = @Name"
In this case, Name is a parameter. This comes in handy because now we do not need to hardcode the values we wanted inside our query's condition, as well as we solve the risk of SQL Injection. To supply a value to the parameter, you can use the following VB.NET code:
'Create Parameter Instead Of Hardcoding Values 'Name = Whatever txtSearch Contains oleAccCommand.Parameters.AddWithValue("Name", txtSearch.Text)
Here, I add a parameter to a Command object (which I will explain later in more detail) called Name and supply the value of whatever text has been entered into the txtSearch TextBox. The user is now able to type any value inside the txtSearch textbox, and it will be added to the final query that will run on the database. Another way to add a parameter looks like this:
command.Parameters.Add("@StudentNumber", _ SqlDbType.Int) command.Parameters("@StudentNumber").Value _ = txtStudentNumber.Text
Here, you create the parameter and set its data type. On the next line, you give it a value. As always, there are numerous ways to skin a cat; whichever one you choose depends on the project at hand, performance, as well as, I suppose your personal preference.
Insert a record into the database table:
Private Sub btnAdd_Click( sender As Object, e As EventArgs) _ Handles btnAdd.Click 'INSERT SQL Query ' 'This Query Inserts Our Input Data Into The Table ' strAccQuery = "Insert Into StudentInfo (StudentName, _ StudentSurname, StudentNumber) Values (@Name, @Surname, _ @StudentNo)" 'Instantiate Connection Object ' oleAccCon = New OleDbConnection(strAccConn) 'Using Structure Simplifies Garbage Collection And Ensures ' 'That The Object Will Be Disposed Of Correctly Afterwards ' Using oleAccCon 'Create Command Object, To Make Use Of SQL Query ' Dim oleAccCommand As New OleDbCommand(strAccQuery, oleAccCon) 'Create Parameters Instead Of Hardcoding Values ' 'Name = Whatever txtName Contains ' 'Surname = Whatever txtSurname Contains ' 'StudentNo = txtStudentNumber Text ' oleAccCommand.Parameters.AddWithValue("Name", _ txtName.Text) oleAccCommand.Parameters.AddWithValue("Surname", _ txtSurname.Text) oleAccCommand.Parameters.AddWithValue("StudentNo", _ txtStudentNumber.Text) 'Open Connection To The Database ' oleAccCon.Open() 'Execute Command As NonQuery As It Doesn't Return Info oleAccCommand.ExecuteNonQuery() 'Inform User That Row Has Been Added ' MessageBox.Show("Added") End Using End Sub
Private Sub btnAdd_Click( sender As Object, e As EventArgs) _ Handles btnAdd.Click 'INSERT SQL Query 'This Query Inserts Our Input Data Into The Table strQuery = "Insert Into StudentInfo (StudentName, _ StudentSurname, StudentNumber) Values (@Name, @Surname, _ @StudentNo)" 'Instantiate Connection Object sqlSQLCon = New SqlConnection(strSQLConn) 'Using Structure Simplifies Garbage Collection And Ensures 'That The Object Will Be Disposed Of Correctly Afterwards Using sqlSQLCon 'Create Command Object, To Make Use Of SQL Query Dim sqlSQLCommand As New SqlCommand(strQuery, sqlSQLCon) 'Create Parameters Instead Of Hardcoding Values 'Name = Whatever txtName Contains 'Surname = Whatever txtSurname Contains 'StudentNo = txtStudentNumber Text sqlSQLCommand.Parameters.AddWithValue("Name", _ txtName.Text) sqlSQLCommand.Parameters.AddWithValue("Surname", _ txtSurname.Text) sqlSQLCommand.Parameters.AddWithValue("StudentNo", _ txtStudentNumber.Text) 'Open Connection To The Database sqlSQLCon.Open() 'Execute Command As NonQuery As It Doesn't Return Info sqlSQLCommand.ExecuteNonQuery() 'Inform User That Row Has Been Added MessageBox.Show("Added") End Using End Sub
This is new! Well, obviously...
The query you created looks like this:
"Insert Into StudentInfo (StudentName, StudentSurname, StudentNumber) Values (@Name, @Surname, @StudentNo)"
This SQL query inserts records into the specified table. In this case, we are dealing with the StudentInfo table. You have to supply the field list inside brackets. This field list is the list of fields in which you want to insert data. You may find that, with Identity columns, you do not have to supply that column here because it gets populated automatically.
After you have specified the field list, you need to supply values to those fields. Makes sense, doesn't it?
So, after the Values keyword, you need to supply the appropriate values in the same order you specified in the field list; otherwise, your fields will contain the wrong data, or won't even accept the supplied data due to the wrong data type. You will notice that here I created three Parameters: Name, Surname, StudentNo, so the obvious next step is to add these parameters to the command object:
Command.Parameters.AddWithValue("Name", _ txtName.Text) Command.Parameters.AddWithValue("Surname", _ txtSurname.Text Command.Parameters.AddWithValue("StudentNo", _ txtStudentNumber.Text)
You may have also noticed that I didn't make use of the DataReader object here. It is because it is not needed as this type of query doesn't return anything back; it simply does what you asked. This is why you need to execute it as NonQuery.
8. Testing Your Apps
Obviously, testing your applications is very important, if not the most part of the software development process. Debugging goes hand in hand with testing; but there are many facets to testing than just testing the application
Testing can be broken into the following categories:
- Stress testing
- Load testing
- User testing
Stress testing involves testing an app to its limits. This usually involves more than one user using the application and literally trying to break the application.
Load testing involves testing your application's performance. The Load refers to the "load of data," so to speak. When your application becomes slow or is generally slow, there is a definite problem. This problem might be because of the database code itself, which can be a long SQL query that executes slowly. You can either optimize the code, or use performance monitoring tools to ascertain the exact problems. I will speak about performance testing a little bit later.
As the name implies, User testing involves users of your application. Usually, testers will test the functionality of the program and simply use it. By having several users using your app (before it gets implemented), you can stumble upon errors and you can fix them before the final product ships.
9. User Controls
As its name implies, a User Control is a user-specific control. Okay, so what does that mean? It means that sometimes there are circumstances where an ordinary Windows Control (those found in the Visual Studio Toolbox) does not suffice for the need at hand. There are times when a combination of Windows Controls fit one particular need; this is where a User Control can be extremely handy. There are a few types of User Controls, and they are:
- A Combination control.. This means that a combination of various Windows controls makes up one functional User Control with all the Windows controls used inside of it.
- An Inherited control. This is a control that simply inherits capabilities from a Windows control, but provides more custom functionality.
- A Custom control.. This is a control that doesn't exist at all even with a combination of other controls.
A component will be output as a DLL. In order to understand what a component is, I cannot put it better than MSDN, here, from which I quote:
".NET components provide a programmable interface that is accessed by consumer applications (often called client applications). The component interface consists of a number of properties, methods, and events that are exposed by the classes contained within the component. In other words, a component is a compiled set of classes that support the services provided by the component. The classes expose their services through the properties, methods, and events that comprise the component's interface."
The benefit of a component is the fact that it is loaded at run time, because it is already pre-compiled. You have to understand that a component is not a full physical program. It is simply a component of a program, hence its name. Many people think that components are just loose classes that may not have a user interface. That is untrue. Anything can be in a component. If you look at the Namespaces that the .NET framework provides, they are solely classes that expose properties and methods. But, when you look at the controls inside the toolbox, they also are components. This is the benefit of true Object Oriented Programming. This allows many a component to make up a full program.
I hope you have enjoyed today's article. Until next time, cheers!