vb设计数据库电子邮件程序



  问题的产生

  Interface Technologies (ITI)的CodeVizor工作组正在努力使他们的新工具引起程序员的注意。在先前的数个月里,成千上万的程序员注册成为ITI's DevCentral的一员,以更方便的试用它们的产品。作为注册的一部份,注册者被询问到他们是否希望得到产品的更新及新产品发布的消息。几乎所有的注册者都希望如此。

  因此,工作团队决定向所有六千名注册用户发送一封个人电子邮件信息(当然得除开那些没有留联系EMail的用户)。但是这就出现了个问题:如果是发群体信件,则就违背了发个人邮件的初衷。我们希望的是发往注册用户的邮件是个人化的。因此,我们就得找出一种解决办法,使得发向6000名注册用户的群体邮件是完全个人的,保密的。

  目的

  我的工作则是写出一个程序,使得它能够进入到DevCentral的注册用户的SQL Server数据库里,然后为每个用户生成一封电子邮件(通过Exchange/Outlook)。

  该程序会将待发的EMail保存至outlook,因此就可以组织邮件发送的过程。最好是小批量发送电子邮件,一次发送500封比较合适。这样就可以令到使用者将错误率降到最低,同样也可以降低服务器的负担,加快网络连接速度。同样我们需在邮件上加上回复地址,这样邮件则可以从DevCentral的邮箱里发出,而不是个人的邮箱。

  以下将是创建该程序的主要步骤

  使用工具及使用目的

  通过使用Visual Basic 5.0写出该EmailMaker。该应用程序通过ODBC来进入数据库,并使用VB automation调用Microsoft Outlook以生成电子邮件文本。

  最开始的目的本来在于设计出一个简单的基于对话项的程序,使之通过点击按钮就可以完成所有的步骤。但是,这也涉及到了更为多的内容:不但会使得该程序在使用上更为灵活,在功能上也更为强大,也会使我更多的了解VB,比如:combobox控件,属性栏,自定义图标,progress栏,多样化窗口,甚至于非常简单的文件保存及文件格式。因此我努力的将这些分散的VB程序应用知识积累起来,使之能成为一个"真正的程序"

  EmailMaker使得用户可以通过数据库向地址列表发送个人化邮件。通过Message Window用户可以书写,编辑并保存邮件内容(同样也可以从其它文本编辑器或文档内复制-粘贴内容)

  完成内容书写及保存后,用户则可以开始单个生成标有地址的邮件。当群体邮件位于指定文件夹里时,Send Email 功能则会要求用户分发全体或一部份邮件。

  问题及解决方案

  开发此程序最大的难点在于Properties form.。

  开发此程序的目标之一在于让所有程序的选项来自combobox中,这样可以更便于安装及使用。我做的非常成功除了一个property(Address Sent From,以下我将会提及)。但是,最重要的一个属性包含了数据库及注册的调用功能,因为程序本身需要鉴别用户的数据库及邮件配置,常通过API来鉴别安装了何种ODBC数据库。

  以下是源代码

  下列数据对于API登录非常有用:


Public Const HKEY_CURRENT_USER = &H80000001

Public Const ERROR_SUCCESS = 0&

Public Const SYNCHRONIZE = &H100000
Public Const STANDARD_RIGHTS_READ = &H20000
Public Const KEY_QUERY_VALUE = &H1
Public Const KEY_ENUMERATE_SUB_KEYS = &H8
Public Const KEY_NOTIFY = &H10

Public Const KEY_READ = ((STANDARD_RIGHTS_READ
KEY_QUERY_VALUE Or _
KEY_ENUMERATE_SUB_KEYS Or _
KEY_NOTIFY) And _
(Not SYNCHRONIZE))

Public Const REG_DWORD = 4


Public Declare Function RegOpenKeyEx Lib "advapi32.dll" _
Alias "RegOpenKeyExA" _
(ByVal hKey As Long, _
ByVal lpSubKey As String, _
ByVal ulOptions As Long, _
ByVal samDesired As Long, phkResult As Long) As Long

Public Declare Function RegEnumValue Lib "advapi32.dll" _
Alias "RegEnumValueA" _
(ByVal hKey As Long, _
ByVal dwIndex As Long, _
ByVal lpValueName As String, _
lpcbValueName As Long, _
ByVal lpReserved As Long, _
lpType As Long, _
lpData As Any, _
lpcbData As Long) As Long

Public Declare Function RegCloseKey Lib "advapi32.dll" _
(ByVal hKey As Long) As Long

'-- The subroutine FillODBCCombo is called when the Properties form is loaded.
'-- First the root ODBC key is opened. By iterating through its sub-keys,
'-- all of the installed DNS's are found and inserted into the DNS ComboBox.


Public Sub FillODBCCombo()

Dim hKey As Long

Dim dwIndex As Long
Dim lpData As Long
Dim lpcbData As Long

Dim lngResult As Long
Dim strResult As String
Dim lpValueName As String
Dim lpcbValueName As Long

'-- 每个ODBC数据源都有一个关键字位于
'-- HKEY_CURRENT_USER\Software\ODBC\ODBC.INI\ODBC Data Sources.上
'-- 通过查找每个关键字,能够收集到安装在ODBC上的所有数据源

lngResult = RegOpenKeyEx(HKEY_CURRENT_USER, _
"Software\ODBC\ODBC.INI\ODBC Data Sources", _
0&, _
KEY_READ, _
hKey)
If lngResult <> ERROR_SUCCESS Then
MsgBox "Error opening ODBC registry key."
Exit Sub
End If

dwIndex = 0

'-- Add each DNS to the combo
Do
lpcbValueName = 1000
lpcbData = 1000
lpValueName = String(lpcbValueName, 0)


'-- The RegEnumValue function allows you to
'-- move through the subkeys one at a time
lngResult = RegEnumValue(hKey, _
dwIndex, _
ByVal lpValueName, _
lpcbValueName, _
0&, _
REG_DWORD, _
ByVal lpData, _
lpcbData)
If lngResult = ERROR_SUCCESS Then
strResult = Left(lpValueName, lpcbValueName)
DSNCombo.AddItem strResult
End If
dwIndex = dwIndex + 1
Loop While lngResult = ERROR_SUCCESS

RegCloseKey hKey
End Sub


  RDO Tables

  为了进入RDO,我在"Microsoft Remote Data Objects 2.0"上添加了一个reference.这个子程序创立了与数据库的连接,而且为Table ComboBox.命名了每个Table 的

  名称

Private Sub FillTableCombo()

'-- Find all the table names using RDO
On Error GoTo DSNTablesError

Dim myEnviroment As rdoEnvironment
Dim myConnection As rdoConnection

Dim strUID As String
Dim strPWD As String

strUID = PropertyForm.UserNameText
strPWD = PropertyForm.PasswordText

Set myEnviroment = rdoEngine.rdoEnvironments(0)

Set myConnection = myEnviroment.OpenConnection(PropertyForm.DSNCombo.Text, _
Connect:="uid=" & strUID & "; pwd=" & strPWD & ";")

TableCombo.Clear
For Each tb In myConnection.rdoTables
TableCombo.AddItem tb.Name
Next

'-- Clear Fields to avoid mismatched data
FieldCombo.Clear

myConnection.Close
myEnviroment.Close

DSNTablesError:
End Sub


  ADODB Fields

  与其为ADODB作一个reference,不如通过objects来存取。此子程序将ComboBox作为一个变量参数,可以用来更新Database properties上的Field combo和Secondary Field Combo.。

  Fill Field Combo使用DNS 及 Table combo boxes提供的信息来打开表格。当型循环会扫描每个域名并将此添加到Field combo上。

Private Sub FillFieldCombo(myCombo As ComboBox)
'-- myCombo - the ComboBox that is to be updated by the subroutine
On Error GoTo DSNTablesError
'--Populate the field combo using ADODB

Dim oTempConnection As Object
Dim oTable As Object

Dim intCount As Integer
Dim intNumOfFields As Integer

Set oTempConnection = CreateObject("ADODB.Connection")
oTempConnection.Open PropertyForm.DSNCombo.Text, _
PropertyForm.UserNameText, PropertyForm.PasswordText
Set oTable = CreateObject("ADODB.RecordSet")
Set oTable.ActiveConnection = oTempConnection

oTable.Source = "SELECT * FROM " & PropertyForm.TableCombo
oTable.Open

intNumOfFields = oTable.Fields.Count
myCombo.Clear

While (intCount < intNumOfFields)
myCombo.AddItem oTable.Fields(intCount).Name
intCount = intCount + 1
Wend

oTable.Close
oTempConnection.Close
Exit Sub

DSNTablesError:
MsgBox "Invalid Table Name"
End Sub


  Outlook Objects

  FillFolderCombo和FillMailboxCombo 子程序非常类似。都是通过开启至OUTLOOK的连接以及增加combos来运作的。FillMailboxCombo:当用户登入另外的邮箱,则会被默认为是Outlook里的最上层文件夹;FillFolderCombo则是进入专门的邮箱的子文件夹并增加Folder combo。

Private Sub FillFolderCombo()
On Error GoTo Err_Folder
' 'Put the names of all available folders in the folderCombo

Dim myOlApp As Object
Dim olNamespace As Object
Dim iCount As Integer
Dim mystr As String


Set myOlApp = CreateObject("Outlook.Application")
Set olNamespace = myOlApp.GetNameSpace("MAPI")

iCount = 1
FolderCombo.Clear
mystr = MailboxCombo

While iCount <= olNamespace.folders(mystr).folders.Count
FolderCombo.AddItem olNamespace.folders(mystr).folders(iCount).Name
iCount = iCount + 1
Wend

Exit Sub
Err_Folder:
MsgBox "Unable to resolve mailbox"
End Sub



Private Sub FillMailboxCombo()
'--Fill in all the names of available mailboxes

Dim myOlApp As Object
Dim olNamespace As Object
Dim iCount As Integer

Set myOlApp = CreateObject("Outlook.Application")
Set olNamespace = myOlApp.GetNameSpace("MAPI")

iCount = 1
MailboxCombo.Clear
While iCount <= olNamespace.folders.Count
MailboxCombo.AddItem olNamespace.folders(iCount).Name
iCount = iCount + 1
Wend
End Sub


  修改和获得注册表设置

  下面是Property form和Apply button's on-click的部份代码。

'-- Property form load event
'-- Load all registry settings
DSNCombo = GetSetting("EmailMaker", "Database", "DSN", "OLE_DB_NWind_Jet")
TableCombo = GetSetting("EmailMaker", "Database", "Table", "Customers")
FieldCombo.Text = GetSetting("EmailMaker", "Database", "Field", "ContactName")
UserNameText = GetSetting("EmailMaker", "Database", "User Name", "")
PasswordText = GetSetting("EmailMaker", "Database", "Password", "")
MailboxCombo = GetSetting("EmailMaker", "Mailbox", "Mailbox", "Mailbox - NorthWind")
FolderCombo = GetSetting("EmailMaker", "Mailbox", "Folder", "Drafts")
FromText = GetSetting("EmailMaker", "Mailbox", "From", "NorthWind")
SecondaryOption = GetSetting("EmailMaker", "Secondary", "On", 0)
SecondFieldCombo.Text = GetSetting("EmailMaker", "Secondary", "Field", "")

'-- Apply button's OnClick event
Private Sub cmdApply_Click()
'-- Save all settings to registry

SaveSetting "EmailMaker", "Database", "DSN", DSNCombo.Text
SaveSetting "EmailMaker", "Database", "Table", TableCombo.Text
SaveSetting "EmailMaker", "Database", "Field", FieldCombo.Text
SaveSetting "EmailMaker", "Database", "User Name", UserNameText
SaveSetting "EmailMaker", "Database", "Password", PasswordText
SaveSetting "EmailMaker", "Mailbox", "Mailbox", MailboxCombo
SaveSetting "EmailMaker", "Mailbox", "Folder", FolderCombo
SaveSetting "EmailMaker", "Mailbox", "From", FromText
SaveSetting "EmailMaker", "Secondary", "On", SecondaryOption
SaveSetting "EmailMaker", "Secondary", "Field", SecondFieldCombo.Text

'-- Reinitialize the main form
MDIMain.Initialize

'-- Disable the Apply button
cmdApply.Enabled = False
End Sub

  当用户选择了数据库,程序就会使用RDO指令来专门化数据库以验证它们的table:然后,增加应用程序的table combo box,通过此,用户可以选择适当的table。最后,通过适当的table,ADODB指令会寻找到table的Field以便在"Fields" combo box上增加适当的域名。

  下面是RDO TABLES 代码

API Call to registry
Public Const HKEY_CURRENT_USER = &H80000001

Public Const ERROR_SUCCESS = 0&

Public Const SYNCHRONIZE = &H100000
Public Const STANDARD_RIGHTS_READ = &H20000
Public Const KEY_QUERY_VALUE = &H1
Public Const KEY_ENUMERATE_SUB_KEYS = &H8
Public Const KEY_NOTIFY = &H10

Public Const KEY_READ = ((STANDARD_RIGHTS_READ Or _
KEY_QUERY_VALUE Or _
KEY_ENUMERATE_SUB_KEYS Or _
KEY_NOTIFY) And _
(Not SYNCHRONIZE))

Public Const REG_DWORD = 4


Public Declare Function RegOpenKeyEx Lib "advapi32.dll" _
Alias "RegOpenKeyExA" _
(ByVal hKey As Long, _
ByVal lpSubKey As String, _
ByVal ulOptions As Long, _
ByVal samDesired As Long, phkResult As Long) As Long

Public Declare Function RegEnumValue Lib "advapi32.dll" _
Alias "RegEnumValueA" _
(ByVal hKey As Long, _
ByVal dwIndex As Long, _
ByVal lpValueName As String, _
lpcbValueName As Long, _
ByVal lpReserved As Long, _
lpType As Long, _
lpData As Any, _
lpcbData As Long) As Long

Public Declare Function RegCloseKey Lib "advapi32.dll" _
(ByVal hKey As Long) As Long

'-- The subroutine FillODBCCombo is called when the Properties form is loaded.
'-- First the root ODBC key is opened. By iterating through its sub-keys,
'-- all of the installed DNS's are found and inserted into the DNS ComboBox.


Public Sub FillODBCCombo()
'-- Load in names of all installed ODBC database (From registry)

Dim hKey As Long

Dim dwIndex As Long
Dim lpData As Long
Dim lpcbData As Long

Dim lngResult As Long
Dim strResult As String
Dim lpValueName As String
Dim lpcbValueName As Long

'-- Each ODBC Data source has a key located in
'-- HKEY_CURRENT_USER\Software\ODBC\ODBC.INI\ODBC Data Sources.
'-- By finding the name of each key, we can gather all the DNS's of
'-- the installed ODBC databases for the current user
lngResult = RegOpenKeyEx(HKEY_CURRENT_USER, _
"Software\ODBC\ODBC.INI\ODBC Data Sources", _
0&, _
KEY_READ, _
hKey)
If lngResult <> ERROR_SUCCESS Then
MsgBox "Error opening ODBC registry key."
Exit Sub
End If

dwIndex = 0

'-- Add each DNS to the combo
Do
lpcbValueName = 1000
lpcbData = 1000
lpValueName = String(lpcbValueName, 0)


'-- The RegEnumValue function allows you to
'-- move through the subkeys one at a time
lngResult = RegEnumValue(hKey, _
dwIndex, _
ByVal lpValueName, _
lpcbValueName, _
0&, _
REG_DWORD, _
ByVal lpData, _
lpcbData)
If lngResult = ERROR_SUCCESS Then
strResult = Left(lpValueName, lpcbValueName)
DSNCombo.AddItem strResult
End If
dwIndex = dwIndex + 1
Loop While lngResult = ERROR_SUCCESS

RegCloseKey hKey
End Sub


  RDO Tables

Private Sub FillTableCombo()

'-- Find all the table names using RDO
On Error GoTo DSNTablesError

Dim myEnviroment As rdoEnvironment
Dim myConnection As rdoConnection

Dim strUID As String
Dim strPWD As String

strUID = PropertyForm.UserNameText
strPWD = PropertyForm.PasswordText

Set myEnviroment = rdoEngine.rdoEnvironments(0)

Set myConnection = myEnviroment.OpenConnection(PropertyForm.DSNCombo.Text, _
Connect:="uid=" & strUID & "; pwd=" & strPWD & ";")

TableCombo.Clear
For Each tb In myConnection.rdoTables
TableCombo.AddItem tb.Name
Next

'-- Clear Fields to avoid mismatched data
FieldCombo.Clear

myConnection.Close
myEnviroment.Close

DSNTablesError:
End Sub

  ADODB Fields

Private Sub FillFieldCombo(myCombo As ComboBox)
'-- myCombo - the ComboBox that is to be updated by the subroutine
On Error GoTo DSNTablesError
'--Populate the field combo using ADODB

Dim oTempConnection As Object
Dim oTable As Object

Dim intCount As Integer
Dim intNumOfFields As Integer

Set oTempConnection = CreateObject("ADODB.Connection")
oTempConnection.Open PropertyForm.DSNCombo.Text, _
PropertyForm.UserNameText, PropertyForm.PasswordText
Set oTable = CreateObject("ADODB.RecordSet")
Set oTable.ActiveConnection = oTempConnection

oTable.Source = "SELECT * FROM " & PropertyForm.TableCombo
oTable.Open

intNumOfFields = oTable.Fields.Count
myCombo.Clear

While (intCount < intNumOfFields)
myCombo.AddItem oTable.Fields(intCount).Name
intCount = intCount + 1
Wend

oTable.Close
oTempConnection.Close
Exit Sub

DSNTablesError:
MsgBox "Invalid Table Name"
End Sub



  Outlook Objects

Private Sub FillFolderCombo()
On Error GoTo Err_Folder
' 'Put the names of all available folders in the folderCombo

Dim myOlApp As Object
Dim olNamespace As Object
Dim iCount As Integer
Dim mystr As String


Set myOlApp = CreateObject("Outlook.Application")
Set olNamespace = myOlApp.GetNameSpace("MAPI")

iCount = 1
FolderCombo.Clear
mystr = MailboxCombo

While iCount <= olNamespace.folders(mystr).folders.Count
FolderCombo.AddItem olNamespace.folders(mystr).folders(iCount).Name
iCount = iCount + 1
Wend

Exit Sub
Err_Folder:
MsgBox "Unable to resolve mailbox"
End Sub



Private Sub FillMailboxCombo()
'--Fill in all the names of available mailboxes

Dim myOlApp As Object
Dim olNamespace As Object
Dim iCount As Integer

Set myOlApp = CreateObject("Outlook.Application")
Set olNamespace = myOlApp.GetNameSpace("MAPI")

iCount = 1
MailboxCombo.Clear
While iCount <= olNamespace.folders.Count
MailboxCombo.AddItem olNamespace.folders(iCount).Name
iCount = iCount + 1
Wend
End Sub



  获得和修改注册表设置

'-- Property form load event
'-- Load all registry settings
DSNCombo = GetSetting("EmailMaker", "Database", "DSN", "OLE_DB_NWind_Jet")
TableCombo = GetSetting("EmailMaker", "Database", "Table", "Customers")
FieldCombo.Text = GetSetting("EmailMaker", "Database", "Field", "ContactName")
UserNameText = GetSetting("EmailMaker", "Database", "User Name", "")
PasswordText = GetSetting("EmailMaker", "Database", "Password", "")
MailboxCombo = GetSetting("EmailMaker", "Mailbox", "Mailbox", "Mailbox - NorthWind")
FolderCombo = GetSetting("EmailMaker", "Mailbox", "Folder", "Drafts")
FromText = GetSetting("EmailMaker", "Mailbox", "From", "NorthWind")
SecondaryOption = GetSetting("EmailMaker", "Secondary", "On", 0)
SecondFieldCombo.Text = GetSetting("EmailMaker", "Secondary", "Field", "")

'-- Apply button's OnClick event
Private Sub cmdApply_Click()
'-- Save all settings to registry

SaveSetting "EmailMaker", "Database", "DSN", DSNCombo.Text
SaveSetting "EmailMaker", "Database", "Table", TableCombo.Text
SaveSetting "EmailMaker", "Database", "Field", FieldCombo.Text
SaveSetting "EmailMaker", "Database", "User Name", UserNameText
SaveSetting "EmailMaker", "Database", "Password", PasswordText
SaveSetting "EmailMaker", "Mailbox", "Mailbox", MailboxCombo
SaveSetting "EmailMaker", "Mailbox", "Folder", FolderCombo
SaveSetting "EmailMaker", "Mailbox", "From", FromText
SaveSetting "EmailMaker", "Secondary", "On", SecondaryOption
SaveSetting "EmailMaker", "Secondary", "Field", SecondFieldCombo.Text

'-- Reinitialize the main form
MDIMain.Initialize

'-- Disable the Apply button
cmdApply.Enabled = False
End Sub


  以下是ADODB FIELDS 代码:


Private Sub FillFolderCombo()
On Error GoTo Err_Folder
' 'Put the names of all available folders in the folderCombo

Dim myOlApp As Object
Dim olNamespace As Object
Dim iCount As Integer
Dim mystr As String


Set myOlApp = CreateObject("Outlook.Application")
Set olNamespace = myOlApp.GetNameSpace("MAPI")

iCount = 1
FolderCombo.Clear
mystr = MailboxCombo

While iCount <= olNamespace.folders(mystr).folders.Count
FolderCombo.AddItem olNamespace.folders(mystr).folders(iCount).Name
iCount = iCount + 1
Wend

Exit Sub
Err_Folder:
MsgBox "Unable to resolve mailbox"
End Sub



Private Sub FillMailboxCombo()
'--Fill in all the names of available mailboxes

Dim myOlApp As Object
Dim olNamespace As Object
Dim iCount As Integer

Set myOlApp = CreateObject("Outlook.Application")
Set olNamespace = myOlApp.GetNameSpace("MAPI")

iCount = 1
MailboxCombo.Clear
While iCount <= olNamespace.folders.Count
MailboxCombo.AddItem olNamespace.folders(iCount).Name
iCount = iCount + 1
Wend
End Sub

  代码:

Private Sub FillFolderCombo()
On Error GoTo Err_Folder
' 'Put the names of all available folders in the folderCombo

Dim myOlApp As Object
Dim olNamespace As Object
Dim iCount As Integer
Dim mystr As String


Set myOlApp = CreateObject("Outlook.Application")
Set olNamespace = myOlApp.GetNameSpace("MAPI")

iCount = 1
FolderCombo.Clear
mystr = MailboxCombo

While iCount <= olNamespace.folders(mystr).folders.Count
FolderCombo.AddItem olNamespace.folders(mystr).folders(iCount).Name
iCount = iCount + 1
Wend

Exit Sub
Err_Folder:
MsgBox "Unable to resolve mailbox"
End Sub



Private Sub FillMailboxCombo()
'--Fill in all the names of available mailboxes

Dim myOlApp As Object
Dim olNamespace As Object
Dim iCount As Integer

Set myOlApp = CreateObject("Outlook.Application")
Set olNamespace = myOlApp.GetNameSpace("MAPI")

iCount = 1
MailboxCombo.Clear
While iCount <= olNamespace.folders.Count
MailboxCombo.AddItem olNamespace.folders(iCount).Name
iCount = iCount + 1
Wend
End Sub

  在此程序里,我创建了Message Properties对话框,以便让用户可以创建或修改"from" field。虽然我没有找出一个行之有效的办法来增加所有有效的地址选择,但是,Sent From的设置在Message Properties 对话框里也是一个可进入的域。

  在Message Editing窗口里有个比较奇怪的现象。当在Message Editing窗口内找开一个message时,窗口的图标会是一个合上的信封。当修改或编辑message旱,图标则会变成有支笔放在信封上的图案。然后,当保存时,图标又会变成合上的信封。这项功能帮助用户了解到是不是自上一次编辑以来对文件有所保存。这个功能运行稳定,除了当Message Editing窗口最大化时。当窗口最大化时,图标则不会改变,我也没有找出为什么会发生这种现象的源由。


【责任编辑:方舟 频道主编:赵家雄