содержание   вперед   назад

5. Второй класс

Начинаем писать второй класс MainForm. Его мы запишем в тот же файл, потому что первый класс фактически просто задал имя программы, а сам внешний вид программы не написан. Первый кусок кода определяет внутренние переменные класса. Если переменные не общие (public), то их принадлежность программе класса можно не афишировать. Но все используемые переменные обязательно нужно декларировать с указанием их типа. Я буду иногда объекты класса называть переменными типа -- это не совсем правильная терминология, но она более привычна для тех, кто раньше с классами не работал. Итак начало.

Разберем, что означает написанное. Слово extends (развивает) означает, что наш класс является потомком класса JFrame. Кроме того, он наследует все методы абстрактного класса ActionListener. Это фактически не класс, а интерфейс как в фортране или С -- он только декларирует список методов. В данном случае наш интерфейс, как следует из его названия, определяет реакцию на действия пользователя, например, выбор иконки в меню.

Далее, мы декларировали статический объект класса JDesktopPane. Этот класс определяет внешнюю панель нашего окна, в которую мы будем помещать внутренние окна. Внутренние окна, также как и диалоги, не имеют своих иконок -- они закрываются и открываются вместе с главным окном. Это удобно, так как окна нашей программы на экране не будут путаться с окнами других программ. Почему надо объявить объект статическим. Потому что он у нас фактически один, но нам может понадобиться обращаться к нему из других классов. Мы будем делать это таким образом -- открывать новый объект класса MainForm и возвращать из него только deskt. Так как он статический -- мы не промахнемся и попадем точно в наше окно. Это станет ясно позднее. Пока просто запомните эти слова. Все остальные типы переменных нам знакомы кроме JMenu и JMenuItem. Но они очевидны и мы ими скоро займемся. Хочу отметить, что все объекты только продекларированы, в то время как массив строк actt уже определен более точно, так как указан его размер.

Начинаем определять методы. Первый метод -- это конструктор. Он обязан быть public и иметь то же имя, что и класс. Он автоматически исполняется при задании объекта с помощью слова new.

Как я уже предупреждал, мы задаем пустой конструктор. Так надо, иначе при получении deskt в других классах мы будем делать лишнюю работу. Следующий метод make() мы уже использовали. Теперь мы будем его определять

Первая строка ясная. Метод public так как мы его использовали в другом классе. Обработка ошибок та же. Следующие строчки задают иконку в левый верхний угол нашего окна. Предполагается, что иконка нарисована в файле с названием icon.png. При работе программы этот файл должен присутствовать в папке программы, иначе будет ошибка. Класс BufferedImage очень важный и интересный класс. Его объект bi содержит нашу картинку в развернутом виде матрицы пикселей. При этом статический метод read() класса javax.imageio.ImageIO прочитывает файл с нашим именем и автоматически декодирует картинку в матрицу пикселей. Чтобы не было конфликтов с методами read() других классов здесь надо полностью указывать весь путь к классу. Далее, класс JFrame имеет функцию (метод) setIconImage(), которая устанавливает эту картинку в качестве иконки окна. Очень важно знать какие типы (классы) какая функция имеет в виде аргументов, иначе получится ошибка и компилятор код забракует. Если указанный код не поставить, то ничего страшного не произойдет. Виртуальная машина сама поставит иконку по умолчанию в виде чашки кофе.

Далее

Эта функция класса JFrame устанавливает имя окна (title). Строку с именем мы уже определили раньше, а теперь используем, и она нам еще не раз понадобится. Двигаемся дальше, пишем

Этот довольно большой кусок текста определяет размеры окна и его положение на экране. Это можно делать разными способами. Рассмотрим сначала то, что написано. Первая строка определяет размеры экрана дисплея в пикселах. Здесь указаны два метода, последовательно выполняемые слева направо. Первый статический метод класса Toolkit возвращает новый объект класса Toolkit, а второй метод уже принадлежит этому объекту. Но нам это даже не обязательно разбирать. Размеры экрана дисплея возвращаются именно такой строкой и именно в объект класса Dimension. Затем мы вынимаем ширину и высоту этого объекта в пикселах в целые переменные km и lm. Потом считываем четыре числа из второй строки нашего файла с именем First.ini. Предполагается, что эти числа записаны с разделителем в виде символа вертикальной черты '|' (ASCII код 124). Сначала мы конкретизируем объект ls класса String, заполняя его второй строкой из нашего файла. Затем, используя статический метод split() нашего класса MyPro, разделяем одну строку в массив строк, тем самым уточняя объект act. В нашем случае индексы этого массива равны 0,1,2,3.

Далее мы видим, что переменные типа целый тоже имеют свой класс Integer и у этого класса есть статический метод parseInt(), который превращает строку в целое число, которое последняя описывает. Применяя этот метод 4 раза получаем нужные нам 4 числа i,j,k,l. Пусть первые два числа задают сдвиг центра нашего окна из центра экрана, а последние два числа задают размеры окна (все в пикселах). Тогда сдвиг центра нужно пересчитать в сдвиг левого верхнего угла, что и делается. Окончательно метод setBounds() класса JFrame задает размеры и положение окна на экране. Отмечу, что вместо него также можно использовать методы setSize(k,l) и setLocation(i,j) для раздельного задания размеров и положения окна. Если использовать метод setLocationRelativeTo(null), то окно автоматически устанавливается в центре экрана. То есть если лень много писать, то весь указанный выше код можно заменить на последнюю функцию и окно всегда будет в центре экрана.

Теперь начинаем наполнять окно содержанием. Фактически мы здесь только установим полоску меню. Для этого запишем следующий код

Здесь мы наконец определили нашу главную панель deskt и затем поместили ее в окно методом setContentPane(). Затем мы установили новое свойство нашего окна. А именно, как его перерисовывать при перемещении по экрану. Мы установили, что при перемещении двигается только контур окна и только в конце перерисовывается все окно. Так быстрее и проще работает компьютер. Наконец, функцией setJMenuBar() мы поставили полоску меню в наше окно, но аргументом является не сама эта полоска -- объект класса JMenuBar, а функция (метод), которая эту полоску возвращает. Это стандартная практика в Java. Эту последнюю функцию нам как раз предстоит определить. А пока нас можно поздравить с завершением первой функции make().

Итак начнем определять меню нашего окна. Опять вспомним, что все параметры окна мы считываем из файла "First.ini". Пусть в третьей строке этого файла будет набито число разных разделов меню. А дальше на каждый раздел будет по одной строке, в которой сначала будет набито имя этого раздела, затем через пробел (или несколько пробелов) его код, затем через пробел будут заданы имена всех подразделов данного раздела, разделенные символом '|', затем через пробел будет задана последовательность мнемонических кодов клавиш (снова разделенные символом '|') и затем через пробел последовательность кодов клавиш ускорителей (снова с '|'). Чтобы было понятнее покажу как это должно выглядеть

Мнемонический код клавиши раздела меню у нас будет открывать раздел через дополнительную клавишу [Alt]. Мнемонический код подраздела открывает подраздел прямо (без [Alt]), но только после того, как раздел уже открыт. И, наконец, ускоритель подраздела меню сразу открывает подраздел и тоже набирается через [Alt]. Поэтому все коды разделов меню и ускорителей подразделов должны быть уникальными и ни разу не повторяться. В Java все константы кодов клавиш имеют длинные имена, заканчивающиеся символами клавиш. Но нам при чтении данных из файла проще задавать числа. Таблицу кодов клавиш в виде чисел можно получить прямо из Java кода соответствующего класса. Ниже я ее привожу в более или менее полном объеме

Клавиши, которые предпочтительно использовать

Клавиши, которые лучше не использовать, но если очень надо, то можно

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

Здесь мы определили переменные и задали объект menub, который мы будем возвращать. Тип функции protected просто означает, что в процессе работы программы результат, выдаваемый этой функцией, нельзя изменять ни при каких условиях

прочитали 3-ю строку файла и вставили ее в аргумент конструктора нового объекта класса StringTokenizer. Объекты этого класса умеют выделять из строки куски текста без пробелов используя пробелы как разделители. С другой стороны, метод split() класса String просто разделяет строку на систему строк, используя регулярные выражения, например, признак конца строки, в то время как для объектов класса String символы разделения строк, используемые в обычных редакторах, ничего не значат. В один объект можно запихнуть хоть целую книгу. Интересно, что вместо написания собственной процедуры разделения строк по указанному символу можно было бы использовать метод split() класса String. Но, как показала практика, не все символы видимо являются регулярными выражениями -- компилятор ошибок не видит, а код работает неправильно. Поэтому пришлось вместо метода split()написать свою процедуру разделения строк. Далее,

определили число разделов меню. Метод nextToken() как раз возвращает очередную порцию текста без пробелов. Далее,

В цикле по разделам меню сначала считываем из файла очередную строку, вводим ее в разделитель текста и берем первый кусок. Это имя раздела. Берем второй кусок и преобразуем в число. Определяем объект класса JMenu с данным именем и данным мнемоническим кодом.

Берем следующий кусок и расщепляем его в массив строк name. Это объект класса String[]. Он описан в самом начале класса.

Поле (переменная) length у любого массива возвращает его размер. Поэтому nsb равно числу элементов массива name. Используя это число определяем размер целых массивов mne и acc, то есть фактически определяем массивы.

Здесь мы два раза переопределили массив act и вынули из него целые числа -- коды клавиш. Но нам еще необходимо задать уникальное имя команды для каждого подраздела меню. Проще всего автоматически сгенерировать эти имена, используя порядковый номер подраздела, что и было сделано. В конце концов массив act[] равен строкам, у которых два первых знака равны ac, а следующие -- текстовому представлению переменной nt. Здесь полезно отметить, что строки можно соединять при помощи знака + и даже соединять с числами, при этом числа автоматически преобразуются в текст. Кроме того, надо собрать весь набор команд в массиве actt[]. Далее,

Здесь после того, как все необходимые массивы определены, вызывается метод createSubMenu(), который формирует раздел меню и затем добавляет его в полоску меню. После завершения полного цикла вся полоса меню, заданная из входных данных определена. Но при желании можно еще принудительно добавить один раздел меню сверх установленных в файле. Пусть он имеет имя About. Это можно сделать более традиционным способом

Здесь используется еще одна тонкость Java -- объявляется массив и тут же он определяется в фигурных скобках перечислением. Вот теперь вся полоса меню задана. А мы полностью написали вторую функцию. Но она использует новую функцию (метод) createSubMenu(). Этот метод имеет абсолютно стандартный вид и вы можете ставить его в каждую свою программу не задумываясь. Я даже не буду его комментировать, а просто приведу код

Обратите внимание, что этот метод ничего не возвращает и оперирует общими переменными класса. Я уже говорил, что клавиши акселераторов подразделов меню надо будет нажимать одновременно с клавишей [Alt]. Эта вторая клавиша определена в виде переменной ActionEvent.ALT_MASK. Так длинно и сложно пишутся числа-константы в Java. Сделано это для того, чтобы было удобно читать код. А до этого были написаны числа, определяющие коды клавиш как KeyEvent.VK_F1 (клавиша [F1]).

Замечу также, что можно было бы написать чуть более сложный код, и в явном виде (через файл First.ini) указать полную комбинацию клавиш, то есть вместо [Alt] можно было бы альтернативно использовать [Ctrl] или даже обе клавиши [Alt] и [Crtl] одновременно с основной клавишей. Такая ситуация может понадобиться для очень сложных программ, имеющих очень много подразделов меню. Далее, иногда необходимо, чтобы подразделы меню имели свои подразделы, то есть меню имело структуру дерева с многими поколениями веток. Это тоже возможно, просто соответствуюший подраздел меню надо задавать не объектом класса JMenuItem, а снова объектом класса JMenu и для него создать свою структуру разделов подразделов меню. Необходимость в этом тоже возникает для достаточно сложных программ.

Итак, меню у нас есть, но нам необходимо обеспечить реакцию нашей программы на выбор каждого раздела меню. Это можно делать самыми разными способами. Но мы пишем шаблон, поэтому проще всего формализовать эту процедуру. Наш класс поддерживает интерфейс ActionListener. Согласно этому интерфейсу мы должны переопределить функцию, написанную ниже

Теперь понятно почему мы аккумулировали все коды команд в одном массиве actt[] заданной длины 200. Размер массива задан как бы с запасом, так как мы не знаем его истинного размера до самого конца прочтения файла First.ini, а у нас есть необходимость использовать его раньше, просто так код проще пишется. Как это работает? При выборе какого-либо раздела меню сгенерируется событие как объект класса ActionEvent и оно будет передано автоматически в нашу функцию actionPerformed(). Метод e.getActionCommand() возвращает код команды для данного события. Мы должны сравнить все наши коды команд с кодом данной команды, и при совпадении мы получаем номер раздела меню в переменную i. Удобно зафиксировать этот номер в статической переменной isn нашего класса MyPro, поскольку мы пока не знаем, где и когда будем использовать этот номер, и вызвать стандартную функцию execPro(). Ну и по ходу я изменил начальный номер в порядок счета номеров, вместо нуля сделал единицу. Фактически можно было бы функцию написать прямо здесь, не вводя новое обозначение, но это неудобно читать.

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

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

Из этого текста видно, что весь код нашей программы мы теперь будем писать в классе runPro, который обязан поддерживать интерфейс Runnable, так как его объект является аргументом объекта класса Thread. При этом мы будем использовать переменную MyPro.isn. То есть нам в том классе придется сделать ветвление и обрабатывать каждое значение этой переменной. Осталось написать функцию addAbout(). В простейшей форме она просто сообщает некий текст в специальном окне. Тогда ее код может быть таким.

Статический метод showMessageDialog() класса JOptionPane -- это полностью готовая программа, которая показывает текст в отдельном окне в режиме диалога, то есть программа ждет и ничего не делает, пока пользователь не нажмет клавишу "OK".

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

Теперь мы закончили написание кода для второго класса, но чтобы закончить программу, нам осталось написать еще два класса MyPro и runPro. Это мы будем делать в других файлах. А этот файл написан и пора подумать о импорте пакетов Java, которые мы использовали. Мы должны добавить указатели на эти пакеты в начале файла. Самый простой способ -- пустить файл на компиляцию и компилятор вам сам скажет каких классов он не нашел. Затем надо посмотреть в каких они пакетах и добавить. Я как раз так и собираюсь проделать. Компилятор не нашел следующих классов
КлассПакет
Action Eventjava.awt.event.*
Action Listenerjava.awt.event.*
BufferedImagejava.awt.image.*
Dimensionjava.awt.*
IOExceptionjava.io.*
JDesktopPanjavax.swing.*
JFramejavax.swing.*
JMenujavax.swing.*
JMenuBarjavax.swing.*
JMenuItemjavax.swing.*
JOptionPanejavax.swing.*
KeyEventjava.awt.event.*
KeyStrokejavax.swing.*
StringTokenizer       java.util.*
SwingUtilitiesjavax.swing.*
Toolkitjava.awt.*
UIManagerjavax.swing.*
Классы, входящие в пакет java.lang.* компилятор нашел, например String. Этот пакет считается основой языка Java и компилятор в нем ищет классы в любой программе автоматически. Все найденные классы имеются в версиях 1.2.2 и выше. Но используемый в явном виде класс javax.imageio.ImageIO появился позднее версии 1.2.2.

Итак, нам необходимо в начало файла поставить следующие строки

После повторной компиляции компилятор не обнаружил всего два класса MyPro и runPro. Это правильно, этих классов мы еще не написали и как раз сейчас этим займемся.

содержание   вперед   назад


.