KRIEGSSPIELE!
Вы хотите отреагировать на этот пост ? Создайте аккаунт всего в несколько кликов или войдите на форум.

О самоуспокоенности неудачников (приложение к заметкам о простых играх)

Перейти вниз

О самоуспокоенности неудачников (приложение к заметкам о простых играх) Empty О самоуспокоенности неудачников (приложение к заметкам о простых играх)

Сообщение автор Gudleifr Вт Апр 25, 2017 11:41 am

Одним из замечательных "дебилизмов" является: "Ориентированность [FORTH] на личность, на кустаря-одиночку, независимого от чужого инструментария". Очень удобно. Не "я не умею писать на FORTH", а "я давно перерос кустарный уровень".

Вспомним Мура - О самоуспокоенности неудачников (приложение к заметкам о простых играх) Leaf10ТЕМА #3, АБЗАЦ #222О самоуспокоенности неудачников (приложение к заметкам о простых играх) Leaf10 - "у нас только голое железо". Что же теперь "классическому фортеру" при покупке нового компьютера сразу сносить операционную систему, стирать BIOS, выламывать процессоры, заменяя их "патентованными FORTH-кристаллами"? Какая такая злая сила запрещает фортеру пользоваться "чужим инструментарием"? Только одна - непонимание FORTH. Научили когда-то писать FORTH в кодах процессора Z80 - с этим знанием и помрут.

Начнем с другого конца. Возьмем обычную визуальную программу под Windows. Из чего она состоит? Окошко. В окошке кнопочки. Нажатие на кнопочки порождает какие-то действия, возможно появление новых окошек с новыми кнопочками... Т.е. мы имеем ПОТОК сообщений от пользователя, СЛОВАРЬ сообщений пользователя, на которые программа реагирует, СТЕК - сохраняющий данные окна родителя, пока пользователь что-то делает со ЗНАЧЕНИЕМ в дочернем окне... (И, конечно, программиста, которому лень решать задачу до конца, и он надеется, что пользователь сам угадает, на какие кнопки жать).

Можно ли написать FORTH-процедуры - ОК-СИМВОЛ-ВЫПОЛНИТЬ-КОМПИЛИРОВАТЬ-СЛЕДУЮЩИЙ , чтобы это заработало как "настоящий FORTH"?

С процедурой СИМВОЛ никаких проблем - уж что-что, а "символы" Win-соообщений Win-программисты распознавать умеют (хотя и далеки от мысли, что те являются неким "языком").

Процедуры ВЫПОЛНИТЬ и СЛЕДУЮЩИЙ, вроде, тоже работают "сами по себе", разве что, все "слова" Win-программы кодовые, а не шитые.

Серьезнее проблема с КОМПИЛИРОВАТЬ - однако и ее Win-программисты решают успешно, причем даже, не на уровне Win-API-программирования, а даже в каком-нибудь Visual Basic (См. далее EXPRESSOR). Добавить в начало не-IMMEDIATE слов фразу "if(STATE) - добавь message в табличку" - не проблема.

Остается только ОК. Опять Мур. Чем сложнее "машина" на которой мы пишем FORTH, тем сложнее ОК - ведь он и должен, в конце концов, выводить эти самые "окошки". Теории FORTH-интерфейса пока не создано, поэтому задача решается строго ро месту. Главное - понимать, что весь вывод Win-Controls должен жить в ОК.

Что мы т.о. имеем? Псевдо-FORTH, который по мере "совершенствования" будет все ближе напоминать "обычный". Например, постепенным сведением ОК к обычной Win-консоли, придумыванием "словам" текстовых имен, формализацией СТЕКА... Как показывает мой опыт, задача, ради решения которой и писалась программа, будет решена гораздо раньше. FORTH полезен не только в виде вставки в программу всего этого обязательного балета "цикла управления", а, даже, в виде простого понимания "что за что отвечает". Куда ему расти дальше? Переходить с уровня на уровень.

И самое смешное. Многие фортеры любят поминать игры, в которых вставляются "программируемые на FORTH виртуальные компьютеры", но свой компьютер как подобную "машину" не воспринимают. Автор упомянутого вначале "дебилизма" - в том числе.

Настоящие фортеры часто даже не знают слова FORTH!


Последний раз редактировалось: Gudleifr (Ср Дек 06, 2017 12:31 pm), всего редактировалось 2 раз(а)
Gudleifr
Gudleifr
Admin

Сообщения : 3211
Дата регистрации : 2017-03-29

Вернуться к началу Перейти вниз

О самоуспокоенности неудачников (приложение к заметкам о простых играх) Empty Re: О самоуспокоенности неудачников (приложение к заметкам о простых играх)

Сообщение автор Gudleifr Сб Сен 02, 2017 11:19 am

EXPRESSOR
Рассмотрим для полноты изложения, как решаются схожие проблемы (построения интересной и неожиданной проблемно-ориентированной программы - "исполнятеля" ЧАСТНОЙ операции) в визуальном программировании. Возьмем пример из замечательной книги - D.F.Scott, РАЗРАБОТКА ПРИКЛАДНЫХ СИСТЕМ НА VISUAL BASIC for Windows (имеются в виду еще 16-разрядные Windows и VS Basic). Автор не стал рассуждать в обычном объектно-ориентированном стиле, а пошел по пути реализации того, что нужно, и как можно проще. В разработке проекта практически отсутствуют стадии решения задачи и программирования, все сводится, по сути, к настройке операционной системы (в основном, конечно, машины Visual Basic). Кроме замечательного владения автором предметом, надо отметить его подачу метода программирования интерпретатора путем последовательного приближения (достаточно близкую по духу книге Броуди). Из недостатков - стремление сделать пользовательско-значимой каждый чих. И, более неприятно отметить небольшое жульничество: ничего более сложного, чем предложенный пример, таким методом написать невозможно - программа станет неуправляемой автором и непонятной для пользователя (тоже, кстати, касается и Броуди)).

D.F.SCOTT, ВВЕДЕНИЕ В EXPRESSOR!
В качестве следующего наглядного примера полностью работоспосоной прикладной программы мы хотели бы представить вам историю создания калькулятора, который первоначально был написан для другой книги. Часть науки программирования - умение делать выводы из опыта - сначала вы делаете попытку, затем исправляете себя, затем сердитесь, что ничего не получается, и, наконец, радуетесь, что все получилось.
***

ПРОБЛЕМА #2: КАЛЬКУЛЯТОР EXPRESSOR
Как профессиональному программисту, вам придется время от времени писать модули, которые могут быть применимы почти в любой прикладной программе, которую вы выпускаете. Кроме исполнения полезных функций, такой модуль может являться вашей "торговой маркой" (немного рекламы в заставках никогда не помешает).

Пользователь при заполнении формы в прикладной программе время от времени может усомниться относительно правильности вводимой цифры. Часто он вынужден держать на рабочем столе карманный калькулятор для перепроверки таких цифр. Microsoft Windows содержит утилиту калькулятора, набор его функций несколько шире, чем у карманного калькулятора, но никак не шире набора функций серьезной настольной машины. Для того, чтобы при помощи такого калькулятора вычислить результат какой-нибудь ходовой финансовой формулы, потребуются еще и карандаш с листом бумаги.
***

Целью ПРОБЛЕМЫ #2 является создание набора модулей, который было бы возможно интегрировать в любую прикладную программу. Мы хотим создать устройство, выглядящее как калькулятор, но предоставляющее пользователю кроме стандартного набора функций калькулятора финансовые функции, подобные тем, которые имеются в электронных таблицах. Этот проект можно использовать и как отдельное приложение в случаях, когда пользователю нужен быстрый доступ к финансовым функциям: пользователь вводит в окна Expressor'a исходные значения, получает результат и помещает его в Clipboard, чем делает его доступным остальным прикладным программам. То, как будет себя вести в нашем проекте поле в форме Visual Basic, очень похоже на действие ячейки электронной таблицы, содержащей формулу.

Первоначальная цель проекта Expressor была несколько туманна; другими словами, мы не имели точного понятия о том, что хотели создать. Мы сомневаемся в том, что каждый писатель представляет себе план своей будущей книги прежде, чем он ее закончит. Также и мы имели довольно ясное понятие о том, что мы желаем получить, без особенного понимания того, как мы будем добиваться этой цели. Мы знали, что хотим иметь программируемый калькулятор, использующий преимущества среды Windows. Мы хотели, чтобы это был калькулятор для пользователя, располагающий общим набором математических и финансовых формул, но без того, чтобы пользователь должен был их помнить. Expressor должен предлагать общаться не посредством клавиш с замысловатыми значками, а посредством близкого к естественному языка. Вот как он будет работать. Ряд окон на лицевой стороне калькулятора служит ячейками памяти. Калькулятор поддерживает список известных ему формул, чьи переменные имеют имена, которые также известны. Когда пользователь выбирает формулу, чтобы решить ее, имена каждой из переменных, составляющих формулу, появляются в окнах. Пользователь может ввести значения этих переменных, чтобы получить решение формулы (более поздние версии Expressor'a поддерживают решения для диапазонов значений). Пользователь может использовать для ввода стандартную клавиатуру калькулятора или непосредственно впечатывать требуемые значения. Цель этого проекта - позволить пользователю вычислять формулы, пользуясь почти естественным языком.
***

КАК РАБОТАЕТ EXPRESSOR. НЕ СДЕЛАНО ЛИ ЭТО УЖЕ?
Калькулятор - один из примеров прикладных программ в Справочнике программиста, сопровождающем каждую копию Visual Basic. Мы игнорировали его полностью, также как и шесть или около того дюжин подобных программ, помещенных в конкурирующих изданиях по Visual Basic. Если мы хотим создать более эффективно действующий калькулятор, мы должны сделать это сами.

Может быть, это и не так, но иногда очень полезно учиться программировать, не прибегая к текстам демонстрационных примеров. В общем, если вы предполагаете стать программистом-новатором, каждое прочитанное методическое пособие укрепляет ваши знания как повторение пройденного, хотя и не обязательно дает новаторские идеи. Цель хронологического изложения проекта Expressor состоит не в том, чтобы научить вас писать калькуляторы, а в том, чтобы предложить вам ознакомиться с нашим опытом включения в программу новаторских функций.
***

ПРИНЦИПИАЛЬНОЕ ЗАМЕЧАНИЕ: При разработке большой прикладной программы выберите одну часть прикладной программы, которую вы можете сделать полностью функциональной, и сконцентрируйтесь на ней, прежде чем перейдете к следующей части.

О самоуспокоенности неудачников (приложение к заметкам о простых играх) C8b810
Рисунок 1.4. Новорожденный Expressor, который пока что просто отображает набранное число

На рисунке 1.4 показан новорожденный Expressor - пока это что-то вроде клавиатуры телефонного аппарата. Нашим первым шагом в написании Expressor'a было установление того, как мы будем это делать: сначала нарисуем кнопки, а затем припишем к ним код. Это программирование наоборот; программист на стандартном БЕЙСИК'е сначала создает математику, а затем оформляет интерфейс программы. Visual Basic поощряет такой "обратный" подход, предоставляя ему немало преимуществ. Создавая объект и присваивая ему свойство .Name, интерпретатор Visual Basic автоматически создает обрамление процедуры, управляющей его поведением.
***

ГДЕ НАЧИНАЕТСЯ ПРОГРАММИРОВАНИЕ?
Мы всегда пишем, частично по психологическим причинам, так, чтобы сначала полностью написать и отладить некоторую небольшую часть проекта. Когда я вижу, что что-нибудь работает правильно, я начинаю понимать, что мне делать дальше. Такое постоянное убеждение в собственных силах позволяет экономить многие часы при разработке сложных программ. В 70-х, до того, как я начал читать учебные статьи журнала Creative Computing, я пытался изучать БЕЙСИК интенсивно исследуя распечатанные на бумаге листинги существующих программ. В то время БЕЙСИК программы имели нумерованные строки - 10, 20, 30 и так далее. Выполнение программы начиналось со строки с наименьшим номером и продолжалось по строкам с последовательными номерами, исключая переходы к подпрограммам, определенным в конце программы.
***

Отметьте, что Visual Basic программы часто начинаются не "с начала", этот факт будет более подробно рассмотрен ниже.
***

Вооружившись добытым из чужих листингов опытом, мы начали наши первые попытки программирования с создания работающего элемента - экрана заставки. Иногда мы производили просто блестящие заставки: с нашим логотипом, видеоэффектами и яркими красками. Но кучи всевозможных заставок, как квинтэссенция добытого из чужих листингов опыта, так и остались на покоробленных дискетах на полке с табличкой "Незавершенка".
***

ПРОГРАММИРОВАНИЕ ПРОТИВ ВАЯНИЯ
Полагать, что черты каждого метода и процесса большой прикладной программы ясны программисту, так же смешно, как и полагать, что скульптор видит черты своей будущей работы в трехметровом кубе мрамора и с ходу приступает к высечению готовой формы при помощи резца и зубила. Скульптор использует глину для изготовления миниатюрной модели своей работы, которую он может подержать в руках, добавлять или убавлять глины на деталях, менять черты будущей скульптуры. Когда он приступает к работе с мрамором, он начинает воссоздавать готовую модель. Сначала создаются обшие черты мраморной фигуры, а затем, в конце работы, создаются окончательные подробности - например, веки и зрачки. Несмотря на изрядную непохожесть, Visual Basic, с его модульной структурой, инструментальными средства для проектирования форм и проч., представляется программисту как грубый эквивалент резца, с которым он атакует блок мрамора, которым является проект Visual Basic. Помните, что всегда существует более аргументов за то, чтобы начать работу на процедурном, а не на глобальном уровне.
***

ОСНОВНОЙ КОНТЕКСТ
Первое, что мы сделаем в модели проекта Expressor, это присвоим кнопкам, начиная с первого ряда, свойства .Name - Button7, Button8, Button9, и так далее. Теперь напишем процедуру для кнопки Button7, которая будет помешать цифру 7 в окно, которое является элементом управления label по имени ReadOut. Вот первый фрагмент кода проекта Expressor:

Код:
   Sub Button7_CIick()
   ReadOut.Caption = ReadOilCaption + "7"
   End Sub

Начиная с этого места, мы начинаем применять логическую обработку действий Expressor'а. Мы не хотим позволить пользователю вводить числа неограниченной длины; должно существовать некоторое приемлемое ограничение. Мы ограничили считывание 19 цифрами (по причинам, которые с тех пор забыты), используя функцию Len() следующим образом:

Код:
   Sub Button7_Click()
   If Len(ReadOut.Caption) < 20 Then
      ReadOut.Caption = ReadOut.Caption + "7"
   End If
   End Sub

МЕТОДИЧЕСКОЕ ЗАМЕЧАНИЕ: Использование отступов в теле предложений - типа того, как в только что использованном If ... Then - улучшает читабельность предложения и помогает разбираться в нем "с первого взгляда". Наличие таких отступов не оказывает влияния на выполнение программы Visual Basic.
***

ИЗБЕЖАНИЕ ПРЕЖДЕВРЕМЕННЫХ И НЕАДЕКВАТНЫХ ОГРАНИЧЕНИЙ
Кнопка десятичной точки, названная ButtonPoint, имеет пустую процедуру Sub ButtonPoint_Click(). Набор цифр может быть интерпретирован как число только в том случае, если он содержит не более чем одну десятичную точку. Программа, следовательно, при попытке ввода десятичной точки должна уметь проверять, имеется ли уже десятичная точка или нет. Такая процедура могла бы, например, преобразовывать алфавитно-цифровое представление содержания метки ReadOut в числовое значение, используя функцию Val(). В случае ошибки, далее, могла бы вызываться подпрограмма обработки ошибочной ситуации, которая разрешала бы конфликт, связанный с избытком десятичных точек в окошке калькулятора. В этой ситуации ButtonPoint - единственный "нарушитель спокойствия", который может сделать из содержимого окошка калькулятора не цифру, потому что любой другой символ, который могут пересылать в ReadOut остальные кнопки,- цифра. Помимо громоздкости рассмотренного выше варианта контроля, функция Val() сможет помешать правильной трансляции содержимого Readout.Caption в числовое значение - например, шестнадцатиричной цифры "С".
***

Кроме того, постоянная переоценка содержимого ReadOut.Caption отрицательно сказывается на быстродействии программы. Поведение приложения становится несколько параноидальным, постоянно обрабатываются запросы типа: "Это все еще число? А теперь? А если так?" - и т.д. Проще сообщить программе один раз, что десятичная точка в ReadOut уже есть и "не добавляйте, пожалуйста, другой". В Visual Basic при запросе интерпретатора относительно состоянии какого-либо элемента вашей программы ответ следует во вполне символической форме. Вам, как программисту, придется написать код для использования этой символики. Другими словами, ответ "Красный это квадрат или белый?" или "Кто из пользователей сети в настоящий момент активен?" будет оформлен интерпретатором при посредстве разработанного вами кода.
***

ФЛАГОВЫЕ ПЕРЕМЕННЫЕ
Однако, если ответ на вопрос, который вы задаете системе может быть истолкован как "да" или "нет", истина или ложь, код для ответа уже существует, и интерпретатор уже готов распознать его. Какую бы символику вы не выбрали для использования, существует одно общее, применимое к символике, правило: так как число возможных символов является конечным и целым, переменная, которая будет представлять в символьной форме ответ, может быть представлена целым числом. Переменная, которая представляет собой ответ типа да/нет - другими словами, двоичное состояние - является флаговой переменной.
***

Флаговая переменная, которая создана для использования в процедуре Sub ButtonPoint_Click() - point_lock, может принимать значения FALSE или TRUE. Предполагая использовать point_lock только в этой процедуре, мы объявили ее как Static. Вот первоначальный вариант этой процедуры:

Код:
   Sub ButtonPoint_Click()
   Static point_lock As Integer
   If point_lock = FALSE Then
      ReadOut.Caption = ReadOut.Caption + "."
      point_lock = TRUE
   End If
   End Sub

Эта процедура запоминает значение point_lock. (В данном контексте это единственная процедура, которая это делает). После того, как строка, расположенная в теле условного предложения выполняется однажды, она больше не может выполнится, пока другая процедура не установит point_lock в FALSE. Очевидно, что должна быть процедура для кнопки "Clear" (сброс), или любая другая команда, которая заканчивает ввод предыдущего числа и начинает считывать следующее число.

Здесь встает следующая проблема: как программа может узнать, где заканчивается одно число и начинается другое? Понятно, что некоторые сообщения можно посылать от функциональных клавиш: когда пользователь калькулятора нажимает "плюс" или "умножить", это должно означать, что он станет вводить в окошко дисплея второе число. Сначала мы решили использовать "отладочную" флаговую переменную, значение которой устанавливалось в TRUE "вручную", чтобы проверить, как будет прерываться ввод предыдущего числа. Мы попробовали следующий фрагмент кода и добились успеха:

Код:
   Sub Button7_Click()
   If Len(ReadOut.Caption) < 20 AND ready = FALSE Then
      Readout.Caption = Readout.Caption + "7"
   Else
      Readout.Caption = "7"
   End If
   End Sub

Операторы, добавленные во второй строке, делают невозможным прибавление "7" к текущему значению ReadOut.Caption пока ready не равно FALSE. Если процедуры, присоединенные к клавишам математических функций, устанавливают ready в TRUE, следующая впечатываемая "7" очищает окошко и оставляет в нем только цифру "7".

Раздумывая, мы поставили следующий вопрос: зачем оценивать в операторе If - Then два логических выражения, соединенных AND, когда можно оценивать одно? Если ready у нас будет числом знаков во введенном в окно ReadOut числе (ранее определяемым как Len(Readout.Caption)), то когда значение ready положительно (отлично от нуля), это должно позволять принять в окно ReadOut еще одну цифру. Если пользователь, нажав функциональную клавишу, завершает ввод числа, процедура _Click для соответствующей кнопки может сообщать об этом установкой ready в 0 (или FALSE). Непривычное суждение, потому что ready никогда не получает значение TRUE!

После включения переменной ready как виртуального флага процедура приобрела следующий вид:

Код:
   Sub Button7_Click()
   If ready < 20 Then
      ReadOut.Caption = ReadOut.Caption + "7"
      ready = ready + 1
   Else
      ReadOut.Caption = "7"
      ready = 1
   End If
   End Sub

В окончательном виде процедура Sub ButtonPoint_Click() выглядела так:

Код:
   Sub ButtonPoint_Click()
   Static point_lock As Integer
   If point_lock = False AND ready < 20 Then
      Readout.Caption = Readout.Caption + "."
      point_lock = TRUE
      ready = ready + 1
   End If
   assess_readout
   End Sub

Наличие вызова assess_readout - результат идеи, перенесенной из калькулятора предыдущей версии. В Visual Basic любой терм в строке, который не является частью существующего словаря зарезервированных слов, рассматривается как обращение к процедуре; вы можете использовать и ключевое слово Call, чтобы идентифицировать вызов процедуры; строки assess_readout и Call assess_readout с точки зрения Visual Basic идентичны.

Первоначально в этой позиции находилась строка:

Код:
   readout_value = Val (Readout.Caption)

Цель этой команды состоит в том, чтобы транслировать текстовый заголовок метки ReadOut в числовое значение, необходимое для проведения дальнейших вычислений. Мы переместили эту команду в процедуру Sub assess_read-out() для того, чтобы позволить себе, в дальнейшем, добавлять в процедуру Sub assess_readout() строки кода, не превращая процедуру Sub ButtonPoint_Click() и прочие в "спагетти". Кроме того, такой подход устраняет избыточность в исходном тексте программы - нет никаких оснований для повторения одиннадцать раз (по числу кнопок на клавиатуре калькулятора) одной и той же процедуры. Но дописывать процедуру Sub assess_readout() пока не требуется, и мы оставим ее в покое, вот она:

Код:
   Sub assess_readout()
   Readout_value = Val(Readout.Caption)
   End Sub

Если бы мы остановили наш проект в этой точке, мы могли бы изъять эту процедуру из проекта и поместить на ее место ту самую одну строку. Честно сказать, в текущем контексте программы можно было бы и не переносить эту строку в подпрограмму, но преимущества Sub assess_readout() вполне окупились в следующих версиях Expressor'a: оставляя "заглушки" в программе, вы упрощаете себе наращивание ее вычислительных возможностей в дальнейшем.
***

УСТРАНЕНИЕ ИЗБЫТОЧНОСТИ, ДУБЛИРУЮЩИЕСЯ УЧАСТКИ И ИЗБЫТОЧНОСТЬ. МАССИВЫ УПРАВЛЯЮЩИХ ЭЛЕМЕНТОВ
Самый простой способ достижения цели в программе не обязательно будет самым эффективным. В программе Expressor есть десять цифровых кнопок, совершающих абсолютно идентичные действия. Самое простое решение в данном случае - воспользоваться функцией редактора кода "Copy and Paste" для размножения текста процедуры для всех десяти кнопок. Однако, разумно воспользовавшись объектно-ориентированным синтаксисом Visual Basic, вы можете использовать всего одну процедуру для определения поведения всех девяти кнопок, которые подают в окно калькулятора отличные от нуля значения.

Для начала ознакомимся с массивами элементов управления, используемых наборами фиксируемых кнопок (они обычно объединены в массив управляющих элементов рамками массивов управляющих элементов). Все отображаемые обьекты внутри рамки массива управляющих элементов получают одно и то же свойство .Name, но каждый индивидуальный объект имеет идентификатор в виде свойства .Index, позволяющий однозначно опознавать его. Вы можете расширять массивы элементов управления, включая и объединяя в них обычные командные кнопки.
***

Отказавшись от раздельных кнопок калькулятора в форме Expressor, и заменив их с массивом элементов управления, названным ButtonPos, мы получили возможность определить поведение этих кнопок единственной, приведенной ниже, процедурой обработки события:

Код:
   Sub ButtonPos_Click(Index As Integer)
   If ready > 0 Then
   If ready < 20 Then
      Readout.Caption = Readout.Caption + Right$(Str$(Index), 1)
      ready = ready + 1
   End If
   Else
      Readout.Caption = Right$(Str$(Index), 1)
      ready = 1
   End If
   assess_readout
   End Sub

Вместо цифр 7, 8, 9 и т.д. использовано выражение Right$(Str$(Index), 1). Массив элементов управления арифметическим способом определяет для нас цифру, которую нужно передать в окошко калькулятора. Клавиша "ноль" ведет себя иначе, чем остальные цифровые клавиши в ситуации, когда была нажата клавиша "сброс" и окошко калькулятора уже содержит 0. Если пользователь нажмет в этот момент клавишу "ноль", "00" в окошке калькулятора появляться не должно. Ввод пользователем нулей должен в такой ситуации отключаться до тех пор, пока в окошке не появится отличное от нуля значение. Мы можем использовать текст процедуры Sub ButtonPos_Click() как заготовку для процедуры Sub Button0_Click() и, отредактировав ее, получим следующее:

Код:
   Sub Button0_Click()
   If ready > 0 AND ready < 20 Then
      Readout.Caption = Readout.Caption + "0"
      ready = ready + 1
   End If
   assess_readout
   End Sub

Ключевое сравнение здесь - ready > 0 в теле предложения If - Then. Оно позволяет исполняться только командам, расположенным между Then и End If. Ноль будет добавлен в окошко только в случае, когда в нем уже есть отличная от нуля цифра. Когда окошко содержит только ноль, значение ready тоже ноль, как будто в окошке вообще нет никакой цифры. Нажатие любой клавиши от 1 до 9 вызывает исполнение строки ready = ready + 1. Теперь, когда ready больше ноля, станут исполняться команды, расположенные между Then и End If в процедуре Sub ButtonO_Click(). Обратите внимание на то, что в процедуре Sub Button0_Click() строка ready = ready + 1 выполняется только в случае, когда условие ready 0 истинно, и что, когда ready = 0, его значение может увеличиться только вне этой процедуры.

Начиная с этого места, вы располагаете функционирующей программой. Она принимает ввод с клавиатуры калькулятора и отображает в окошке калькулятора введенное число. Эта программа не делает ничего действительно полезного - но она работает, и вы уже можете сохранить ее на флоппи диск и поместить в свой архив для использования в будущем в любом месте, где вам потребуется ввод чисел с цифровой клавиатуры при помощи мыши. Конструированием этой программы вы завершили первый этап разработки проекта Expressor. Теперь передохните, съешьте бутерброд, полюбуйтесь на эффекты скринсэйвера и отправимся дальше.


Последний раз редактировалось: Gudleifr (Ср Июн 01, 2022 10:48 pm), всего редактировалось 1 раз(а)
Gudleifr
Gudleifr
Admin

Сообщения : 3211
Дата регистрации : 2017-03-29

Вернуться к началу Перейти вниз

О самоуспокоенности неудачников (приложение к заметкам о простых играх) Empty Re: О самоуспокоенности неудачников (приложение к заметкам о простых играх)

Сообщение автор Gudleifr Сб Сен 02, 2017 11:47 am

D.F.SCOTT, КАЛЬКУЛЯТОР EXPRESSOR II

СПОСОБ ЗАПИСИ
Чтобы продемонстрировать исключительную роль языка в вычислениях, мы вернемся теперь к теме, которую отложили в Главе 1, "Вычисления с языком". Что нужно добавить к тому, на чем мы остановились в проекте Expressor I,- четыре функциональные клавиши справа от цифровой клавиатуры калькулятора. В современных карманных калькуляторах распространены две модели записи. Наиболее распространенная модель - алгебраическая запись, или "TI нотация" (названная так после того, как фирма Texas Instruments сделала ее популярной). Менее распространенная модель используется в мошных научных программируемых калькуляторах - особенно в калькуляторах производства HEWLETT-PACKARD - она называется обратная польская нотация (Reverse Polish Notation - RPN). Различие между ними касается порядка, в котором пользователь вводит операнды и операторы выражения. Рисунок 2.1 показывает различия между двумя системами записи.

О самоуспокоенности неудачников (приложение к заметкам о простых играх) C8b910
Рисунок 2.1. Мгновенная и отложенная реакции

В Visual Basic нажатие на командную кнопку активизирует процедуру обработки события, которая исполняет функцию, присвоенную этой кнопке. Непосредственно проблема с выполнением алгебраической операции состоит в том, что нажатие кнопки должно инициализировать процедуру обработки события другой кнопки - например, нажатие кнопки "плюс", если до этого были введены два числа с нажатием функциональной кнопки после ввода первого, должно инициализировать арифметическую операцию, заданную нажатием функциональной кнопки между вводом первого и второго числа. А если была нажата клавиша "плюс" после ввода первого (после сброса калькулятора) числа, не должно произойти ничего. Но событие должно дожидаться очереди на обработку до нажатия следующей функциональной клавиши. RPN не ставит такой проблемы, потому что нажатие клавиши "ENTER" кладет операнд на стек сразу после ее нажатия, а нажатие клавиши "умножить" сразу вызывает перемножение операндов, находящихся на стеке. RPN очевидным образом более подходит к естественной структуре Visual Basic, и мы выбрали RPN с самого сначала и совершенно искренне. Это - самый простой способ выполнить задачу.

Хотя пользователю может показаться, что калькулятор решает длинные уравнения, фактически программа чрезвычайно проста. При использовании и TI, и RPN нотации программа вычисления помнит только два числа - введенное последним и введенное до него. В проекте Expressor мы выбрали этим значениям имена readout_value и combine_value соответственно. Мы придумали это перед завершением подпрограмм кнопок и записали следующие объявления в области общих объявлений глобального модуля EXPRESOR.GBL:

Код:
Global readout_value As Double, combine_value As Double
 Global ready As Integer

ПОЧЕМУ ГЛОБАЛЬНЫЕ? ПЛАНИРОВАНИЕ НА БУДУЩЕЕ В ГЛОБАЛЬНОМ МОДУЛЕ
Сейчас вы могли бы задать резонный вопрос, почему мы выносим глобальные объявления в одномодульном приложении, состоящем из одной формы, в отдельный модуль? Переменная объявленная на уровне модуля в области общих объявлений формы EXPRESOR.FRM будет иметь ту же самую полную область видимости глобальной переменной проекта. Вернемся опять к нашим рассуждениям, связанным с планированием на будущее. На будущее мы запланировали включить в калькулятор функцию рисования графиков. Предположили заранее, что три этих переменных будут использовать все дополнительные модули, которые мы можем впоследствии добавить, и на этом основании вынесли их объявления в отдельный модуль (между прочим, предположение о том, что доступ к ready, переменной, которая содержит число введенных пользователем цифр, понадобится другим модулям, не оправдалось. Она оказалась нужна только в форме калькулятора, так что позже мы перенесли ее объявление на уровень модуля и отредактировали его следующим образом: Dim ready As Integer).

Первой мы реализовали функцию сложения. Вот ее текст:

Код:
Sub Enter_Click()
 assess_readout
 readout_value = readout_value + combine_value
 combine_value = readout_value
 Readout.Caption = Str$(readout_value)
 ready = 0
 End Sub

Эта процедура работает, как и любой калькулятор: калькулятор хранит одно число в памяти - мы назвали его combine_value - второе число находится в окошке калькулятора. Когда вызывается процедура, она использует эти значения как операнды функции. Результат отображается на дисплее, и это значение присваивается переменной combine_value. Переменная ready в конце процедуры становится равной 0, теперь, если пользователь нажмет цифровую клавишу, окошко калькулятора будет немедленно очищено; программа в этой точке полагает, что в окошке не содержится никакого реального числа.
***

ПРИМЕНЕНИЕ МЕТОДА "ВЫРЕЗАТЬ И ВКЛЕИТЬ"
Вот процедуры функций вычитания и умножения:

Код:
Sub Minus_Click ()
 assess_readout
 readout_value = readout_value - combine_value
 combine_value = readout_value
 Readout.Caption = Str$(readout_value)
 ready = 0
 End Sub

 Sub Times_Click ()
 assess_readout
 readout_value = readout_value * combine_value
 combine_value = readout_value
 Readout.Caption = Str$(readout_value)
 ready = 0
 End Sub

Эта часть работы демонстрирует мощность редактора кода Visual Basic, позволяющего "размножить" текст этих процедур при помоши функции Clipboard, "вырезать и вклеить". После "вклеивания" осталось сменить только оператор в третьей сверху строке. Операции деления требуется небольшая модификация:

Код:
Sub DividedBy_Click ()
 assess_readout
 If readout_value <> 0 AND combine_value <> 0 Then
 readout_value = readout_value / combine_value
 End If
 combine_value = readout_value
 Readout.Caption = Str$(readout_value)
 ready = 0
 End Sub

ИЗБЕЖАНИЕ ДЕЛЕНИЯ НА НУЛЬ Интерпретатор Visual Basic не позволяет использовать значение 0 в качестве знаменателя ни для какой математической операции, включая деление, по той причине, что в таком случае не существует реального результата. Если вы попробуете делить на нуль или на переменную чье значение - нуль (или на неинициализированную переменную), программа, выдав сообщение об ошибке - "Division by Zero", остановится. Вот почему операция деления заключена здесь внутри условного предложения; лучше запретить такую операцию, чем вызвать аварийное завершение программы. Недостаток объяснений в этом случае может несколько смутить пользователя, но любой, кто пробует разделить на нуль, будет, во всяком случае, смущен еще больше; в дальнейшем вы можете самостоятельно добавить процедуру обработчика ошибки, информирующую пользователя о его некорректных действиях и приводящую калькулятор в исходное состояние. Вообше-то информировать пользователя о недопустимости деления на ноль - задача не для прикладной программы, а для средней школы. Но обработчики ошибок писать все-таки необходимо - программа, которую хоть как то можно называть программой, должна быть полностью неуязвима со стороны ошибочных действий пользователя. "Виснуть" или выдавать "General fault error" программа может в единственном случае - по компьютеру бьют кувалдой (с плеча). Даже баги фирмы Microsoft не могут служить оправданием - перед выпуском своего детиша в свет вы обязаны выявить все угрозы и нейтрализовать их!

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

Самая простая функция изо всех, что нам встречались до сих пор, будет функция клавиши "процент", чья процедура приведена ниже:

Код:
Sub Percent_Click()
 readout_value = readout_value/100
 Readout.Caption = Str$(readout_value)
 ready = 0
 End Sub

Если вы пользовались кнопкой "процент" на карманном калькуляторе, вы знаете, что можете получить 45% от числа, введя как множитель [4] [5] [%], а не [.] [4] [5], хотя обе последовательности и приводят к одинаковому результату. Процедура "процент" не изменяет значения первого операнда, ничего не предпринимая в отношении combine_value. Вот почему вы не находите этой переменной в процедуре.

Очень странно, но на написание процедуры очистки окошка калькулятора мы затратили больше времени, чем на кнопку процента.

Код:
Sub ClearAll_Click()
 Readout.Caption = "0"
 readout_value = 0
 combine_value = 0
 ready = 0
 End Sub

 Sub ClearEntry_Click()
 Readout.Caption = "0"
 readout_value = 0
 ready = 0
 End Sub

Процедура Sub ClearAll_Click() относится к кнопке "С" (очистить), а процедура Sub ClearEntry_Click() к кнопке "СЕ" (очистить дисплей). Кнопка "СЕ" стирает только цифру в окошке калькулятора, но не забывает значения первого операнда. Кнопка "С" заставляет калькулятор забыть все числа. Время, затраченное на эти процедуры, ушло на раздумья, куда поместить строку ready = 0. Существовал вариант проекта с глобальной флаговой переменной function_on, принимающей значение TRUE, когда нажимается функциональная клавиша. Позже мы удалили эту переменную, поняв, что можно обнулять ready всякий раз, когда function_on принимает значение TRUE.
***

ТЕХНИЧЕСКОЕ ЗАМЕЧАНИЕ: Чтобы устранить избыточность, потратьте время на исследование состояний программы и созданных вами до сих пор флаговых переменных, чтобы выявить параллельно работающие. Если вы найдете такие переменные, вы сможете устранить некоторые избыточные переменные полностью или повторно объявить их с более широкой областью видимости, чтобы сделать их доступными большему количеству локальных процедур.
***

Вы теперь имеете еще одну работающую программу, Expressor I - теперь калькулятор. Его функциональность перешла на следующий уровень, и теперь вы можете сохранить проект и взять паузу из кофе, бутерброда и скринсэйвера, являющихся неотъемлемыми частями процесса программирования.
***

ВКЛЮЧЕНИЕ УНИКАЛЬНЫХ ФУНКЦИОНАЛЬНЫХ ВОЗМОЖНОСТЕЙ
Самое время добавить что-нибудь в наш проект, чтобы превратить этот простой калькулятор в уникальный Expressor, который мы задумали. Мы хотим, чтобы пользователь видел набор формул, которые способен решить Expressor, написанных на естественном языке, а в пробелы, оставленные для значений, пользователю будет позволено занести требуемые числа. Имена формул, запрограммированных в Expressor'е, появляются в разворачиваюшемся списке (list box), называемом CalcList. Чтобы решить формулу, пользователь сначала вызывает эту формулу из списка, затем заполняет цифрами затребованные значения. Текстовые значения CalcList присваиваются во время обработки события _Load стартовой формы. Вот текст процедуры:

Код:
Sub Form_Load()
 CalcList.AddItem "Surface Area of RC Cylinder"
 CalcList.AddItem "Volume of RC Cylinder"
 CalcList.AddItem "Zone Area of Sphere"
 CalcList.AddItem "Force of Earth/Body Attraction"
 CalcList.AddItem "Doppler Shift Transmitted Freq."
 label$(0, 0) = "Radius of right circular cylinder"
 label$(0, 1) = "Height of cylinder"
 label$(1, 0) = "Radius of right circular cylinder"
 label$(1, 1) = "Height of cylinder"
 label$(2, 0) = "Radius of sphere"
 label$(2, 1) = "Height of zone"
 label$(3, 0) = "Mass of Earth"
 label$(3, 1) = "Mass of body in Earth's grav. field"
 label$(3, 2) = "Radius of Earth"
 label$(3, 3) = "Distance of body above Earth's surface"
 label$(4, 0) = "Observer velocity"
 label$(4, 1) = "Source velocity"
 label$(4, 2) = "Observed frequency"
 label$(4, 3) = "Velocity of wave"
 End Sub

Для начальных тестов проекта мы выбрали набор из пяти формул. Переменная типа массив label$() содержит описания каждого из параметров всех пяти формул. Когда пользователь выбирает формулу из CalcList, интерпретатор Visual Basic определяет номер выбранного из списка элемента и реагирует, помещая соответствующие описания из label$() в пяти окошках, принадлежащих массиву элементов управления ParamText(). Следующая процедура выполняет этот маневр:

Код:
Sub CalcList_Click()
 For n = 0 То 4
 ParamText(n).Caption = label$(CalcList.ListIndex, n) + " "
 Next n
 Clear_Params
 End Sub

Первый элемент в list box Visual Basic имеет номер 0, второй элемент 1 и так далее. Чтобы соответствовать этим легендарным смещениям, первая размерность двумерного массива label$() пронумерована с 0. Номер .ListIndex для списка формул соответствует непосредственно первой размерности label$().

В list box этой панели номер текущего выбора из этого списка - свойство list box .ListIndex. Это свойство используется для указания на набор описаний параметров. CalcList.ListIndex - указатель на первую размерность массива label$(), которая действует как элемент набора для каждого пакета описания параметров. Label$(4, n) относится к описанию - типа "Velocity of wave" - которое содержится в элементе набора 4. Несколько пробелов добавляются к концу текста так, что некоторое пространство отделяет текст от правой границы окошка.
***

ТЕХНИЧЕСКОЕ ЗАМЕЧАНИЕ: Сделайте индексные номера связанных массивов и управляющих элементов типа list точно соответствующими. Иначе вам придется использовать расчеты, чтобы преобразовать индекс одного массива в индекс другого при установлении отношений между двумя массивами.
***

Теперь labels в соответствующих окошках указывают пользователю, какие переменные формул надо ввести. Массив этих переменных называется param(), и он не требует никаких управляемых событиями процедур для работы. Пользователь может впечатать требуемые цифры, или может передать результат вычисления, находящийся в окошке результата калькулятора, в нужное место, используя ряд кнопок другого массива управляющих элементов, называемого StoreBank(). Процедура для него - ничто другое, как передача текста, как показано здесь:

Код:
Sub StoreBank_Click(Index As Integer)
 param(Index).text = Readout.Caption
 ready = 0
 End Sub

Здесь вы видите изюминку всей утилиты - вычисление по описанию формул калькулятора на естественном языке. Вот она. Повторим еше раз - самый функциональный элемент прикладной программы может быть самым маленьким. Схема решения формулы на этой стадии развития Expressor'a - "отладочная". Когда пользователь нажимает кнопку Apply Formula, событие _Click этой кнопки "ветвит" программу, ставя в соответствие окошку результата нужную формулу. Вот "отладочный" вариант процедуры:

Код:
Sub ApplyFormula_Click ()
 For in = 0 To 4
 p(in) = Val(param(in).Text)
 Next in
 ndx = CalcList.ListIndex
 Select Case ndx
 Case 0
 solution = surf_area_rccyl(p(0), p(1))
 Case 1
 solution = volume_rccyl(p(0), p(1))
 Case 2
 solution = zone_sphere(p(0), p(1))
 Case 3
 solution = force_att(p(0), p(1), p(2), p(3))
 Case 4
 solution = dopp_shift(p(0), p(1), p(2), p(3))
 Case Else
 Exit Sub
 End Select
 Readout.Caption = Str$(solution)
 End Sub

Массив р() был обьявлен на уровне модуля, так что он доступен всем процедурам, дающим решение формул. Еще одно достижение на ниве устранения избыточности кода.
***

SELECT CASE ЧАСТО ПРЕДПОЧТИТЕЛЬНЕЙ МНОЖЕСТВЕННЫХ СОТО
В первоначальном варианте этой процедуры использовались многократные переходы старого стиля с оператором On - GoTo. Мы начали программировать на БЕЙСИКе в 1978-м, так что мы написали достаточное количество GoTo, чтобы убедить себя, что современная методология программирования позволить нам отказаться от GoTo не в состоянии. В данном варианте мы пересмотрели схему, использовав значительно более эффективное предложение Select Case. Мы не из тех, кто полностью отрицает Goto, и защищаем его всякий раз, когда убеждены, что его использование уместно. Когда мы найдем подходящее место для него, вы узнаете об этом первыми.

Теперь еше одно отступление перед кратким отдыхом. Expressor I теперь, кажется, функционирует адекватно. Некоторые программисты называют это "успех"; мы называем это "перерыв на обед". Работа еше далеко не закончена, просто настало самое время поесть или, может быть, поспать.
***

Листинги первой версии - О самоуспокоенности неудачников (приложение к заметкам о простых играх) Leaf10TXT, 0.02МбО самоуспокоенности неудачников (приложение к заметкам о простых играх) Leaf10.
***

ПЕРЕОСМЫСЛЕНИЕ СУЩЕСТВУЮЩЕЙ СТРУКТУРЫ
Программы на Visual Basic, состоящие из одной формы, это, как правило, утилиты, потому что, по логике вещей, программа, имеющая единственное окно, в состоянии предоставить только одну функцию.

Примером такой утилиты может служить проект калькулятора Expressor I. Чтобы превратить утилиту в прикладную программу, степень функциональных возможностей утилиты должна быть расширена путем добавления еще нескольких полезных функций. В этой главе вы увидите, как утилита Expressor превратится в прикладную программу Expressor II, в которой появится функция построения графиков. Эти графики будут представлять из себя настоящие графические изображения, которые могут быть экспортированы в другую прикладную программу под Windows, которая может использовать такое изображение, например, в текстовый процессор. В прикладной программе функциональные возможности многозначны и могут быть выстроены в некую иерархию, в то время как функциональная возможность утилиты - обособлена и уникальна - наличествует только одна истинная прикладная функция.
***

ДА, ЭТО ПРИХОДИТ С ПРАКТИКОЙ
Новая панель Expressor'a работает так: чтобы решить нужную формулу, пользователь выбирает эту формулу из списка. Набор описаний параметров появляется в тех же самых пяти белых окошках, что и в Expressor I. Пользователь может решать формулу для любого одиночного набора известных значений двумя способами. Он может впечатать каждый параметр формулы напротив соответствующего описания или он может использовать RPN калькулятор, чтобы вычислить значение параметра и передать его в нужное окошко, нажав фиолетовую стрелку около нужного окошка. После того как пользователь введет параметры, он нажимает кнопку Display. В окошке результата калькулятора появится значение результата вычисления по формулы.
***

EXPRESSOR МОЖЕТ ДАВАТЬ РЕШЕНИЕ ДЛЯ ДИАПАЗОНОВ ЗНАЧЕНИЙ
Теперь пользователю Expressor'a для получения решений для диапазона значений доступен новый механизм построения диаграмм. Чтобы указать, какой параметр будет действовать как диапазон для диаграммы, пользователь должен нажать одну из кнопок напротив окошка параметра, в исходном состоянии содержащую надпись "---". После нажатия надпись на кнопке меняется на "х>>", окошко справа от кнопки "х>>" должно содержать максимальное значение диапазона. Окошко слева от кнопки должно содержать минимальное значение диапазона. Например, одна из новых формул, добавленных в Expressor II,- функция синусоиды. Чтобы получить решение для всех углов между -360 и 360, пользователь вводит -360 в окошко параметра, куда он обычно вводит значения для кнопки Display. Затем нажимает на кнопку "---" справа от окошка, что превратит эту кнопку в "х>>" (только одна кнопка может иметь вид "х>>", маркер диапазона). Если теперь в окошке калькулятора содержится число, оно будет копироваться в окошко максимального значения диапазона; это обычный text box, число в него можно и просто впечатать. После того как пользователь ввел параметры и определил диапазон значений для построения диаграммы, диаграмма результатов решения формулы для всех значений в диапазоне будет выведена на экран после нажатия пользователем кнопки Chart. Форма диаграммы отображается моментально, но черчение графика может занять какое-то время (другими словами, это не так быстро, как в Excel). Через некоторое время появится полноценный график с отметками значений на осях. Ниже приведены процедуры, содержащиеся в форме Expressor II (см. листинги второй версии - О самоуспокоенности неудачников (приложение к заметкам о простых играх) Leaf10TXT, 0.04МбО самоуспокоенности неудачников (приложение к заметкам о простых играх) Leaf10).
***

Общие объявления:

Код:
Dim label$(15, 4)
 Dim point_lock As Integer
 Dim readout_value As Double, combine_value As Double
 Dim ready As Integer
 Dim solution As Single, V2 As Single

Переменная типа массив label$() теперь содержит 15 наборов описаний параметров, которые отображаются внутри окошек параметров всякий раз, когда формула выбирается из списка. Переменная point_lock - флажок, не позволяющий пользователю поместить более одной десятичной точки в число. Readout_value и combine_value функционируют так же, как прежде; в readout_value содержится последнее введенное в окошко калькулятора число, в combine_value - предыдущее число. Переменная ready - регистр, который определяет, будет ли следующая введенная цифра или символ очищать содержимое дисплея калькулятора. Этот регистр устанавливается в ноль всякий раз, когда нажимается функциональная клавиша; ready отделяет одно значение от следующего, поэтому две последовательности цифр не смешиваются. Переменная solutions - приемник для результатов из модуля вычислений - EXPRSOR2.BAS. И наконец, V2 хранит значение, отображаемое во втором, меньшем дисплее калькулятора.

В процедуре Sub Form_Load() переменной label$() присваиваются значения текстовых описаний параметров формул для отображения в окошках описания параметров. Четыре арифметических функции - такие же, какие были в Expressor I; так же как и функция "процент", которая делит значение readout_value на 100. Добавлены следующие новые арифметические и тригонометрические функции: Sub Reciprocal_Click(), Sub Logarithm_Click(), Sub NaturalLog_Click(), Sub Root_Click(), PowerRaise_Click(), Sub Arctangent_Click(), Sub Sine_Click(), Sub Cosine_Click(), Sub Tangent_Click().

О самоуспокоенности неудачников (приложение к заметкам о простых играх) C8ba10
Рисунок 2.2. Прикладная программа Expressor II

И Sub Logarithm_Click() и Sub PowerRaise_Click() заимствуют значение второго параметра из переменной V2. Пользователь может найти это значение в новом text box SecondValue, который действует как память калькулятора. Пользователь может передать в него значение из основного дисплея калькулятора, нажав изогнутую фиолетовую стрелку; числовое значение в него тоже можно впечатать. Чтобы возвести число на дисплее калькулятора в степень, пользователь вводит значение степени в этот второй текстовый блок. Это значение становится значением переменной V2, благодаря следующей процедуре:

Код:
Sub SecondValue_Change()
 V2 = Val(SecondVaiue.Text)
 End Sub

Предположим, что на дисплее калькулятора находится 2, а окошко Second-Value содержит 3. После нажатия на кнопку "x^y", на дисплее появится 8 или 2 в кубе. Использование окошка "памяти" таким образом позволяет отказаться от пяти или шести кнопок, зарезервированных под возведение в квадрат, куб или пятую или двенадцатую степени. Теперь предположите, что пользователю требуется найти корень пятой или двенадцатой степени. Он вводит степень корня в дисплей калькулятора и нажимает функциональную кнопку "1/y". Теперь на дисплее обратная величина введенного значения. Ее нужно перенести в "память" - на второй дисплей калькулятора,- нажав изогнутую стрелку. Затем в дисплей калькулятора вводится подкоренное число и нажимается кнопка "x^y". Результат - значение x^(1/y), который является математическим синонимом y-того корня из x.

Следующая подпрограмма совершает перенос значения основного дисплея во второй дисплей:

Код:
Sub Store2nd_Click()
 SecondValue.Text = Readout.Caption
 V2 = Val(SecondValue.Text)
 End Sub

РАЗМЕРЕННАЯ ПОСТУПЬ ПРОГРЕССА
Помните ту процедуру из одной строки в проекте Expressor I - она появляется и во второй версии калькулятора:

Код:
Sub assess_readout()
 readout_value = Val(Readout.Caption)
 End Sub

Обратите внимание, мы отвели целую процедуру Sub под одну строку. В проекте Expressor I мы хотели оставить эту процедуру как "заглушку" для будущего расширения программы. Вызов процедуры assess_readout присутствует во всех арифметических функциях процедур_Click. Чтобы добавить еше один процесс в подпрограмму, которая исполняется после каждого нажатия на кнопку, мы можем просто добавить строки в процедуру Sub assess_readout() без необходимости переписывать подпрограммы кнопок.

Как вы можете видеть, мы не добавили в эту процедуру ни одной строки. Оставляемые нами "заглушки" программе вреда не причиняют. Такая техника, применение "заглушек" - основа структурного программирования - оставлять для новых идей открытый путь.
***

ТЕХНИЧЕСКОЕ ЗАМЕЧАНИЕ: Продолжайте применять средства планирования наперед даже когда причины к сему не очевидны.
***

Следующая процедура, Sub CalcList_Click(), вызывается всякий раз, когда пользователь делает выбор из списка формул. Она присваивает набор описаний параметров, сформированный ранее в процедуре Sub Fornn_Load(), и помещает их в соответствующие окошки, а затем очишает области значений параметров.

Код:
Sub CalcList_Click()
 For n = 0 То 4
 ParamText(n).Caption = label$(CalcList.ListIndex, n) + " "
 Next n
 Clear_Params
 End Sub

Предыдущая процедура обращается к процедуре Sub Clear_Params():

Код:
Sub Clear_Params()
 For pl = 0 To 4
 Param(pl).Text =""
 ParamTo(pl).Text =""
 Next pl
 ClearAILCIick
 End Sub

Помните, две кавычки друг за друг другом означают "пустое значение", присваивания в этой процедуре освобождают text box.

В проекте Expressor II мы заменили пять кнопок массива StoreBank() массивом управляющих элементов picture box с тем же самым именем. Для свойств .Picture мы использовали фиолетовую смотрящую влево стрелку из библиотеки изображений Visual Basic. Справа от StoreBank() - несколько темно-серых кнопок, используемых для выбора параметра, который будет представляться осью X на диаграмме. Подпрограмма обработки события_Click, поступающего от этих кнопок следующая:

Код:
Sub AxisSelect_Click (Index As Integer)
 If ParamTo(Index).Text = "0" Or ParamTo(Index).Text = "" Then
 ParamTo(Index).Text = Readout.Caption
 End If
 ready = 0
 Select Case AxisSelect(Index).Caption
 Case "---"
 If xslot = -1 Then
 AxisSelect(Index).Caption = "x >>"
 xslot = Index
 For correct = 0 To 4
 If correct <> Index Then
 AxisSelect(correct).Caption = "---"
 End If
 Next correct
 End If
 Case "x >>"
 If xslot = Index Then
 AxisSelect(Index).Caption = "---"
 xslot = -1
 End If
 End Select
 End Sub

Вот та же процедура на псевдокоде:

Код:
'Процедура для нажатия на одну из кнопок выбора оси:
 Если текстовое окошко около кнопки, которая нажималась,
 содержит "0" или пустое, тогда
 Поместить содержимое основного дисплея в это окошко.
 Конец условия.
 Основной дисплей готов ко сбросу и приему нового числа.
 'Исследование текста внутри кнопок,
 Предположим, что кнопка содержит"-",
 Тогда, если мы еще не выбрали диапазон х-оси, то
 Поместить "х>>" маркер в эту кнопку.
 Сообщить, что диапазон х-оси - здесь.
 Просмотр всех остальных кнопок.
 Для всех кнопок, отличных от этой, то
 Поместить в каждую кнопку "---".
 Конец условия.
 Исследовать следующую кнопку.
 Конец условия.
 С другой стороны, если здесь уже "х>>",
 Тогда, потому что мы уже выбрали окошко оси, тогда
 Поместить в этой кнопке "---"
 Сообщить, что не опрошенных окошек больше нет.
 Конец условия.
 Конец предположения.
 Конец процедуры.

Expressor II готов для реализации модуля рисования диаграмм, который отображает результат вычисления по формуле для диапазона возможных входных значений параметра. Мы оставили часть модификации для Главы 3, "Модель Прикладной программы".


Последний раз редактировалось: Gudleifr (Ср Дек 06, 2017 12:39 pm), всего редактировалось 2 раз(а)
Gudleifr
Gudleifr
Admin

Сообщения : 3211
Дата регистрации : 2017-03-29

Вернуться к началу Перейти вниз

О самоуспокоенности неудачников (приложение к заметкам о простых играх) Empty Re: О самоуспокоенности неудачников (приложение к заметкам о простых играх)

Сообщение автор Gudleifr Сб Сен 02, 2017 11:55 am

D.F.SCOTT, КАЛЬКУЛЯТОР EXPRESSOR II. ПРОДОЛЖЕНИЕ

Оставленный нами в Главе 2, "Структура Visual basic", Калькулятор Expressor II дал пользователю доступ к модулю построения диаграмм. Первоначальной целью проекта Expressor было решение формулы и отображение результата на дисплее калькулятора. Expressor II решает ту же самую формулу для диапазона значений, предоставляя пользователю возможность задать максимальное и минимальное значения диапазона. Этот модуль будет принимать ввод пользователя в виде выбора формулы для решения, решать эту формулу несколько сотен раз вместо одного и выводить результаты на X/Y диаграмме в отдельной форме.

Проект формы диаграммы - совершенно простое дело, форма чисто графическая (см. текст EXPCHART.FRM). В основном это - много пустого места с несколькими элементами управления вдоль нижней границы, как показано ниже.

О самоуспокоенности неудачников (приложение к заметкам о простых играх) C8bb10
Рисунок 4.6. Поле диаграммы в проекте Expressor

ОПРЕДЕЛЕНИЕ КОНТЕКСТОВ EXPRESSOR II
При методе, которым Expressor разрабатывался до сих пор, процедуры Function, выполняющие вычисления по формулам, помешались в обший модуль EXPRSOR2.BAS. Подобные же процедуры будут требоваться для построения диаграмм в Expressor II, и мы предполагаем оставить эти процедуры там, где они находятся теперь. Главная причина, по которой процедуры построения диаграмм помешаются в EXPRSOR2.BAS, а не в EXPCHART.FRM,- процедуры получают некоторые из введенных данных из формы главной панели калькулятора, а процедура из одного модуля формы не может вызывать процедуру из другого модуля формы непосредственно.

Панель Expressor'a не может обратиться к форме диаграммы; два модуля формы живут в разных мирах, между которыми нет связи. Обший модуль может действовать как посредник между двумя формами, как проводник для данных, которыми они обмениваются. Это мост контекстов между двумя формами, чьи собственные контексты ограничены границами их модулей. Процедура построения диаграмм - намного более сложная и объемная процедура, чем те, с которыми мы имели дело до сих пор. Чтобы книга не была похожа на стопу листингов, мы приведем ее здесь в виде небольших фрагментов.
***

EXPRESR2.BAS. ОБШИЕ ОБЪЯВЛЕНИЯ:

Код:
   Dim xi(50) As Single, yi(50) As Single, xln(50) As Single, yln(50) As Single
   Dim charty(1000)

Рисунок 4.6 показывает область рисования диаграммы проекта Expressor. Вместо использования средств элемента расширения GRAPH.VBX, поставляемого с профессиональной версией VB, мы решили написать оригинальную процедуру построения диаграмм. Результаты действия этой процедуры будут уникальными и более адаптивными к потенциальным целям других прикладных программ.

Вот кратко описание работы, выполняемой механизмом построения диаграмм проекта Expressor: основной picture box в форме диаграммы содержит прямоугольное поле, которое в свою очередь содержит диаграмму. Размеры этого прямоугольного поля построения графика - совершенно произвольные и могут изменяться, чтобы приспосабливаться к меняющемуся интерьеру. Поле разделено на 1000 длинных, высоких столбцов. Каждый столбец представляет диапазон возможных значений по Y-оси. Формула, которую пользователь выбрал на главной панели калькулятора, рассчитывается 1000 раз. Значение каждого результата сохраняется в массиве charty(1000). Каждый раз, когда происходит получение результата, рассчитанного по формуле, в одном из столбцов, по порядку справа налево, выводится точка. Чем больше значение элемента из charty(), тем выше выводится точка на графике и наоборот.

В форме панели калькулятора пользователь вводит максимальное и минимальное значения диапазона. Диапазон делится на 1000 равных частей. Крайний левый столбец диаграммы представляет решение уравнения для минимального значения; крайний правый столбец представляет решение для максимального значения. Каждый столбец диаграммы представляет интервал значения, равный 1/1000 разницы между максимальным и минимальным значениями диапазона. Другими словами, ширина каждого столбца - 1/1000 общего расстояния между левым и правым значениями по X-оси.

Процедура построения диаграммы отмечает самое большое и самое малое значения решений для выбранной формулы. Эти значения становятся максимумом и минимумом для диапазона, представляемого Y-осью диаграммы. Масштабирующие метки помешаются через регулярные интервалы по левой и нижней границам прямоугольника диаграммы, обозначая значения по обеим осям. На оси наносится 50 масштабирующих меток. Цифровые значения масштабирующих меток для осей X и Y хранятся в переменных типа массив xi() и yi(); координаты этих меток на форме диаграммы хранятся в переменных типа массив xln() и yln().

Вот текст процедуры, строяшей эти замечательные диаграммы.
ОБЛАСТЬ ОБЩИХ ПРОЦЕДУР, ЧАСТЬ 1:

Код:
   Sub CreateChart(xmin As Single, xmax As Single, xslot As Integer)
   Chart.Show
   Chart.ChartArea.Cls
   title$ = Panel.CalcList.Text
   Chart.ChartArea.FontSize = 16
   Try:
   If Chart.ChartArea.TextWidth(title$) > Chart.ChartArea.Width Then
      Chart.ChartArea.FontSize = Chart.ChartArea.FontSize - 1
      GoTo Try
   End If

Заголовок процедуры принимает минимальное и максимальное значения диапазона xmin и xmax как параметры, наряду с индексом окошка xslot, из которого они взяты. Показывается окно диаграммы, чтобы пользователь не терялся в догадках перед "зависшей" панелью калькулятора, а видел, что программа работает. Заголовок диаграммы, title$, принимает значение имени формулы, выбранной на панели калькулятора из выпадающего списка. Далее подпрограмма определяет, какой размер шрифта, начиная с 16 пунктов, использовать для текста заголовка. Используя .TextWidth() метод, программа проверяет, помещается ли строка title$ в пространство ChartArea, и если нет, размер шрифта уменьшается на 1 пункт. Да, мы использовали GoTo. Мы надеемся, что вы также не потрясены. Технический редактор мог бы прокомментировать, что мы могли или должны были использовать предложение Do-Loop, но мы иногда имеем тенденцию к уклонению от таких ходов. Мы обучались программированию на старом БЕЙСИКе, так что использовали GoTo; мы еше полагаем, что оператор GoTo делает свою работу.
***

РИСОВАНИЕ ПЕРОМ В PICTURE BOX
Когда вы дочитаете текст этой процедуры, попробуйте вообразить эту диаграмму рисуемой механическим пером. Это перо находится на кронштейне, чье шасси не только перемешает перо, но и регистрирует его позицию. Текущие координаты пера могут быть установлены как (CurrentX, CurrentY). Если что-нибудь должно выводиться в форму, рисование начинается в этой точке. Таким образом, когда рисуется текст или линия, воображайте, что перо перемешается как в механическом графопостроителе. Стартовая позиция пера, по умолчанию - верхний левый угол.

ЧАСТЬ 2:

Код:
   Chart.ChartArea.ForeColor = 65535
   Chart.ChartArea.CurrentX = (Chart.ChartArea.Width / 2) -
      (Chart.ChartArea.TextWidth(title$) / 2)
   Chart.ChartArea.Print title$
   Chart.ChartArea.FontSize = 8.25
   Chart.ChartArea.CurrentY = Chart.ChartArea.CurrentY + 100
   charttop = Chart.ChartArea.CurrentY
   Chart.ChartArea.CurrentX = 0

Вообразите, что механический графопостроитель сейчас выдвинул перо с ярко-желтыми чернилами (65535). Заголовок диаграммы будет отцентрован относительно середины формы. Вообразите теперь механическую руку, перемешаюшуюся по X-оси (горизонтальной) к центру формы и затем обратно влево на половину запроектированной ширины заголовка. Помните, .TextWidth метод может возвращать ширину текста прежде, чем он будет напечатан. После того, заголовок напечатан, размер шрифта уменьшается до 8.25 пункта - размер шрифта для нанесения масштабирующих меток.

.CurrentY пера перемешается вниз на 100 twips. Текущая позиция Chart.ChartArea.CurrentY регистрируется как крайняя верхняя позиция. Процедура теперь располагает точкой, отмечающей верхнюю границу диаграммы. Диаграмма имеет переменный размер; она пытается занять так много места, насколько возможно, "подбирая" все, что осталось ей после вывода текста. Затем перо снова перемещается в левый край диаграммы. Это - виртуальный эквивалент возврата каретки.

О самоуспокоенности неудачников (приложение к заметкам о простых играх) C8bc10
Рисунок 4.7. Как переменные отмечают движение виртуального пера

Рисунок 4.7 изображает движения виртуального кронштейа с пером, сделанные им до сих пор. После того, как нарисованы и подписаны масштабируюшие метки, настает время для вызова механизма решения формулы для всех 1000 столбцов.
***

ЧАСТЬ 3:

Код:
   frmula = Panel.CalcList.ListIndex + 1
   intval = (xmax - xmin) / 1000
   For in = 0 To 4
      p(in) = Val(Panel.Param(in).Text)
   Next in
   If xmin = xmax Then
      Panel.Show
   Exit Sub
   End If

Индексный номер решаемой формулы извлекается из формы панели калькулятора. Кстати, здесь должны помешаться полные ссылки на форму и зависимый графический объект - например, Panel.CalcList.Listlndex и Chart.ChartArea.CurrentY - потому что эти ссылки появляются в общем модуле, в процедурно неспецифическом контексте. При определении всех графических объектов в обшей процедуре, вы должны определять их абсолютный источник, который часто называется родительским объектом.

Диапазон значений выводимого графика определяется как (xmax - xmin). Этот диапазон делится на 1000, чтобы получить intval, значение интервала. Это значение может интерпретироваться как "ширина" каждого столбца в графике, но не геометрически, а в цифровом выражении. Если геометрическая ширина графика представляется Y-осью, простирающейся между левыми и правыми сторонами области построения графика, то 1/1000 этой ширины представляется точкой, оставляемой одним пикселем. Стандартные параметры формулы извлекаются в переменную типа массив р(). Вся процедура теперь может потерпеть крах, если между минимальным и максимальным значениями нет никакой разницы,- в таком случае при продолжении работы будет построен неправильный график.
***

ЧАСТЬ 4:

Код:
   For pval = xmin To xmax Step intval
      p(xslot) = pval
      charty(px) = calculate(frmula)
      If px = 0 Then
         ymax = charty(px)
         ymin = charty(px)
      Else
         If charty(px) > ymax Then
            ymax = charty(px)
         End If
         If charty(px) < ymin Then
            ymin = charty(px)
         End If
      End If
      px = px + 1
   Next pval

Это определяющий цикл в процедуре. Поскольку intval равен 1/1000 разницы между минимумом xmin и максимумом хтах, этот цикл выполняется ровно 1000 раз. В этом случае неважно, насколько мала или велика разница между xmin и xmax, потому что значение Step уже определилось, приняв величину intval. Счетчик этого цикла хранится в pval; каждому из 1000 столбцов в графике pval предоставляет значения аргументов формулы.

Переменная xslot передана этой процедуре как параметр. Она хранит индекс окошка минимальных и максимальных значений диапазона на панели, из которого взяты эти значения. Далее р(), массиву значений параметров, передается текущее значение pval. Обращение к функции calculate() - такое же обращение, какое вызывалось нажатием кнопки Apply Formula на панели калькулятора; так что механизм построения диаграммы работает, фактически, так, же если бы мы нажимали на кнопку Apply Formula тысячу раз. Каждое решение сохраняется в массиве charty() в позиции с номером рх. Переменная рх хранит номep текущего столбца, от 0 до 999. В конце цикла этот номер вручную увеличивается прежде, чем программа перейдет к следующему столбцу.

Условное предложение проверяет, выводится ли сейчас точка в первом столбце - рх = 0. Если да, то условное предложение заключает, что первый столбец содержит и самое большое и самое маленькое значения решения, отмеченные до сих пор.
***

ЧАСТЬ 5:

Код:
   dif = Abs(ymax - ymin)
   If ymin > ymax Then
      dif = -dif
   End If
   hght = Chart.ChartArea.Height - charttop - 100
   yticks = Int(Chart.ChartArea.Height / 450)
   intervaly = hght / yticks
   For plt = 0 To (yticks - 1)
      yi(plt) = ymax - ((dif / (yticks - 1)) * plt)
      If yi(plt) < 1 Then
         pl$ = Format$(yi(plt), "0.######")
      Else
         pl$ = Format$(yi(plt), "################.##")
      End If
      Chart.ChartArea.Print pl$
      Chart.ChartArea.CurrentX = 0
      Chart.ChartArea.CurrentY =
         Chart.ChartArea.CurrentY + intervaly - 200
      w = Chart.ChartArea.TextWidth(pl$)
      If w > wide Then
         wide = w
      End If
   Next plt

Предложение цикла в части 5 имеет дело с другим диапазоном, имеющим, как границы, самое большое значение результата вычисления по формуле Ymax и самое малое значение результата Ymin. Обшая высота области в ChartArea, которая будет содержать построенную диаграмму, есть ChartArea.Height минус значение charttop, найденное ранее, минус 100 для дополнительного поля. Этот размер сохраняется как переменная hght.

Масштабирующие метки на левой стороне диаграммы обозначают относительные значения по Y-оси. Приемлемое число масштабирующих меток (yticks) зависит от текущего размера диаграммы и вычисляется делением полной высоты диаграммы на 450. Затем вся высота области диаграммы, представляемая переменной hght, делится на это число меток для получения значения интервала (inlervaly) меток по оси Y. Метки здесь не представляют значений графика, как можно подумать, а масштабную сетку значений графика.

Счетчик цикла plt проходит значения от 0 до yticks-1. Значение каждой масштабирующей метки по Y-оси сохраняется в переменной типа массив yi(). В верхней части диаграммы метка должна отметить самое большое значение результата вычисления по формуле, найденное до сих пор, представляемое ymax. В первой итерации цикла pit=0, и, таким образом, ((dif/(yticks-1))*plt) также равняется нулю. Следовательно, ничто не вычитается из ymax в первой итерации, и первая метка отмечает максимальное значение.

В последующих итерациях, для всех значений где plt>0, значение интервала вычисляется как dif/(yticks-1). Это - значение интервала (не размер) для расстояния между масштабирующими метками по оси Y. Этот интервал умножается на plt, которая следит за числом интервалов, рассчитанным выше. Результат - значение, которое вычитается из ymax, чтобы получить значение метки текущего интервала. Это значение сохраняется в yi(plt).

Обратите внимание на то, что часть графических подпрограмм не хочет знать, как велико yi(plt), но требует его ширины. Строковая переменная pl$ принимает текстовую форму значения yi(plt) для текущего интервала. Если это значение меньше, чем 1, выводится шесть дополнительных десятичных пробелов, как это задано шестью диезами после десятичной точки в описателе формата "0.######". Если значение yi(plt) больше, чем 1, выводится 16 цифр, сопровождаемых двумя десятичными местами после точки.

О самоуспокоенности неудачников (приложение к заметкам о простых играх) C8bd10
Рисунок 4.8. Дальнейшие движения пера

Содержимое pl$ выводится на диаграмму (хотя вы этого еше не видите), и вывод этой строки, конечно, перемешает перо вправо. После того, как вывод завершен, .CurrentX перемешается обратно в крайнюю левую позицию области диаграммы, a .CurrentY перемешается вниз на размер интервала intervaly минус 100 twips для будущей строки. Это - позиция для вывода следующей масштабирующей метки. На рисунке 4.8 приведен маршрут пера, продолжающий маршрут с рисунка 4.7.

Переменная w используется для вычисления ширины только что напечатанного числа, другая переменная, wide, следит за самым широким числом, спечатанным до сих пор. Эту ширину вспоминают в первой команде в следующей части процедуры:
***

ЧАСТЬ 6:

Код:
   chartleft = wide + 100
   tick = charttop
   For plt = 0 To (yticks - 1)
      Chart.ChartArea.Line (chartleft - 100, tick)
         -(chartleft - 1, tick)
      yln(plt) = tick
      tick = tick + intervaly
   Next plt

Переменная chartleft теперь содержит точку, представляющую крайнюю левую границу диаграммы. Это поле величиной в самое широкое напечатанное до сих пор по левой стороне число плюс 100 twips для дополнительного поля. Затем, переменная tick принимает значение верхней границы диаграммы по Y-оси. Эта переменная используется для вывода небольших штрихов, отходящих от крайней левой границы диаграммы, отмечающих на оси значение уже напечатанных масштабирующих меток.
***

ЧАСТЬ 7:

Код:
   chartbottom = tick - intervaly
   w = Chart.ChartArea.TextWidth(Str$(xmax))
   chartright = Chart.ChartArea.Width - (w / 2)
   wdth = chartright - chartleft
   yaxlin = Chart.ChartArea.CurrentY + 150
   dif = Abs(xmax - xmin)
   If xmin > xmax Then
      dif = -dif
   End If
   xticks = Int(Chart.ChartArea.Width / 1100)
   intervalx = wdth / xticks
   For plt = 0 To xticks
      xi(plt) = xmin + ((dif / xticks) * plt)
      If xi(plt) < 1 Then
         pl$ = Format$(xi(plt), "0.######")
      Else
         pl$ = Format$(xi(plt), "################.##")
      End If
      Chart.ChartArea.CurrentX = chartleft + (intervalx * plt)
      tick = Chart.ChartArea.CurrentX
      xln(plt) = tick
      Chart.ChartArea.CurrentY = yaxlin
      w = Chart.ChartArea.TextWidth(pl$)
      Chart.ChartArea.CurrentX = Chart.ChartArea.CurrentX - (w / 2)
      Chart.ChartArea.Print pl$;
      Chart.ChartArea.Line (tick, yaxlin - 99)-(tick, yaxlin - 50)
   Next plt

Теперь снова выводятся масштабирующие метки, но теперь поперек диаграммы. Инструкции Части 7 частично воспроизводят подпрограмму для Y-оси, откорректированную для оси X. Обратите внимание на то, что значения chartbottom и chartright вычисляются сразу же. Нижняя часть диаграммы - там, где выводилась последняя метка по Y-оси. Правый край диаграммы обозначается значением xmax, длина строки максимального значения метки по X-оси, разделенная пополам и вычтенная из ширины всей ChartArea, грубо центрирует последнюю выводимую метку по Х-оси относительно правой границы области графика.

Небольшое различие между Х- и Y- подпрограммами вывода меток состоит в том, что в одном plt цикле выводятся и значение меток, и штрихи меток. Каждое значение метки центрируется относительно штриха метки.
***

ЧАСТЬ 8:

Код:
   Chart.ChartArea.Print
   pmmt$ = RTrim$(Panel.ParamText(xslot).Caption)
   Chart.ChartArea.CurrentX = (Chart.ChartArea.Width / 2) -
      (Chart.ChartArea.TextWidth(pmmt$) / 2)
   Chart.ChartArea.Print pmmt$
   Chart.ChartArea.Line (chartleft, charttop)-
      (chartright, chartbottom), QBColor(0), BF
   Chart.ChartArea.Line (chartleft, charttop)
      -(chartright, chartbottom), QBColor(8), B
   For ypt = 1 To yticks - 2
      For xpt = 1 To xticks - 1
         Chart.ChartArea.PSet (xln(xpt), yln(ypt)), QBColor(2)
      Next xpt
   Next ypt

Текст, описывающий параметры графика извлекается из панели калькулятора и центрируется под построенной диаграммой. Затем область, содержащая собственно график, закрашивается черным и получает серый бордюр, ypе цикл размещает небольшие зеленые точки, отмечающие пересечения линий интервалов внутри диаграммы.
***

ЧАСТЬ 9:

Код:
   intspcx = (chartright - chartleft) / 1000
   If ymax - ymin <> 0 Then
      ratioy = (chartbottom - charttop) / (ymax - ymin)
   Else
      ratioy = chartbottom - charttop
   End If
   For plot = 0 To 999
      Chart.ChartArea.PSet (chartleft + (intspcx * plot),
         chartbottom - ((charty(plot) - ymin) * ratioy)),
         QBColor(10)
   Next plot
   End Sub

Наконец настает время вывести 1000 значений решений формулы, которые загружены в массивы. Число twips между chartright и chartleft делится на 1000, чтобы получить значение интервала, равное ширине каждого столбца, который нужно вывести. Далее вычисляется соотношение масштабов между chartbottom и charttop, но только в случае, когда имеется разница между ymin и ymax - если график не представляет собой прямую горизонтальную линию. Если график представляет собой прямую горизонтальную линию, то деление на (ymax - ymin) не производится, чтобы не вызвать ошибку деления на ноль.

В последнем цикле яркая зеленая точка помешается в каждый столбец в горизонтальной позиции равной chartleft плюс ширина столбца, умноженная на номер столбца; и в вертикальной позиции, равной значению решения для столбца, минус минимальное значение решения.
***

Вот процедура, которая вызывает новый модуль построения диаграмм, когда пользователь нажимает кнопку Chart:

Код:
   Sub ShowChart_Click()
   If xslot = TRUE Then Exit Sub
   If CalcList.ListIndex = -1 Then Exit Sub
   xmin = Val(Param(xslot).Text)
   xmax = Val(ParamTo(xslot).Text)
   CreateChart xmin, xmax, xslot
   End Sub

Эта процедура прерывается, если xslot = TRUE, что означает, что пользователь не задавал интервала для получения решения на диаграмме; и если CalcList.ListIndex = -1, если пользователь не выбрал формулы. Минимальные и максимальные значения диапазона, для которого строится диаграмма, извлекаются из текстовых блоков и помешаются в переменные xmin и xmax. Эти два значения, вместе с индексом окошка, из которого они взяты, передаются как параметры в процедуру CreateChart, которая, собственно, и строит диаграмму.
***

СТАРТОВЫЙ НАБОР ФУНКЦИЙ EXPRESSOR II
Процедура Sub main() проекта Expressor II загружает в память модули панели калькулятора и диаграммы. Панель калькулятора при этом отображается и становится активной.

Код:
   Sub main()
   Panel.Show
   Load Chart
   End Sub

Мы не будем разделять следующую процедуру на части.

Как мы установили прежде, подпрограмма построения диаграмм делает 1000 обращений к функции calcuiate(), которая дает решение для выбранной формулы из списка на панели формулы. Эта функция действует прежде всего как переключатель, что очевидно из следующей распечатки:

Код:
   Function calculate(frmula As Integer)
   Select Case frmula
      Case 0
         calculate = surf_area_rccyl(p(0), p(1))
      Case 1
         calculate = volume_rccyl(p(0), p(1))
      Case 2
         calculate = zone_sphere(p(0), p(1))
      Case 3
         calculate = force_att(p(0), p(1), p(2), p(3))
      Case 4
         calculate = dopp_shift(p(0), p(1), p(2), p(3))
      Case 5
         calculate = escape_velo(p(0), p(1))
      Case 6
         calculate = grav_force(p(0), p(1), p(2))
      Case 7
         calculate = sinewave(p(0), p(1))
      Case 8
         calculate = parabola(p(0), p(1), p(2))
      Case 9
         calculate = median(p(0), p(1), p(2))
      Case 10
         calculate = perpendulum(p(0), p(1))
      Case 11
         calculate = circorbit(p(0), p(1))
   End Select
   End Function

Процедура получает номер формулы и прямо вызывает соответствующую проциедуру вычисления, используя для ветвления предложение Select Case. Каждый переход Case переводит программу к подпрограмме вычисления по формуле, возвращаемое этой подпрограммой значение становится результатом функции calculate().

Определения функций см. в листингах.

Константы PI, GRAV и gracc объявлены в глобальном модуле. Если вы планируете добавить в Expressor II функции, которые используют константы, вы можете объявить дополнительные константы в этом месте. Если вы собираетесь добавить в проект Expressor II дополнительные функции, проделайте следующие шаги:
- В процедуре Sub Form_Load() EXPRSOR2.FRM добавьте имя формулы в конец списка формул в сегменте процедуры CalcList.Addltem. Это позволит вам видеть имя формулы в выпадающем списке на панели калькулятора и в заголовке диаграммы.
- Ваша формула может иметь до пяти параметров, индексированных от 0 до 4. Добавьте имена этих параметров в конец списка label$(), начиная с параметра #0 - представляющего вторую размерность этой двумерной переменной типа массив. Значение первой размерности равно следующему доступному номеру после последнего в списке.
- Может потребоваться переписать объявление Dim Label$() в общей области объявлений EXPRESR2.FRM, если число функций у вас превысит объявленное ранее 16.
- В функции calculate() в EXPRESR2.BAS добавьте еще один Case, номер которого по порядку соответствует номеру формулы в CalcList, в конец списка в предложении Select Case. Эта процедура действует как переключатель, который направляет программу к вашей подпрограмме вычисления функции.
- В конце этой подпрограммы сделайте функциональное объявление для вашей персональной функции. Оно может содержать переменные с именами по вашему выбору внутри круглых скобок, пока они представляются одинарной точностью. После того, как вы нажмете Enter, интерпретатор Visual Basic создаст обрамление для вашей функции, автоматически добавив команду End Function.
- Ваша функция может содержать так много строк, сколько необходимо, пока имя переменной, возвращающей значение телу вызывающей программы, и имя вашей функции одни и те же.
Gudleifr
Gudleifr
Admin

Сообщения : 3211
Дата регистрации : 2017-03-29

Вернуться к началу Перейти вниз

О самоуспокоенности неудачников (приложение к заметкам о простых играх) Empty Re: О самоуспокоенности неудачников (приложение к заметкам о простых играх)

Сообщение автор Gudleifr Сб Сен 02, 2017 12:00 pm

D.F.SCOTT, КАЛЬКУЛЯТОР EXPRESSOR III

О самоуспокоенности неудачников (приложение к заметкам о простых играх) C8be10
Рисунок 8.5. Мутирующий Expressor

Рисунок 8.5 демонстрирует Expressor после того, как он стал Expressor'oM III. Видно, что мы как следует поработали с элементами управления группы 3D Controls. Перед тем как успокоиться, мы увлеченно экспериментировали с размещением командных кнопок внутри новых рамок, представленных коллекцией профессионального расширения VB, в поисках дополнительных функциональных возможностей интерфейса.

Что делает сервисную программу типа Expressor сугубо пригодной для "всплытия" из других приложений, так это то, что все функциональные возможсти программы сделаны доступными пользователю на одной панели, в одном модальном окне. Нет никакой поддержки вычислительного процесса Expressor вне модулей Expressor, поскольку он разрабатывался нами именно как калькулятор, как автономная карманная коробочка. Модуль рисования диаграмм - исключение, но лишь постольку, поскольку он требует достаточного количества места на экране для корректной работы.

Простите нас за то, что мы просим вас самостоятельно обратиться к текстам для более подробного рассмотрения использованных приемов программирования и дизайна формы. Мы сохранили содержание фзрмы Expressor Ml в виде текста и выяснили, что только установки свойств формы займут 65 книжных страниц. Даже если наш технический редактор решит использовать шестипунктовый шрифт, облегчения читателю это не принесет. Мы решили не превращать книгу в отчет об осадках во Внутренней Монголии за период с 1925 по 1936 год и оставить Expressor III в листингах - О самоуспокоенности неудачников (приложение к заметкам о простых играх) Leaf10TXT, 0.11МбО самоуспокоенности неудачников (приложение к заметкам о простых играх) Leaf10.

Позвольте взамен кратко описать вам, как мы изменяли внешний вид этой прикладной программы. Новое окошко калькулятора использует шрифт AGOpus Type 1 производства фирмы Tilde. Если вы хотите установить Expressor III в системе, в которой отсутствует шрифт AGOpus в форматах Adobe или TrueType, установите свойство .Fontname окошка в значение по умолчанию, шрифт MS Sans Serif, который является довольно разборчивым и до некоторой степени привлекательным. Окошко калькулятора является элементом управления label, чье свойство .BackStyle установлено в 0 - Transparent (прозрачный). Это сделано затем, чтобы другой элемент управления label мог занимать тот же самый регион формы, что и область окошка калькулятора, и мог одновременно ним отображать свое значение.

Мы переместили то, что было кнопкой Apply Formula, направо от окошка калькулятора; теперь это кнопка Solve. Expressor II имел на этом месте кнопку Cut. Мы заключили, что это, в конце концов, не место для кнопки Cut, поскольку она будет использоваться, по нашей оценке, гораздо реже, чем кнопка Formula/Solve, а верхний правый угол - самое удобное для глаз пользователя место. Такого внешнего вида кнопки Solve - "ров с водой вокруг замка" - мы добились, поместив командную кнопку Solve внутри элемента управления SSPanel, чьи свойства: .BevelOuter установлено в 1 - Inset (вдавлено); .BevelWidth установлено в 3. Поместить кнопку внутрь фрейма SSPanel очень просто, потому что свойство .AutoSize элемента управления SSPanel было заранее установлено в 3 - AutoSize Child to Panel (дочерний элемент привязан размерами к родителю). Командная кнопка, помешенная внутрь фрейма SSPanel, занимает все доступное пространство внутри фрейма.

На форме теперь семь окошек для ввода параметров против пяти, присутствуюших в Expressor II,- для некоторых финансовых формул, которые очень хотелось включить, пяти было просто недостаточно. Исправить индекс массива управления в исходном тексте не было никакой проблемой; циклы от 0 до 4 были заменены на циклы от 0 до 6. Эти элементы управления располагаются внутри большой 3-мерной панели SSPanel, чье свойство .BevelOuter установлено в 1 - Inset, а свойство .BevelWidth установлено в 3. Помните, что 3-мерная панель SSPanel работает как стандартный Visual Basic элемент управления frame: элемент управления, помещенный в периметр панели "принадлежит" ей. Когда вы перемещаете по форме панель, вы перемешаете и ее содержимое. По это причине, для того, чтобы перенести окошки параметров внутрь SSPaneL воспользовались функцией редактора среды "вырежи и вклей" (Cut-and-Paste). Сначала окошки были "вырезаны" (Cut) в буфер Windows (Clipboard), затем курсором мыши была указана нужная панель SSPanel, и окошки были в нее вставлены (Paste) из буфера. Выбрать несколько окошек (или любых других элементов управления) сразу, для группового вырезания или копирования, можно поочередно выбирая их мышью при зажатой клавише Shift. Затем мы, добавив, при помощи того же Clipboard, два недостающих окошка, выровняли их на новом месте внутри SSPanel.

Остальные кнопки из нижней части формы также были перенесены в соответствуюшие панели SSPanel при помощи Clipboard. Эффект "лестницы" в этих панелях был достигнут установкой свойств .BevelInner и .BevelOuter в 2 - Raised (поднято), свойства .BevelWidth в 1, и свойства .BorderWidth в 3. Цифровые и функциональные клавиши - стандартные командные кнопки VB, хотя для придания им более квадратного, менее округлого вида, свойства .BackColor этих зпок установлены в темно-синий цвет. В общем, цветовые назначения свойств не оказывают влияния на командные кнопки (даже если их свойства BackColor отличаются от серого, кнопки все равно остаются серыми); но четыре одиночных пикселя в углах командной кнопки VB к установке цвета чувствительны (для того они там и находятся). Белый .BackColor кнопки визуально закругляет ее углы, темные цвета - заостряют.

Кнопка Paste на панели Expressor III теперь расположена рядом с кнопкой Cut в нижней части формы, слева от новых опций выбора базы исчисления. Свойства .BevelInner и .BevelOuter панели-владельца этих кнопок установлены в 1 - Insert, для визуального выделения пары кнопок, работающих с Clipboard, от остальных. Самый новый и, возможно, наиболее интересный элемент управления на форме - устройство памяти в нижнем левом углу. Это устройство помнит не только значения, но и текстовые описания этих значений; значения, заесенные в это устройство, сохраняются на диске и будут присутствовать во с последующих сеансах работы калькулятора (если вы не удалите их кнопкой Delete). Таким образом, можно запоминать не только промежуточные результаты вычислений, но и значения важных физических или математических констант, сопровождаемые пояснениями,- например, значение числа Пи или костанты Планка. Пользователь должен впечатать числовое значение в белый текстовый блок устройства памяти, а текстовое описание в серое окно выпадающего списка под ним и нажать кнопку Add. Удаление значения из списка памяти выполняется кнопкой Delete. Кнопка Recall помещает значение из списка в дисплей калькулятора.

Извлечение значения из одной семи ячеек "энергозависимой" памяти производится нажатием одной из кнопок "М1"..."М7", расположенных справа от набора окошек параметров формулы, что вызовет пересылку значения из окошка соответствующего параметра в окошко дисплея калькулятора. Выбор ячейки памяти (окошка параметра), в которую будет занесено значение из белого текстового поля устройства памяти, производится при помощи элемента управления SpinButton. Это пользовательский элемент управления, обеспечиваемый файлом SPIN.VBX. В текстовом поле справа от SpinButton прокручиваются надписи от "М1" до "М7", обозначающие окошки в массиве параметров формулы. Этот SpinButton просто устанавливает окошко-адресат, из числа окошек параметров, в которое будет переслано значение из белого текстового поля устройства памяти; нажатие на текстовое поле справа от SpinButton пересылает число, отображаемое в настоящее время в блоке памяти, в окошко параметра. Элемент управления SpinButton был одним из недостающих в оригинальной среде Visual Basic элементов управления, быстро восполненных профессиональноной версией VB.
***

КОГДА "ЛЕГКО" И "САМООЧЕВИДНО" - РАЗНЫЕ ВЕШИ
Мы сомневаемся относительно того, что Expressor'ом, в его настоящем виде, возможно оснастить какую бы то ни было прикладную программу. Мы нарушили большинство правил легкости использования, замышляя и реализуя эту утилиту.

Вспомните, как вы в первый раз взяли в руки программируемый калькулятор Texas Instruments или HEWLETT-PACKARD: были ли вы способны понять, как они работают, глядя на пластиковую коробочку в руках? Вообразите себе разноцветные кнопки, надписанные в два этажа. Вообразите, что некоторые клавиши имели по две, а то и по три разных функции. Помните модель HP 1970-х, которая сохраняла программы на магнитных карточках, которые засовывались в калькулятор, как кредитные карточки в банкомат? Узнали вы, как использовать эти калькуляторы, когда увидели их? И когда вы, наконец, задаете одну программу клавише А, другую клавише В и третью клавише С, каково было вам запоминать запрограммированные клавиши?

Тот факт, что программируемые калькуляторы достаточно сложны в эксплуатаиии, не может служить оправданием разработчику сервисной утилиты, который, симулируя в ней их функции, предоставляет пользователю столько же доступных инструментов, сколько их содержится на лицевой панели пластиковой коробки. В написании подобных программ обычно наступает момент, когда акцент на легко доступные пользователю-новичку инструменты отвергается разработчиком раз и навсегда в пользу установления модели использования, которая может в конечном счете стать знакомой любому пользователю (и даже новичку, и не всегда благодаря вам, а, например, благодаря пристрастию вашего нового пользователя к раскладыванию пасьянса под Windows). Знакомство приносит комфорт и с комфортом приходит в конечном счете легкость в использовании.

Новичок, вероятно, не сразу усвоит приемы работы с окошками параметров Expressor III. Мы внимательно выслушивали всех, кто испытывал затруднения при освоении Expressor'a. Expressor является примером программы, в которой знакомство является ключом к эффективности действий. За много лет мы так и не сумели понять тех людей, которые обьясняли нам, почему WordPerfect для DOS для них такая простая в использовании программа. По нашему мнению, запоминание пяти или шести различных функций для каждой из 12 функциональных клавиш, чьи номера не имеют никакой логической, синтаксической, символической или вообще какой-либо поверхностной связи с функциями, которые им назначены, самая бесполезная трата человеческого действия. С точки зрения большого количества пользователей, WordPerfect для DOS, однако, становился знакомой программой. Он им нравится потому, что они знают его. Они отказываются наращивать вычислительные возможности, переходя на WordPerfect аля Windows просто потому, что он выглядит по-другому.
***

ПЕРВАЯ СТРАНИЦА
Что делает прикладные программы с графическим интерфейсом дружелюбными для тех пользователей, кто предпринял погружение в них,- так это геометрия и символика. Люди запоминают изображения и расположение, в целом, лучше, чем они запоминают слова или, тем более, сокращения.

Композиция становится важным принципом вычислений в той же мере, в каковой она является важной в книгопечатании. Композиции таких газет, как Wall Street Jornal и USA Today, и таких журналов, как Times, претерпели значительную эволюцию, но эти издания умели оставаться хорошо приспосабливаемыми. Композиция периодического издания несет, кроме информационной, навигационные функции; никому неохота находить интересующие его материалы после трехчасового исследования кипы бумаги - люди привыкли находить нужную информацию в привычных местах, размещенной в определенном порядке по значимости и темам.

Когда журнал меняется, некоторое количество номеров кажутся незнакомыми; но если новая композиция хорошо разработана, проста для навигации и сбалансирована, она становится такой же удобный как и прежде.

Во время написания этой книги одной из главных жалоб программистов графических прикладных программ была такая, что изображения становятся инструментом маркетинга, что изготовители добавляют и будут их добавлять в свои приложения в изобилии, даже если изображения будут мешать работе пользователя. Пока эти программисты ведут поиск принципов дизайна, органично влившегося бы в модели использования их прикладных программ, эти принципы уже существует и преуспевают сегодня, и они находится на вооружении технических редакторов и дизайнеров издательств новостей. Если работа авторов состоит в том, чтобы сделать материал кратким и читабельным, верстальщики и дизайнеры дают материалу, если хотите, голос. Они придают тексту краски, которые отсутствуют в нем, когда он не читается вслух.

Пока стандарты голоса не установлены, и голос становится существенным элементом в вычислениях. Мы, как программисты, должны полагаться на смешение артистизма и консерватизма в композиции приложения, чтобы дать нашим прикладным программам голос - некоторые ритм и диапазон,- позволяющие запасть пользователю в память. Стандартизированный голос примерно так же незабвенен, как шумы пьяного дебоша. Не позволяйте никаким выпускам стандартов убедить вас, что стиль - незначительная сторона проблемы вычислений. Стиль так же важен в программе, как и в книге. Как художники, мы полагаем, что свой стиль мы сможем выработать только сами.

Поскольку легкость в использовании определена сегодня множеством разных слов, выкиньте ее на минуту из головы. Читатель серьезных статей ожидает непрерывности повествования, краткой вводки и консервативного вида, не раздражающего глаза аляповатым текстом и кричашими заголовками, а дурак ждет графических украшений, опекающих читателя, врезок с напоминаниями и библейских примеров. Раздумывая, как представить вашу прикладную программу пользователю, лучшее, что вы можете сделать,- перенести эти принципы на вычисления - возможно ценой функции "Cut" (чик-чик!) редактора среды VB.
***

ЖЕСТКО СМОНТИРОВАННЫЙ БЛОК КНОПОК
Набор кнопок калькулятора может показаться пользователю не вполне оригинальным устройством. Однако кнопкам можно присвоить несколько различных функций, доступ к которым будет определяться текущим состоянием калькулятора. Но несмотря на различные функции, присвоенные кнопке, события от нее будет обрабатывать одна и та же процедура. Фактически вы будете иметь дело не с набором отдельных графических объектов, но скорее с одним большим объектом, разделенным на чувствительные сегменты.


Последний раз редактировалось: Gudleifr (Ср Дек 06, 2017 12:38 pm), всего редактировалось 1 раз(а)
Gudleifr
Gudleifr
Admin

Сообщения : 3211
Дата регистрации : 2017-03-29

Вернуться к началу Перейти вниз

О самоуспокоенности неудачников (приложение к заметкам о простых играх) Empty Re: О самоуспокоенности неудачников (приложение к заметкам о простых играх)

Сообщение автор Gudleifr Сб Сен 02, 2017 12:11 pm

D.F.SCOTT, КАЛЬКУЛЯТОР EXPRESSOR III. ПРОДОЛЖЕНИЕ

С самого начала мы задумали сделать Expressor программируемым калькулятором. Такая прикладная программа должна поддерживать некоторую систему запоминания последовательности нажатия кнопок. Первая проблема, с которой мы столкнулись, состояла в том, что почти каждая кнопка на панели калькулятора, за исключением цифровых, имела собственную процедуру события _Click с независимым контекстом. Если мы должны изобрести команду, которая помешала бы некоторое подобие идентификатора нажатой кнопки в массив в памяти, мы будем должны вызывать эту команду из каждой процедуры события _Click.

Вот наш первоначальный план того, как Expressor будет запоминать собственные программы:
- Объявляется глобальный массив, который присваивает произвольное значение каждой кнопке, которая может быть запрограммирована.
- Второй глобальный массив обьявляется для хранения заводимых в калькулятор, равно как и исполняемых калькулятором, программ.
- Процедура _Click каждой кнопки загружает в программу эквивалент значения, соответствующий ей в первом глобальном массиве. Далее программа сохраняется на диск как файл данных с последовательным доступом с собственным уникальным именем файла.
- При выполнении запомненной программы набор кодов программы вызывается селектором формулы по уникальному имени файла данных с диска.
- Нажатие кнопки Solve делает процедуру Sub Solve_Click() нервным центром выполнения внутренней программы калькулятора. Каждое значение в файле последовательного доступа оценивается большим предложением Select Case, и для каждого значения Case исполняется процедура, которая обычно выполняется по вызову процедуры события_Click соответствующей кнопки.

С точки зрения программиста эта модель не будет слишком трудна для исполнения и будет состоять в размножении и переносе частей исходного кода средствами функции "вырежи-и-вклей" редактора среды VB из соответствующих процедур _Click. Одна из проблем работы в Visual Basic, по сравнению с работой в более ранних версиях БЕЙСИКа, состоит в том, что программист лишен возможности обозревать полный текст приложения, одновременно на экране можно видеть только одну процедуру модуля, и программист не в состоянии оценить избыточности исходного кода "на глаз". Изготовление программы на старом БЕЙСИКе состояло из процесса трансляции в исполнимый файл единого большого модуля исходного текста. Такое свойство среды VB затрудняет программисту определение первичных и периферийных контекстов, но, по крайней мере, помогает программисту заметить, что он становится слишком повторяющимся. Копируя команды процедур _Click в другие части прикладной программы Expressor, мы можем направить тот же самый физический процесс по любому из нескольких возможных направлений, которые будут невероятны для программиста на старом БЕЙСИКе.
***

СЖАТИЕ МОДЕЛИ ИСПОЛЬЗОВАНИЯ ПРИ ПОМОЩИ МАССИВОВ ЭЛЕМЕНТОВ УПРАВЛЕНИЯ
В первоначальной модели использования Expressor III каждая кнопка на панели калькулятора имела собственную процедуру события_С lick, как это было в Expressor II. Каждая локальная процедура тогда вызывала процедуру из общего модуля, что и служило событием запуска собственно функций калькулятора. В выбранной нами позже модели использования каждая программируемая кнопка на панели калькулятора стала зарегистрированной внутри коллективного массива управления Button(), который и управляет теперь запуском функций калькулятора. Обратите внимание на уменьшение в объеме; новая модель использования выполняет ту же самую работу с меньшими капиталовложениями в исходный текст. Теперь программируемость Expressor'a работает так:
- В обшем модуле объявлена структура Туре, описывающая последовательность кнопок, необходимых для исполнения программы (шагов программы может быть до 100), наряду с семью параметрами и их описаниями.
- Каждая кнопка на панели, которая может быть включена в программу калькулятора, перерисовывается как часть массива управления, названного Button(). Вместо примерно 38, для записи нажатых кнопок в программу используется одна процедура. Идентификатором каждой кнопки служит, конечно, свойство .Index массива управления.
- После запоминания нажатой кнопки для направления процесса к уникальным подпрограммам, присвоенным каждой кнопке, используется набор предложений Select Case. Фактически все программируемые кнопки проходят через проверку в процедуре Sub Button_Click() перед тем, как исполнится присвоенная им рабочая функция.

Деление контекстов прикладной программы изменилось очень незначительно. Большинство управляющих механизмов осталось в EXPRSOR3.FRM, модуле формы Panel. Существующий набор процедур Function для решения общих формул, созданный для EXPRSOR2.BAS, сохранился в EXPRSOR3.BAS, единственном обшем модуле, содержащем также глобальные объявления и объявление Туре, описывающее структуру переменной для хранения запоминаемых программ. Модуль формы может вызывать процедуры общего модуля, но не наоборот. В обший модуль введены процедуры, которые адресуют видимое содержание более чем одной формы, однако применение расширенного синтаксиса, включающего ссылку на имя объекта, в командах, адресующих графический объект, позволяет интерпретатору знать, какая форма является источником. Для отображения таблиц данных добавился новый модуль формы, похожий на электронную таблицу; этот модуль будет рассмотрен нами позже.

Сконцентрируемся теперь на модификациях в модуле формы в том порядке, в каком они были проделаны; начнем с общего модуля проекта Expressor III:
***

EXPRSOR3.BAS. ГЛОБАЛЬНЫЕ ОБЪЯВЛЕНИЯ

Код:
Global xmin As Single, xmax As Single
 Global ymin As Single, ymax As Single
 Global xslot As Integer, yslot As Integer
 Global norows As Integer, nocolumns As Integer
 Global p(7) As Double
 Global frmula As Integer
 Global maxlen(500) As Integer
 Global noconsts As Integer
 Global convBase As Integer, convValue As Double
 Global title$, xaxis$, yaxis$
 Global learnmode As Integer, formulanow As Integer,
 formulaplace As Integer
 Global partitn As Integer, nofms As Integer
 
 Type Memory
 Constant As Double
 title As String
 End Type
 Type Formula
 title As String * 128               '128
 length As Integer                   '  2
 setting As Integer                  '  2
 Pattern(100) As Integer             '200
 Variable(6) As Double               ' 56
 VariableName(6) As String * 128     '896
 VariableAxis(6) As Integer          ' 14
 VariableTo(6) As Double             ' 56
 End Type
 'add                                   2
 'record length is...................1356
 
 Global mem(1000) As Memory
 Global fn(1000) As Formula
 
 Global Const PI = 3.1415927
 Global Const GRAV = .000000000066732'gravitational constant
 Global Const gracc = 9.80665    'acceleration of free fall in m/s^-2
 
 Dim xi(50) As Single, yi(50) As Single,
 xln(50) As Single, yln(50) As Single
 Dim charty(1000)
 
 'File Open/Save Dialog Flags
 Global Const OFN_OVERWRITEPROMPT = &H2&
 Global Const OFN_HIDEREADONLY = &H4&
 Global Const OFN_PATHMUSTEXIST = &H800&
 Global Const OFN_FILEMUSTEXIST = &H1000&

Новый табличный модуль решает формулы для двух различных диапазонов значений, между xmin и xmax (те же переменные, что используются модулем диаграмм) и между ymin и ymax no Y оси таблицы. Индексами, указывающими, из которого окошка выбирается значение диапазона параметра, для модулей диаграмм и таблицы, являются xslot и yslot. Предположите, например, что пользователь хочет видеть решения формулы, рассчитывающей количество топлива, необходимого баллистической ракете для выхода на стационарную орбиту вокруг земли. Параметры формулы - полезная нагрузка ракеты, расход ракетного топлива в кубических сантиметрах в секунду и координаты места пуска. При использовании X/Y диаграммы пользователь может получить кривую, описывающую дисперсию решений только по оси X. Вы можете еще помнить, как запускается механизм рисования диаграмм: пользователь выбирает окошко параметра, который будет представлять ось X, задает начальное и конечное значение этого параметра и нажимает кнопку Chart, чтобы визуально наблюдать дисперсию значений решений формулы для всех возможных значений диапазона параметра.

Таблица позволяет пользователю видеть прокручиваемый список решений формулы для всех возможных комбинаций значений ДВУХ параметров в диапазонах. Глобальные переменные norows и nocolumns содержат размеры таблицы в ячейках.

Массив р() появился в проекте одним из первых; он содержит значения переменных в левом блоке параметров. Его значения используются для получения единственного решения формулы, которое будет помещено в окошко дисплея калькулятора. Переменная frmula содержит индексный номер формулы, находящийся в данный момент в селекторе формулы. Массив maxlen(), как вы скоро увидите, позволяет определить ширину столбца таблицы.

Переменная noconsts содержит число элементов, содержащихся в настоящее время в списке нового устройства памяти. Файл содержимого устройства памяти сохраняется на диск непрерывно, поэтому всякий раз, когда Expressor запускается снова, значения, занесенные в память, восстанавливаются. Переменные convBase и convValue зарезервированы под основание и значение различных систем счисления - Expressor III позволяет работать с десятичными, шестнадцатиричными, восьмиричными и двоичными значениями.

Переменные title$, xaxis$ и yaxis$ содержат строчные значения, выбираемые из блока параметров и помещаемые в качестве заголовков в таблицы и диаграммы. Переменная learnmode - флажок, обозначающий, находится ли калькулятор в режиме запоминания нажимаемых кнопок, formulanow содержит номер запоминаемой формулы и formulaplace содержит индекс кнопки, которую запоминает эта формула. Переменная partitn содержит номер первой формулы в списке, который является программой Expressor'a; эта формула следует за последней жестко закодированной формулой списка. И наконец, nofms содержит общее число запомненных (не жестко закодированных) в настоящее время формул.

Теперь рассмотрим элементы структуры Туре. Каждой запоминаемой формуле присваивается заголовок title, общее число запомненных кнопок length; и setting, указывающая, находится ли калькулятор в режиме обратной польской или алгебраической (T.I.) записи. Нажатые кнопки сохраняются в массиве pattern(), имеющем размер 100 элементов. Параметры из блока параметров запоминаются в Variable(), описания каждого параметра в VariableName(), установки диапазона значений в VariableAxis() и параметры диапазона (правый банк) в VariabIeTo(). 1000 констант может быть запомнена в mem(), и 1000 формул в fn().

Последняя часть раздела объявлений EXPRSOR3.BAS содержит не-глобальные объявления служебных переменных Dim, используемых при построении диаграмм и массива charty(), содержащего значения решений формулы по оси X.

Теперь, когда вам кажется, что объявлено достаточное количество переменных, обратитесь к листингу, содержащему объявления уровня формы Panel.
***

РАЗДЕЛ ОБЩИХ ОБЪЯВЛЕНИЙ EXPRSOR3.FRM

Код:
Dim label$(1000, 6)
 Dim point_lock As Integer, function_on As Integer
 Dim readout_value As Double, combine_value As Double
 Dim ready As Integer
 Dim solution As Single
 Dim oldbase As Integer
 Dim cformat$
 Dim rpnalg As Integer, lastbutton As Integer, execbutton As Integer
 Dim ParenHold(100) As Double, FunctionPrior(100) As Integer, ParenPlace As Integer
 Dim memspin As Integer
 Dim learnfmla(100) As Integer

На уровне формы массив label$() содержит текстовые описания всех формул: и жестко закодированных, и запомненных. Вы можете еще помнить назначение флаговой переменной point_lock, появившейся в самом первом варианте Expressor - она отслеживала появление в дисплее калькулятора десятичной точки; ее новый дубликат, function_on, служит для подобной же цели в многоклавишных операциях. Если для регистрации функции требуется более чем одна клавиша, и несколько первых уже введены, function_on принимает значение TRUE.

Переменная ready служит для слежения за количеством цифр и готовностью операнда на дисплее калькулятора, a solution содержит единственное решение формулы, помещаемое в окошко дисплея в стандартном режиме калькулятора. Переменная oldbase следит за тем, в какой системе счисления находилось последнее введенное или преобразованное число при преобразовании значения в окошке дисплея калькулятора между различными системами счисления. Полуконстанта cformat$ содержит описатель формата, в котором число должно появиться в окошке дисплея калькулятора.
***

РЕАЛИЗАЦИЯ ДВУХ СИСТЕМ АЛГЕБРАИЧЕСКОЙ ЗАПИСИ
Новая версия Expressor должна быть способна работать и с обратной польской, и с алгебраической записью выражений. Адаптация к последнему варианту требует, чтобы калькулятор запоминал нажатую перед последней нажатой функциональную клавишу. Вы, наверное, помните наши рассуждения о том, что калькулятор должен быть способен запоминать свой предпоследний шаг, чтобы пользователь мог вводить функции в том порядке, в каком они появлялись бы на листе бумаге. Когда вы вводите:

2 x 3 + 6 =

в алгебраической записи, операция умножения не происходит после нажатия клавиши "x", но происходит, когда пользователь нажимает "+" - калькулятор исполняет предпоследнюю введенную функцию. В обратной польской записи та же самая формула будет вводиться как:

2 [Enter] 3 x 6 [Enter]

(кнопка [Enter] исполняет функцию кнопки плюс). Пользователь калькулятора должен думать несколько "наоборот", но калькулятор выполняет функции в тот момент, когда нажимается соответствующая клавиша; так что с точки зрения машины такая запись не является "обратной".

В нижней части панели калькулятора находится пара фиксируемых кнопок для установки системы записи операций калькулятора. Текущее состояние этой установки хранится в переменной rpnalg. Когда включена алгебраическая запись, lastbutton содержит индексный номер предыдущей функции, поэтому местная процедура события _Click выполняется всякий раз, когда нажимается следующая функциональная клавиша и execbutton принимает индексный номер текущей функциональной клавиши, клавиши нажатой после lastbutton. Одна из побочных выгод использования коллективной процедуры для обработки нажатия на кнопку состоит в том, что для запоминания и выбора функциональной клавиши необходим только один набор команд.
***

РЕАЛИЗАЦИЯ ОБРАБОТКИ СКОБОК
Следующие три переменных относятся к системе, которая потребовала у нас приблизительно три дня тяжелой работы для полной отладки. Кнопки круглых скобок используются для создания подформулы внутри текущей формулы и необходимы в финансовых уравнениях, в которых дроби возводятся в степень. Когда пользователь нажимает левую круглую скобку, состояние текущей формулы сохраняется. Массив ParenHold() содержит значение промежуточной суммы выражения в момент нажатия левой круглой скобки. Если активна алгебраическая запись, массив FunctionPrior() содержит последнюю нажатую функциональную клавишу, что позволяет активизировать эту функцию в момент нажатия следующей за правой круглой скобкой функциональной клавиши. Переменная ParenPlace следит за количеством активных в настоящий момент подформул в скобках.

Переменная memspin содержит имя параметра, которому пересылается текущее содержание устройства памяти. И, наконец, переменная learnfmla() содержит запоминаемую в настоящее время программу. Мы поняли, что мы нуждаемся в этом массиве, слишком поздно, когда при записи формулы на диск потребовалось заполнить поля соответствующей составной переменной, содержащей программу. Для эффективной работы процесса запоминания программы необходим полный доступ к полям содержащей ее составной переменной, которую и дублирует learnfmla().

Вот мы и дошли до процедуры Sub Form_Load(). Так как вы уже знакомы с процессом загрузки параметров жестко закодированных формул по Sub Form_Load() из Expressor II, мы поместим многоточия на месте их длинных списков. Что вам важно отметить в этом листинге, так это процессы загрузки инициализирующих значений калькулятора с диска:

Код:
Sub Form_Load()
 On Error GoTo NoFile1
 Load Chart
 Load ExTable
 CalcList.AddItem "Surface Area of RC Cylinder"
 .........
 label$(0, 0) = "Radius of right circular cylinder"
 label$(0, 1) = "Height of cylinder"
 .........
 formulanow = 12
 partitn = formulanow
 '***** A reminder to the programmer:  If any new formulas are
 '  to be added to the source code rather than to the learned
 '  formula list, the value of variable _formulanow_ above
 '  should be incremented with each function added.   -DFS
 xslot = -1
 yslot = -1
 oldbase = 10
 lastbutton = -1
 memspin = 1
 MemSlot.Caption = "M" + LTrim$(Str$(memspin))
 cformat$ = "####################.00"
 Panel.Show
 Button(17).SetFocus
 Open "exprcnst.dat" For Input As #1
 Input #1, noconsts
 For loadlist = 0 To noconsts - 1
 Input #1, mem(loadlist).Constant
 Input #1, mem(loadlist).Title
 Next loadlist
 Close #1
 GoTo errorskip1
 NoFile1:
 mem(0).Constant = 3.1415927
 mem(0).Title = "Pi"
 mem(1).Constant = .000000000066732
 mem(1).Title = "Gravitational constant"
 mem(2).Constant = 9.80665
 mem(2).Title = "Acceleration of free fall"
 noconsts = 3
 Open "exprcnst.dat" For Output As #1
 Print #1, noconsts
 For savelist = 0 To 2
 Print #1, mem(savelist).Constant
 Print #1, mem(savelist).Title
 Next savelist
 Close #1
 On Error GoTo 0
 Resume errorskip1
 errorskip1:
 For dump = 0 To noconsts - 1
 Memory.AddItem mem(dump).Title
 Next dump
 On Error GoTo nodata
 Open "exprfmla.dat" For Random As #2 Len = 1356
 Get #2, 1, nofms
 If nofms = 0 Then GoTo errorskip2
 For acquire = 1 To nofms
 Get #2, acquire + 1, fn(acquire)
 CalcList.AddItem fn(acquire).Title
 For layin = 0 To 6
 label$(acquire + partitn, layin) =
 fn(acquire).VariableName$(layin)
 Next layin
 Next acquire
 formulanow = formulanow + nofms
 Close #2
 Exit Sub
 nodata:
 nada% = 0
 Put #2, 1, nada%
 Resume errorskip2:
 errorskip2:
 Close #2
 End Sub

Начнем описание со второго пропуска. Переменная formulanow - индекс в списке формул, указывающий на место, в которое помешается следующая запомненная формула, первоначально устанавливается в 12. Разделение помещается здесь, потому что первые 12 (от 0 до 11) формул жестко закодированы. Далее несколько значений инициализируются как -1; в данном случае это не замещение значения TRUE, а индикатор значения "не-активно", поскольку индексы первых элементов левого и правого банков параметров и первой программируемой кнопки на панели имеют значение 0. Значение по умолчанию формально объявленной переменной - нуль, и нуль может интерпретироваться программой как реальное значение.

Далее, считыванием значений из файла последовательного доступа EXPRCNST.DAT, загружаются в устройство памяти сохраненные калькулятором константы. Обратите внимание на обработчик ошибки по метке NoFile1:. Если в системе пользователя нужный файл не существует, создаются и сохраняются на диск три константы. Обратите внимание также на то, что процедура обработки ошибки завершается командой Resume errorskip1. Метка errorskip1: находится немедленно после оператора Resume. В случае, если ошибка не регистрируется, переход осуществляется на эту метку. Это необходимый (хотя и непроизводительный) шаг. Интерпретатор будет полагать, что исправляет последствия ошибки до тех пор, пока не натолкнется на оператор Resume.

Константы устройства памяти сохраняются на диск с использованием схемы последовательного доступа, в то время как файл формул сохраняется с использованием схемы произвольного доступа. В обоих файлах первый вход содержит число записей в файле; предложение цикла исполняется с 1 по это число. В случае структуры fn(), объявленной ранее As Formula, предложение цикла, загружающее каждую запись в память, одновременно загружает элемент VariableName$ каждой записи в двумерный массив label$, поэтому когда пользователь выбирает формулу, заголовки блока параметров могут мгновенно отображаться. В label$ запоминаются не формулы, а только их описания. Обратите внимание на второй обработчик ошибки, вызываемый в случае отсутствия файла формул EXPRFMLA.DAT; он создает файл формул для будущего использования, но сохраняет в файле нуль, указывающий на отсутствие в нем записей.

Рассмотрим процессы, происходящие в Expressor после завершения процессов инициализации. В этой точке прикладная программа впадает в свойственное управляемым событиями программам состояние "спячки", ожидая ввода пользователя. Как вы уже знаете, мы переработали модель использования так, чтобы все кнопки, которые могут быть использованы как часть программы калькулятора, совместно использовали одну общую процедуру события:

Код:
Sub Button_Click(Index As Integer)
 Button(17).SetFocus
 FormulaHistory.Caption = FormulaHistory.Caption + Button(Index).Tag
 If learnmode = True Then
 learnfmla(formulaplace) = Index
 formulaplace = formulaplace + 1
 End If
 Select Case True
 Case Index > 34
 MemRecall Index - 35
 Exit Sub
 Case Index > 0 And Index < 10
 ButtonPos Index
 Exit Sub
 Case Index > 9 And Index < 16
 ButtonHex Index
 Exit Sub
 Case Index = 0
 Button0
 Exit Sub
 Case Index = 16
 ButtonPoint
 Exit Sub
 End Select
 function_on = True
 Select Case Index
 Case 22
 PosNeg
 Case 23
 Reciprocal
 Case 24
 Root
 Case 25
 Sine
 Case 26
 Logarithm
 Case 27
 PowerRaise
 Case 28
 Cosine
 Case 29
 NaturalLog
 Case 30
 Arctangent
 Case 31
 Tangent
 Case 32
 ParenLeft
 Case 33
 ParenRight
 Case 34
 Percent
 End Select
 If rpnalg = 0 Then
 execbutton = Index
 Else
 If Index > 16 And Index < 22 Then
 execbutton = lastbutton
 lastbutton = Index
 Else
 execbutton = Index
 End If
 End If
 Select Case True
 Case execbutton = 17 And rpnalg = 0
 EnterPlus
 Case execbutton = 18 And rpnalg = 1
 EnterPlus
 End Select
 Select Case execbutton
 Case -1
 EnterPlus
 Case 19
 Minus
 Case 20
 Times
 Case 21
 DividedBy
 End Select
 If Index = 17 And rpnalg = 1 Then
 Equals
 End If
 End Sub

О самоуспокоенности неудачников (приложение к заметкам о простых играх) C8bf10
Рисунок 9.2. Карта индексов массива Button()

При конструировании формы EXPRESOR3.FRM для присвоения индексов элементам массива управления Button() нами была использована программа VBAssist фирмы Sheridan Software (см. рисунок 9.2).

Обратите внимание на вызовы процедур в предложениях Select Case. Все процедуры события _Click, которые использовались этими кнопками массива Button(), переместились в область обших процедур, что не изменяет способа их действия; это только дает возможность процедуре Sub Button_Click() действовать как переключателю программируемых кнопок панели.

Процедура Sub Button_Click() обрабатывает события ото всех элементов управления, отмеченных на этой карте. Первые десять элементов управления массива, пронумерованные от 0 до 9, являются цифровыми кнопками 0-9; следующие шесть - кнопки A-F для ввода шестнадцатиричных цифр. Первое предложение Select Case процедуры Sub Button_Click() выясняет, была ли нажата цифровая кнопка или одна из новых кнопок системы памяти "М1"..."М7".

Если индекс кнопки "прошел" это предложение Select Case, кнопка должна быть функциональной и флажок function_on принимает значение TRUE. Далее предложение Select Case проверяет кнопку на предмет соответствия специальной математической функции. Если индекс "проходит" и это предложение Select Case, кнопка должна быть простой арифметической функцией.

Вот где потребуется индикатор способа записи выражения. Переменная rpnalg представляет систему записи выражения, которая используется в настоящий момент; если rpnalg содержит нуль, условное предложение заключает, что вы используете RPN (Reverse Polish Notation - обратную польскую запись). Остаток условного предложения проверяет значение переменной execbutton; если RPN активна, оно равно Index. Если калькулятор находится в режиме алгебраической записи, значение предыдущей введенной функции - представляемое lastbutton - присваивается execbutton, a Index замешает значение lastbutton для того, чтобы быть извлеченным, когда будет нажата следующая арифметическая кнопка. Алгебраическая система помнит последнюю функцию и исполняет ее, когда вводится следующая.

Когда кнопка Enter показана, она в действительности закрывает кнопку "плюс". Когда пользователь выбирает, режим алгебраической записи, свойство .Width кнопки Enter уменьшается вдвое и появляется кнопка "плюс". Одна и та же процедура Sub EnterPlus() совершает процесс сложения в обеих системах записи. Новая процедура, Sub Equals(), обрабатывает кнопку "равно", когда активна алгебраическая запись:

Код:
Sub Equals()
 assess_readout
 FormulaHistory.Caption = FormulaHistory.Caption + Readout.Caption
 ready = 0
 lastbutton = -1
 execbutton = -1
 point_lock = False
 function_on = False
 tally
 readout_value = 0
 combine_value = 0
 End-Sub

В смысле математических действий процедура Sub Equals() не предпринимает ровным счетом ничего. Она выполняет необходимые действия для сброса калькулятора, подготавливая его для следующей операции. Функции выполняющие вычисления уже отработали ко времени, когда запускается Sub Equals().

Помните процедуру-заглушку Sub assess_readout() из первых версий Expressor, которая была оставлена нами для следования хорошему стилю программирования? В этой версии мы, наконец, используем преимущества такого стиля. Sub assess_readout() теперь заведует процессом преобразования значений из и в десятичную систему:

Код:
Sub assess_readout()
 If function_on = True Then
 Select Case NumBase(1).Value
 Case False
 Select Case True
 Case NumBase(0) = True
 convBase = 16
 Case NumBase(1) = True
 convBase = 10
 Case NumBase(2) = True
 convBase = 8
 Case NumBase(3) = True
 convBase = 2
 End Select
 send$ = Readout.Caption
 readout_value = ConvertTo10(send$, convBase)
 Case True
 readout_value = Val(Readout.Caption)
 If CurRound.Value = True Then
 Readout.Caption =
 Format$(readout_value, cformat$)
 Else
 Readout.Caption =
 LTrim$(Str$(readout_value))
 End If
 End Select
 End If
 End Sub

Любые значения, передаваемые арифметическим функциям и представленные не в десятичной системе счисления, приводятся в десятичный вид функцией ConvertTo10(), которая получает управление, когда точка опции установки системы счисления указывает на не-десятичную систему. Если значение десятичное, оно освобождается от ведущих пробелов и форматируется для отображения в финансовом формате, если пользователь отметил крестиком check box "$.00". Время от времени для переформатирования значения в окошке дисплея калькулятора вызывается процедура Sub tally():

Код:
Sub tally()
 If CurRound.Value = True Then
 Readout.Caption = Format$(readout_value, cformat$)
 Else
 Readout.Caption = Str$(readout_value)
 End If
 End Sub

Подобно всем остальным наборам опций, опции установки системы счисления представляют собой массив элементов управления.

Код:
Sub NumBase_Click(Index As Integer, Value As Integer)
 display$ = Readout.Caption
 Select Case Index
 Case 0
 convBase = 16
 For ndx = 2 To 15
 Button(ndx).Enabled = True
 Next ndx
 Case 1
 convBase = 10
 For ndx = 2 To 9
 Button(ndx).Enabled = True
 Next ndx
 For ndx = 10 To 15
 Button(ndx).Enabled = False
 Next ndx
 Case 2
 convBase = 8
 For ndx = 2 To 7
 Button(ndx).Enabled = True
 Next ndx
 For ndx = 8 To 15
 Button(ndx).Enabled = False
 Next ndx
 Case 3
 convBase = 2
 For ndx = 2 To 15
 Button(ndx).Enabled = False
 Next ndx
 End Select
 Select Case True
 Case oldbase = 1 And Index <> 1
 Readout.Caption = ConvertFrom10$(display$, convBase)
 Case oldbase <> 1 And Index = 1
 newvalue = ConvertTo10(display$, oldbase)
 Readout.Caption = Str$(newvalue)
 Case oldbase <> 1 And Index <> 1
 newvalue = ConvertTo10(display$, oldbase)
 send$ = Str$(newvalue)
 Readout.Caption = ConvertFrom10$(send$, convBase)
 End Select
 oldbase = convBase
 Button(17).SetFocus
 End Sub

Первое предложение Select Case в Sub NumBase_Click() отключает все цифровые кнопки, которые не поддерживаются текущей системой счисления. Последнее предложение Select Case сравнивает предыдущую систему счисления oldbase с текущей, обозначенной свойством .Index массива управления. Если старая была десятичной, а текущая нет, вызывается функция ConvertFrom10$(). Если старая система была не десятичной, а текущая является таковой, вызывается функция ConvertTo10(). Обратите внимание на то, что функция ConvertFrom10$() возвращает строку, поскольку результат может содержать символы A-F, которые интерпретатор не распознает как число, хотя калькулятор и моделирует шестнадцатиричную арифметику. И наконец, если предыдущая система была не десятичной и текущая система не является десятичной, значение результата вычисления сначала преобразовывается в десятичное и затем еще раз преобразовывается в новую систему счисления, поскольку процедуры, производящие математические вычисления, требуют передачи им десятичных значений. Если старая система и текущая система-десятичные, никакого преобразования не требуется.
Gudleifr
Gudleifr
Admin

Сообщения : 3211
Дата регистрации : 2017-03-29

Вернуться к началу Перейти вниз

О самоуспокоенности неудачников (приложение к заметкам о простых играх) Empty Re: О самоуспокоенности неудачников (приложение к заметкам о простых играх)

Сообщение автор Gudleifr Сб Сен 02, 2017 12:12 pm

D.F.SCOTT, КАЛЬКУЛЯТОР EXPRESSOR III. ПРОДОЛЖЕНИЕ

ПРОГРАММИРОВАНИЕ КЛАВИАТУРЫ ДЛЯ ДУБЛИРОВАНИЯ КОМАНДНЫХ КНОПОК. НАЗНАЧЕНИЕ КЛАВИШ-ПСЕВДОНИМОВ КОМАНДНЫМ КНОПКАМ
В конце работы мы сделали Expressor III способным на принятие ввода от клавиатуры. В Visual Basic этот процесс не так прост, как можно подумать; командные кнопки не имеют свойств, поддерживающих значение "клавиша-псевдоним". Нажатие на клавишу рассматривается как событие _KeyPress; один из графических объектов в форме Panel должен быть назначен на роль "приемника" этого события. Форма Panel сама является графическим объектом и кажется наиболее вероятным кандидатом на эту роль; но в виде исключения из обычного для Visual Basic порядка вешей, объект форма не может быть приемником события _KeyPress.

В результате на роль приемника должна быть назначена одна из кнопок на панели, но Visual Basic снова препятствует этому процессу. Чтобы кнопка могла получить событие, она должна держать фокус - другими словами, вокруг этой кнопки должен быть черный ореол, когда на клавиатуре нажимается клавиша-псевдоним. Когда для нажатия командной кнопки используется мышь, такое нажатие передает фокус этой кнопке; если фокус держит одна кнопка, мышь передаст его любой другой кнопке, на которую ею нажимают.

Поэтому процедуры обработки ввода пользователя должны всегда возвращать фокус кнопке, которая владела им до их исполнения, что совершается командой Button(17).SetFocus. Очевидно, что "приемником" события _KeyPress может быть любая кнопка массива Button(), и мы могли остановиться на любой; но проблема состоит еше и в том, что это должна быть кнопка со свойством .Default, имеющим значение TRUE. Кнопка Enter лучший кандидат на "постоянного" владельца фокуса, если вы позволяете пользователю, эксплуатировать клавиатуру для управления калькулятором. Если фокусу позволено перемешаться по элементам массива Button(), цель клавиши ENTER состоит в том, чтобы активизировать любую из кнопок, держашую в настоящий момент фокус, потому что кнопка, держащая фокус, становится кнопкой по умолчанию и может быть нажата при помощи клавиши ENTER клавиатуры.

Так что имеет прямой смысл использовать как приемник ввода с клавиатуры кнопку Enter панели калькулятора (которая исполняет также функцию кнопки "равно", когда Expressor находится в режиме алгебраической записи); по крайней мере случайное нажатие ENTER на клавиатуре будет означать как максимум нажатие кнопки Enter на панели калькулятора. Вот процедура, обрабатывающая событие _KeyPress кнопки #17:

Код:
Sub Button_KeyPress(Index As Integer, KeyAscii As Integer)
 Dim ndx As Integer
 Select Case True
 Case KeyAscii > 47 And KeyAscii < 58
 ndx = KeyAscii - 48
 Case KeyAscii = 46
 ndx = 16
 Case (KeyAscii > 64 And KeyAscii < 71) And NumBase(0) = True
 ndx = 75 - KeyAscii
 Case KeyAscii = 45
 ndx = 19
 Case KeyAscii = 42
 ndx = 20
 Case KeyAscii = 47
 ndx = 21
 Case KeyAscii = 43 And rpnalg = 1
 ndx = 18
 Case KeyAscii = 13 And rpnalg = 1
 ndx = 18
 Case KeyAscii = 13 And rpnalg = 0
 ndx = 17
 End Select
 Button_Click ndx
 End Sub

Это процедура-переключатель, которая вызывает еше одну процедуру-переключатель. После того, как процедура определяет значение ASCII кода нажатой клавиши, она присваивает индекс соответствующей кнопки-псевдонима переменной ndx, и управление переходит к Sub Button_CIick(), которая получает в параметре ndx то же значение, которое она получила бы при нажатии кнопки на панели мышью.
***

ПРОЦЕСС ВВОДА ПРОГРАММЫ В КАЛЬКУЛЯТОР
Вот как работает часть, отвечающая за "программируемость" калькулятора: пользователь задает параметры, впечатывая их описания в окошки описания параметров, которые ведут себя как стандартные text box, доступные для редактирования. Заголовок новой формулы впечатывается в список формул, который является теперь доступным для редактирования combo box. Значения констант и диапазонов вводятся в окошки параметров и диапазонов. Затем пользователь нажимает кнопку Learn и эксплуатирует Expressor так, как он делает это обычно. Каждая нажатая значащая кнопка запоминается, пока пользователь снова не нажмет кнопку, которая теперь читается "Save". Вот как это получается:

Код:
Sub Learn_Click()
 If learnmode = False Then
 learnmode = True
 formulanow = formulanow + 1
 formulaplace = 1
 Learn.Caption = "Save"
 Forget.Caption = "Abort"
 Else
 Learn.Caption = "Learn"
 rec = formulanow - partitn
 fn(rec).Title = CalcList.Text
 fn(rec).Length = formulaplace
 fn(rec).Setting = rpnalg
 CalcList.AddItem CalcList.Text
 For sv = 0 To 6
 fn(rec).Variable(sv) = Val(Param(sv).Text)
 fn(rec).VariableName(sv) = ParamText(sv).Text
 Select Case AxisSelect(sv).Caption
 Case "x >>"
 fn(rec).VariableAxis(sv) = 1
 Case "y >>"
 fn(rec).VariableAxis(sv) = 2
 End Select
 fn(rec).VariableTo(sv) = Val(ParamTo(sv).Text)
 Next sv
 For sv = 0 To formulaplace
 fn(rec).Pattern(sv) = learnfmla(sv)
 Next sv
 Open "exprfmla.dat" For Random As #2 Len = 1356
 Put #2, rec + 1, fn(rec)
 Close #2
 End If
 Button(17).SetFocus
 End Sub

Кнопка Learn или, точнее сказать, кнопка, которая читается "Learn" по умолчанию, выполняет функции и запоминания, и сохранения запомненной формулы. Перевести калькулятор в режим запоминания несложно; место для формулы выделяется переменной formulanow, и индекс запоминаемой кнопки, formulaplace, устанавливается в 1. После смены значения флаговой переменной learnmode на TRUE, Sub Button_Click() заботится о размещении индекса каждой новой нажатой кнопки в массиве learnfmla() и приращении formulaplace. Когда кнопка находится в режиме "Save", процедура загружает в текущую составную переменную fn() заголовки формулы, длину записи, режим записи выражений (RPN или алгебраический), назначения осей и наконец непосредственно формулу.

Выполнение запомненной формулы - простое дело. Кнопка Solve заменила кнопку Apply Formula; вот процедура соответствующего события_Click:

Код:
Sub Solve_Click()
 frmula = CalcList.ListIndex + 1
 If frmula <= partitn Then
 For in = 0 To 6
 p(in) = Val(Param(in).Text)
 Next in
 solution = calculate(frmula)
 Else
 solution = recite(frmula)
 End If
 Readout.Caption = Str$(solution)
 ready = 0
 Button(17).SetFocus
 End Sub

Помните, переменная partitn делит список формул на аппаратную часть и запомненную часть. Если выбранная формула - одна из запомненных формул, вызывается новая функция, recite():

Код:
Function recite(frmula) As Double
 Open "exprfmla.dat" For Random As #2 Len = 1356
 num% = frmula - partitn
 Get #2, num% + 1, fn(num%)
 ln% = fn(num%).Length
 rpnalg = fn(num%).Setting
 Notation(rpnalg).Value = True
 For rec% = 1 To ln%
 Button_Click fn(num%).Pattern(rec%)
 Next rec%
 recite = Val(Readout.Caption)
 Close #2
 End Function

Выбранная формула восстанавливается из файла прямого доступа EXPRFMLA.DAT; вычитание значения partitn из индекса выбранной формулы дает число на единицу меньшее, чем номер нужной записи; вот почему в операторе Get #2 имеется "+1". Калькулятор устанавливается в требуемое состояние, и программа исполняется циклом из одной инструкции точно так, как если она была записаны на перфоленте. Цикл извлекает индексы кнопок, запомненных программой, и передает их как параметры Sub Button_Click(). Функция recite() возвращает значение, которое установится на дисплее калькулятора после завершения работы программы.

Процедура кнопки Forget имеет подобное же строение:

Код:
Sub Forget_Click()
 If learnmode = False Then
 If CalcList.ListIndex + 1 < partitn Then
 MsgBox "This particular formula is hard-coded into the
 program and cannot be deleted",
 48, "Expressor 3 Message"
 Exit Sub
 Else
 resp% = MsgBox("Are you sure you wish to delete this
 formula?", 36, "Expressor 3 Message")
 If resp% = 7 Then Exit Sub
 For shift = formulanow + 1 To nofms
 fn(shift - 1).Title = fn(shift).Title
 fn(shift - 1).Setting = fn(shift).Setting
 fn(shift - 1).Length = fn(shift).Length
 For pat% = 0 To 100
 fn(shift - 1).Pattern(pat%) =
 fn(shift).Pattern(pat%)
 Next pat%
 For slot% = 0 To 6
 fn(shift - 1).Variable(slot%) =
 fn(shift).Variable(slot%)
 fn(shift - 1).VariableName(slot%) =
 fn(shift).VariableName$(slot%)
 fn(shift - 1).VariableAxis(slot%) =
 fn(shift).Variable(slot%)
 fn(shift - 1).VariableTo(slot%) =
 fn(shift).VariableTo(slot%)
 label$(shift - 1, slot%) =
 label$(shift, slot%)
 Next slot%
 Next shift
 nofms = nofms - 1
 Open "exprfmla.dat" For Random As #2 Len = 1356
 Put #2, 1, nofms
 For deposit = 1 To nofms
 Put #2, deposit + 1, fn(deposit)
 Next deposit
 Close #2
 End If
 Else
 formulanow = formulanow - 1
 formulaplace = 0
 Learn.Caption = "Learn"
 Forget.Caption = "Forget"
 learnmode = False
 End If
 End Sub

Процедура заставляет массив "забыть" один из своих элементов. Цикл проходит с удаляемого элемента до конца массива, переменная этого цикла - shift. Элемент массива сразу после shift принимает значение элемента, на который указывает shift, такое дублирование и смещение продолжается до конца массива. Число формул уменьшается на единицу, и файл данных, начиная с точки стирания, переписывается заново.
***

ПОДГОТОВКА ДЛЯ ВЫЧИСЛЕНИЙ В ДИАПАЗОНАХ ПО ДВУМ ОСЯМ
Как вы еше можете помнить, в Expressor II пользователь для того, чтобы указать, которое из окошек параметров должно содержать значение диапазона для построения диаграммы, нажимал кнопку справа от окошка параметра. Заголовок этой кнопки менялся на "x >>", показывая, что напротив нее теперь находится значение максимума Х-оси.

Для систем с двумя осями - типа электронной таблицы - тот же самый набор кнопок должен быть способен отображать и "y >>", для индикации параметра, представляющего диапазон значений по Y-оси. Вот измененная процедура события этого специфического массива управления:

Код:
Sub AxisSelect_Click(Index As Integer)
 If ParamTo(Index).Text = "0" Or ParamTo(Index).Text = "" Then
 ParamTo(Index).Text = Readout.Caption
 End If
 ready = 0
 Select Case AxisSelect(Index).Caption
 Case "---"
 If xslot <> Index Then
 AxisSelect(Index).Caption = "x >>"
 xslot = Index
 For correct = 0 To 6
 If correct <> Index And
 AxisSelect(correct).Caption =
 "x >>" Then
 AxisSelect(correct).Caption = "---"
 End If
 Next correct
 Exit Sub
 End If
 If xslot = Index Then
 AxisSelect(Index).Caption = "y >>"
 yslot = Index
 xslot = -1
 For correct = 0 To 6
 If correct <> Index And
 AxisSelect(correct).Caption =
 "y >>" Then
 AxisSelect(correct).Caption = "---"
 End If
 Next correct
 Exit Sub
 End If
 Case "x >>"
 If yslot <> Index Then
 AxisSelect(Index).Caption = "y >>"
 yslot = Index
 xslot = -1
 For correct = 0 To 6
 If correct <> Index And
 AxisSelect(correct).Caption =
 "y >>" Then
 AxisSelect(correct).Caption =
 "x >>"
 For corr2 = 0 To 6
 If corr2 <> correct And
 AxisSelect(corr2).Caption
 = "x >>" Then
 AxisSelect(corr2).Caption
 = "---"
 End If
 Next corr2
 xslot = correct
 End If
 Next correct
 Exit Sub
 End If
 Case "y >>"
 If yslot = Index Then
 AxisSelect(Index).Caption = "---"
 yslot = -1
 End If
 End Select
 End Sub

Описание этой процедуры похоже на старую игру "Да и Нет не говорите...". Если пользователь нажмет кнопку, отмеченную "---", эта кнопка становится отмеченной "x >>", а любая другая кнопка, отмеченная в настоящий момент "х >>", становится отмеченной "---". Если пользователь нажмет кнопку, отмеченную "х >>", она становится отмеченной "y >>", но если какая-нибудь другая кнопка уже отмечена "y >>", эта кнопка, отмеченная "y >>", становится отмеченной "x >>", но если в это время существует другая кнопка, отмеченная "x >>", она становится отмеченной "---". Вы еше с нами? Если пользователь нажмет кнопку, отмеченную "y >>", она становится отмеченной "--". И так далее. В любом случае эта процедура гарантирует, что в любой момент будет иметься только одна кнопка, отмечающая диапазон по X-оси, и только одна кнопка, отмечающая диапазон по Y-оси, и не будет не иметься кнопки, отмечающей диапазон по Y-оси, если нет кнопки, отмечающей диапазон по X-оси.

И наконец, форма таблицы. Мы бы все еще писали эту часть программы и никогда бы не закончили книгу, если бы не существовало пользовательского элемента управления CRID.VBX.

Стиль этой формы в значительной степени заимствован у формы диаграммы.
***

EXRSOR3.BAS
Процедура, управляющая таблицей Expressor III, структурно очень похожа на процедуру построения диаграмм: в ней присутствует вложенный цикл для получения решений формулы для диапазона по Y-оси. Для рассмотрения разобьем эту процедуру на части:
***

ЧАСТЬ 1:

Код:
Sub CreateExTable(xmin As Single, xmax As Single, ymin As Single,
 ymax As Single, xslot As Integer, yslot As Integer)
 ExTable.Show
 nocolumns = 12
 norows = 22
 ExTable.NoCols.Caption = LTrim$(Str$(nocolumns)) + " Columns"
 ExTable.NoRos.Caption = LTrim$(Str$(norows)) + " Rows"
 For dm = 0 To nocolumns
 maxlen(dm) = 600
 Next dm
 ExTable.WriteArea.Cols = nocolumns + 1
 ExTable.WriteArea.Rows = norows + 1

Здесь присваиваются значения свойствам .Cols и .Rows. Массив maxlen() хранит ширину самой широкой в столбце строки для каждого столбца, чтобы процедура мосла позже установить максимальную ширину этого столбца таблицы, равной ширине самой широкой в нем строки. Поскольку эта процедура находится вне модуля формы таблицы, для ссылок на свойства элемента управления grid употребляется полный объектный синтаксис.
***

ЧАСТЬ 2:

Код:
title$ = Panel.CalcList.Text
 ExTable.ExTableTitle.FontSize = 20
 Try2:
 If ExTable.ExTableTitle.TextWidth(title$) >
 ExTable.ExTableTitle.Width Then
 ExTable.ExTableTitle.FontSize =
 ExTable.ExTableTitle.FontSize - 1
 GoTo Try2
 End If
 ExTable.ExTableTitle.Print title$
 xaxis$ = Panel.ParamText(xslot).Text
 ExTable.XAxisName.FontSize = 16
 Try3:
 If ExTable.XAxisName.TextWidth(xaxis$) >
 ExTable.XAxisName.Width Then
 ExTable.XAxisName.FontSize =
 ExTable.XAxisName.FontSize - 1
 GoTo Try3
 End If
 ExTable.XAxisName.Print xaxis$
 yaxis$ = UCase$(Panel.ParamText(yslot).Text)
 For prnty = 1 To Len(yaxis$)
 pn$ = Mid$(yaxis$, prnty, 1)
 ExTable.YAxisName.Print pn$
 Next prnty

Эта часть кода печатает заголовки таблицы и имена осей. Если вы читали эту книгу с начала, вы, вероятно, уже знакомы с процессом установки размера шрифта для picture box, в котором требуемый размер шрифта определяется в цикле If - GoTo, в котором свойство TextWidth() объекта сравнивается с его же свойством .Width, в случае неуспеха сравнения размер шрифта - свойство объекта .Fontsize - уменьшается на 1 пункт, и цикл снова производит сравнение; операция повторяется до тех пор, пока размер шрифта не станет удовлетворять условию.

Этот процесс исполняется для заголовка таблицы и для заголовка X-оси. Размеры шрифта для заголовка Y-оси не подбираются; заголовок Y-оси располагается по вертикали, поэтому этот заголовок всегда помешается в узкой вертикальной полосе вдоль левой стороны таблицы. Visual Basic не имеет никаких функций для печати текста иначе, чем по горизонтали, стандартными средствами Visual Basic невозможно вывести поперечный текст. Windows API содержит функции, позволяющие выводить поперечный текст, но по этому воробью стрелять из пушки необязательно.
***

ЧАСТЬ 3

Код:
frmula = Panel.CalcList.ListIndex + 1
 intrvalx = (xmax - xmin) / nocolumns
 intrvaly = (ymax - ymin) / norows
 For in = 0 To 6
 p(in) = Val(Panel.Param(in).Text)
 Next in
 If xmin = xmax Or ymin = ymax Then
 Panel.Show
 Exit Sub
 End If
 ExTable.WriteArea.Row = 0
 xl = xmin
 For xaxlabl = 1 To nocolumns
 ExTable.WriteArea.Col = xaxlabl
 s$ = Str$(xl)
 If Len(s$) * 100 > maxlen(xaxlabl) Then
 maxlen(xaxlabl) = Len(s$) * 100
 ExTable.WriteArea.ColWidth(xaxlabl) =
 maxlen(xaxlabl)
 End If
 ExTable.WriteArea.Text = s$
 xl = xl + intrvalx
 Next xaxlabl
 ExTable.WriteArea.Col = 0
 yl = ymin
 For yaxlabl = 1 To norows
 ExTable.WriteArea.Row = yaxlabl
 s$ = Str$(yl)
 If Len(s$) * 100 > maxlen(0) Then
 maxlen(0) = Len(s$) * 100
 ExTable.WriteArea.ColWidth(0) = maxlen(0)
 End If
 ExTable.WriteArea.Text = s$
 yl = yl + intrvaly
 Next yaxlabl

Каждый столбец содержит значения результатов вычислений по формуле для входных значений диапазона оси X в равных прирашениях увеличения (или уменьшения) значений от значения из левого банка параметров к значению из правого. Интервалы равных прирашений значений параметров формулы по осям X и Y - intrvalx и intrvaly вычисляются делением разницы соответствующих значений из левого и правого банков параметров на число столбцов и строк соответственно. В отличие от модуля построения диаграмм число строк и столбцов таблицы может корректироваться пользователем на лету. Предложения циклов For xaxlabl и For yaxlabl выясняют, достаточно ли ширины текущей ячейки для того, чтобы вместить текст. Поскольку содержимое ячеек элемента управления grid ведет себя подобно тексту в text box, функцию-свойство .TextWidth() нельзя использовать для определения длины текстовой строки в твипах; вместо этого используется условное значение length_of_character * 100.
***

ЧАСТЬ 4

Код:
ExTable.WriteArea.Row = 0
 ExTable.WriteArea.Col = 1
 For cvaly = ymin To ymax Step intrvaly
 p(yslot) = cvaly
 If ExTable.WriteArea.Row < ExTable.WriteArea.Rows - 1 Then
 ExTable.WriteArea.Row = ExTable.WriteArea.Row + 1
 End If
 For cvalx = xmin To xmax Step intrvalx
 p(xslot) = cvalx
 s$ = LTrim$(Str$(calculate(frmula)))
 If Len(s$) * 100 > maxlen(ExTable.WriteArea.Col) Then
 maxlen(ExTable.WriteArea.Col) = Len(s$) * 100
 ExTable.WriteArea.ColWidth(ExTable.WriteArea.Col)
 = maxlen(ExTable.WriteArea.Col)
 End If
 ExTable.WriteArea.Text = s$
 If ExTable.WriteArea.Col <
 ExTable.WriteArea.Cols - 1 Then
 ExTable.WriteArea.Col =
 ExTable.WriteArea.Col + 1
 End If
 Next cvalx
 ExTable.WriteArea.Col = 1
 Next cvaly
 End Sub

Последний раздел содержит вложенный цикл, который построчно заполняет ячейки таблицы решениями формулы. Помните, что свойства .Row и .Rows, и .Col и .Cols имеют различное назначение: .Row и .Col адресуют текущую строку и текущий столбец, a .Rows и .Cols устанавливают общее число строк и столбцов в таблице соответственно. Читателя может путать способ присвоения текста ячейке таблицы; текстовое значение присваивается свойству .Text объекта grid, ссылка на адрес ячейки, получающей текст, в этой операции отсутствует. Дело здесь в том, что ячейкой-адресатом является ячейка, на которую указывают свойства объекта grid .Row и .Col. Эта команда представляет собой небольшое нарушение правил объектно-ориентированного синтаксиса, но поскольку Visual Basic не истинно объектно-ориентированный язык, мы сомневаемся, чтобы кто-нибудь наказал за это его или нас.

Предложение вложенного цикла не выполняет никаких вычислений по формуле; но если вы тщательно изучите эту часть процедуры, то, вероятно, заметите команду, которая вызывает исполнение процедуры вычисления: s$=LTrim$(str$(calculate(frmula).

Глубоко запрятанный вызов при каждом проходе внутреннего цикла возвращает в s$ строковый эквивалент значения решения формулы. Длина s$ приблизительно проверяется на предмет помещения в ширину текущего столбца. Ширина столбца корректируется изменением свойства .ColWidth().
Gudleifr
Gudleifr
Admin

Сообщения : 3211
Дата регистрации : 2017-03-29

Вернуться к началу Перейти вниз

О самоуспокоенности неудачников (приложение к заметкам о простых играх) Empty Re: О самоуспокоенности неудачников (приложение к заметкам о простых играх)

Сообщение автор Gudleifr Сб Сен 02, 2017 12:14 pm

Этим огромным куском я пытаюсь подытожить BASIC-путь построения игры. Когда-то, эта книга оказала на меня столь огромное влияние, что чуть было не увлекся визуальным программированием и даже создал пару "проблемно-ориентированных модулей". Сейчас они кажутся жутко избыточными. Ведь, источник этой избыточности показан в процитированном отрывке со всей беспощадностью.
Посмотрите, сначала мы взяли один язык - описания проектов (в своей чисто визуально-описательной части достаточно похожий на мои "деревья"), затем, на нем написан язык интерфейса калькулятора (обычным, уже рассмотренным в первой части, BASIC-способом); и, наконец, поверх них положен еще третий язык - программирования калькулятора (тут BASIC уже начинает трещать по швам). Связаны все три языка исключительно общим графическим интерфейсом - никакого смыслового наследования не существует в принципе.
С чем, собственно, попытаюсь побороться в оставшейся части этих заметок.
(А, все-таки, не написать ли такой калькулятор для "Лунолета"?)
Gudleifr
Gudleifr
Admin

Сообщения : 3211
Дата регистрации : 2017-03-29

Вернуться к началу Перейти вниз

О самоуспокоенности неудачников (приложение к заметкам о простых играх) Empty Re: О самоуспокоенности неудачников (приложение к заметкам о простых играх)

Сообщение автор Gudleifr Пн Дек 11, 2017 4:58 pm

До кучи: 8 глава UNIX Кернигана и Пайка (О самоуспокоенности неудачников (приложение к заметкам о простых играх) Leaf10PDF, 1.77МбО самоуспокоенности неудачников (приложение к заметкам о простых играх) Leaf10) - как создать калькулятор тамошними средствами. Можно видеть, что многие FORTH-понятия рождаются в процессе сами по себе. Более того, в UNIX изначально заложены мощные "FORTH-средства", делающие написание специальной FORTH-системы под него избыточным.
Gudleifr
Gudleifr
Admin

Сообщения : 3211
Дата регистрации : 2017-03-29

Вернуться к началу Перейти вниз

Вернуться к началу

- Похожие темы

 
Права доступа к этому форуму:
Вы не можете отвечать на сообщения