Delphi 3. Библиотека программиста

Перетаскивание: как это делается


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

Я остановил свой выбор на компоненте OvcCalendar, входящем в пакет VCL-компонентов Orpheus фирмы TurboPower Software. Этот мощный маленький компонент заполняет датами все ячейки, отображая при необходимости дни предыдущего и/или следующего месяца. Удаляя стандартный заголовок календаря, я могу быть уверен, что каждая ячейка соответствует какой-нибудь дате. Поскольку все ячейки имеют одинаковые размеры, вычислить абсолют ную дату по координатам мыши в OvcCalendar оказывается несложно.

Примечание

Компонент OvcCalendar вместе с остальными компонентами семейства Orpheus (специаль ная пробная версия) находится в каталоге \ORPHEUS прилагаемого CD-ROM. Последнюю пробную версию пакета всегда можно получить на Web-узле TurboPower по адресу http://www.turbopower.com.

Для своего расследования я создал приложение с единственной формой, на которой находятся текстовое поле, строковая сетка TStringGrid и календарь (и, разумеется, вездесущая кнопка для выхода из приложения). Общая идея такова: вы вводите строку в текстовом поле, затем перетаскиваете и бросаете ее на календарь, где она ассоциируется с определенной датой. Затем строка, содержащая дату и введенный текст, заносится в TStringGrid. Внешний вид формы для рабочей версии этого приложения показан на рис. 14.1.

Рис.14.1. Эксперимент с перетаскиванием

Я начал с шага 1. Было бы вполне логично перехватывать сообщения о нажатии кнопки мыши, поступающие от текстового поля. Идея оказалась удачной — но лишь в определенной степени. Перетаскивание из текстового поля приводит к непредвиденным последствиям — событие OnMouseEvent в контексте текстового поля уже имеет стандартный смысл, оно применяется для выделения текста. Используя это событие для перетаскивания, я тем самым теряю возможность выделить часть текста, перетаскивая над ней курсор.

Обработчик события OnMouseDown получает некоторые сведения — ссылку на объект, от которого поступило сообщение; параметр, идентифицирующий нажатую кнопку мыши; другой параметр, определяющий состояние клавиш Shift, Ctrl и Alt; и, наконец, координаты x и y курсора. В нашем случае сообщение поступает от текстового поля, поскольку именно в нем начинается операция перетаскивания. Координаты курсора можно игнорировать — меня не интересует, из какой именно точки поля начинается перетаскивание. Наконец, перетаскивание должно начинаться только при нажатии левой кнопки мыши (позднее выяснилось, что необходимо дополнительно отфильтровать двойные щелчки, поскольку их использование для перетаскивания приводит к странным побочным эффектам).

Все просто. Окончательный вид кода приведен в листинге 14.1.

Листинг 14.1. Обработчик события для инициализации перетаскивания

procedure TDDDemoForm.EditBoxMouseDown( Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin if (Button = mbLeft) and (EditBox.Text <> "") and not (ssDouble in Shift) then TEdit(Sender).BeginDrag(False); end;

При тестировании исходной версии обработчика я обнаружил, что перетаскивание можно начать двумя способами. Если аргумент метода BeginDrag равен True, перетаскивание начинается сразу же после нажатия кнопки мыши, а если False — откладывается до тех пор, пока мышь не сдвинется на несколько пикселей. Второй вариант показался мне более естественным. Кроме того, я добавил проверку, которая блокировала попытки перетащить пустую строку. Преобразование типа, используемое при вызове метода BeginDrag, почти всегда необходимо при работе со ссылками на объекты Sender и Source, которые передаются обработчикам событий.

Настало время заняться шагом 2. Обработчику события OnDragOver передается несколько параметров. Параметр Source определяет объект, в котором началось перетаскивание (в нашем случае — текстовое поле). Параметр Sender обозначает объект, вызвавший событие, потенциальный приемник для операции перетаскивания (в нашем случае — календарь). Параметры X и Y
содержат относительные координаты курсора мыши внутри Sender, а State определяет состояние перетаскиваемого объекта (объект входит в границы Sender, покидает их или перемещается внутри Sender). Хотя для процесса перетаски вания предоставляется курсор по умолчанию, информация о состоянии позволяет легко выбрать собственный курсор для каждой стадии процесса. Наконец, присутствует логический параметр Accept, передаваемый по ссылке.

Цель игры — на основании представленной информации принять решение о том, можно ли завершить операцию перетаскивания. Ситуация выглядит так, словно пилот маленького самолета (Source) обращается к наземному наблюдателю: «Сообщаю свои координаты относительно поля, где вы находитесь. Можно ли сбрасывать груз?»

Как оказалось, выбор OvcCalendar сделал мою работу тривиальной: для сбрасывания подходит любая точка внутри клиентской области календаря. Исходный текст приведен в листинге 14.2.

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

procedure TDDDemoForm.CalendarDragOver (Sender, Source: TObject; X, Y: Integer; State: TDragState; var Accept: Boolean); begin Accept := True; end;

Содержание раздела