Калькулятор на Visual Basic 6 с массивами элементов управления (Control Arrays)

РазделVisual Basic 6УровеньНовичок Чтение9 минОпубликовано ТегиVB6Control ArraysWinForms

Массивы элементов управления позволяют обработать десять цифровых кнопок одной процедурой. Разбираем приём на примере калькулятора и исправляем ошибки, которые прячутся в наивной версии.

Главное. Массив элементов управления сводит десятки однотипных обработчиков к одному с параметром Index, а числа в калькуляторе нужно хранить в Double, иначе оператор + склеит строки.

Что такое массив элементов управления

В 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 для вывода.

Совпадение Index и цифры на кнопке удобно, но не обязательно. В коде можно брать значение из Caption, тогда порядок индексов не важен.

Базовая версия и её скрытая ошибка

Логика простого калькулятора укладывается в две переменные уровня формы. Когда пользователь жмёт оператор, программа запоминает число из поля и сам оператор, а поле очищает. По кнопке «=» выполняется отложенное действие.

В первоначальном варианте числа хранились в строковых переменных, и строка FirstPart + txtResult.Text для сложения работала неверно: оператор + в VB6 при двух строковых операндах склеивает их, так что 2 + 3 давало 23. Вычитание и умножение при этом работали, потому что для них VB неявно преобразует строки в числа. Отсюда правило: считать надо в Double, а строку переводить в число явно.

frmCalc.frm: основаVB6
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$ с явным шаблоном.

frmCalc.frm: вычислениеVB6
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. Заблокированное поле не даёт вводить буквы, но остаётся читаемым и позволяет выделить и скопировать результат, тогда как отключённое поле выглядит серым.

frmCalc.frm: клавиатураVB6
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 удаляет созданный так элемент.

Этим пользуются, когда количество элементов заранее неизвестно: кнопки по списку из базы, ячейки сетки, строки анкеты. Все они автоматически получают общий обработчик событий, ничего дополнительно подключать не нужно.

Как это делают сегодня

В VB.NET массивов элементов управления нет. Их заменяет один обработчик, подписанный на несколько кнопок через Handles btn0.Click, btn1.Click или AddHandler в цикле, а отправителя определяют по параметру sender и свойству Tag. В WPF и Blazor кнопки калькулятора обычно генерируют из коллекции через ItemsControl или @foreach, передавая значение в команду.

Вопросы и ответы

Почему 2 + 3 в калькуляторе даёт 23?

Оба операнда строки, и оператор + в VB6 их склеивает. Храните числа в Double и переводите текст явно через Val или CDbl.

Как создать массив элементов управления?

Скопируйте элемент на форме и вставьте копию, согласившись на создание массива, или задайте одинаковое имя и свойство Index вручную.

Почему форма не реагирует на клавиатуру?

Не включено свойство KeyPreview. Без него нажатия получает только элемент с фокусом, и события формы не срабатывают.

Читайте также