Об авторе. Программист, сотрудник Центра Высоких Технологий (
http://www.htc-cs.com/)
Меню "Инструменты" (Tools) делают для того, чтобы из нашей
программы было легко вызвать какие-то другие утилиты,
предназначенные для решения задач того же круга. Удобно, если
это меню будет настраиваемым (customizeable). Попытаюсь
рассказать про один из вариантов реализации такого меню.
Задачи, которые мы должны решить:
Вызов программы с заданными аргументами
Хранение набора инструментов в памяти
Составление меню Tools
Команда Tools|Customize Tools
Сохранение набора инструментов
Это действительно отдельные задачи - в том смысле, что они
слабо зависят друг от друга. Ниже я покажу как я решал эти
задачи в своей программе.
Вызов программы с заданными аргументами
Наиболее
общий способ запуска программы под Win32 - это API-функция
CreateProcess. Но нам нет нужды опускаться на столь низкий
уровень. Можно было бы воспользоваться WinExec - это тоже
вызов WinAPI, но с гораздо меньшим числом аргументов. Для
наших нужд вполне достаточно VB-функции Shell - по сути,
обертки над WinExec.
Приведу пример процедуры, запускающей программу с заданными
аргументами:
Sub RunProgram(ByVal sPath As String, ByVal sArguments As String)
If sPath = "" Then
MsgBox "Executable path is empty.", vbExclamation
Exit Sub
End If
If sArguments <> "" Then sPath = sPath & " " & sArguments
Dim okFailed As Boolean, sDesc As String
On Error Resume Next
Shell sPath, vbNormalFocus
okFailed = (Err <> 0)
If okFailed Then sDesc = Err.Description
On Error GoTo 0
Err.Clear
If okFailed Then _
MsgBox "Cannot run the program (""" & sDesc & """).",_
vbExclamation
End Sub
Хранение набора инструментов в памяти
Каждый
инструмент описывается тремя строками - путь к программе,
аргументы, а также название этой программы в меню Tools. Все
что нам нужно - сохранять набор (возможно пустой), состоящий
из таких структур.
Существует несколько вариантов реализации хранения.
Например следующий - в каком-либо модуле программы описываем
структуру, динамический массив таких структур и счетчик
элементов массива:
Public Type TToolDesc
Name As String
Path As String
Arguments As String
End Type
Public Tools() As TToolDesc
Public ToolsCount As Integer
В такой реализации добавление нового элемента
выглядит так:
ToolsCount = ToolsCount + 1
ReDim Preserve Tools(1 To ToolsCount)
Tools(ToolsCount).Name = sName
Tools(ToolsCount).Path = sPath
Tools(ToolsCount).Arguments = sArgs
Удаление элемента с номером iDeleted:
Dim i As Integer
For i = iDeleted To ToolsCount - 1
Tools(i) = Tools(i + 1)
Next i
ToolsCount = ToolsCount - 1
If ToolsCount > 0 Then _
ReDim Preserve Tools(1 To ToolsCount)
Примечание. Использование коллекции в данном
случае невозможно, т.к. поместить составной тип в коллекцию не
получится. Вместо составного типа можно сделать отдельный
класс, но добавлять в проект новый класс только для
объединения трех строк в структуру - это показалось мне
излишним. Вместо переменной ToolsCount можно было бы
использовать функции получения границ массива (LBound,
UBound), но они приводят к ошибке в случае если массив пуст.
Составление меню Tools
Прежде всего - как должно
выглядеть меню Tools? Мне кажется, примерно так. Сначала идут
инструменты, причем каждый пункт называется так как это задано
пользователем. Потом - разделитель (separator), затем -
неизменяемые пункты, например - Options или Customize Tools.
Нам нужен код, который будет заполнять меню Tools именами
инструментов из массива Tools. Этот код нужно вызывать в двух
местах - при старте программы и после закрытия диалога Tools
Customize.
Опять же, можно найти несколько вариантов реализации. Для
себя я решил использовать т.н. control array. Признаком того,
что какой либо элемент формы (control) входит в control array
является непустое свойство Index. Преимущество использования
control array для нас в том, что в него легко добавлять новые
элементы (в нашем случае - пункты меню) оператором Load. Меню
в моей программе выглядит так:
Name Index Caption
cmdToolsCmd 0 ""
cmdToolsSep1 "-"
cmdToolsCustomize "Customize Tools..."
cmdCustomizeToolbar "Customize Toolbar..."
cmdToolsSep2 "-"
cmdOptions "&Options"
Процедура заполнения меню выглядит так:
Sub FillToolsMenu()
'Remove all menu items
Dim nCount As Integer
nCount = 0
On Error GoTo EndRemove
While True
nCount = nCount + 1
Unload cmdToolsCmd(nCount)
Wend
EndRemove:
On Error GoTo 0
cmdToolsCmd(0).Visible = (ToolsCount > 0)
cmdToolsSep1.Visible = (ToolsCount > 0)
Dim i As Integer
For i = 1 To ToolsCount
If i > 1 Then Load cmdToolsCmd(i - 1)
cmdToolsCmd(i - 1).Caption = Tools(i).name
Next i
End Sub
Примечание. При удалении элементов control array
я пользуюсь тем, что при удалении единственного элемента
массива возникает ошибка: в control array должен быть хотя бы
один элемент. Если массив инструментов пуст - скрываем как
этот единственный элемент, так и следующий за ним разделитель.
Команда Tools|Customize Tools
Диалог настройки
набора инструментов может быть отдельным окном или вкладкой в
общем диалоге опций. В нем как минимум должен быть список
пунктов меню, а также команды для добавления и удаления
пунктов. Удобно также, если будут кнопки для перемещения
пунктов меню выше и ниже по меню инструментов.
Программирование диалогов - большая тема для отдельного
разговора и здесь я не буду обсуждать конкретные варианты
реализации. Приведу лишь мой вариант облика этого диалога:
Сохранение набора инструментов
Набор инструментов
- это такие же настройки программы, как и любые другие.
Поэтому и хранить их нужно так же, как вы сохраняете все
остальные настройки.
Параметры для своих программ я сохраняю в реестре. Поэтому
мой код для сохранения инструментов выглядит так:
sRegKey = AppRegistryKey & "Tools"
RegistryDelete HKEY_CURRENT_USER, sRegKey
Dim i As Integer, sRegTool As String
For i = 1 To ToolsCount
sRegTool = sRegKey & "\" & CStr(i)
RegistrySet HKEY_CURRENT_USER, sRegTool
RegistrySet HKEY_CURRENT_USER, sRegTool, "Name",_
REG_SZ, Tools(i).name
RegistrySet HKEY_CURRENT_USER, sRegTool, "Path",_
REG_SZ, Tools(i).Path
RegistrySet HKEY_CURRENT_USER, sRegTool, "Arguments", _
REG_SZ, Tools(i).Arguments
Next i
Соответственно, загрузка набора инструментов в
память выглядит так:
sRegKey = AppRegistryKey & "Tools"
ToolsCount = 0
Dim Keys As Variant, sRegTool As String, i As Integer
RegistryKeys HKEY_CURRENT_USER, sRegKey, Keys
If IsArray(Keys) Then
Debug.Assert (LBound(Keys) = 0) 'Zero-based array is returned
ToolsCount = UBound(Keys) + 1
ReDim Tools(1 To ToolsCount)
For i = 0 To UBound(Keys)
sRegTool = sRegKey & "\" & Keys(i)
RegistryGet HKEY_CURRENT_USER, sRegTool, "Name",_
REG_SZ, Tools(i + 1).name
RegistryGet HKEY_CURRENT_USER, sRegTool, "Path",_
REG_SZ, Tools(i + 1).Path
RegistryGet HKEY_CURRENT_USER, sRegTool, "Arguments", _
REG_SZ, Tools(i + 1).Arguments
Next i
End If
Примечание. Стандартные средства VB для работы с
реестром слишком примитивны. С другой стороны, использовать
напрямую API-функции неудобно из-за большого объема
дополнительной работы. Поэтому мной были написаны обертки
(wrapper) над API-вызовами. Думаю, их применение понятно из
контекста.