In this chapter you'll learn how to use MAPI services to create a threaded discussion client. Threaded discussions clients are used quite frequently in groupware settings and on the Internet. The biggest difference between a discussion tool and an e-mail client is that the e-mail client is designed primarily for conducting one-to-one conversations. The discussion tool, however, is designed to support multiple parties, all participating in the same discussion. Even though MAPI services were originally deployed as only an e-mail solution, Microsoft has added several features to MAPI that now make it an excellent platform for building discussion applications.
Before getting into the details of building the tool, you'll learn a bit more about the differences between e-mail and discussion communications. You'll also learn how to take advantage of little-known MAPI properties and new features of the OLE Messaging library that make it easy to put together a standalone discussion tool that can be deployed from within a Local Area Network, over a Wide Area Network, or even across the Internet. You'll learn
As an added bonus, after you've completed the programming example in this chapter, you'll have a set of library functions that allow you to easily add threaded discussion capabilities to new or existing Visual Basic applications.
By now, most of us have used electronic discussion forums at least once in our lives. And there are many who use them every single day. The advantage of a discussion forum over direct person-to-person e-mail is that you can communicate with a large number of people without generating a large number of messages. In fact, reduced traffic is one of the biggest reasons to consider employing discussion groups as part of your electronic message system.
There are a number of other advantages to using discussion groups instead of e-mail. Often, it is easier to find answers to technical problems if you can go to a place where all the "techies" hang out. Discussion forums can work the same way. Instead of trying to e-mail several people in attempts to solve your problem, you can often find a discussion forum where you can send a single request that will reach hundreds (possibly thousands) of people who may be able to help you. So discussion forums can increase your ability to get your problems solved sooner.
Another good way to highlight the differences between e-mail and discussion groups is by comparing several face-to-face meetings (e-mail messages) with a single staff meeting where everyone shows up at the same place at the same time (the discussion forum). Using forum communications can reduce the amount of time you need to spend communicating. You can say it once and reach lots of people instead of having to repeat your message in lots of single e-mail messages.
Companies use forums as a way to communicate information of general interest as well as a way to take advantage of specialized expertise within an organization. Where corporate e-mail can lead to isolated individuals communicating in a vacuum, discussion groups can foster increased interaction with others and a sense of belonging to a special group-even if one or more of the team members is half-way around the world.
Now that you have a general idea of what discussion groups are and how you can use them in your organization, you're ready to review the three key concepts that make discussion systems different from e-mail systems.
First, and most important, users participating in an online discussion forum do not send messages to individuals. Instead they send their messaging to a single location where all messages are stored. In the MAPI model, the storage location is called a folder. When you create MAPI-based forum tools, you'll select a folder (or folders) to hold all the correspondence from others participating in the forum.
Note |
There are times when forum participants will initiate private e-mail correspondence in order to cover a topic in more depth or to delve deeper into an aspect of the discussion that would not be interesting to most of the other members. |
Then, after each message has been composed, you just post the message to the discussion folder for others to see. They, if appropriate, respond to your message with their own comments. The key point to remember is that messages are addressed to locations, not people.
Another key aspect of discussion groups is the ability to track topics in a thread that leads from the beginning of the discussion to the end. As each participant replies to an existing message, a relationship is established between the source message and the reply. Of course, it is perfectly correct (and quite common) to generate a "reply to a reply." This can continue on indefinitely. And in the process an intricate web of ideas and comments all collect in the storage folder that houses the discussion group.
Some discussion tools simply list the messages in the chronological order in which they were received. These are referred to as non-threaded or flat-table discussion tools (see Figure 10.1).
Figure 10.1 : Viewing messages in flat format, chronological order.
Non-threaded tools (often called readers) can be handy, but most people prefer the discussion tools that provide threaded discussion tracking. With threaded tools, each conversation that is initiated is a branch. Each branch can have its own subsequent conversation thread. And those threads can be combined with others, too. Viewing discussion messages as related threads is really handy (see Figure 10.2).
Figure 10.2 : Viewing messages in threaded order.
MAPI uses two special properties of the Message object to keep track of each branch and sub-branch of a threaded discussion. These two properties are the ConversationTopic and the ConversationIndex properties. The ConversationTopic is usually a readable string. This is much like the subject line of a mail message. Conversation topics for a discussion forum for a typical computer firm might be
Note |
Some forums have fixed topics and only allow users to select from a predetermined list when posting messages. Other forums allow users to enter free-form text into the topic field. |
MAPI uses the ConversationIndex property to keep track of conversation threads within the topic group. This is usually done using some type of numbering scheme. Microsoft recommends that programmers use the same method that is employed by Microsoft Exchange when populating the ConversationIndex field. Microsoft Exchange uses the CoCreateGuid API call to generate a unique number to place in the ConversationIndex field. New messages that respond to an existing message should inherit the ConversationIndex of the previous message and append their own index value to the right. This way, each new branch of the conversation has a longer ConversationIndex. You'll get a better look at this technique when you build the sample Visual Basic project later in this chapter.
Note |
The CoCreateGuid API generates a unique value based on the system date (among other things). You can use any method you wish to keep track of conversation threads, but it is highly recommended that you use the method described here. It is quick, effective, and requires very few computing resources. |
The last major concept to deal with when building discussion tools is the use of the MAPI Update method instead of the Send method. In all the examples covered in the book so far, when you complete a new message and want to place it into the MAPI message pool, you use the Send method (or some type of Send API call) to release the message to MAPI for delivery. This works when you have a person to whom you are addressing the message. However, because discussion messages are not addressed to persons but to folders instead, you cannot "send" new messages. Instead you use the Update method to update the contents of a MAPI folder. In fact, attempting to use the Send method to deliver forum messages will result in a MAPI error.
Tip |
The key idea here is to think of updating the contents of a MAPI folder as opposed to sending a message to a person. |
The actual process of placing a message in a MAPI folder includes adding a blank message to the target folder, setting that message's subject and text body (just like any mail message) and then setting several other message properties, too. Each of the properties shown in Table 10.1 must be set properly before you invoke the Update method to place a message in a MAPI folder.
Note |
You'll get to see the details of creating a forum message when you build the forum tool later in this chapter. The main thing to keep in mind now is that you do not use the Send method to place messages in folders. |
So there are the three key points to remember about the differences between e-mail and discussion messages:
Now it's time to use Visual Basic to build the example forum tool.
For the rest of the chapter, you'll build a complete discussion tool that can be used to track ongoing messages in a target folder. You'll be able to use the program to read messages, generate replies to existing messages, and start new discussion threads. You'll also be able to select the target folder for the discussion. This way, you can use the same tool to monitor more than one discussion.
Note |
The forum tool described in this chapter is a very basic project. Not much time will be spent on the user interface. This is done to keep focus on the general issues of creating a discussion tool for MAPI systems. If you plan to put a version of this project into production, you'll want to add some bells and whistles to make this a more friendly product. |
You'll use the OLE Messaging library (OML) to access MAPI services for this project. The OML gives you all the features you need to be able to manipulate message and folder properties within the MAPI file system. Although you can use the MAPI.OCX to read most of the discussion-related properties, you cannot use the OCX to place messages in a target MAPI folder. Also, you cannot use the simple MAPI API declarations to gain access to target folders or to manipulate message properties.
Tip |
If you are planning to build any software that manipulates folders, you'll need to use the OLE Messaging library. No other Microsoft programming interface (besides the C MAPI interface) allows you to gain access to the folders collection. |
There is one main code module and several forms in the Discuss project you'll build here.
Note |
Before you start coding the Discuss project, make sure you have added the OLE Messaging type library. Do this by selecting Tools | References from the main menu of Visual Basic. You will need to install the OLE Messaging library from the MSDN CD-ROM before you can locate it using the "Tools | References" menu option. |
The MAPIPost Code Library contains several very important routines. These routines will be used throughout the project to gain access to folder collections and message collections within the folders. This also holds routines for posting new messages and generating replies to existing messages. There are also several routines for performing folder and message searches. You'll be able to use the routines in this module in other MAPI-related projects.
First, start a new Visual Basic project and add a BAS module (Insert | Module). Set its Name property to MAPIPost and save the file as MAPIPOST.BAS. Now add the code shown in Listing 10.1 to the declaration section of the form.
Listing 10.1. Adding the declarations for the module.
Option Explicit
'
' OLE message objects
Public objSession As Object
Public objMsgColl As Object
Public objMsg As Object
Public objRecipColl As Object
Public objRecip As Object
Public objAttachColl As Object
Public objAttach As Object
Public objAddrEntry As Object
Public objUserEntry As Object
Public objFolderColl As Object
Public objFolder As Object
Public objInfoStoreColl As Object
Public objInfoStore As Object
Public gnIndentlevel as Integer
Public cStoreID As String
'
' UDT for store/folder pairs
Type FolderType
Name As String
FolderID As String
StoreID As String
End Type
'
Public FolderRec() As FolderType ' members
Public iFldrCnt As Integer ' pointer
'
' UDT for message/conversation pairs
Type MsgType
Subject As String
Topic As String
ConvIndex As String
MsgID As String
Date As Date
End Type
'
Public MsgRec() As MsgType ' members
Public iMsgCnt As Integer ' pointer
'
' type for creating Exchange-compliant timestamp
Type GUID
guid1 As Long
guid2 As Long
Listing 10.1. continued
guid3 As Long
guid4 As Long
End Type
'
Declare Function CoCreateGuid Lib "OLE32.DLL" (pGuid As GUID) As Long
Public Const S_OK = 0
You'll notice the usual OLE Messaging library object declarations along with two user-defined types that will be used to keep track of message and folder collections. This will come in quite handy as you'll see later on. There is also the CoCreateGuid API call. You'll use this to generate unique ConversationIndex values for your message threads.
Next you need to add code that starts and ends MAPI services. Listing 10.2 shows the OLEMAPIStart and OLEMAPIEnd routines. Add them to your project.
Listing 10.2. Adding the OLEMAPIStart and OLEMAPIEnd routines.
Public Sub OLEMAPIStart(cUserProfile As String)
'
' start an OLE MAPI session
'
On Error Resume Next
Set objSession = CreateObject("MAPI.Session")
objSession.Logon profilename:=cUserProfile
'
If Err <> 0 Then
MsgBox "Unable to Start MAPI Services!", vbCritical, "OLEMAPIStart"
End
End If
'
End Sub
Public Sub OLEMAPIEnd()
'
On Error Resume Next
objSession.Logoff ' end mapi session
'
End Sub
The next routines are used to build a collection of all the folders in all the message stores available to the workstation. You'll remember that MAPI 1.0 allows more than one message store for each workstation. Typically, users will have a personal message store, a server-based collection of messages, and possibly a message store related to an outside messaging service (such as Sprint, CompuServe, and so on). Each one of these stores has its own set of folders, too. The routine in Listing 10.3 shows you how you can enumerate all the folders in all the message stores and build a local user-defined type that you can use to locate and manipulate MAPI folders. Add the code shown in Listing 10.3 to your project.
Listing 10.3. Adding the CollectFolders routine.
Public Sub CollectFolders(Optional ctrl As Variant)
'
' build folder tree
'
Dim objRootFolder As Object
Dim lobjFolders As Object
Dim nIter As Integer
Dim nStoreCount as Integer
Dim bLoadCtrl As Boolean
'
' loading optional display?
If IsMissing(ctrl) Or IsNull(ctrl) Then
ctrl = Null
bLoadCtrl = False
Else
bLoadCtrl = True
End If
'
gnIndentlevel = 0
cStoreID = ""
'
' get collection of information stores
Set objInfoStoreColl = objSession.InfoStores
nStoreCount = objInfoStoreColl.Count
'
' walk through all stores to get folders
For nIter = 1 To nStoreCount
iFldrCnt = iFldrCnt + 1
ReDim Preserve FolderRec(iFldrCnt)
FolderRec(iFldrCnt).StoreID = objInfoStoreColl.Item(nIter).ID
FolderRec(iFldrCnt).FolderID = "" ' no folder for a store!
FolderRec(iFldrCnt).Name = objInfoStoreColl.Item(nIter).Name
cStoreID = FolderRec(iFldrCnt).StoreID ' hold this for the other folders
'
' add to display control
If bLoadCtrl = True Then
ctrl.AddItem FolderRec(iFldrCnt).Name ' add to display
End If
'
' point to top of store and start loading
Set objRootFolder = objInfoStoreColl.Item(nIter).RootFolder
Set objFolderColl = objRootFolder.Folders
Set lobjFolders = objFolderColl
LoadFolder lobjFolders, ctrl
gnIndentlevel = 0
Next nIter
'
End Sub
You'll see that the routine in Listing 10.3 walks through all the attached message stores and calls the LoadFolders routine to actually collect all the folders in a message store. This routine also allows you to pass an option list control (list box, combo box, or outline control). The routine will use this control to build an onscreen pick list of the available folders.
Now add the LoadFolders routine from Listing 10.4 to your project.
Listing 10.4. Adding the LoadFolders routine.
Sub LoadFolder(aFolders As Object, Optional ctrl As Variant)
'
' look for folders in the collection
'
Dim mobjFolder As Object
Dim mFolderCol As Object
Dim bLoadCtrl As Boolean
'
gnIndentlevel = gnIndentlevel + 1
'
If IsMissing(ctrl) Or IsNull(ctrl) Then
bLoadCtrl = False
Else
bLoadCtrl = True
End If
'
Set mobjFolder = aFolders.GetFirst
While Not mobjFolder Is Nothing
iFldrCnt = iFldrCnt + 1
ReDim Preserve FolderRec(iFldrCnt)
'
FolderRec(iFldrCnt).StoreID = cStoreID
FolderRec(iFldrCnt).FolderID = mobjFolder.ID
FolderRec(iFldrCnt).Name = mobjFolder.Name
'
' optionally load screen display
If bLoadCtrl = True Then
ctrl.AddItem Space(gnIndentlevel * 5) & FolderRec(iFldrCnt).Name
End If
'
' look for nested folders
Set mFolderCol = mobjFolder.Folders
LoadFolder mFolderCol, ctrl
'
' done with nested folders
gnIndentlevel = gnIndentlevel - 1
Set mobjFolder = aFolders.GetNext
Wend
'
End Sub
Notice that this routine is called recursively in order to collect all the folders that might be found within a folder. Since MAPI places no restrictions on how many levels of folders may be defined, you need to use a recursive routine to locate all the available folders.
Once you have built the folder collection, you'll need a method for pulling information out of the collection. Listing 10.5 shows a function that will take the friendly name of a folder and return the unique folder ID and store ID. Add this to your project.
Listing 10.5. Adding the GetFolderRec function.
Public Function GetFolderRec(cFolderName As String) As FolderType
'
' take name, return structure
'
Dim x As Integer
Dim y As Integer
'
' start w/ a blank return
GetFolderRec.StoreID = ""
GetFolderRec.FolderID = ""
GetFolderRec.Name = ""
'
y = UBound(FolderRec) ' get total recs in array
'
' walk through array
For x = 0 To y - 1
If Trim(UCase(FolderRec(x).Name)) = Trim(UCase(cFolderName)) Then
GetFolderRec = FolderRec(x)
Exit Function
End If
Next x
'
End Function
Warning |
This routine will return the folder record of the first folder with the name you request. Because MAPI allows users to define two folders with the same name, this routine may not always return the results expected. This works fine for most projects, but you should keep it in mind when developing MAPI search tools. |
You also need some tools for collecting and accessing all the discussion messages in a folder. Listing 10.6 shows the routine that you can use to collect all messages into a local user-defined type. Add the routine to your project.
Listing 10.6. Adding the OLEMAPIGetMsgs routine.
Public Sub OLEMAPIGetMsgs(cFolderName As String, Optional ctrl As Variant)
'
' get all the discussion messages
'
Dim uFolder As FolderType
ReDim MsgRec(0)
Dim bLoadCtrl As Boolean
'
Set objFolder = Nothing
Set objMsgColl = Nothing
Set objMsg = Nothing
'
iMsgCnt = 0
uFolder = GetFolderRec(cFolderName)
'
' check for optional control
If IsMissing(ctrl) Or IsNull(ctrl) Then
ctrl = Null
bLoadCtrl = False
Else
bLoadCtrl = True
ctrl.Clear
End If
'
' now walk through folder to find discussion msgs
If uFolder.Name = "" Then
MsgBox "Unable to locate folder!", vbExclamation, uFolder.Name
Exit Sub
End If
'
' open store, folder
Set objFolder = objSession.GetFolder(uFolder.FolderID, uFolder.StoreID)
If objFolder Is Nothing Then
MsgBox "Unable to open folder!", vbExclamation, uFolder.Name
Exit Sub
End If
'
' get message collection
Set objMsgColl = objFolder.Messages
If objMsgColl Is Nothing Then
MsgBox "No messages in folder", vbExclamation, uFolder.Name
Exit Sub
End If
'
' ok, get first message
On Error Resume Next
Set objMsg = objMsgColl.GetFirst
'
' now walk through folder to get all discussion msgs
Do Until objMsg Is Nothing
If objMsg.Type = "IPM.Discuss" Then
iMsgCnt = iMsgCnt + 1
ReDim Preserve MsgRec(iMsgCnt)
MsgRec(iMsgCnt).Subject = objMsg.Subject
MsgRec(iMsgCnt).Topic = objMsg.ConversationTopic
MsgRec(iMsgCnt).ConvIndex = objMsg.ConversationIndex
MsgRec(iMsgCnt).MsgID = objMsg.ID
MsgRec(iMsgCnt).Date = objMsg.TimeReceived
'
' optionally load control
If bLoadCtrl = True Then
ctrl.AddItem objMsg.ConversationIndex
End If
End If
Set objMsg = objMsgColl.GetNext
Loop
'
Beep ' tell 'em you're done!
'
End Sub
You'll notice that this routine allows you to place the messages in a list control for sorting and display. You may also have noticed that the field placed in the controls is the ConversationIndex property. Sorting the messages on the ConversationIndex property will automatically give you the threaded list you need. You can then take the sorted list from the list control and use that to generate an onscreen display of the subject or other properties of the message-all in threaded order!
You'll need three different routines to access messages from the user-defined array. First, you need a routine that allows you to pass in a pointer to the sorted list and that returns the ConversationIndex of a message. Listing 10.7 shows how this is done.
Listing 10.7. Adding the MsgIndex function.
Public Function MsgIndex(ctrl As Control, iPtr As Integer) As String
'
' accept pointer to sorted list control
' and the sorted list control
' return msg convIndex property
'
MsgIndex = ctrl.List(iPtr)
'
End Function
Next you need a routine that takes the conversation index and returns the complete internal message structure. Listing 10.8 shows you how to do this step.
Listing 10.8. Adding the MsgPtr function.
Public Function MsgPtr(cIndex As String) As MsgType
'
' accepts conversation index, returns msg type
'
Dim x As Integer
Dim y As Integer
'
y = UBound(MsgRec)
'
For x = 1 To y
If MsgRec(x).ConvIndex = cIndex Then
MsgPtr = MsgRec(x)
Exit Function
End If
Next x
'
End Function
Finally, you can also use a function that returns the Message user-defined type based on the direct pointer. Add the code from listing 10.9 to your project.
Listing 10.9. Adding the GetMsgRec function.
Public Function GetMsgRec(iPointer As Integer) As MsgType
'
' accept pointer, return strucutre
'
'
' start w blank records
GetMsgRec.MsgID = ""
GetMsgRec.ConvIndex = ""
GetMsgRec.Subject = ""
GetMsgRec.Topic = ""
'
' now try to find it
If iPointer < 0 Or iPointer > UBound(MsgRec) Then
MsgBox "Invalid Message pointer!", vbExclamation, "GetMsgRec"
Exit Function
Else
GetMsgRec = MsgRec(iPointer + 1)
End If
'
End Function
While you're coding the message routines, add the FillOutline subroutine shown in Listing 10.10. This routine loads an outline control from the sorted list. The outline can then be displayed to the user.
Listing 10.10. Adding the FillOutline routine.
Public Sub FillOutline(ctrlIn As Control, ctrlOut As Control)
'
' accept a sorted list box as input
' copy the recs to an outline w/ indents
'
Dim x As Integer
Dim uMsg As MsgType
'
ctrlOut.Clear ' throw all the old stuff out
'
' load the outline in sorted order
For x = 0 To ctrlIn.ListCount - 1
uMsg = MsgPtr(ctrlIn.List(x))
ctrlOut.AddItem uMsg.Subject & " (" & Format(uMsg.Date, "general date") & Â")"
If bThreaded = True Then
ctrlOut.Indent(x) = Len(ctrlIn.List(x)) / 16
End If
Next x
'
' expand all nodes for viewing
For x = 0 To ctrlOut.ListCount - 1
If ctrlOut.HasSubItems(x) = True Then
ctrlOut.Expand(x) = True
End If
Next x
'
End Sub
The FillOutline routine also makes sure threaded messages are indented properly and expands the entire message tree for users to see the various branches.
One more handy routine is the MakeTimeStamp function. This will be used to generate the ConversationIndex values. Add the code from Listing 10.11 to your project.
Listing 10.11. Adding the MakeTimeStamp routine.
Public Function MakeTimeStamp() As String
'
' create Exchange-type time stamp
'
Dim lResult As Long
Dim lGuid As GUID
'
On Error GoTo LocalErr
'
lResult = CoCreateGuid(lGuid)
If lResult = S_OK Then
MakeTimeStamp = Hex$(lGuid.guid1) & Hex$(lGuid.guid2)
Else
MakeTimeStamp = "00000000" ' zeroes
End If
Exit Function
LocalErr:
MsgBox "Error " & Str(Err) & ": " & Error$(Err)
MakeTimeStamp = "00000000"
Exit Function
'
End Function
Only two routines remain: OLEMAPIPostMsg and OLEMAPIReplyMsg. The OLEMAPIPostMsg routine builds and posts a new message thread to the target folder. Add the code from Listing 10.12 to your project.
Listing 10.12. Adding the OLEMAPIPostMsg routine.
Public Sub OLEMAPIPostMsg(cFolderName As String, cTopic As String, cSubject As String, cBody As String)
'
' post a message to the folder (starts a new thread)
'
' --------------
' Inputs:
' cFolderName - name of target folder
' cTopic - general discussion topic
' cSubject - user's subject line
' cBody - user's body text
' --------------
'
Dim uFolder As FolderType
'
' get folder structure
uFolder = GetFolderRec(cFolderName) ' get the structure
If uFolder.FolderID = "" Then
MsgBox "Unable to Locate Folder!", vbExclamation, cFolderName
Exit Sub
End If
'
' open folder, store
Set objFolder = objSession.GetFolder(uFolder.FolderID, uFolder.StoreID)
If objFolder Is Nothing Then
MsgBox "Unable to open folder!", vbExclamation, uFolder.Name
Exit Sub
End If
'
' create new message
Set objMsg = objFolder.Messages.Add
If objMsg Is Nothing Then
MsgBox "Unable to create new message in folder!", vbExclamation, ÂuFolder.Name
Exit Sub
End If
'
' fix up subject/topic
If cTopic = "" And cSubject = "" Then
cTopic = "Thread #" & Format(Now(), "YYMMDDHHmm")
End If
'
If cTopic = "" And cSubject <> "" Then
cTopic = cSubject
End If
'
If cSubject = "" And cTopic <> "" Then
cSubject = cTopic
End If
'
' now compose the message
With objMsg
'
' set user-supplied info
.Subject = cSubject
.Text = cBody
'
' set stock properties
.Type = "IPM.Discuss"
.ConversationTopic = cTopic
.ConversationIndex = MakeTimeStamp
.TimeSent = Now()
.TimeReceived = .TimeSent
.Submitted = True
.Unread = True
.Sent = True
'
.Update ' force msg into the folder
End With
'
objFolder.Update ' update the folder object
'
End Sub
There are quite a few things going on in this routine. First, it locates the folder and message store where the message will be posted. Then a new message object is created, populated with the appropriate values, and posted (using the Update method) to the target folder.
The OLEMAPIReplyMsg is quite similar, but this method carries information forward from the source message to make sure that the conversation thread is maintained. Add the code from Listing 10.13 to your project.
Listing 10.13. Adding the OLEMAPIReplyMsg routine.
Public Sub OLEMAPIReply(FolderName As String, iCount As Integer, cSubject As ÂString, cBody As String)
'
' post a message reply (continues existing thread)
'
' -----------------
' Inputs:
' FolderName - string name of target folder
' iCount - index into msg array (points to source msg)
' cSubject - user's subject line (if null uses "RE:" & source subject)
' cBody - user's msg body
' -----------------
'
Dim cIndex As String
Dim uMsg As MsgType
Dim uFolder As FolderType
Dim objSourceMsg As Object
'
' check msg pointer
If iCount < 0 Then
MsgBox "No Message Selected", vbExclamation, "OLEMAPIReply"
Exit Sub
End If
'
' get folder for posting
uFolder = GetFolderRec(FolderName)
If uFolder.FolderID = "" Then
MsgBox "Unable to locate folder!", vbExclamation, FolderName
Exit Sub
End If
'
' get source message
cIndex = MsgIndex(frmMsgs.list1, iCount)
uMsg = MsgPtr(cIndex)
Set objSourceMsg = objSession.GetMessage(uMsg.MsgID, uFolder.StoreID)
If objSourceMsg Is Nothing Then
MsgBox "Unable to Load selected Message!", vbExclamation, uMsg.Subject
Exit Sub
End If
'
' open target store, folder
Set objFolder = objSession.GetFolder(uFolder.FolderID, uFolder.StoreID)
If objFolder Is Nothing Then
MsgBox "Unable to open target folder", vbExclamation, uFolder.Name
Exit Sub
End If
'
' create a new blank message object
Set objMsg = objFolder.Messages.Add
If objMsg Is Nothing Then
MsgBox "Unable to add message to folder", vbExclamation, objFolder.Name
Exit Sub
End If
'
' fix up target subject, if needed
If cSubject = "" Then
cSubject = "RE: " & objSourceMsg.Subject
End If
'
' now compose reply msg
With objMsg
'
' user properties
.Subject = cSubject
.Text = cBody
'
' stock properties
.Type = "IPM.Discuss"
.ConversationTopic = objSourceMsg.ConversationTopic
.ConversationIndex = objSourceMsg.ConversationIndex & MakeTimeStamp
.TimeSent = Now()
.TimeReceived = .TimeSent
.Submitted = True
.Unread = True
.Sent = True
'
.Update ' force msg into folder
End With
'
objFolder.Update ' update folder object
'
End Sub
That's all there is to this module. Save the module (MAPIPOST.BAS) and the project (DISCUSS.VBP) before moving on to the next section.
The two main forms for the Discuss project are the MDI Discuss form and the Msgs form. The MDI form presents a button array and hosts all the other forms. The Msgs form is used to display the threaded discussion list.
You'll also need to add a few values to a short BAS module. These are project-level values that are used throughout the project. Add a BAS module, set its Name property to ModDiscuss and save it as MODDISCUSS.BAS. Now add the code shown in Listing 10.14 to the general declaration section of the form.
Listing 10.14. Adding project-level declarations.
Option Explicit
'
' constants
Public Const dscRead = 0
Public Const dscNewPost = 1
Public Const dscReply = 2
'
' variables
Public cGroup As String
Public cProfile As String
Public bThreaded As Boolean
That's all you need to add to this form. Save it (MODDISCUSS.BAS) and close it now.
The Discuss form is the MDI form that controls the entire project. Refer to Figure 10.3 and Table 10.2 when laying out the Discuss form.
Figure 10.3 : Laying out the Discuss form.
Control | Property | Setting |
VB.MDIForm | Name | mdiDiscuss |
BackColor | &H8000000C& | |
Caption | "Discuss" | |
ClientHeight | 5685 | |
ClientLeft | 735 | |
ClientTop | 1710 | |
ClientWidth | 9210 | |
Height | 6090 | |
Left | 675 | |
LinkTopic | "MDIForm1" | |
Top | 1365 | |
Width | 9330 | |
WindowState | 2 'Maximized | |
VB.PictureBox | Name | Picture2 |
Align | 2 'Align Bottom | |
Height | 345 | |
Left | 0 | |
ScaleHeight | 285 | |
ScaleWidth | 9150 | |
TabIndex | 8 | |
Top | 5340 | |
Width | 9210 | |
VB.Label | Name | lblStatus |
Caption | "Label1" | |
Height | 315 | |
Left | 60 | |
TabIndex | 9 | |
Top | 0 | |
Width | 9135 | |
VB.PictureBox | Name | Picture1 |
Align | 1 'Align Top | |
Height | 495 | |
Left | 0 | |
ScaleHeight | 435 | |
ScaleWidth | 9150 | |
TabIndex | 0 | |
Top | 0 | |
Width | 9210 | |
VB.CommandButton | Name | cmdMain |
Caption | "&Close" | |
Font | ||
name="MS Sans Serif" | ||
charset=0 | ||
weight=700 | ||
size=8.25 | ||
underline=0 'False | ||
italic=0 'False | ||
strikethrough=0 'False | ||
Height | 300 | |
Index | 6 | |
Left | 7680 | |
TabIndex | 7 | |
Top | 60 | |
Width | 1200 | |
VB.CommandButton | Name | cmdMain |
Caption | "&About" | |
Font | ||
name= "MS Sans Serif" | ||
charset=0 | ||
weight=700 | ||
size=8.25 | ||
underline=0 'False | ||
italic=0 'False | ||
strikethrough=0 'False | ||
Height | 300 | |
Index | 5 | |
Left | 6420 | |
TabIndex | 6 | |
Top | 60 | |
Width | 1200 | |
VB.CommandButton | Name | cmdMain |
Caption | "&Options" | |
Font | ||
name="MS Sans Serif" | ||
charset=0 | ||
weight=700 | ||
size=8.25 | ||
underline=0 'False | ||
italic=0 'False | ||
strikethrough=0 'False | ||
Height | 300 | |
Index | 4 | |
Left | 5160 | |
TabIndex | 5 | |
Top | 60 | |
Width | 1200 | |
VB.CommandButton | Name | cmdMain |
Caption | "&Load Msgs" | |
Font | ||
name="MS Sans Serif" | ||
charset=0 | ||
weight=700 | ||
size=8.25 | ||
underline=0 'False | ||
italic=0 'False | ||
strikethrough=0 'False | ||
Height | 300 | |
Index | 3 | |
Left | 3840 | |
TabIndex | 4 | |
Top | 60 | |
Width | 1200 | |
VB.CommandButton | Name | cmdMain |
Caption | "&Reply" | |
Font | ||
name="MS Sans Serif" | ||
charset=0 | ||
weight=700 | ||
size=8.25 | ||
underline=0 'False | ||
italic=0 'False | ||
strikethrough=0 'False | ||
Height | 300 | |
Index | 2 | |
Left | 2580 | |
TabIndex | 3 | |
Top | 60 | |
Width | 1200 | |
VB.CommandButton | Name | cmdMain |
Caption | "&New Post" | |
Font | ||
name="MS Sans Serif" | ||
charset=0 | ||
weight=700 | ||
size=8.25 | ||
underline=0 'False | ||
italic=0 'False | ||
strikethrough=0 'False | ||
Height | 300 | |
Index | 1 | |
Left | 1320 | |
TabIndex | 2 | |
Top | 60 | |
Width | 1200 | |
VB.CommandButton | Name | cmdMain |
Caption | "R&ead" | |
Font | ||
name="MS Sans Serif" | ||
charset=0 | ||
weight=700 | ||
size=8.25 | ||
underline=0 'False | ||
italic=0 'False | ||
strikethrough=0 'False | ||
Height | 300 | |
Index | 0 | |
Left | 60 | |
TabIndex | 1 | |
Top | 60 | |
Width | 1200 |
After you complete the form layout, save the form as MDIDISCUSS.FRM before you begin coding.
Most of the code in the MDI Discuss form is needed to react to user buttons or basic form events. There are also two custom routines used for reading existing messages and creating new ones.
Listing 10.15 shows the MDIForm_Load event. Add this to your project.
Listing 10.15. Coding the MDIForm_Load event.
Private Sub MDIForm_Load()
'
' startup
'
cGroup = GetSetting(App.EXEName, "Options", "Group", "CDG")
cProfile = GetSetting(App.EXEName, "Options", "Profile", "")
bThreaded = GetSetting(App.EXEName, "Options", "Threaded", True)
'
lblStatus = "Logging into Discussion Group [" & cGroup & "]..."
'
OLEMAPIStart cProfile
CollectFolders
OLEMAPIGetMsgs cGroup, frmMsgs.list1
FillOutline frmMsgs.list1, frmMsgs.Outline1
frmMsgs.Show
lblStatus = ""
'
End Sub
Listing 10.16 shows the code for the Activate and Resize events of the form. Add these two routines to your project.
Listing 10.16. Adding the MDIForm_Activate and MDIForm_Resize events.
Private Sub MDIForm_Activate()
'
Me.Caption = "Discuss [" & cGroup & "]"
'
End Sub
Private Sub MDIForm_Resize()
'
' adjust buttons on form
'
Dim iFactor As Integer
Dim iPosition As Integer
Dim x As Integer
'
iFactor = Me.Width / 7 ' # of buttons
If iFactor < 1200 Then iFactor = 1200
'
For x = 0 To 6
cmdMain(x).Width = iFactor * 0.9
cmdMain(x).Left = iFactor * x + 60
Next x
'
End Sub
Finally, add the code from Listing 10.17 to the MDIForm_Unload event.
Listing 10.17. Adding the MDIForm_Unload event code.
Private Sub MDIForm_Unload(Cancel As Integer)
'
' cleanup before leaving
'
'
' write to registry
SaveSetting App.EXEName, "Options", "Profile", cProfile
SaveSetting App.EXEName, "Options", "Group", cGroup
SaveSetting App.EXEName, "Options", "Threaded", bThreaded
'
'drop all loaded forms
'
Dim x As Integer
Dim y As Integer
'
x = Forms.Count - 1
For y = x To 0 Step -1
Unload Forms(y)
Next
End
'
End Sub
Next, you need to add code to the cmdMain_Click event to handle user selections from the button array. Listing 10.18 shows the code to handle this.
Listing 10.18. Coding the cmdMain_Click event.
Private Sub cmdMain_Click(Index As Integer)
'
' handle main user selections
'
Dim uFolder As FolderType
Dim uMsgRec As MsgType
Dim objMsg As Object
'
Select Case Index
Case 0 ' read
LoadMsgRec dscRead
Case 1 ' new post
NewMsgRec
Case 2 ' reply
LoadMsgRec dscReply
Case 3 ' load msgs
OLEMAPIGetMsgs cGroup, frmMsgs.list1
FillOutline frmMsgs.list1, frmMsgs.Outline1
Case 4 ' options
frmOptions.Show
Case 5 ' about
frmAbout.Show
Case 6 ' close
Unload Me
End Select
'
End Sub
You can see that the cmdMain routine calls two other routines: LoadMsgRec and NewMsgRec. You need to add these routines to your project. First, add the NewMsgRec subroutine to the form. Add the code from Listing 10.19.
Listing 10.19. Adding the NerwMsgRec routine.
Public Sub NewMsgRec()
'
' start a new posting
'
Load frmNote
frmNote.txtSubject = "Msg #" & Format(Now(), "YYMMDDHHmm")
frmNote.txtTopic = frmNote.txtSubject
frmNote.txtBody = ""
frmNote.lblMode = dscNewPost
frmNote.Show
'
End Sub
The code in Listing 10.19 initializes values on the note form and then calls the form for the user.
The code for the LoadMsgRec routine is a bit more complicated. This routine must first locate the selected message, load it into the form, and then call the note form. Add the code in Listing 10.20 to your form.
Listing 10.20. Adding the LoadMsgRec routine.
Public Sub LoadMsgRec(iMode As Integer)
'
' read a message record
'
Dim uFolder As FolderType
Dim uMsgRec As MsgType
Dim cIndex As String
'
' first get folder type
uFolder = GetFolderRec(cGroup)
If uFolder.FolderID = "" Then
MsgBox "Unable to locate Folder", vbExclamation, "ReadMsgRec"
Exit Sub
End If
'
' get message type
cIndex = MsgIndex(frmMsgs.list1, frmMsgs.Outline1.ListIndex)
uMsgRec = MsgPtr(cIndex)
If uMsgRec.MsgID = "" Then
MsgBox "Unable to locate Message", vbExclamation, "ReadMsgRec"
Exit Sub
End If
'
' now load real msg from MAPI
Set objMsg = objSession.GetMessage(uMsgRec.MsgID, uFolder.StoreID)
If objMsg Is Nothing Then
MsgBox "Unable to Load Message.", vbExclamation, "ReadMsgRec"
Exit Sub
End If
'
' must be good, stuff the form
Load frmNote
'
frmNote.txtTopic = objMsg.ConversationTopic
If iMode = dscReply Then
frmNote.txtSubject = "RE: " & objMsg.Subject
Else
frmNote.txtSubject = objMsg.Subject
End If
frmNote.txtBody = objMsg.Text
'
' mark it for read/reply and show it
frmNote.lblMode = iMode
frmNote.lblMsgNbr = frmMsgs.Outline1.ListIndex
frmNote.Show
'
End Sub
That's all for the MDI Discuss form. Save it as MDIDISCUSS.FRM, and save the project as DISCUSS.VBP before you continue.
The Msgs form is used to show the threaded discussion list. Refer to Figure 10.4 and Table 10.3 for details on laying out the form.
Figure 10.4 : Laying out the Msgs form.
Control | Property | Setting |
VB.Form | Name | frmMsgs |
Caption | "Form1" | |
ClientHeight | 2955 | |
ClientLeft | 1140 | |
ClientTop | 1515 | |
ClientWidth | 5175 | |
Height | 3360 | |
Left | 1080 | |
LinkTopic | "Form1" | |
MDIChild | -1 'True | |
ScaleHeight | 2955 | |
ScaleWidth | 5175 | |
Top | 1170 | |
Width | 5295 | |
VB.ListBox | Name | List1 |
Height | 840 | |
Left | 3060 | |
Sorted | -1 'True | |
TabIndex | 1 | |
Top | 1200 | |
Visible | 0 'False | |
Width | 1095 | |
MSOutl.Outline | Name | Outline1 |
Height | 2715 | |
Left | 120 | |
TabIndex | 0 | |
Top | 60 | |
Width | 4935 | |
Font | ||
Name="MS Sans Serif" | ||
Size=8.25 | ||
Charset=0 | ||
Weight=700 | ||
Underline=0 'False | ||
Italic=0 'False | ||
Strikethrough=0 'False |
Note |
You'll notice that the form contains both an outline control and a list box control. The list box control is not visible at run time. It is used to automatically sort the messages by conversation index. |
There is very little code to the form. Listing 10.21 shows all the code you need to add to the Msgs form.
Listing 10.21. Coding the Msgs form.
Private Sub Form_Load()
'
Me.Left = 0
Me.Top = 0
'
Me.Caption = "Discussion Message List"
'
End Sub
Private Sub Form_Resize()
'
' expand outline control to fill form
'
If Me.WindowState <> vbMinimized Then
Outline1.Left = Me.ScaleLeft
Outline1.Top = Me.ScaleTop
Outline1.Width = Me.ScaleWidth
Outline1.Height = Me.ScaleHeight
End If
End Sub
Private Sub Outline1_DblClick()
'
mdiDiscuss.LoadMsgRec dscRead
'
End Sub
Save this form as DSCMSGS.FRM and update the project before going to the next step.
There are three other forms you need to add to the project. The Note form will be used to read and reply to messages; the Options form allows the user to set and store some program options; and the About dialog box contains typical program information.
Add a new form to the project and set its Name property to frmNote. Refer to Table 10.4 and Figure 10.5 in laying out the note form.
Figure 10.5 : Laying out the Note form.
Control | Property | Setting |
VB.Form | Name | frmNote |
Caption | "Form2" | |
ClientHeight | 4380 | |
ClientLeft | 1080 | |
ClientTop | 1995 | |
ClientWidth | 6255 | |
Height | 4785 | |
Left | 1020 | |
LinkTopic | "Form2" | |
MDIChild | -1 'True | |
ScaleHeight | 4380 | |
ScaleWidth | 6255 | |
Top | 1650 | |
Width | 6375 | |
VB.CommandButton | Name | cmdBtn |
Caption | "Cancel" | |
Font | ||
name="MS Sans Serif" | ||
charset=0 | ||
weight=700 | ||
size=8.25 | ||
underline=0 'False | ||
italic=0 'False | ||
strikethrough=0 'False | ||
Height | 300 | |
Index | 1 | |
Left | 3600 | |
TabIndex | 8 | |
Top | 3840 | |
Width | 1200 | |
VB.CommandButton | Name | cmdBtn |
Caption | "OK" | |
Font | ||
name="MS Sans Serif" | ||
charset=0 | ||
weight=700 | ||
size=8.25 | ||
underline=0 'False | ||
italic=0 'False | ||
strikethrough=0 'False | ||
Height | 300 | |
Index | 0 | |
Left | 4920 | |
TabIndex | 5 | |
Top | 3840 | |
Width | 1200 | |
VB.TextBox | Name | txtTopic |
Font | ||
name="MS Sans Serif" | ||
charset=0 | ||
weight=700 | ||
size=8.25 | ||
underline=0 'False | ||
italic=0 'False | ||
strikethrough=0 'False | ||
Height | 300 | |
Left | 1320 | |
TabIndex | 2 | |
Text | "Text3" | |
Top | 120 | |
Width | 4800 | |
VB.TextBox | Name | txtSubject |
Font | ||
name="MS Sans Serif" | ||
charset=0 | ||
weight=700 | ||
size=8.25 | ||
underline=0 'False | ||
italic=0 'False | ||
strikethrough=0 'False | ||
Height | 300 | |
Left | 1320 | |
TabIndex | 1 | |
Text | "Text2" | |
Top | 480 | |
Width | 4800 | |
VB.TextBox | Name | txtBody |
Font | ||
name="MS Sans Serif" | ||
charset=0 | ||
weight=700 | ||
size=8.25 | ||
underline=0 'False | ||
italic=0 'False | ||
strikethrough=0 'False | ||
Height | 2835 | |
Left | 120 | |
MultiLine | -1 'True | |
ScrollBars | 2 'Vertical | |
TabIndex | 0 | |
Text | "dscNote.frx":0000 | |
Top | 840 | |
Width | 6000 | |
VB.Label | Name | lblMsgNbr |
Caption | "Label6" | |
Height | 255 | |
Left | 420 | |
TabIndex | 7 | |
Top | 4020 | |
Visible | 0 'False | |
Width | 1275 | |
VB.Label | Name | lblMode |
Caption | "Label5" | |
Height | 255 | |
Left | 1200 | |
TabIndex | 6 | |
Top | 3840 | |
Visible | 0 'False | |
Width | 2055 | |
VB.Label | Name | Label4 |
Caption | "Subject:" | |
Font | ||
name="MS Sans Serif" | ||
charset=0 | ||
weight=700 | ||
size=8.25 | ||
underline=0 'False | ||
italic=0 'False | ||
strikethrough=0 'False | ||
Height | 300 | |
Left | 120 | |
TabIndex | 4 | |
Top | 480 | |
Width | 1200 | |
VB.Label | Name | Label3 |
Caption | "Topic:" | |
Font | ||
name="MS Sans Serif" | ||
charset=0 | ||
weight=700 | ||
size=8.25 | ||
underline=0 'False | ||
italic=0 'False | ||
VB.Form | Name | frmNote |
strikethrough=0 'False | ||
Height | 300 | |
Left | 120 | |
TabIndex | 3 | |
Top | 120 | |
Width | 1200 |
After laying out the form, save it (DSCNOTE.FRM) before you add the code.
Along with the typical form-related events, you need to add code to handle user button selections and a custom routine to establish the mode of the form (read, reply, or new message).
First, Listing 10.22 shows all the code for the Form_Load, Form_Activate, and Form_Resize events. Add this to your project.
Listing 10.22. Adding the Form_Load, Form_Activate, and Form_Resize code.
Private Sub Form_Load()
'
' init on first entry
'
Label1 = "" ' from
Text1 = "" ' topic
Text2 = "" ' subject
Text3 = "" ' body
'
End Sub
Private Sub Form_Activate()
'
StatusUpdate
'
End Sub
Private Sub Form_Resize()
'
' adjust controls for form
'
If Me.WindowState <> vbMinimized Then
txtBody.Left = Me.ScaleLeft
txtBody.Top = 900
txtBody.Width = Me.ScaleWidth
txtBody.Height = Me.ScaleHeight - (1320)
'
txtTopic.Width = Me.ScaleWidth - 1320
txtSubject.Width = Me.ScaleWidth - 1320
'
cmdBtn(0).Left = Me.ScaleWidth - 1440
cmdBtn(1).Left = Me.ScaleWidth - 2880
cmdBtn(1).Top = Me.ScaleHeight - 420
cmdBtn(0).Top = Me.ScaleHeight - 420
End If
End Sub
Worth mentioning here is the Form_Resize code. This code will adjust controls on the form to fill out as much (or as little) screen area as is allowed.
Another routine that deals with form controls is the StatusUpdate routine (Listing 10.23). This routine toggles the various controls based on the mode of the form (read, reply, or new message). Add the code from Listing 10.23 to your form.
Listing 10.23. Adding the StatusUpdate routine.
Public Sub StatusUpdate()
'
' check form status
'
Select Case lblMode
Case Is = dscRead
txtTopic.Enabled = False
txtSubject.Enabled = False
txtBody.Enabled = False
cmdBtn(0).Caption = "Reply"
cmdBtn(0).SetFocus
Me.Caption = "Discussion Read Form [" & txtSubject & "]"
Case Is = dscReply
txtTopic.Enabled = False
txtSubject.Enabled = True
txtBody.Enabled = True
cmdBtn(0).Caption = "Post"
txtSubject.SetFocus
Me.Caption = "Discussion Reply Form [" & txtSubject & "]"
Case Is = dscNewPost
txtTopic.Enabled = True
txtSubject.Enabled = True
txtBody.Enabled = True
cmdBtn(0).Caption = "Post"
txtTopic.SetFocus
Me.Caption = "Discussion Post New Topic Form"
End Select
'
End Sub
The last routine you need to add to the form is the code for the cmdBtn_Click event. This also uses the mode of the form to determine just what the program will do when the user presses the OK button. Add the code from Listing 10.24 to your form.
Listing 10.24. Adding the cmdBtn_Click event code.
Private Sub cmdBtn_Click(Index As Integer)
'
' handle use selections
'
Select Case Index
Case 0 ' new post
Select Case lblMode
Case Is = dscNewPost
OLEMAPIPostMsg cGroup, txtTopic, txtSubject, txtBody
MsgBox "Message has been posted!", vbInformation, "Discuss OLE"
OLEMAPIGetMsgs cGroup, frmMsgs.list1
FillOutline frmMsgs.list1, frmMsgs.Outline1
Unload Me
Case Is = dscReply
OLEMAPIReply cGroup, Val(lblMsgNbr), txtSubject, txtBody
MsgBox "Reply has been sent!", vbInformation
OLEMAPIGetMsgs cGroup, frmMsgs.list1
FillOutline frmMsgs.list1, frmMsgs.Outline1
Unload Me
Case Is = dscRead
lblMode = dscReply
txtSubject = "RE: " & txtSubject
txtBody = String(45, 45) & Chr(13) & Chr(10) & txtBody & Chr(13) & Chr(10) & String(45, 45) & Chr(13) & Chr(10)
Form_Activate
End Select
Case 1 ' close
Unload Me
End Select
'
End Sub
After you complete the code, save the form (DSCNOTE.FRM) and project (DISCUSS.VBP) before you go to the next section.
The Options form allows the user to select the folder for the discussion group, enter a MAPI profile, and turn the message display from flat to threaded. Refer to Figure 10.6 and Table 10.5 for laying out the Options form.
Figure 10.6 : Laying out the Options form.
Control | Property | Setting |
VB.Form | Name | frmOptions |
BorderStyle | 3 'Fixed Dialog | |
Caption | "Form1" | |
ClientHeight | 3270 | |
ClientLeft | 2370 | |
ClientTop | 2670 | |
ClientWidth | 5250 | |
Height | 3675 | |
Left | 2310 | |
LinkTopic | "Form1" | |
MaxButton | 0 'False | |
MDIChild | -1 'True | |
MinButton | 0 'False | |
ScaleHeight | 3270 | |
ScaleWidth | 5250 | |
ShowInTaskbar | 0 'False | |
Top | 2325 | |
Width | 5370 | |
VB.CheckBox | Name | chkThreaded |
Caption | "View Message Threads" | |
Font | ||
name="MS Sans Serif" | ||
charset=0 | ||
weight=700 | ||
size=8.25 | ||
underline=0 'False | ||
italic=0 'False | ||
strikethrough=0 'False | ||
Height | 435 | |
Left | 2700 | |
TabIndex | 7 | |
Top | 900 | |
Width | 2355 | |
VB.CommandButton | Name | cmdBtn |
Caption | "OK" | |
Font | ||
name="MS Sans Serif" | ||
charset=0 | ||
weight=700 | ||
size=8.25 | ||
underline=0 'False | ||
italic=0 'False | ||
strikethrough=0 'False | ||
Height | 300 | |
Index | 1 | |
Left | 3840 | |
TabIndex | 5 | |
Top | 2340 | |
Width | 1200 | |
VB.CommandButton | Name | cmdBtn |
Caption | "Cancel" | |
Font | ||
name="MS Sans Serif" | ||
charset=0 | ||
weight=700 | ||
size=8.25 | ||
underline=0 'False | ||
italic=0 'False | ||
strikethrough=0 'False | ||
Height | 300 | |
Index | 0 | |
Left | 3840 | |
TabIndex | 4 | |
Top | 2700 | |
Width | 1200 | |
VB.TextBox | Name | txtProfile |
Font | ||
name="MS Sans Serif" | ||
charset=0 | ||
weight=700 | ||
size=8.25 | ||
underline=0 'False | ||
italic=0 'False | ||
strikethrough=0 'False | ||
Height | 300 | |
Left | 2700 | |
TabIndex | 3 | |
Top | 480 | |
Width | 2400 | |
VB.ListBox | Name | lstGroup |
Font | ||
name="MS Sans Serif" | ||
charset=0 | ||
weight=700 | ||
size=8.25 | ||
underline=0 'False | ||
italic=0 'False | ||
strikethrough=0 'False | ||
Height | 2205 | |
Left | 120 | |
TabIndex | 1 | |
Top | 840 | |
Width | 2400 | |
VB.Label | Name | lblSelected |
BorderStyle | 1 'Fixed Single | |
Font | ||
name="MS Sans Serif" | ||
charset=0 | ||
weight=700 | ||
size=8.25 | ||
underline=0 'False | ||
italic=0 'False | ||
strikethrough=0 'False | ||
Height | 300 | |
Left | 120 | |
TabIndex | 6 | |
Top | 480 | |
Width | 2400 | |
VB.Label | Name | lblProfile |
BorderStyle | 1 'Fixed Single | |
Caption | "Enter Login Profile:" | |
Font | ||
name="MS Sans Serif" | ||
charset=0 | ||
weight=700 | ||
size=8.25 | ||
underline=0 'False | ||
italic=0 'False | ||
strikethrough=0 'False | ||
Height | 300 | |
Left | 2700 | |
TabIndex | 2 | |
Top | 120 | |
Width | 2400 | |
VB.Label | Name | lblGroup |
BorderStyle | 1 'Fixed Single | |
Caption | "Select Discussion Group:" | |
Font | ||
name="MS Sans Serif" | ||
charset=0 | ||
weight=700 | ||
size=8.25 | ||
underline=0 'False | ||
italic=0 'False | ||
strikethrough=0 'False | ||
Height | 300 | |
Left | 120 | |
TabIndex | 0 | |
Top | 120 | |
Width | 2400 |
It's a good idea to save this form (DSCOPTIONS.FRM) before you start coding.
There is not much code for the Options form. Listing 10.25 shows the code for the Form_Load event.
Listing 10.25. Coding the Form_Load event.
Private Sub Form_Load()
'
' set up form
'
Me.Caption = "Discuss Options"
txtProfile = cProfile
lblSelected = cGroup
chkThreaded.Value = IIf(bThreaded = 1, 1, 0)
CollectFolders lstGroup
'
Me.Left = (Screen.Width - Me.Width) / 2
Me.Top = (Screen.Height - Me.Height) / 2
'
End Sub
The next code to add is for the cmdBtn_Click event. This routine will save the user's choices in the system registry for later recall. Add the code from Listing 10.26 to your form.
Listing 10.26. Adding the cmdBtn_Click code.
Private Sub cmdBtn_Click(Index As Integer)
'
' handle user selection
'
Select Case Index
Case 0 ' cancel
' no action
Case 1 ' OK
'
MousePointer = vbHourglass
'
' save to vars
cGroup = Trim(lblSelected)
cProfile = Trim(txtProfile)
bThreaded = IIf(chkThreaded = 1, True, False)
'
' save to registry
SaveSetting App.EXEName, "Options", "Profile", Trim(cProfile)
SaveSetting App.EXEName, "Options", "Group", Trim(cGroup)
SaveSetting App.EXEName, "Options", "Threaded", IIf(chkThreaded.Value = Â1, 1, 0)
'
' update list & main form
OLEMAPIGetMsgs cGroup, frmMsgs.list1
FillOutline frmMsgs.list1, frmMsgs.Outline1
mdiDiscuss.Caption = "Discuss [" & cGroup & "]"
'
MousePointer = vbNormal
'
End Select
'
Unload Me
'
End Sub
Finally, you need to add one line to the lstGroup_DblClick
event:
Private Sub lstGroup_DblClick()
'
lblSelected = Trim(lstGroup.List(lstGroup.ListIndex))
'
End Sub
Now save the form (DSCOPTIONS.FRM) and update the project. Only one more form to go!
The About dialog box contains basic information about the project. Refer to Figure 10.7 and Table 10.6 for laying out the form.
Figure 10.7 : Laying out the About dialog box.
Control | Property | Setting |
VB.Form | Name | frmAbout |
BorderStyle | 1 'Fixed Single | |
Caption | "Form1" | |
ClientHeight | 1785 | |
ClientLeft | 2820 | |
ClientTop | 3165 | |
ClientWidth | 4335 | |
Height | 2190 | |
Left | 2760 | |
LinkTopic | "Form1" | |
MaxButton | 0 'False | |
MDIChild | -1 'True | |
MinButton | 0 'False | |
ScaleHeight | 1785 | |
ScaleWidth | 4335 | |
Top | 2820 | |
Width | 4455 | |
VB.CommandButton | Name | cmdOK |
Caption | "OK" | |
Font | ||
name="MS Sans Serif" | ||
charset=0 | ||
weight=700 | ||
size=8.25 | ||
underline=0 'False | ||
italic=0 'False | ||
strikethrough=0 'False | ||
Height | 300 | |
Left | 3000 | |
TabIndex | 0 | |
Top | 1380 | |
Width | 1200 | |
VB.Image | Name | Image1 |
Height | 1215 | |
Left | 120 | |
Picture | "dscAbout.frx":0000 | |
Stretch | -1 'True | |
Top | 60 | |
Width | 1275 | |
VB.Label | Name | Label1 |
Caption | "Label1" | |
Font | ||
name="MS Sans Serif" | ||
charset=0 | ||
weight=700 | ||
size=8.25 | ||
underline=0 'False | ||
italic=0 'False | ||
strikethrough=0 'False | ||
Height | 1095 | |
Left | 1560 | |
TabIndex | 1 | |
Top | 120 | |
Width | 2655 |
You only need to add code to the Form_Load event and the cmdOK_click event. Listing 10.27 shows all the code for the About form.
Listing 10.27. Coding the Form_Load and cmdOK_Click events.
Private Sub cmdOK_Click()
Unload Me
End Sub
Private Sub Form_Load()
'
' set up about box
'
Me.Caption = "About " & App.ProductName
'
Image1.Stretch = True
'
Label1.Caption = App.FileDescription & Chr(13) & Chr(13)
Label1 = Label1 & App.LegalCopyright & Chr(13) & Chr(13)
'
Me.Left = (Screen.Width - Me.Width) / 2
Me.Top = (Screen.Height - Me.Height) / 2
'
End Sub
That's the last of the code for the Discuss project. Save this form (DSCABOUT.FRM) and update the project before you begin testing.
Once you've coded and compiled the project, you'll be able to load it onto any workstation that has MAPI services installed and available.
Note |
The discussion tool will not work in a Remote Mail setup. You need to have direct access to at least one message store. |
When you first start Discuss, you'll be asked to log into MAPI. One of the first things you should do is open the Options page and select a target folder and set up your default user profile (see Figure 10.8).
Figure 10.8 : Setting Discuss options.
You can select any folder as the discussion target. However, since the idea is to carry on discussions with other people, you'll typically select a public folder as the discussion target.
Note |
If you are working on a standalone machine, you can select any personal folder for this demonstration. Just remember that no one but you can see the results! |
The program will list only messages that have their Type property set to IPM.Discuss. This means the first time you select a target folder, you won't see any messages. So the next thing you need to do is add a message!
Press New Post to add a new thread to the folder (see Figure 10.9).
Figure 10.9 : Adding a new thread to the folder.
You can also highlight any message in the discussion and double-click it to read it. When you press the Reply button at the bottom of a read form, it automatically turns into a reply form. Even the subject and message body are updated to show that this is a reply (see Figure 10.10).
Figure 10.10 : Replying to a message.
In this chapter you learned how to use the OLE Messaging library to create online, threaded discussion tools using MAPI services. You learned that there are three ways in which discussion messages differ from mail messages:
You also learned how to build routines that collect all the message stores available to a workstation, all the folders in each of those stores, and all the messages within a folder. You now know how to compose a message for Update (rather than Send) and how to use the CoCreateGuid API to generate unique conversation index values used in threaded discussion tools.
Best of all, most of the heart of this example program can be used to add discussion capabilities to other Visual Basic projects.