Исходник программы, показывающей пример перетаскивания элементов ListBox при помощи Drag & Drop 4-мя способами с подсветкой места, куда будет перемещен элемент списка.
Общие принципы перетаскивания
- Компонент ArrangeListbox1 использует встроенные возможности перетаскивания Delphi. События в Listbox1 используются со свойством DragType, установленным на dmAutomatic. Не считая комментариев, в этом простейшем примере всего 12 строк кода.
- OnDragStart вызывается при старте перетаскивания. Все, что нам нужно здесь сделать, это сохранить индекс элемента, по которому кликнули мышью (на который будет указывать свойство ItemIndex, я назвал его DraggedIndex).
- Поскольку мы выполняем перетаскивание в одном и том же списке, нам нужно использовать событие выхода OnDragOver, чтобы установить для параметра Accept значение True.
- И, наконец, когда пользователь отпускает левую кнопку мыши, вызывается событие OnDragEnd, передающее координаты X и Y курсора. Метод TListBox ItemAtPoint преобразует это в индекс текущего элемента, назовем его DropIndex. Затем можно вызвать метод TListBox Move, чтобы переместить элемент DraggedIndex в расположение DropIndex. Перемещение автоматически переместит предметы к месту эвакуации перетаскиваемого предмета.
В первой версии мне не понравились две вещи. Мы не даем подробных указаний о том, где будет брошен элемент, кроме кончика стрелки курсора. Некоторые программы рисуют жирную горизонтальную линию там, куда будет перемещен элемент. Мне понравилась эта идея. Кроме того, если список содержит больше элементов, чем может быть отображено на одной странице, единственный способ перетащить его дальше - это оставить элемент на одном конце области отображения, прокрутить вверх или вниз и продолжить перетаскивание. Я бы хотел, чтобы происходила какая-то «автопрокрутка», когда я перемещаю курсор выше или ниже границы списка, и в этом направлении есть неотображаемые элементы.
ArrangeListBox2 обращается к первой из этих функций, давая визуальную индикацию того, куда будет брошен элемент. Обработка события TListbox OnDrawItem содержит код для рисования текста элемента и горизонтальную линию выше или ниже текста (вверх, если перетаскиваемый элемент находится ниже точки перетаскивания, и вниз, если перетаскиваемый элемент находится выше точки перетаскивания). Обработка события OnDragOver берет на себя задачу принудительной перерисовки элементов, которые необходимо перерисовать. Как это работает: ListBoxDragOver вызывает метод Tlistbox ItemFromPoint для преобразования координат X, Y в индекс перетаскиваемого элемента. Затем он вызывает событие ListBoxDrawItem для предыдущего индекса перетаскивания, чтобы стереть жирную строку, а затем снова вызывает его для нового элемента индекса перетаскивания, чтобы нарисовать жирную линию в новом месте.
Третья версия, ArrangeListBox3, решает проблему прокрутки. Я подумал, что это невозможно решить с помощью стандартных операций перетаскивания, поэтому я заменил обработчики событий перетаскивания OnDragStart, OnDragOver и OnDragDrop на обработчики событий мыши OnMouseDown, OnMouseMove и OnMouseUp.
Событие OnMouseDown может установить флаг (Draggedflag), говорящий о том, что происходит перетаскивание, и может вызвать Mouse.SetCapture, чтобы «захватить» мышь и вызвать все движения мыши и операции нажатия кнопки для уведомления наших процедур выхода, даже если мышка покидает границы списка.
Когда мышь находится в пределах границ ListBox, события мыши работают так же, как и их аналоги перетаскивания. Когда событие OnMouseMove обнаруживает, что Draggedflag имеет значение true, указатель мыши находится над или под списком, и список может быть прокручен в этом направлении, тогда он включает новый элемент управления таймером (ScrollList), который появляется каждые 1/4 секунды для прокрутки пунктов списка в соответствующем направлении. Это продолжается до тех пор, пока не останется элементов для прокрутки, или мышь не переместится обратно в границы списка, или кнопка мыши не будет отпущена.
Событие OnMouseUp просто переместит вас к элементу, если указатель мыши указывает на элемент, сбросит Draggedflag и отключит таймер ScrollList.