
Калькулятор на Visual Basic 6 с массивами элементов управления (Control Arrays)
Массивы элементов управления позволяют обработать десять цифровых кнопок одной процедурой. Разбираем приём на примере калькулятора и исправляем ошибки, которые прячутся в наивной версии.
Что такое массив элементов управления
В VB6 несколько элементов управления одного типа могут носить одно имя и различаться только свойством Index. Такая группа называется массивом элементов управления (control array). У всей группы общий набор событий, а в каждый обработчик дополнительно приходит параметр Index, по которому видно, какой именно элемент сработал.
Приём недооценён, хотя экономит много кода. Калькулятор без массивов требует десяти почти одинаковых процедур Command0_Click, Command1_Click и так далее. С массивом остаётся одна процедура в одну строку. То же относится к группам переключателей, полям анкеты, клеткам игрового поля.
Подготовка формы
Положите на форму CommandButton с именем cmdDigit и подписью 0. Скопируйте его (Ctrl+C) и вставьте (Ctrl+V). Среда спросит: «You already have a control named cmdDigit. Do you want to create a control array?». Ответьте «Да», и у кнопок появятся индексы 0 и 1. Повторите вставку, пока не получится десять кнопок, и задайте им подписи от 0 до 9 так, чтобы Index совпадал с цифрой.
Так же создайте второй массив cmdOperator из четырёх кнопок с подписями +, -, *, /. Отдельно добавьте кнопки cmdEquals и cmdClear и текстовое поле txtResult для вывода.
Базовая версия и её скрытая ошибка
Логика простого калькулятора укладывается в две переменные уровня формы. Когда пользователь жмёт оператор, программа запоминает число из поля и сам оператор, а поле очищает. По кнопке «=» выполняется отложенное действие.
В первоначальном варианте числа хранились в строковых переменных, и строка FirstPart + txtResult.Text для сложения работала неверно: оператор + в VB6 при двух строковых операндах склеивает их, так что 2 + 3 давало 23. Вычитание и умножение при этом работали, потому что для них VB неявно преобразует строки в числа. Отсюда правило: считать надо в Double, а строку переводить в число явно.
Option Explicit
Private m_First As Double ' первое число
Private m_Op As String ' запомненный оператор
Private Sub cmdDigit_Click(Index As Integer)
AddDigit cmdDigit(Index).Caption
End Sub
Private Sub cmdOperator_Click(Index As Integer)
RegisterOperator cmdOperator(Index).Caption
End Sub
Private Sub AddDigit(ByVal d As String)
txtResult.Text = txtResult.Text & d
End Sub
Private Sub RegisterOperator(ByVal op As String)
m_First = Val(txtResult.Text) ' Val понимает только точку
m_Op = op
txtResult.Text = ""
End Sub
Private Sub cmdClear_Click()
m_First = 0: m_Op = "": txtResult.Text = ""
End SubКнопка «=» и деление на ноль
Вычисление собрано в Select Case по запомненному оператору. Отдельно проверяется деление на ноль: без проверки VB6 выбросит ошибку 11 и, если обработчика нет, закроет программу. Если оператор не задан (пользователь нажал «=» сразу), процедура ничего не делает.
Функция Val всегда ожидает точку как десятичный разделитель и не зависит от региональных настроек. Поэтому кнопку десятичного разделителя делайте с подписью «.», а для показа результата используйте Str$ или Format$ с явным шаблоном.
Private Sub cmdEquals_Click()
Dim second As Double, res As Double
If m_Op = "" Then Exit Sub
second = Val(txtResult.Text)
Select Case m_Op
Case "+": res = m_First + second
Case "-": res = m_First - second
Case "*": res = m_First * second
Case "/"
If second = 0 Then
txtResult.Text = "Ошибка": m_Op = ""
Exit Sub
End If
res = m_First / second
End Select
txtResult.Text = Trim$(Str$(res))
m_Op = ""
End SubВвод с клавиатуры через KeyPreview
Нажимать мышью каждую цифру неудобно. Если у формы включить KeyPreview, все нажатия клавиш сначала попадут в события формы, а уже потом в элемент с фокусом. В Form_KeyDown остаётся сопоставить коды клавиш с действиями и вызвать те же AddDigit и RegisterOperator, что и кнопки.
Цифры верхнего ряда имеют коды от 48 до 57 (vbKey0 до vbKey9), цифры дополнительной клавиатуры от 96 до 105 (vbKeyNumpad0 до vbKeyNumpad9). Для операторов на цифровом блоке есть константы vbKeyAdd, vbKeySubtract, vbKeyMultiply и vbKeyDivide. Backspace удаляет последний символ, Enter вызывает cmdEquals_Click.
Поле txtResult лучше сделать Locked = True, а не Enabled = False. Заблокированное поле не даёт вводить буквы, но остаётся читаемым и позволяет выделить и скопировать результат, тогда как отключённое поле выглядит серым.
Private Sub Form_Load()
Me.KeyPreview = True
txtResult.Locked = True
End Sub
Private Sub Form_KeyDown(KeyCode As Integer, Shift As Integer)
Select Case KeyCode
Case vbKey0 To vbKey9: AddDigit Chr$(KeyCode)
Case vbKeyNumpad0 To vbKeyNumpad9: AddDigit Chr$(KeyCode - 48)
Case vbKeyAdd: RegisterOperator "+"
Case vbKeySubtract: RegisterOperator "-"
Case vbKeyMultiply: RegisterOperator "*"
Case vbKeyDivide: RegisterOperator "/"
Case vbKeyReturn: cmdEquals_Click
Case vbKeyBack
If Len(txtResult.Text) > 0 Then _
txtResult.Text = Left$(txtResult.Text, Len(txtResult.Text) - 1)
End Select
KeyCode = 0 ' не передаём клавишу дальше
End SubСоздание элементов массива во время работы
Массив можно расширять и из кода. Если на форме есть хотя бы один элемент с Index = 0, оператор Load cmdDigit(10) создаст копию с новым индексом. Новый элемент невидим, его нужно расположить через Move и показать через Visible = True. Оператор Unload удаляет созданный так элемент.
Этим пользуются, когда количество элементов заранее неизвестно: кнопки по списку из базы, ячейки сетки, строки анкеты. Все они автоматически получают общий обработчик событий, ничего дополнительно подключать не нужно.
Как это делают сегодня
Вопросы и ответы
Почему 2 + 3 в калькуляторе даёт 23?
Оба операнда строки, и оператор + в VB6 их склеивает. Храните числа в Double и переводите текст явно через Val или CDbl.
Как создать массив элементов управления?
Скопируйте элемент на форме и вставьте копию, согласившись на создание массива, или задайте одинаковое имя и свойство Index вручную.
Почему форма не реагирует на клавиатуру?
Не включено свойство KeyPreview. Без него нажатия получает только элемент с фокусом, и события формы не срабатывают.