Tuesday, September 16, 2014

Programming with structure without making it too complicated


Program example/sample with a good structure

Outlook 2013 addin that create a toolbar and a button on it
Also define a handler to call a sub with the button we added

Once upon a time, I programmed multiple dimension arrays, all numbered and dynamically accessibles by numbers.
A year later I told myself: never again!

Why? Because a program like this is like diving. When you are 30 feet deep, you are comfortable. But when you restart from the top a year later, the descent is long...

So i decided to adopt a standard, a plan to follow, and here it is.

Situation:
You get some code example on internet
You try to run it in visual studio, but a lot of objects are not reconized

Problem:
You are missing libraries (references, dll)

Solution:
Always include comments with what library to include in the code

Example:
    '=== this is a addin for outlook 2013
    '=== target framework: .net framework 4.5
    '=== compile: any cpu
    '=== programming: visual studio 2013
    '=== references: just default office 15 references and visual studio ones, no custom
    '=== software: office 15 (2013) must be installed
    '=== software: .net framework 4.5 must be installed but office 2013 requires it anyway
    '=== system: windows 7 64 bits sp1
---------------------------------------

Situation:
You have many functions and subs in your program

Problem:
You have to add a parameter to that sub you call because you forgot a situation that just emerged
But you already have 100 lines calling this sub...
You have to change all the line calling it for that

Solution:
use structures variables (vb)
or type variables (scripts)
You pass the structure as parameter to the sub or function
When you add one more parameter, you can reprogram the code inside the sub, but you do not have to change all the lines calling it, they still pass only one structure to the sub

Example:
'=== structure to pass to a function to create a toolbar in outlook

    Public Structure bar_param
        Dim bar_Exist01 As Integer   '=== if bar exist we do not recreate
        Dim bar_Caption01 As String   '=== name of bar
        Dim bar_inexplorer01 As Integer '=== create in main outlook window
        Dim bar_ininspector01 As Integer '=== create in message window
    End Structure

'=== strucutre to pass to a function to create a button in the bar we created just before

    Public Structure but_param
        Dim bar_obj As Object
        Dim but_exist01 As Integer
        Dim but_Caption01 As String
        Dim but_tooltip01 As String
        Dim but_onaction01 As String
        Dim but_face01 As Integer           '=== icon
        Dim but_inexplorer01 As Integer     '=== in main outlook window
        Dim but_ininspector01 As Integer    '=== in message window
        Dim but_bar01 As Object             '=== bar to add button to
    End Structure

As you can see, but is abbreviation for button
Some poeple would say get a more significative name
But no, we also need clarity of code (and i am too lazy to type long names)
Too much text and you drown in your own code

The number at the end of the parameters are to show that these variables are not system variables, but custom/user made
------------------------------------------

Situation:
Your code is spaghetti
Your main loop is 1000 lines

Problem:
You have no main sub to look at to understand the program with one look
It's all a big pack of lines with lots of exceptions add along the way
You had to do that because you have many global variables used everywhere

Solution:
You main sub should be 1 to 2 screen big
Use functions even if you have to pass them lots of parameters to make them work
Always verify if the function return something
Then your main sub can continue processing only if the functions did return something


Example:
    Private Sub ThisAddIn_Startup() Handles Me.Startup

        Dim button01 As Office.CommandBarButton
        Dim bar01 As Office.CommandBar

        Dim objNet = CreateObject("WScript.Network")
        '=== get username logon from local machine
        usenam = LCase(objNet.UserName)

        '====== BAR addition
        Dim toolbarcnt = 0
        '=== dynamic number of parameters by passing a structure to the function
        '=== you can add parameters you want to opass to the functon in structure without modifying the code that call the function
        Dim bar_param01 As bar_param

        bar_param01.bar_Exist01 = 0
        bar_param01.bar_Caption01 = "Hyperzip01"
        bar_param01.bar_inexplorer01 = 1
        bar_param01.bar_ininspector01 = 0

        '=== if we want more toolbars, we extend the array and repeat same thing with a different bar name

        bar01 = toolbar_Add(bar_param01)

        Dim buttoncnt = 0
        Dim but_param01(buttoncnt) As but_param

        If Not bar01 Is Nothing Then

                   '====== buttons creation in explorer (main outlook windows)

In this example, we call a function to create the outlook bar
If the resulting bar (bar01) is nothing we obviously cannot continue and create the buttons on the bar
We create only one bar, so we defined a structure (bar_param01) as one variable, not an array of variables

The next time we use structures as an array containing many buttons parameters to create
We ony have one button here, but we can define the array of parameters to be bigger if we want to create more than one

            '====== buttons creation in explorer (main outlook windows)
            but_param01(buttoncnt).but_exist01 = 0
            but_param01(buttoncnt).but_Caption01 = "HyperZIP"
            but_param01(buttoncnt).but_tooltip01 = "Zip des fichiers ou dossiers et envoie un lien de téléchargement"
            but_param01(buttoncnt).but_onaction01 = "hyperZIP"
            but_param01(buttoncnt).but_face01 = 5432
            but_param01(buttoncnt).but_inexplorer01 = 1
            but_param01(buttoncnt).but_ininspector01 = 0
            but_param01(buttoncnt).but_bar01 = bar01

            '=== redim of more buttons to create
            'buttoncnt = buttoncnt + 1
            'ReDim Preserve but_param01(buttoncnt)

Loop through the array of parameters to create all buttons

            For i = 0 To buttoncnt
                button01 = button_Add(but_param01(buttoncnt))
                If Not button01 Is Nothing Then
                    '=== button created in a bar
                    'MsgBox("bar name: " & bar01.name & vbCrLf & "Button name: " & button01.Caption)

                    '=== every handler must be connected to a sub, so we cant use dynamic name for the call here, we use fixed name hyperzip_click_01
                    If but_param01(buttoncnt).but_Caption01 = "HyperZIP" Then AddHandler button01.Click, AddressOf hyperzip_click_01

if the button was not created, we will have a fatal error

                Else
                    '=== error the button was not created
                    MsgBox("WARNING - button (explorer - outlook window) not existing or created" & vbCrLf & but_param01(buttoncnt).but_Caption01 & vbCrLf & "next button")
                End If
            Next
        Else

if the bar was not created we will have a fatal error

            '=== error bar was not created
            MsgBox("ERROR - fatal - toolbar (explorer - outlook window) not existing or created" & vbCrLf & bar_param01.bar_Caption01 & vbCrLf & "exiting program")
            Exit Sub
        End If

As you can see, we create a bar, and continue the main sub only if the bar exist
Then we create a button in the bar and continue only if the button exist

As this addin will be called by thoses buttons, there is no need to continue if the bar or the button does not exist after we tried to create it


Debugging:
There is no error trapping, but eventually, there should be only in the lower level sub
If you trap error before calling a function (in main sub), you will never know where the program crash as it will mostly report an error at the line calling the function
(this happen if your function is in another class, in same class, it's usually not a problem)

here is the rest of the code:

    Function toolbar_Add(bar_param01 As bar_param) As Office.CommandBar

        '=== add a toolbar object in outlook explorer or inspector (message)

        '=== with a structure as parameter, we can add as many arguments we want before calling this function without modifying all the line that use it
        '=== outlook objects for "complements"

        Dim explorer01 As Microsoft.Office.Interop.Outlook.Explorer
        Dim inspector01 As Microsoft.Office.Interop.Outlook.Inspector
        explorer01 = Globals.ThisAddIn.Application.ActiveExplorer
        inspector01 = Globals.ThisAddIn.Application.ActiveInspector

        Dim bars01 As Object

        If bar_param01.bar_inexplorer01 = 1 Then
            '=== 1 = in message
            bars01 = explorer01.CommandBars
        End If
        If bar_param01.bar_ininspector01 = 1 Then
            '=== 0 = in outlook (main)
            bars01 = inspector01.CommandBars
        End If

        '=== check if in all bars in a bar with same name exist
        For Each bar01 In bars01
            If Trim(LCase(bar01.Name)) = Trim(LCase(bar_param01.bar_Caption01)) Then
                'bar01.Delete
                If InStr(LCase(bar01.Name), "test01") Then
                    'MsgBox("stas2 found")
                End If
                bar_param01.bar_Exist01 = 1
                toolbar_Add = bar01
            End If
        Next

        '=== add tolbar if not already there
        If bar_param01.bar_Exist01 = 0 Then
            '=== we are in outlook main window
            If bar_param01.bar_inexplorer01 = 1 Then
                toolbar_Add = explorer01.CommandBars.Add(bar_param01.bar_Caption01)

            ElseIf bar_param01.bar_ininspector01 = 1 Then
                '=== we are in a message
                toolbar_Add = inspector01.CommandBars.Add(bar_param01.bar_Caption01)
            End If
        End If

        '=== make toolbar visible
        If toolbar_Add IsNot Nothing Then
            toolbar_Add.Name = bar_param01.bar_Caption01
            toolbar_Add.Visible = True
            If bar_param01.bar_Exist01 = 0 Then
                toolbar_Add.Position = Office.MsoBarPosition.msoBarTop
            End If
        End If

        Return toolbar_Add

    End Function

    Function button_Add(but_param01 As but_param) As Office.CommandBarButton

        '=== add a button in a toolbar
        '=== the bar to add the button to is in the parameters structure

        '=== compteur de boutons pour pouvoir en ajouter n'importe où dans la matrice
        '========================= button in toolbar

        '=== outlook objects for "complements"
        Dim inspector01 As Microsoft.Office.Interop.Outlook.Inspector
        Dim explorer01 As Microsoft.Office.Interop.Outlook.Explorer
        explorer01 = Globals.ThisAddIn.Application.ActiveExplorer
        inspector01 = Globals.ThisAddIn.Application.ActiveInspector

        Dim button01 As Object

        '=== delete all buttons in bar
        For Each button01 In but_param01.but_bar01.Controls
            button01.Delete()
        Next

        '=== button add

        If but_param01.but_inexplorer01 = 1 Then
            '=== in outlook main windows
            button01 = explorer01.CommandBars(but_param01.but_bar01.name).Controls.Add(Type:=Office.MsoControlType.msoControlButton, Before:=1)
        ElseIf but_param01.but_ininspector01 = 1 Then
            '=== in message
            button01 = inspector01.CommandBars(but_param01.but_bar01.name).Controls.Add(Type:=Office.MsoControlType.msoControlButton, Before:=1)
        End If

        If Not button01 Is Nothing Then
            With button01
                'buttons(depnum, 0)

                .Caption = but_param01.but_Caption01
                .TooltipText = but_param01.but_tooltip01
                .Enabled = True
                .Visible = True
                .OnAction = but_param01.but_onaction01
                '.OnAction = "!<" &  & ">"
                .tag = but_param01.but_Caption01
                .Style = Office.MsoButtonStyle.msoButtonIconAndCaption
                .FaceId = but_param01.but_face01

                'Dim Icon01 = LoadPicture("C:\_stas\outlook\bou_HYPERZIP.bmp")

            End With

        End If
        button_Add = button01

    End Function

    Private Sub hyperzip_click_01(ByVal ctrl As Office.CommandBarButton, ByRef Cancel As Boolean)

        'MsgBox("You clicked: " + ctrl.Caption)

        '=== in 2013, you create a class called "form", then create a new object (window aka form) from this class, then use it
        Dim form01 As New Form1
        '=== show the form
        form01.Show()

    End Sub

    Private Sub ThisAddIn_Shutdown() Handles Me.Shutdown
        hyperzip = Nothing
    End Sub


End Class