|
| 1 | +% !TEX encoding = UTF8 |
| 2 | +% !TEX spellcheck = ru_RU |
| 3 | +% !TEX root = ../seminars.tex |
| 4 | + |
| 5 | +%%=============================================== |
| 6 | +\chapter{Графические пользовательские интерфейсы} |
| 7 | +%%=============================================== |
| 8 | + |
| 9 | +%%==================================== |
| 10 | +\section{Окно с кнопкой \texttt{Quit}} |
| 11 | +%%==================================== |
| 12 | +Выполним упражнение~1 из~\textbookref{главы~16} учебника. Для~этого пронаследуем \code{My\_window} от~класса \code{Simple\_window} из~библиотеки \code{Graph\_lib}. Затем добавим по~аналогии с~кнопкой \code{Next} кнопку \code{Quit}, как показано на~рисунке~\ref{fig:mywindow}. |
| 13 | + |
| 14 | +\begin{figure}[ht] |
| 15 | + {\centering |
| 16 | + \includegraphics[width=0.6\textwidth]{images/my_window.png} |
| 17 | + |
| 18 | + } |
| 19 | + \caption{Простое окно \code{My\_window} с кнопкой \code{Quit}} |
| 20 | + \label{fig:mywindow} |
| 21 | +\end{figure} |
| 22 | + |
| 23 | +Из~документации к~библиотеке \name{FLTK} известно, что если все окна становятся скрытыми, то цикл обработки событий прекращает работу. То есть для~реализации функции \code{quit()} можно воспользоваться методом \code{hide()}. |
| 24 | + |
| 25 | +\cppfile[firstline=11, lastline=21]{projects/ch16/chessboard/board.h} |
| 26 | + |
| 27 | +Конструктор нашего окна принимает те же аргументы, что и \code{Simple\_window}. Мы добавляем инициализацию кнопки \code{Quit}. Нужно сместить её вниз на~высоту кнопки \code{Next}, которую добавляет базовый класс, а также связать её с~функцией-обработчиком \code{cb\_quit()}: |
| 28 | + |
| 29 | +\cppfile[firstline=7, lastline=12]{projects/ch16/chessboard/board.cpp} |
| 30 | + |
| 31 | +\textbf{NB!} Обратим внимание на одну деталь, которую нам пришлось изменить в~изначальной версии кода библиотеки \code{Graph\_lib}: |
| 32 | + |
| 33 | +\cppfile[firstline=8, lastline=14]{projects/lib/Graph_lib/GUI.cpp} |
| 34 | + |
| 35 | +\noindent В~строке~12 мы передаём указатель на~текущий объект класса (\code{this}), то есть указатель на~кнопку (\code{Button}), а не~указатель на~окно, как это ожидается в~примерах обработчиков, показаных в~\textbookref{главе~16} учебника. |
| 36 | + |
| 37 | +Отметим, что здесь мы можем передавать, вообще говоря, любые данные. Позже библиотека \name{FLTK} вернёт нам указатель на~эти данные в~функцию обратного вызова: |
| 38 | + |
| 39 | +\cppfile[firstline=14, lastline=18]{projects/ch16/chessboard/board.cpp} |
| 40 | + |
| 41 | +\noindent Параметр \code{widget} как раз и есть тот самый адрес кнопки, который мы передаём при~вызове функции \code{Button::attach()}, когда связываем кнопку с~окном: |
| 42 | + |
| 43 | +\cppfile[firstline=11, lastline=11]{projects/ch16/chessboard/board.cpp} |
| 44 | + |
| 45 | +Оказывается, во многих задачах удобнее иметь указатель на~наш виджет. А, так как он хранит адрес окна, с~которым связан, мы можем получить доступ к~нашему окну и, в~конце концов, вызвать метод \code{quit()}: |
| 46 | + |
| 47 | +\cppfile[firstline=17, lastline=17]{projects/ch16/chessboard/board.cpp} |
| 48 | + |
| 49 | +\noindent Здесь использован оператор \code{dynamic\_cast}, который выполняет приведение типа от~ссылки на~объект базового класса к~ссылке на~объект производного класса (\textenglish{down cast}). Такое преобразование в~общем случае небезопасно. Данный оператор выполняет проверку во~время выполнения программы и возбуждает исключение \code{std::bad\_cast} в~случае ошибки. |
| 50 | + |
| 51 | + |
| 52 | + |
| 53 | +%%======================= |
| 54 | +\section{Шахматная доска} |
| 55 | +%%======================= |
| 56 | +Покажем, как можно создать клеточное поле и взаимодействовать с~ним на~примере упражнения~2 из~\textbookref{главы~16}. Эти идеи можно использовать для~создания практически любой игры с~клеточным полем: шашек, шахмат, сапёра, морского боя, пять в ряд и других. |
| 57 | + |
| 58 | +Учитывая последующую доработку, код удобно сразу распределить между~несколькими файлами, как это предлагается в~таблице~\ref{tab:chessboard}. |
| 59 | + |
| 60 | +\begin{table}[ht] |
| 61 | + {\centering\begin{tabular}{ll} |
| 62 | + \toprule |
| 63 | + \code{board.h} & класс \code{Chessboard} для~шахматной доски, а также \code{My\_window} \\ |
| 64 | + \code{board.cpp} & \\[0.5em] |
| 65 | + |
| 66 | + \code{cell.h} & класс \code{Cell} для~шахматной клетки \\ |
| 67 | + \code{cell.cpp} & \\[0.5em] |
| 68 | + |
| 69 | + \code{main.cpp} & \\ |
| 70 | + \bottomrule |
| 71 | + \end{tabular} |
| 72 | + |
| 73 | + } |
| 74 | + \medskip |
| 75 | + \caption{Распределение кода <<шахматной доски>> между файлами} |
| 76 | + \label{tab:chessboard} |
| 77 | +\end{table} |
| 78 | + |
| 79 | +Размеры клеток и, соответственно, окна зафиксируем. Впоследствии, такое поведение можно изменить. Клетки представим квадратными кнопками и будем хранить их в~\code{Vector\_ref}. Метки строк и столбцов доски можно нарисовать при~помощи объектов \code{Marks}. Окно, согласно заданию, пронаследуем от~\code{My\_window} из~предыдущего упражнения. |
| 80 | + |
| 81 | +\begin{figure}[ht] |
| 82 | + {\centering |
| 83 | + \includegraphics[width=0.5\textwidth]{images/chessboard.png} |
| 84 | + |
| 85 | + } |
| 86 | + \caption{Окно с шахматной доской \(4\times 4\)} |
| 87 | + \label{fig:chessboard} |
| 88 | +\end{figure} |
| 89 | + |
| 90 | +Итак, давайте разбираться по~порядку. |
| 91 | + |
| 92 | + |
| 93 | + |
| 94 | +%%=========================== |
| 95 | +\paragraph{Шахматная клетка.} |
| 96 | +%%=========================== |
| 97 | +Прежде всего, создадим шахматную клетку "--- класс \code{Cell}. Клетки на~шахматной доске могут быть только белыми или чёрными. Это свойство мы выразим, используя перечисление \code{Cell::Type}. Размер клетки зафиксируем на~этапе компиляции. Тогда разумно сделать его константой класса (\code{static}), чтобы обращаться из~любой точки программы без~указания объекта \code{Cell::size}. |
| 98 | + |
| 99 | +Нам придётся перекрыть метод \code{attach()}. Почему? |
| 100 | + |
| 101 | +Также добавим пару методов \code{activate()}/\code{deactivate()} для~управления подсветкой активной клетки (клетка~\code{b2} на~рисунке~\ref{fig:chessboard}), то есть той клетки, которую выбрал мышкой пользователь. |
| 102 | + |
| 103 | +В~заголовочном файле \code{cell.h} разместим определение класса: |
| 104 | + |
| 105 | +\cppfile[firstline=8, lastline=32]{projects/ch16/chessboard/cell.h} |
| 106 | + |
| 107 | +Реализация методов проста и не~требует дополнительных пояснений, кроме того, что \code{pw} "--- это защищённый член класса \code{Graph\_lib::Widget}, связанный с~реальным графическим виджетом из~\name{FLTK}. |
| 108 | + |
| 109 | +Код разместим в~файле \code{cell.cpp}: |
| 110 | + |
| 111 | +\cppfile[firstline=5, lastline=32]{projects/ch16/chessboard/cell.cpp} |
| 112 | + |
| 113 | + |
| 114 | + |
| 115 | +%%========================== |
| 116 | +\paragraph{Шахматная доска.} |
| 117 | +%%========================== |
| 118 | +Основные моменты обозначены выше. Отметим здесь, что размер доски (\(N\times N\)) фиксируем на~этапе компиляции. Введём дополнительную константу \(N_{max}\), чтобы пользователь класса не~превышал определённый размер. Дело в~том, что позже мы будем добавлять подписи клеток и пока рассчитываем дизайн максимально на~8--9 клеток по~высоте. Дальше придётся что-то придумывать или каким-то образом выравнивать текстовые метки, состоящие из~двух цифр. (Это оставляем на~подумать.) |
| 119 | + |
| 120 | +В~заголовочный файл \code{board.h} добавим определение класса: |
| 121 | + |
| 122 | +\cppfile[firstline=23, lastline=51]{projects/ch16/chessboard/board.h} |
| 123 | + |
| 124 | +Реализацию методов класса добавим в~файл \code{board.cpp}. |
| 125 | + |
| 126 | +Конструктор задаёт размеры окна, создаёт клетки, располагая их в~соответствующих позициях, и рисует метки строк и столбцов доски. Зафиксируем размеры окна, чтобы пользователь не~смог его растягивать или сжимать. Сделать это позволяет функция \code{size\_range()} "--- метод окна \name{FLTK}. |
| 127 | + |
| 128 | +\cppfile[firstline=48, lastline=67]{projects/ch16/chessboard/board.cpp} |
| 129 | +\cpp/ .../ |
| 130 | +\cppfile[firstline=79, lastline=79]{projects/ch16/chessboard/board.cpp} |
| 131 | + |
| 132 | +Цвет клетки (или тип) вычисляется на~основе номеров строки и столбца, определяющих её положение на~доске. Клетка в~левом нижнем углу имеет чёрный цвет. Обратим внимание, что клетки добавлялись в~линейный массив по~порядку в~соответствии с~направлением снизу вверх и слева направо, как принято в~шахматах. |
| 133 | + |
| 134 | +\cppfile[firstline=20, lastline=26]{projects/ch16/chessboard/board.cpp} |
| 135 | + |
| 136 | + |
| 137 | + |
| 138 | +%%========================= |
| 139 | +\paragraph{Подписи клеток.} |
| 140 | +%%========================= |
| 141 | +Метки \code{Marks} допускают всего лишь один символ, поэтому мы используем ограничение \code{N\_max} для~максимального размера доски. Если потребуется вывести двузначные номера клеток, придётся разработать новый класс или добавить метки при~помощи неименованных объектов \code{Text}. |
| 142 | + |
| 143 | +А пока воспользуемся простой реализацией (добавляем код в конструктор): |
| 144 | + |
| 145 | +\cppfile[firstline=67, lastline=78]{projects/ch16/chessboard/board.cpp} |
| 146 | + |
| 147 | +\noindent и парой внешних вспомогательных функций: |
| 148 | + |
| 149 | +\cppfile[firstline=28, lastline=46]{projects/ch16/chessboard/board.cpp} |
| 150 | + |
| 151 | +\noindent Таким образом, получается вид, как на~рисунке~\ref{fig:chessboard}. |
| 152 | + |
| 153 | + |
| 154 | + |
| 155 | +%%========================================= |
| 156 | +\paragraph{Взаимодействие с пользователем.} |
| 157 | +%%========================================= |
| 158 | +В~функцию-обработчик нажатия на~кнопку-клетку добавим простую логику самой шахматной доски. Можно: |
| 159 | +\begin{itemize} |
| 160 | + \item выделить любую клетку; |
| 161 | + \item переместить выделение, выбрав другую клетку; |
| 162 | + \item снять выделение, нажав на~выделенную клетку повторно. |
| 163 | +\end{itemize} |
| 164 | + |
| 165 | +\noindent Реализация относительно проста, однако, здесь удобно использовать переменную-указатель \code{selected}. (Указатели будут обсуждаться в~\textbookref{главе~17} учебника.) Мы запоминаем адрес <<активной>> клетки в~переменной \code{selected}. Значение \code{nullptr} говорит, что <<активная>> клетка не~выбрана. |
| 166 | + |
| 167 | +\cppfile[firstline=81, lastline=103]{projects/ch16/chessboard/board.cpp} |
| 168 | + |
| 169 | +\noindent\textbf{NB!} Если в~отображении виджета что-то изменилось, то, скорее всего, потребуется перерисовать его вручную. Для~этого используйте \code{Fl::redraw()}. |
| 170 | + |
| 171 | +Теперь настало время собрать и запустить программу целиком. Добавьте необходимые заголовки, функцию \code{main()}. Создайте окно \code{Chessboard} и запустите цикл обработки событий: |
| 172 | + |
| 173 | +\cppfile[firstline=17, lastline=18]{projects/ch16/chessboard/main.cpp} |
| 174 | + |
| 175 | + |
| 176 | + |
| 177 | +%%=============================== |
| 178 | +\section{Добавление фигур. Шашки} |
| 179 | +%%=============================== |
| 180 | + |
| 181 | +\begin{figure}[ht] |
| 182 | + {\centering |
| 183 | + \includegraphics[width=0.5\textwidth]{images/checkers.png} |
| 184 | + |
| 185 | + } |
| 186 | + \caption{Шахматная доска \(4\times 4\) с~шашками} |
| 187 | + \label{fig:checkers} |
| 188 | +\end{figure} |
| 189 | + |
| 190 | +\todo{Описание будет добавлено позже.} |
| 191 | + |
| 192 | + |
| 193 | + |
| 194 | +%%================ |
| 195 | +\WhatToReadSection |
| 196 | +%%================ |
| 197 | +\textcite{Stroustrup:2016:ru}: \textbf{глава~17} |
| 198 | + |
| 199 | + |
| 200 | + |
| 201 | +%%=============== |
| 202 | +\ExercisesSection |
| 203 | +%%=============== |
| 204 | +\begin{exercise} |
| 205 | +\item Выполните упражнения из~\textbookref{главы~16} учебника. |
| 206 | + |
| 207 | +\end{exercise} |
0 commit comments