Какое событие класса control отвечает за работу с клавиатурой
Перейти к содержимому

Какое событие класса control отвечает за работу с клавиатурой

  • автор:

Элементы управления

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

  • Anchor : Определяет, как элемент будет растягиваться
  • BackColor : Определяет фоновый цвет элемента
  • BackgroundImage : Определяет фоновое изображение элемента
  • ContextMenu : Контекстное меню, которое открывается при нажатии на элемент правой кнопкой мыши. Задается с помощью элемента ContextMenu
  • Cursor : Представляет, как будет отображаться курсор мыши при наведении на элемент
  • Dock : Задает расположение элемента на форме
  • Enabled : Определяет, будет ли доступен элемент для использования. Если это свойство имеет значение False, то элемент блокируется.
  • Font : Устанавливает шрифт текста для элемента
  • ForeColor : Определяет цвет шрифта
  • Location : Определяет координаты верхнего левого угла элемента управления
  • Name : Имя элемента управления
  • Size : Определяет размер элемента
  • Width : ширина элемента
  • Height : высота элемента
  • TabIndex : Определяет порядок обхода элемента по нажатию на клавишу Tab
  • Tag : Позволяет сохранять значение, ассоциированное с этим элементом управления

Кнопка

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

При нажатии на кнопку на форме в редакторе Visual Studio мы по умолчанию попадаем в код обработчика события Click , который будет выполняться при нажатии:

private void button1_Click(object sender, EventArgs e)

Оформление кнопки

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

  • Flat — Кнопка имеет плоский вид
  • Popup — Кнопка приобретает объемный вид при наведении на нее указателя, в иных случаях она имеет плоский вид
  • Standard — Кнопка имеет объемный вид (используется по умолчанию)
  • System — Вид кнопки зависит от операционной системы

Изображение на кнопке

Как и для многих элементов управления, для кнопки можно задавать изображение с помощью свойства BackgroundImage. Однако мы можем также управлять размещением текста и изображения на кнопки. Для этого надо использовать свойство TextImageRelation . Оно приобретает следующие значения:

  • Overlay : текст накладывается на изображение
  • ImageAboveText : изображение располагается над текстом
  • TextAboveImage : текст располагается над изображением
  • ImageBeforeText : изображение располагается перед текстом
  • TextBeforeImage : текст располагается перед изображением

Например, установим для кнопки изображение. Для этого выберем кнопку и в окне Свойств нажмем на поле Image (не путать с BackgroundImage). Нам откроется диалоговое окно установи изображения:

Установка изображения для кнопки

В этом окне выберем опцию Local Resource и нажмем на кнопку Import , после чего нам откроется диалоговое окно для выбора файла изображения.

После выбора изображения мы можем установить свойство ImageAlign , которое управляет позиционированием изображения на кнопке:

ImageAlign

Нам доступны 9 вариантов, с помощью которых мы можем прикрепить изображение к определенной стороне кнопки. Оставим здесь значение по умолчанию — MiddleCenter , то есть позиционирование по центру.

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

Кнопка с изображением в Windows Forms

Клавиши быстрого доступа

При работе с формами при использовании клавиатуры очень удобно пользоваться клавишами быстрого доступа. При нажатии на клавиатуре комбинации клавиш At+некоторый символ, будет вызываться определенная кнопка. Например, зададим для некоторой кнопки свойство Text равное &Аватар . Первый знак — амперсанд — определяет ту букву, которая будет подчеркнута. В данном случае надпись будет выглядеть как А ватар. И теперь чтобы вызвать событие Click, нам достаточно нажать на комбинацию клавиш Alt+А.

Кнопки по умолчанию

Форма, на которой размещаются все элементы управления, имеет свойства, позволяющие назначать кнопку по умолчанию и кнопку отмены.

Так, свойство формы AcceptButton позволяет назначать кнопку по умолчанию, которая будет срабатывать по нажатию на клавишу Enter.

Аналогично работает свойство формы CancelButton , которое назначает кнопку отмены. Назначив такую кнопку, мы можем вызвать ее нажатие, нажав на клавишу Esc.

События

Обычно, чтобы приложение с графическим интерфейсом что-то делало, должны происходить те или иные события, чаще всего представляющие собой воздействие человека на элементы GUI.

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

Типы событий

При вызове метода bind событие передается в качестве первого аргумента.

Передача события в метод bind

Название события заключается в кавычки, а также в угловые скобки < и >. События описывается с помощью зарезервированных ключевых слов.

Часто используемые события, производимые мышью:

  • – клик левой кнопкой мыши
  • – клик средней кнопкой мыши
  • – клик правой кнопкой мыши
  • – двойной клик левой кнопкой мыши
  • – движение мыши
  • и т. д.
from tkinter import * def b1(event): root.title("Левая кнопка мыши") def b3(event): root.title("Правая кнопка мыши") def move(event): x = event.x y = event.y s = "Движение мышью <>x<>".format(x, y) root.title(s) root = Tk() root.minsize(width=500, height=400) root.bind('', b1) root.bind('', b3) root.bind('', move) root.mainloop()

В этой программе меняется надпись в заголовке главного окна в зависимости от того, двигается мышь, щелкают левой или правой кнопкой.

Событие ( Event ) – это один из объектов tkinter . У событий есть атрибуты, как и у многих других объектов. В примере в функции move извлекаются значения атрибутов x и y объекта event , в которых хранятся координаты местоположения курсора мыши в пределах виджета, по отношению к которому было сгенерировано событие. В данном случае виджетом является главное окно, а событием – , т. е. перемещение мыши.

В программе ниже выводится информация об экземпляре Event и некоторым его свойствам. Все атрибуты можно посмотреть с помощью команды dir(event) . У разных событий они одни и те же, меняются только значения. Для тех или иных событий часть атрибутов не имеет смысла, такие свойства имеют значения по умолчанию.

В примере хотя обрабатывается событие нажатия клавиши клавиатуры, в поля x , y , x_root , y_root сохраняются координаты положения на экране курсора мыши.

from tkinter import * def event_info(event): print(type(event)) print(event) print(event.time) print(event.x_root) print(event.y_root) root = Tk() root.bind('a', event_info) root.mainloop()

Пример выполнения программы:

  1853276 505 402

Для событий с клавиатуры буквенные клавиши можно записывать без угловых скобок (например, ‘a’ ).

Для неалфавитных клавиш существуют специальные зарезервированные слова. Например, — нажатие клавиши Enter , — пробел. (Заметим, что есть событие , которое не имеет отношения к нажатию клавиши Enter , а происходит, когда курсор заходит в пределы виджета.)

События Enter и Leave

from tkinter import * def enter_leave(event): if event.type == '7': event.widget['text'] = 'In' elif event.type == '8': event.widget['text'] = 'Out' root = Tk() lab1 = Label(width=20, height=3, bg='white') lab1.pack() lab1.bind('', enter_leave) lab1.bind('', enter_leave) lab2 = Label(width=20, height=3, bg='black', fg='white') lab2.pack() lab2.bind('', enter_leave) lab2.bind('', enter_leave) root.mainloop()

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

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

Свойство event.widget содержит ссылку на виджет, сгенерировавший событие. Свойство event.type описывает, что это было за событие. У каждого события есть имя и номер. С помощью выражения print(repr(event.type)) можно посмотреть его полное описание. При этом на одних платформах str(event.type) возвращает имя события (например, ‘Enter’), на других – строковое представление номера события (например, ‘7’).

Вернемся к событиям клавиатуры. Сочетания клавиш пишутся через тире. В случае использования так называемого модификатора, он указывается первым, детали на третьем месте. Например, — одновременное нажатие клавиш Shift и стрелки вверх, – движение мышью с зажатой левой кнопкой и клавишей Ctrl .

from tkinter import * def exit_win(event): root.destroy() def to_label(event): t = ent.get() lbl.configure(text=t) def select_all(event): def select_all2(widget): widget.selection_range(0, END) widget.icursor(END) # курсор в конец root.after(10, select_all2, event.widget) root = Tk() ent = Entry(width=40) ent.focus_set() ent.pack() lbl = Label(height=3, fg='orange', bg='darkgreen', font="Verdana 24") lbl.pack(fill=X) ent.bind('', to_label) ent.bind('', select_all) root.bind('', exit_win) root.mainloop()

Пример программы, обрабатывающей события

Здесь сочетание клавиш Ctrl+a выделяет текст в поле. Без root.after() выделение не работает. Метод after выполняет функцию, указанную во втором аргументе, через промежуток времени, указанный в первом аргументе. В третьем аргументе передается значение атрибута widget объекта event . В данном случае им будет поле ent . Именно оно будет передано как аргумент в функцию select_all2 и присвоено параметру widget .

Практическая работа

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

Цвет фона экземпляра Text светлосерый ( lightgrey ), когда поле не в фокусе, и белый, когда имеет фокус.

Событие получения фокуса обозначается как , потери – как .

Для справки: фокус перемещается по виджетам при нажатии Tab , Ctrl+Tab , Shift+Tab , а также при клике по ним мышью (к кнопкам последнее не относится).

GUI программы

Курс с примерами решений практических работ: pdf-версия

X Скрыть Наверх

Tkinter. Программирование GUI на Python

C-sharp / Основы Windows Forms C# (изучаем основные формы)

Windows Forms За последние несколько лет Web-ориентированные приложения стали чрезвычайно популярными. Возможность размещать всю логику приложений на централизованном сервере выглядит очень привлекательной с точки зрения администратора. Развертывание программного обеспечения, базирующегося на клиенте, очень трудно, особенно, если оно основано на COM-объектах. Недостаток Web-ориентированных приложений состоит в том, что они не могут предоставить достаточно богатых возможностей пользователю. Платформа .NET Framework позволяет разрабатывать интеллектуальные клиентские приложения с богатыми возможностями, при этом избегая проблем с развертыванием и “DLL-адом”, как это было раньше. Независимо от того, что будет выбрано — Windows Forms или Windows Presentation Foundation (см. главу 34) — разработка или развертывание клиентских приложений теперь не представляет особой сложности. Windows Forms уже оказал влияние на разработки для Windows. Теперь, когда приложение находится на начальной стадии проектирования, принять решение о том, нужно ли строить Web-ориентированное приложение либо же полностью клиентское, стало немного труднее. Клиентские приложения Windows могут быть разработаны быстро и эффективно, при этом они предлагают пользователям гораздо более широкие возможности. Windows Forms покажется вам знакомым, если у вас есть опыт разработки на Visual Basic. Вы создаете новые формы (также известные как окна или диалоги) в той же манере — перетаскивая и размещая элементы управления из панели инструментов на поверхность визуального дизайнера форм (Form Designer). Однако если ваш опыт в основном касается классического стиля языка C для Windows-программирования, где приходилось создавать конвейеры сообщений и отслеживать эти сообщения, или же если вы — программист, применяющий MFC, то в этом случае вы обнаружите, что и здесь при необходимости у вас есть возможность работать с низкоуровневыми деталями. Вы можете переопределить оконную процедуру (WndProc) и перехватывать сообщения, но в действительности вас удивит, что делать это придется нечасто. В этой главе мы рассмотрим следующие аспекты Windows Forms: класс Form ; иерархия классов Windows Forms; элементы управления и компоненты, являющиеся частью пространства имен System.Windows.Forms ; меню и панели инструментов; создание элементов управления; создание пользовательских элементов управления.

Глава 31. Windows Forms 1055 Создание приложения Windows Forms Первое, что необходимо сделать — создать приложение Windows Forms. Для примера создадим пустую форму и отобразим ее на экране. При разработке этого примера мы не будем использовать Visual Studio .NET. Наберем его в текстовом редакторе и соберем с помощью компилятора командной строки. Ниже показан код примера. using System; using System.Windows.Forms; namespace NotepadForms < public class MyForm : System.Windows.Forms.Form < public MyForm() < >[STAThread] static void Main() < Application.Run(new MyForm()); >> > Когда мы скомпилируем и запустим этот пример, то получим маленькую пустую форму без заголовка. Никаких реальных функций, но это — Windows Forms. В приведенном коде заслуживают внимания две вещи. Первая — тот факт, что при создании класса MyForm используется наследование. Следующая строка объявляет MyForm как наследника System.Windows.Forms.Form : public class MyForm : System.Windows.Forms.Form Класс Form — один из главных классов в пространстве имен System.Windows.Forms . Следующий фрагмент кода стоит рассмотреть более подробно: [STAThread] static void Main() < Application.Run(new MyForm()); >Main — точка входа по умолчанию в любое клиентское приложение на C#. Как правило, в более крупных приложениях метод Main() не будет находиться в классе формы, а скорее в классе, отвечающем за процесс запуска. В данном случае вы должны установить имя такого запускающего класса в диалоговом окне свойств проекта. Обратите внимание на атрибут [STAThread] . Он устанавливает модель многопоточности COM в STA (однопоточный апартамент). Модель многопоточности STA требуется для взаимодействия с COM и устанавливается по умолчанию в каждом проекте Windows Forms. Метод Application.Run() отвечает за запуск стандартного цикла сообщений приложения. Application.Run() имеет три перегрузки. Первая из них не принимает параметров; вторая принимает в качестве параметра объект ApplicationContext . В нашем примере объект MyForm становится главной формой приложения. Это означает, что когда форма закрывается, то приложение завершается. Используя класс ApplicationContext , можно в большей степени контролировать завершение главного цикла сообщений и выход из приложения.

1056 Часть V. Презентации Класс Application содержит в себе очень полезную функциональность. Он предоставляет группу статических методов и свойств для управления процессом запуска и останова приложения, а также обеспечивает доступ к сообщениям Windows, обрабатываемым приложением. В табл. 31.1 перечислены некоторые из этих наиболее полезных методов и свойств. Таблица 31.1. Некоторые полезные методы и свойства класса Application

Метод/свойство Описание
CommonAppDataPath Путь к данным, общий для всех пользователей приложения. Обычно
это БазовыйПуть\Название компании\Название продукта\Версия ,
где БазовыйПуть — C:\Documents and Settings\имя пользователя\
ExecutablePath ApplicationData . Если путь не существует, он будет создан.
Путь и имя исполняемого файла, запускающего приложение.
LocalUserAppDataPath Подобно , но с тем отличием, что поддерживается
CommonAppDataPath
роуминг (перемещаемость).
MessageLoop True или false — в зависимости от того, существует ли цикл сообще-
StartupPath ний в текущем потоке.
Подобно , с тем отличием, что имя файла не возвра-
щается. ExecutablePath
AddMessageFilter Используется для предварительной обработки сообщений. Объект,
реализующий IMessageFilter , позволяет фильтровать сообщения в
цикле или организовать специальную обработку, выполняемую перед
тем, как сообщение попадет в цикл.
DoEvents Аналогично оператору DoEvents языка Visual Basic. Позволяет обрабо-
тать сообщения в очереди.
EnableVisualStyles Обеспечивает визуальный стиль Windows XP для различных визуальных
элементов приложения. Существуют две перегрузки, принимающие
информацию манифеста. Одна работает с потоком манифеста, вто-
рая — принимает полное имя и путь файла манифеста.
Exit и ExitThread Exit завершает текущий работающий цикл сообщений и вызывает вы-
ход из приложения. ExitThread завершает цикл сообщений и закры-
вает все окна текущего потока.

А теперь как будет выглядеть это приложение, если его сгенерировать в Visual Studio 2008? Первое, что следует отметить — будет создано два файла. Причина в том, что Visual Studio 2008 использует возможность частичных ( partial ) классов и выделяет весь код, сгенерированный визуальным дизайнером, в отдельный файл. Если используется имя по умолчанию — Form1 , то эти два файла будет называться Form1.cs и Form1.Designer.cs . Если только у вас не включена опция Show All Files (Показать все файлы) в меню Project (Проект), то вы не увидите в проводнике Solution Explorer файла Form1.Designer.cs . Ниже показан код этих двух файлов, сгенерированных Visual Studio. Сначала — Form1.cs : using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text;

Глава 31. Windows Forms 1057 using System.Windows.Forms; namespace VisualStudioForm < public partial class Form1 : Form < public Form1() < InitializeComponent(); >> > Здесь мы видим только операторы using и простой конструктор. А вот код Form1. Designer.cs : namespace VisualStudioForm < partial class Form1 < ///

/// Required designer variable. ///

private System.ComponentModel.IContainer components = null; ///

/// Clean up any resources being used. ///

/// < param name="disposing" >true if managed resources should be disposed; otherwise, false. < /param >protected override void Dispose(bool disposing) < if (disposing && (components != null)) < components.Dispose(); >base.Dispose(disposing); > #region Windows Form Designer generated code ///

/// Required method for Designer support — do not modify /// the contents of this method with the code editor. ///

private void InitializeComponent() < this.components = new System.ComponentModel.Container(); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.Text = "Form1"; >#endregion > > Файл, сгенерированный дизайнером форм, редко подвергается ручному редактированию. Единственным исключением может быть случай, когда необходима специальная обработка в методе Dispose() . Метод InitializeComponent мы обсудим позднее в этой главе. Если взглянуть на этот код примера приложения в целом, то мы увидим, что он намного длиннее, чем простой пример командной строки. Здесь перед началом класса присутствует несколько операторов using , и большинство из них в данном примере не нужны. Однако их присутствие ничем не мешает. Класс Form1 наследуется от

1058 Часть V. Презентации System.Windows.Forms.Form , как и в предыдущем, введенном в Notepad примере, но в этой точке начинаются расхождения. Во-первых, в файле Form1.Designer появляется строка: private System.ComponentModel.IContainer components = null; В данном примере эта строка кода ничего не делает. Но, добавляя компонент в форму, вы можете также добавить его в объект components , который представляет собой контейнер. Причина добавления этого контейнера — в необходимости правильной обработки уничтожения формы. Класс формы поддерживает интерфейс IDisposable , потому что он реализован в классе Component . Когда компонент добавляется в контейнер, то этот контейнер должен позаботиться о корректном уничтожении своего содержимого при закрытии формы. Это можно увидеть в методе Dispose нашего примера: protected override void Dispose(bool disposing) < if (disposing && (components != null)) < components.Dispose(); >base.Dispose(disposing); > Здесь мы видим, что когда вызывается метод Dispose формы, то также вызывается метод Dispose объекта components , поскольку он содержит в себе другие компоненты, которые также должны быть корректно удалены. Конструктор класса Form1 , находящийся в файле Form1.cs , выглядит так: public Form1() < InitializeComponent(); >Обратите внимание на вызов InitializeComponent() . Метод InitializeComponent() находится в файле Form1.Designer.cs и делает то, что следует из его наименования — инициализирует все элементы управления, которые могут быть добавлены к форме. Он также инициализирует свойства формы. В нашем примере метод InitializeComponent() выглядит следующим образом: private void InitializeComponent() < this.components = new System.ComponentModel.Container(); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.Text = "Form1"; >Как видите, здесь присутствует лишь базовый код инициализации. Этот метод связан с визуальным дизайнером Visual Studio. Когда в форму вносятся изменения в дизайнере, они отражаются на InitializeComponent() . Если вы вносите любые изменения в InitializeComponent() , то следующий раз после того, как что-то будет изменено в дизайнере, эти ручные изменения будут утеряны. InitializeComponent() повторно генерируется после каждого изменения дизайна формы. Если возникает необходимость добавить некоторый дополнительный код для формы или элементов управления и компонентов формы, это должно быть сделано после вызова InitializeComponent() . Этот метод также отвечает за создание экземпляров элементов управления, поэтому любой вызов, ссылающийся на них, выполненный до InitializeComponent() , завершится возбуждением исключения нулевой ссылки.

Глава 31. Windows Forms 1059 Чтобы добавить элемент управления или компонент в форму, нажмите комбинацию клавиш или выберите пункт меню View Toolbox (Вид Панель инструментов) в среде Visual Studio .NET. Щелкните правой кнопкой мыши на Form1.cs в проводнике Solution Explorer и в появившемся контекстном меню выберите пункт View Designer (Показать дизайнер). Выберите элемент управления Button и перетащите на поверхность формы в визуальном дизайнере. Можно также дважды щелкнуть на выбранном элементе управления, и он будет добавлен в форму. То же самое следует проделать с элементом TextBox . Теперь, после добавления этих двух элементов управления на форму, метод InitializeComponent() расширяется и содержит такой код: private void InitializeComponent() < this.button1 = new System.Windows.Forms.Button(); this.textBox1 = new System.Windows.Forms.TextBox(); this.SuspendLayout(); // // button1 this.button1.Location = new System.Drawing.Point(77, 137); this.button1.Name = "button1"; this.button1.Size = new System.Drawing.Size(75, 23); this.button1.TabIndex = 0; this.button1.Text = "button1"; this.button1.UseVisualStyleBackColor = true; // textBox1 // this.textBox1.Location = new System.Drawing.Point(67, 75); this.textBox1.Name = "textBox1"; this.textBox1.Size = new System.Drawing.Size(100, 20); this.textBox1.TabIndex = 1; // // Form1 // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size(284, 264); this.Controls.Add(this.textBox1); this.Controls.Add(this.button1); this.Name = "Form1"; this.Text = "Form1"; this.ResumeLayout(false); this.PerformLayout(); >Если посмотреть на первые три строки кода этого метода, мы увидим в них создание экземпляров элементов управления Button и TextBox . Обратите внимание на имена, присвоенные им — textBox1 и button1 . По умолчанию дизайнер в качестве имен использует имя класса элемента управления, дополненное целым числом. Когда вы добавляете следующую кнопку, дизайнер называет ее button2 и т.д. Следующая строка — часть пары SuspendLayout / ResumeLayout . Метод SuspendLayout() временно приостанавливает события размещения, которые имеют место при первоначальной инициализации элемента управления. В конце метода вызывается ResumeLayout() , чтобы вернуть все в норму. В сложной форме с множеством элементов управления метод InitializeComponent() может стать достаточно большим.

1060 Часть V. Презентации Чтобы изменить значения свойств элемента управления, нужно либо нажать , либо выбрать пункт меню View Properties Window (Вид Окно свойств). Это окно позволяет модифицировать большинство свойств элемента управления или компонента. При внесении изменений в окне свойств метод InitializeComponent() автоматически переписывается с тем, чтобы отразить новые значения свойств. Например, изменив свойство Text на My Button в окне свойств, получим следующий код в InitializeComponent() : // // button1 // this.button1.Location = new System.Drawing.Point(77, 137); this.button1.Name = «button1»; this.button1.Size = new System.Drawing.Size(75, 23); this.button1.TabIndex = 0; this.button1.Text = «My Button»; this.button1.UseVisualStyleBackColor = true; Даже если вы предпочитаете использовать какой-то редактор, отличный от Visual Studio .NET, то наверняка захотите включать функции вроде InitializeComponent() в свои проекты. Сохранение всего кода инициализации в одном месте обеспечит возможность его вызова из каждого конструктора. Иерархия классов Важность понимания иерархии становится очевидной в процессе проектирования и конструирования пользовательских элементов управления. Если такой элемент управления унаследован от конкретного библиотечного элемента управления, например, когда создается текстовое поле с некоторыми дополнительными методами и свойствами, то имеет смысл унаследовать его от обычного текстового поля и затем переопределить и добавить необходимые методы. Однако если приходится создавать элемент управления, который не соответствует ни одному из существующих в .NET Framework, то его придется унаследовать от одного из базовых классов: Control или ScrollableControl , если нужны возможности прокрутки, либо ContainerControl , если он должен служить контейнером для других элементов управления. Остальная часть этой главы посвящена изучению большинства из этих классов — как они работают вместе, и как их можно использовать для построения профессионально выглядящих клиентских приложений. Класс Control Пространство имен System.Windows.Forms включает один класс, который является базовым почти для всех элементов управления и форм — System.Windows.Forms. Control . Он реализует основную функциональность для создания экранов, которые видит пользователь. Класс Control унаследован от System.ComponentModel.Component . Класс Component обеспечивает классу Control инфраструктуру, необходимую для того, чтобы его можно было перетаскивать и помещать на поле дизайнера, а также, чтобы он мог включать в себя другие элементы управления. Класс Control предлагает огромный объем функциональности классам, наследуемым от него. Этот список слишком большой, чтобы приводить его здесь, поэтому в данном разделе мы рассмотрим только наиболее важные возможности, предоставляемые классом Control . Позднее в этой главе, когда мы будем рассматривать специфические элементы управления, основанные на Control , мы увидим эти методы и свойства в коде примеров. Следующие

Глава 31. Windows Forms 1061 подразделы группируют методы и свойства по их функциональности, поэтому взаимосвязанные элементы могут быть рассмотрены вместе. Размер и местоположение Размер и местоположение элементов управления определяются свойствами Height , Width , Top , Bottom , Left и Right , вместе с дополняющими их Size и Location . Отличие состоит в том, что Height , Width , Top , Bottom , Left и Right принимают одно целое значение. Size принимает значение структуры Size , а Location — значение структуры Point . Структуры Size и Point включают в себя координаты X, Y. Point обычно описывает местоположение, а Size — высоту и ширину объекта. Size и Point определены в пространстве имен System.Drawing . Обе структуры очень похожи в том, что представляют пары координат X, Y, но, кроме того — переопределенные операции, упрощающие сравнения и преобразования. Вы можете, например, складывать вместе две структуры Size . В случае структуры Point операция сложения переопределена таким образом, что можно прибавить к Point структуру Size и получить в результате Point . Это дает эффект прибавления расстояния к местоположению, чтобы получить новое местоположение, что очень удобно для динамического создания форм и элементов управления. Свойство Bounds возвращает объект Rectangle , представляющий экранную область, занятую элементом управления. Эта область включает полосы прокрутки и заголовка. Rectangle также относится к пространству имен System.Drawing . Свойство ClientSize — структура Size , представляющая клиентскую область элемента управления за вычетом полос прокрутки и заголовка. Методы PointToClient и PointToScreen — удобные методы преобразования, которые принимают Point и возвращают Point . Метод PointToClient принимает структуру Point , представляющую экранные координаты, и транслирует их в координаты текущего клиентского объекта. Это удобно для операций перетаскивания. Метод PointToScreen выполняет обратную операцию — принимает координаты в клиентском объекте и транслирует их в экранные координаты. Методы RectangleToScreen и ScreenToRectangle выполняют те же операции, но со структурами Rectangle вместо Point . Свойство Dock определяет, к какой грани родительского элемента управления должен пристыковываться данный элемент. Перечисление DockStyle задает возможные значения этого свойства. Они могут быть такими: Top , Bottom , Right , Left , Fill и None . Значение Fill устанавливает размер данного элемента управления равным размеру родительского. Свойство Anchor (якорь) прикрепляет грань данного элемента управления к грани родительского элемента управления. Это отличается от стыковки (docking) тем, что не устанавливает грань дочернего элемента управления точно на грань родительского, а просто выдерживает постоянное расстояние между ними. Например, если якорь правой грани элемента управления установлен на правую грань родительского элемента, и если родитель изменяет размер, то правая грань данного элемента сохраняет постоянную дистанцию от правой грани родителя — т.е. он изменяет размер вместе с родителем. Свойство Anchor принимает значения из перечисления AnchorStyle , а именно: Top , Bottom , Left , Right и None . Устанавливая эти значения, можно заставить элемент управления изменять свой размер динамически вместе с родителем. Таким образом, кнопки и текстовые поля не будут усечены или скрыты при изменении размеров формы пользователем. Свойства Dock и Anchor применяются в сочетании с компоновками элементов управления Flow и Table (о которых мы поговорим позднее в этой главе) и позволя-

1062 Часть V. Презентации ют создавать очень сложные пользовательские окна. Изменение размеров окна может оказаться достаточно непростой задачей для сложных форм с множеством элементов управления. Эти инструменты существенно облегчают задачу. Внешний вид Свойства, имеющие отношение к внешнему виду элемента управления — это BackColor и ForeColor , которые принимают объект System.Drawing.Color в качестве значения. Свойство BackGroundImage принимает объект графического образа как значение. Класс System.Drawing.Image — абстрактный класс, служащий в качестве базового для классов Bitmap и Metafile . Свойство BackgroundImageLayout использует перечисление ImageLayout для определения способа отображения графического образа в элементе управления. Допустимые значения таковы: Center , Tile , Stretch , Zoom или None . Свойства Font и Text работают с надписями. Чтобы изменить Font , необходимо создать объект Font . При создании этого объекта указывается имя, стиль и размер шрифта. Взаимодействие с пользователем Взаимодействие с пользователем лучше всего описывается серией событий, которые генерирует элемент управления и на которые он реагирует. Некоторые из наиболее часто используемых событий: Click , DoubleClick , KeyDown , KeyPress , Validating и Paint . События, связанные с мышью — Click , DoubleClick , MouseDown , MouseUp , MouseEnter , MouseLeave и MouseHover — описывают взаимодействие мыши и экранного элемента управления. Если вы обрабатываете оба события — Click и DoubleClick — то всякий раз, когда перехватывается событие DoubleClick , также возбуждается и событие Click . Это может привести к нежелательным последствиям при неправильной обработке. К тому же и Click , и DoubleClick принимают в качестве аргумента EventArgs , в то время как события MouseDown и MouseUp принимают MouseEventArgs . Структура MouseEventArgs содержит несколько частей полезной информации — например, о кнопке, на которой был выполнен щелчок, количестве щелчков на кнопке, количестве щелчков колесика мыши (при условии его наличия), текущих координатах X и Y указателя мыши. Если нужен доступ к любой подобной информации, то вместо событий Click или DoubleClick потребуется обрабатывать события MouseDown и MouseUp . События клавиатуры работают подобным образом. Объем необходимой информации определяет выбор обрабатываемых событий. Для простейших случаев событие KeyPress принимает KeyPressEventArgs . Эта структура включает KeyChar , представляющий символ нажатой клавиши. Свойство Handled используется для определения того, было ли событие обработано. Установив значение Handled в true , можно добиться того, что событие не будет передано операционной системе для совершения стандартной обработки. Если необходима дополнительная информация о нажатой клавише, то больше подойдут события KeyDown или KeyUp . Оба принимают структуру KeyEventArgs . Свойства KeyEventArgs включают признак одновременного состояния клавиш , или . Свойство KeyCode возвращает значение типа перечисления Keys , идентифицирующее нажатую клавишу. В отличие от свойства KeyPressEventArgs.KeyChar , свойство KeyCode сообщает о каждой клавише клавиатуры, а не только о буквенно-цифровых клавишах. Свойство KeyData возвращает значение типа Keys , а также устанавливает модификатор. Значение модификатора со-

Глава 31. Windows Forms 1063 провождает значение клавиши, объединяясь с ним двоичной логической операцией “ИЛИ”. Таким образом, можно получить информацию о том, была ли одновременно нажата клавиша или . Свойство KeyValue — целое значение из перечисления Keys . Свойство Modifiers содержит значение типа Keys , представляющее нажатые модифицирующие клавиши. Если было нажато более одной такой клавиши, их значения объединяются операцией “ИЛИ”. События клавиш поступают в следующем порядке: 1. KeyDown 2. KeyPress 3. KeyUp События Validating , Validated , Enter , Leave , GotFocus и LostFocus имеют отношение к получению фокуса элементами управления (т.е. когда становятся активными) и утере его. Это случается, когда пользователь нажатием клавиши переходит к данному элементу управления либо выбирает его мышью. Может показаться, что события Enter , Leave , GotFocus и LostFocus очень похожи в том, что они делают. События GotFocus и LostFocus относятся к низкоуровневым, и связаны с событиями Windows WM_SETFOCUS и WM_KILLFOCUS . Обычно когда возможно, лучше использовать события Enter и Leave . События Validating и Validated возбуждаются при проверке данных в элементе управления. Эти события принимают аргумент CancelEventArgs . С его помощью можно отменить последующие события, установив свойство Cancel в true . Если вы разрабатываете собственный проверочный код, и проверка завершается неудачно, то в этом случае можно установить Cancel в true — тогда элемент управления не утратит фокус. Validating происходит во время проверки, а Validated — после нее. Порядок возникновения событий следующий: 1. Enter 2. GotFocus 3. Leave 4. Validating 5. Validated 6. LostFocus Понимание последовательности этих событий важно, чтобы избежать рекурсивных ситуаций. Например, попытка установить фокус элемента управления внутри обработчика события LostFocus создает ситуацию взаимоблокировки в цикле событий, и приложение перестает реагировать на внешние воздействия. Функциональность Windows Пространство имен System.Windows.Forms — одно из немногих, полагающихся на функциональность операционной системы Windows. Класс Control — хороший тому пример. Если выполнить дизассемблирование System.Windows.Forms.dll , то можно увидеть список ссылок на класс UnsafeNativeMethods . Среда .NET Framework использует этот класс как оболочку для всех стандартных вызовов Win32 API. Благодаря возможности взаимодействия с Win32 API, внешний вид и поведение стандартного приложения Windows можно обеспечить средствами пространства имен System. Windows.Forms . Функциональность, которая поддерживает взаимодействие с Windows, включает свойства Handle и IsHandleCreated . Свойство Handle возвращает IntPtr , содержащий HWND (дескриптор окна) элемента управления. Дескриптор окна — это HWND , уни-

Какое событие класса control отвечает за работу с клавиатурой

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

1 – Выберите подходящий внешний вид

Время идет, версии Java обновляются, появляются новые компоненты Swing и новые возможности при создании интерфейсов Java-приложений, не меняется лишь одно – отвратительный внешний вид Swing, используемый по умолчанию. Все так же он вызывает оправданный ужас и попытку вспомнить, как давно такие интерфейсы были современными. Так было и с первым вариантом, внешним видом с именем Metal, так остается и сейчас, с используемым по умолчанию внешним видом Ocean.

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

Так что, как бы тщательно вы не разработали свое приложение и его поведение, внешний вид все равно будет не слишком красив. Выход прост – использовать то самое знаменитое качество Swing, способность изменить внешний вид быстро и для всех компонентов. Замените внешний вид по умолчанию на другой. Очень много таких библиотек можно найти в сети, и большая часть из них бесплатна.

К примеру, окно входа в систему, которое было спроектировано в книге о Swing, в стандартном внешнем виде выглядит так:

Оставим в стороне «порку» цветов, шрифтов и тому подобных деталей внешнего вида по умолчанию для художников, правда, один вопрос все равно так и срывается с языка – зачем же все надписи сделаны жирными, хотя всем известно что жирной делается та надпись, что несет в себе большую смысловую нагрузку, в сравнении с остальными. Не будем углубляться в детали, а будем считать что жирные шрифты применяются по умолчанию в силу исторических причин и совместимости с первыми версиями Swing.

Теперь, для примера, возьмем великолепный внешний вид Substance и нейтральную тему SubstanceBusinessLookAndFeel. Ссылки на его GitHub есть на сайте. Получим мы следующее:

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

Если вам захочется популярное в среде программистов “темное ощущение”, можно выбрать одну из темных тем Substance, например SubstanceGraphiteLookAndFeel:

Ну а смену внешнего вида лучше всего провести еще до того, как будет создан какой-либо компонент Swing, чтобы избежать ненужной работы. Делается это простым вызовом в классе UIManager:

UIManager.setLookAndFeel(new org.pushingpixels.substance.api.skin.SubstanceBusinessLookAndFeel()); 

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

К подобранному внешнему виду нелишне также подобрать и значки, цвета и стиль сообщений, если они содержат цветной текст или разные шрифты. Хороший дизайнер (который с программистом в одном флаконе сочетается редко) здесь будет весьма кстати. Для полноты картины вы также можете отключить системные заголовки окон и использовать внешний вид окон из выбранного вами внешнего вида Swing, именнjavaо так мы и поступили в примерах здесь, это тоже просто:

JDialog.setDefaultLookAndFeelDecorated(true); JFrame.setDefaultLookAndFeelDecorated(true); 

2 – Соблюдайте рекомендации для своего внешнего вида

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

Начать можно с изучения рекомендаций для интерфейсов от Microsoft и Apple, а в книге мы кратко просмотрели что рекомендует Sun для интерфейсов, написанных с применением внешнего вида Ocean, применяемого по умолчанию в приложениях Swing. Даже следуя этим нехитрым советам, вы многократно увеличите гармонию во внешнем виде своего приложения.

Самый простой способ следовать рекомендациям – применять менеджер расположения, который автоматически вставляет между компонентами рекомендованные расстояния – к примеру, GroupLayout (даже с помощью визуального дизайнера NetBeans Matisse) или MigLayout, или вручную пользоваться классам LayoutStyle, который сообщает эти расстояния. Все это мы затрагивали во втором издании книги.

3 – Удостоверьтесь в отзывчивости интерфейса

Неакуратно скроенное Swing-приложение сразу легко заметить по подвисающему и вовремя не перерисовывающемуся интерфейсу, когда так и хочется в бешенстве молотить по клавишам Control-Alt-Del. Это верный признак выполнения тяжелых вычислений в потоке рассылки графических событий, который является единственным двигателем событий от пользователя к приложению. Загружать его любой работой кроме обновления экрана совершенно недопустимо.

При создании приложений Swing действуйте по следующим простым правилам:

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

Помочь лучше структурировать код и не загромождать его витиеватыми вызовами Runnable/SwingUtilities.invokeLater() сможет прекрасно известный инструмент SwingWorker. Не забывайте, что выполнение задач в параллельных потоках означает работу в многозадачном окружении, где стоит заботиться не только о синхронизации, но и о проблемах чрезмерного разрастания количества потоков. Вы можете прочитать мою заметку об управлении SwingWorker, где мы частично решаем эти проблемы.

4 – Обеспечьте доступ с клавиатуры

Чем более профессиональным является ваше приложение (назовем его профессиональным, если работая на нем люди зарабатывают себе деньги), тем больше стоит стремиться к тому, чтобы сократить временные задержки от выполнения всех важных операций. Вспомните среды программирования (IDE) – программисты любят те среды, которые молниеносно реагируют на их команды, вставляя новый класс, выкладывая приложение на сервер или запуская отладку. Ползание в десятке встроенных меню в поиске нужной команды напрочь отбивает желание пользоваться приложением.

Пока не придумано ничего более ускоряющего работу, чем клавиатурные сокращения. Классические Control-S чтобы сохранить работу, Control-C чтобы скопировать выделенные данные и многое другое экономят уйму времени. Чем важнее действие, тем быстрее оно должно исполняться на клавиатуре (выбирайте рядом стоящие клавиши).

Реализация клавиатурных сокращений в Swing проста, расширяема, легко меняется через файлы настроек или свойств, и встроена в базовый класс JComponent. Все происходит через карты входных событий (InputMap) и реакций на них (ActionMap). В карту входных событий вы кладете объект, описывающий клавиатурное сокращение (KeyStroke) и указываете, в каком случае оно работает – когда компонент владеет фокусом или в более общем случае. В карте реакций будет хранится действие Action, которое исполняется в случае нажатия нужного сочетания клавиш. Действия Action великолепно применяются многократно в системе меню, панелях инструментов, обычных компонентах и клавиатурных сокращениях, так что вам не придется писать одно и то же много раз.

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

5 – Включите поддержку Drag’n’Drop

С выходом Java 6 и появлением в компонентах Swing такого элементарного метода как setDragEnabled() и вспомогательного классаTransferHandler жизнь стала прекрасна как никогда и встроить поддержку технологии Drag’n’Drop стало очень просто. Достаточно просто включить ее и определить что же будет выдавать компонент когда пользователь что-то «вытаскивает» и куда он будет определять новые данные когда пользователь «бросает» что-то на него.

Сама философия Drag’n’Drop очень естественна для людей, однако как правило быстрее все сделать на клавиатуре, если приложение профессиональное и не терпит никаких временных задержек. Нельзя сказать чтобы манипуляторы (мыши и тачпады, как правило) отличались хорошим разрешением, да и наши руки неточно передают желание попасть в точку на экран. Однако для визуальных конструкторов из достаточно крупных блоков данная технология подходит как нельзя кстати.

Впрочем, включайте ее всегда – это красиво и украшает скучный быт, даже если работа намного быстрее выполняется с клавиатуры. Ваше приложение в любом случае получит жирный плюс, а работа по включению Drag’n’Drop в Swing совсем не сложна. Детали мы рассматривали в последней главе второго издания книги.

6 – Подсказывайте везде где только можно

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

Отлично помогают различные подсказки и автоматическое заполнение данных, особенно в сложных или длинных формах, полях для ввода данных в труднозапоминаемых форматах. Все это прекрасные кандидаты на то, чтобы вести себя как автоматический справочник и эксперт для очень занятого и не имеющиго времени зубрить премудрости вашего приложения пользователя. Начните с простейших подсказок Swing setTooltipText(), и незабывайте что в каждую подсказку можно встроить целую HTML-страницу, хотя конечно слишком много тоже плохо – никто не собирается читать ваши сочинения – лишь полезную выжимку, квинтэссенцию важного.

Если это возможно, в каждом поле для ввода данных должна присутствовать та или иная подсказка – начиная от простейшего варианта навроде выпадающего списка JComboBox, разрешающего редактирование, до сложных подсказок, которые автоматически анализируют каждый введенный пользователем символ и предлагают ему несколько вариантов. Вспомните среды разработки (IDE) – они намного увеличивают вашу производительность с помощью своих подсказок.

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

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

7 – Побольше эффектов и анимации с Java2D и J(X)Layer

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

Графические библиотеки Swing включают в себя Java2D, полноценную, богатую библиотеку для работы с графическими примитивами, градиентами, эффектами, преобразованиями и многим другим. Всего этого достаточно чтобы сделать ваше приложение совершенно неотразимым, учитывая достаточно хорошую производительность графических систем Java в последних выпусках JDK. Конечно, необходимо знать меру и упор делать все же на основной функциональности, но всегда есть места для небольших украшений, полуразмытых сообщений, плавных пропаданий диалогов, ускоренной прокрутки и других маленьких радостей.

При встраивании графических эффектов необходимо четко понимать процесс рисования в Swing. Второе издание сделало на этом особый упор, и мы обсуждаем процесс рисования в отдельной главе, касаясь всех его аспектов, однако по умолчанию все рисование происходит в пределах одного компонента. Использование прозрачной панели (glass pane) как правило малоперспективно, а layered pane ограничивает ваши возможности по встраиванию спецэффектов, так как не предназначена для динамически меняющих свои размеры интерфейсов. Здесь на передний план выступает компонент JXLayer, способный разместить в себе ваш интерфейс и при этом дать вам возможность покуражиться над его внешним видом. Мы также кратко описали возможности JXLayer во втором издании.

8 – Копирование и вставка

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

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

9 – Обеспечьте путь к отступлению (Undo/Redo)

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

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

К сожалению, нельзя сказать чтобы Swing обеспечивал нам встроенную мощную систему отката изменений (undo/redo). Есть базовые классы для хранения истории изменений и их манипуляции (все это можно видеть в классе javax.swing.undo), однако ничем конкретным они нам не помогут, по сути это достаточно простые контейнеры специализированных данных. Только лишь текстовые компоненты обладают некоторым уровнем отката изменений по умолчанию.

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

10 – Мгновенная реакция на изменения

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

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

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

Еще более дружественно предоставлять некий промежуточный результат вычислений, который хотя бы примерно показывает будущий конечный результат. Как только данные будут готовы или операция завершится, его можно плавно заменить на настоящий, используя при этом какие-нибудь графические эффекты, плавное проявление, полупрозрачнось и так далее. Даже приложение с большим временем задержки на любую операцию (latency) может быть очень дружественным, используя подобные техники – и таким образом намного улучшить намного более важную для пользователей характеристику системы – незамедлительное время ответа, хотя бы визуальное (responsiveness).

И – Правильно распределите пространство

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

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

Однако в большинстве случаев проблема места на экране требует творческого подхода и не решается использованием нескольких дополнительных панелей и прокрутки. Как правило, стоит заново обдумать принцип расположения компонентов на экране и их функциональную нагрузку, прежде чем решать разместить их во вкладках или панели прокрутки. Однотипные данные лучше всего разместить в таблицах, вместо списков или текстовых полей – они покажут такие данные более компактно. Вместо больших обычных кнопок JButton можно применить панели инструментов. Наконец, часть менее значимых частей интерфейса можно скрыть в сворачиваемых панелях (есть множество реализаций, к примеру, в библиотеке SwingX — JXCollapsiblePane), и скрыть их по умолчанию. Пользователь сможет открыть их в случае необходимости, ну а в большинстве случаев это не понадобится и освободит немало места.

Итоги

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

© All rights reserved. Powered by Hugo and Minimal

Добавить комментарий

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