Книга: C# для профессионалов. Том II

Ответ на ввод пользователя

Ответ на ввод пользователя

До сих пор, за исключением пеню File в примере CapsEditor, все, что делались в этой главе, было односторонним, так как приложение общалось с пользователем, выводя информацию на экране. Почти любое программное обеспечение работает в обоих направлениях — пользователь также может общаться с программным обеспечением. Мы собираемся теперь добавить эту возможность в CapsEditor.

Заставить приложение GDI+ ответить на пользовательский ввод в действительности значительно проще, чем писать код для рисования на экране, и мы в главе 9 уже рассмотрели, как обрабатывать ввод пользователя. По сути для этого необходимо переопределить методы из класса Form, которые вызываются из соответствующего обработчика событий почти так же, как вызывается OnPaint(), когда инициируется событие Paint.

Для случая обнаружения, когда пользователь щелкнул мышью или переместил мышь, функции которые можно будет переопределить включают следующие:

Метод Когда вызывается
OnClick(EventArgs е) Сделан щелчок мышью
OnDoublеСlick(EventArgs е) Сделан двойной щелчок мышью
OnMouseDown(MouseEventArgs е) Нажата левая кнопка мыши
OnMouseHover(MouseEventArgs е) Мышь остается неподвижной после перемещения
OnMouseMove(MouseEventArgs е) Мышь перемещается
OnMouseUp(MouseEventArgs e) Левая кнопка мыши отпущена

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

OnKeyDown(KeyEventArgs е) Клавиша нажата
OnKeyPress(KeyPressEventArgs е) Клавиша нажата и отпущена
OnKeyUp(KeyEventArgs e) Нажатая клавиша отпущена

Отметим, что некоторые из этих событий перекрываются. Например, нажатие пользователем кнопки мыши порождает событие MouseDown. Если кнопка немедленно снова освобождается, то это порождает событие MouseUp и событие Click. Также некоторые из этих методов получают аргумент, который выводится из аргумента EventArgs и поэтому может использоваться для предоставления дополнительных данных об определенном событии. MouseEventArgs имеет два свойства — X и Y, которые задают координаты мыши во время нажатия кнопки. KeyEventArgs и KeyPressEventArgs имеют свойства, указывающие, какая клавиша или клавиши имеют отношение к событию.

Затем разработчик должен продумать логику последующих действий. Только одно замечание. Вполне вероятно, что приложение GDI+ потребует создания большего объема логики, чем приложение Windows.Forms. Это связано с тем, что в приложении Windows.Forms приходится отвечать на высокоуровневые события (например, TextChanged для текстового поля). При использовании GDI+ события являются более базовыми — пользователь щелкает мышью или нажимает клавишу h. Действие, которое предпринимает приложение, скорее всего зависит от последовательности событий, а не от одного события. Например, в Word for Windows, чтобы выбрать некоторый текст, пользователь обычно щелкает левой кнопкой мыши, перемещает мышь и освобождает левую кнопку мыши. Если пользователь просто нажмет, а затем освободит левую кнопку мыши, Word не выделит никакой текст, он просто переместит курсор текста в место, где был курсор мыши. Поэтому в точке, где пользователь нажимает левую кнопку мыши, нельзя еще сказать, что пользователь собирается делать. Приложение будет получать событие MouseDown, но, предположим, что требуется, чтобы приложение вело себя так же, как это делает Word for Windows. Нам ничего не остается, кроме как записать, что произошел щелчок мыши с курсором в определенной позиции. Когда будет получено событие MouseMove, вы захотите проверить в только что сделанных записях, не нажата ли в данный момент левая кнопка, и если это так, то выделить текст, поскольку пользователь его выбрал. Когда пользователь освобождает левую кнопку мыши (в методе OnMouseUp()), происходит проверка, было ли выполнено какое-либо перемещение, пока мышь была нажата, а далее нужно действовать соответственно. Только в этой точке последовательность завершается.

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

Золотое правило состоит в тщательном продумывании логики каждой комбинации движения мыши или щелчка и события клавиатуры, которое может инициировать пользователь, а также обеспечении того, чтобы приложение отвечало способом, который интуитивно понятен и согласован с обычно ожидаемым поведением приложений в каждом случае. Большая часть работы здесь будет состоять в обдумывании, а не в кодировании, хотя кодирование потребуется достаточно кропотливое, поэтому может понадобиться принять во внимание множество комбинаций ввода пользователя. Например, что должно делать приложение, если пользователь начинает вводить текст, когда одна из кнопок мыши нажата? Это покажется невероятной комбинацией, но рано или поздно какой-то пользователь попробует так сделать.

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

Это несложная задача, но имеется одна заминка. Необходимо перехватить событие Doubleclick, но приведенная выше таблица показывает, что это событие имеет параметр EventArgs, а не параметр MouseEventArgs. Проблема в том, что необходимо знать, где находится мышь, когда пользователь делает двойной щелчок, если требуется правильно определить строку текста, которая будет переводиться в верхний регистр, и для этого требуется параметр MouseEventArgs. Существует два способа обойти эту проблему. Один состоит в использовании статического метода Control.MousePosition, который реализован объектом Form1, чтобы найти положение мыши, как в следующем коде:

protected override void OnDoubleClick(EventArgs e) {
 Point MouseLocation = Control.MousePosition;
 // обработать двойной щелчок

В большинстве случаев это срабатывает. Однако могут быть сложности, если приложение (или даже некоторое другое приложение с более высоким приоритетом) выполняет некоторые интенсивные вычисления в момент двойного щелчка пользователя. В таком случае возможно, что обработчик событий OnDoubleClick() не будет вызван в течение полсекунды. Задержки такого рода вряд ли желательны, так как они начинают раздражать пользователей достаточно быстро. Половины секунды вполне достаточно для перемещения мыши на половину экрана, — и в этом случае выполнение OnDoubleClick() может произойти в совершенно другом месте.

Лучший подход состоит в использовании одного из многих наложений между значениями событий мыши. Первая часть двойного щелчка мыши включает нажатие левой кнопки. Это означает, что если вызывается OnDoubleClick(), то мы знаем, что также был вызван OnMouseDown() с курсором мыши в том же месте. Можно использовать перезагружаемую версию OnMouseDown() для записи положения мыши при использовании в OnDoubleClick(). Этот подход используется в CapsEditor:

protected override void OnMouseDown(MouseEventArgs e) {
 base.OnMouseDown(e);
 this.mouseDoubleClickPosition = new Point(e.X, e.Y);
}

Теперь посмотрим на перезагруженную версию OnDoubleClick(). Здесь придется выполнить немного больше работы:

protected override void OnDoubleClick(EventArgs e) {
 int i = PageCoordinatesToLineIndex(this.mouseDoubleClickPosition);
 if (i >= 0) {
  TextLineInformation lineToBeChanged =
   (TextLineInformation) documentLines[i];
  lineToBeChanged.Text = lineToBeChanged.Text.ToUpper();
  Graphics dc = this.CreateGraphics();
  uint newWidth = (uint)dc.MeasureString(lineToBeChanged.Text, mainFont).Width:
  if (newWidth > lineToBeChanged.Width) lineToBeChanged.Width = newWidth;
  if(newWidth+2*margin > this.documentSize.Width) {
   this.documentSize.Width = (int)newWidth;
   this.AutoScrollMinSize = this.documentSize;
  }
  Rectangle changedRectangle =
   new Rectangle(
   LineIndexToPageCoordinates(i), new Size((int)newWidth, (int)this.lineHeight));
  this.Invalidate(changedRectangle);
 }
 base.OnDoubleClick(e);
}

Начнем работу с вызова PageCoordinatesToLineIndex() для определения, над какой строкой текста находится курсор мыши, когда пользователь делает двойной щелчок. Если этот вызов возвращает -1, то никакого текста под курсом нет, поэтому ничего делать не надо (за исключением, конечно, вызова версии OnDoubleClick() базового класса, чтобы позволить Windows выполнить обработку по умолчанию. Это никогда не надо забывать делать.).

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

Затем код использует Graphics.MeasureString() для определения новой ширины текста. Здесь имеется две возможности:

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

? Вторая: размер документа может не измениться.

В любом случае необходимо перерисовать изображение на экране, вызывая Invalidate(). Только одна строка изменилась, поэтому нет необходимости перерисовывать весь документ. Вместо этого надо определить границы прямоугольника, который содержит только измененную строку, чтобы можно было передать этот прямоугольник в метод Invalidate() для перерисовывания только этой строки текста. Именно так делает представленный выше код. Вызов Invalidate() приведет к вызову OnPaint(), когда окончательно закончит работу обработчик события мыши. Вспомните предыдущие замечания в этой главе о трудностях в задании точки прерывания в OnPaint(). Если при выполнении примера задать точку прерывания в OnPaint() для перехвата получающегося действия рисования, то обнаружится, что параметр PaintEventArgs для OnPaint действительно содержит область вырезания, которая соответствует указанному прямоугольнику. И так как метод OnPaint() был перезагружен, чтобы аккуратно вычленить область вырезания, то будет перерисована только одна требуемая строка текста.

Оглавление книги


Генерация: 1.102. Запросов К БД/Cache: 3 / 0
поделиться
Вверх Вниз