СоХабр закрыт.

С 13.05.2019 изменения постов больше не отслеживаются, и новые посты не сохраняются.

| сохранено

H Реализация Drag and Drop на языке C# в Visual Studio в черновиках Из песочницы Tutorial

Вступление


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

Итак, о чем же будет сегодняшний наш разговор. Как вы поняли из названия, я расскажу как реализовать Drag and Drop на языке C# в Visual Studio. Думаю многие начинающие программисты сталкивались с такой проблемой, когда существует несколько списков и вы хотите перетащить элементы из одного в другой, но придумать как это сделать или найти понятный мануал не могли. И ведь так хочется, чтоб в вашем приложении было все красиво и современно, а приходилось обходится простым выбором элемента и переносом его в другой по нажатию на кнопку. Надеюсь сегодня этим простым руководством я смогу всё-таки помочь парочек юных бойцов(я и сам такой, как мне кажется) и развеять любые проблемы связанные с реализацией данного функционала.

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

С чего все начиналось


Вкратце расскажу с чего все начиналось и что мы с вами должны будем сделать(ТЗ). Началось всё с того, что моей задачей стала реализация автомата с напитками, причём полная реализация, с учетом всех нюансов, таких как замена напитков, их цен, пополнение кассы автомата без использование кода и многое другое, что могло быть опущено при других обстоятельствах. Но я подумал, если делать, то максимально реалистичнои решил добавить drag and drop денег из кошелька в слот для денег с чем пришлось изрядно помучатся, но что придало моей программе изюминку.

Техническое задание

У нас имеется два объекта: ListView, который представляет наш кошелёк и ListBox, в котором при перетаскивании будет появляться название купюры либо монеты. Необходимо создать программу, которая позволит перетаскивать элементы из объекта ListView в ListBox без дополнительных кнопок.

Ну, что ж, вызов принят. Приступим.

Шаг 1 Создание рабочей области


Создаем новый проект Windows Form Applicarion и добавляем на форму следующие элементы:
  1. ListView. Наш кошелёк откуда мы будем перетаскивать денежку. Напомню, что все элементы будут представлены в виде изображений монет и купюр.
  2. ListBox. Список, куда мы будем всё это перетаскивать, в котором будут отображаться названия номиналов.
  3. ImageList. Именно отсюда мы получим наши изображения денег.
  4. Label. Вспомогательный элемент, который будет показывать в какую позицию будет добавлено название в ListBox.


Шаг 2 Подготовка элементов


Добавляем в ImageList картинки отсюда. Называем их так, чтоб вам потом было понятно, где какая картинка. Позже поймете для чего. В свойствах ListView есть поле View, я выбрал режим просмотра Large icon, но вы можете выбрать Small icon. В зависимости от выбора в поле LargeImageList или SmallImageList выбираете имя вашего объекта ImageList. Но это еще не всё. Теперь открываем свойство Items для всё того же ListView и добавляем новый элемент. Для него в поле значение ImageIndex изображение, а в поле Tag прописываем текст, согласно номиналу на изображении. И так добавляем все необходимые нам элементы.
Теперь все картинки отображаются в ListView и имеют «название»(Tag). Настраиваем по размерам все объекты и балуемся настройками внешнего вида на свой вкус. Советую каждый элемент называть так, чтоб было понятно для чего он.
Вот что получилось у меня:
image

Шаг 3 Код


Пришло время самой важной и самой сложной части. В целом, скопировать и вставить будет не сложно, но важно понять код! Выжпрограммисты;)

Добавляем события для объектов.
Для ListView:
  1. MouseDown
  2. MouseUp
  3. MouseMove

Для ListBox:
  1. DragOver
  2. DragDrop
  3. DragEnter
  4. DragLeave


Теперь всё готов для написания кода. Включаем внимательность, отключаем мобильные телефоны и другие отвлекающие факторы(кота тоже можно отключить).
В первую очередь добавим в начало класса переменные, которые нам пригодятся в дальнейшем. Описывать их не буду, так как их предназначение вы поймете в коде.
        private int indexOfItemUnderMouseToDrag;
        private int indexOfItemUnderMouseToDrop;        

        private Rectangle dragBoxFromMouseDown;
        private Point screenOffset;


ListView MouseDown

Это событие будем происходить в тот момент, когда вы нажмете на левую кнопку мыши в объекте ListView.
       private void ListDragSource_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e) 
        {
            // Получаем индекс выбранного элемента 
            indexOfItemUnderMouseToDrag = ListDragSource.Items.IndexOf( ListDragSource.GetItemAt(e.X, e.Y) );

            if (indexOfItemUnderMouseToDrag != ListBox.NoMatches) 
            {


                // DragSize  показывает на сколько можно сместить мышку, чтоб произошло событие
                Size dragSize = SystemInformation.DragSize;

                // Создаем прямоугольник в центре которого расположен курсор
                dragBoxFromMouseDown = new Rectangle(new Point(e.X - (dragSize.Width /2),
                                                               e.Y - (dragSize.Height /2)), dragSize);
            } 
            else
                // Сбрасываем наш прямоугольник если мышка не на каком-либо элементе в ListView.
                dragBoxFromMouseDown = Rectangle.Empty;
        }


ListView MouseUp

Отпустив кнопку мышь мы автоматически «бросаем» объект.
            private void ListDragSource_MouseUp(object sender, System.Windows.Forms.MouseEventArgs e) {
            // Сбросить прямоугольник если кнопка отпущена
            dragBoxFromMouseDown = Rectangle.Empty;
        }


ListView MouseMove

Двигая мышку мы вызываем это событие, причём работать оно начинает только если мы вышли за пределы нашего «кошелька».
Хочу обратить ваше внимание на строку:
 ListDragSource.Items.RemoveAt(indexOfItemUnderMouseToDrag);   

Ее можно так же запрограммировать не только на удаление, но и на уменьшение количества таких элементов в списке, если их несколько. В данном случае всё зависит от цели и вашей фантазии. Выбор действия(перемещение или копирование) описан в части DragOver.
        private void ListDragSource_MouseMove(object sender, System.Windows.Forms.MouseEventArgs e)
        {
            if ((e.Button & MouseButtons.Left) == MouseButtons.Left)
            {
                // Если курсор вышел за пределы ListView - начинаем перетаскивание
                if (dragBoxFromMouseDown != Rectangle.Empty &&
                    !dragBoxFromMouseDown.Contains(e.X, e.Y))
                {
                    // screenOffset служить для определения границ экрана
                    screenOffset = SystemInformation.WorkingArea.Location;

                    DragDropEffects dropEffect = ListDragSource.DoDragDrop(ListDragSource.Items[indexOfItemUnderMouseToDrag], 
                        DragDropEffects.All);

                    // Если было выбрано "перемещение" удалить элемент из ListView
                    if (dropEffect == DragDropEffects.Move)
                    {
                        //Также можете ничего не удалять или уменьшать количество(изменять какие-то параметры)
                        ListDragSource.Items.RemoveAt(indexOfItemUnderMouseToDrag);    

                        // Если в списке есть элементы вставляем новый элемент над выбранным
                        if (indexOfItemUnderMouseToDrag > 0)
                            ListDragSource.Items[indexOfItemUnderMouseToDrag - 1].Selected = true;

                        else if (ListDragSource.Items.Count > 0)
                            // Вставляем на место первого элемента
                            ListDragSource.Items[0].Selected = true;
                    }
                }
            }
        }


ListBox DragOver

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

 private void ListDragTarget_DragOver(object sender, System.Windows.Forms.DragEventArgs e) 
        {
            if ((e.KeyState & 8) == 8 && 
                (e.AllowedEffect & DragDropEffects.Copy) == DragDropEffects.Copy) 
            {
                 // Ctrl для копирования
                e.Effect = DragDropEffects.Copy;
            } 
            else if ((e.AllowedEffect & DragDropEffects.Move) == DragDropEffects.Move)  
            {
                // По умолчанию перемещение
                e.Effect = DragDropEffects.Move;
            } 
            else
                e.Effect = DragDropEffects.None;
                // Можно добавить другие кнопки или действия при перетаскивании

             // Получаем индекс над которым расположен курсор
             // Так как позиция мышки вычисляется по в координатах всего экрана конвертируем 
             // координаты относительно рабочего окна
            indexOfItemUnderMouseToDrop = 
                ListDragTarget.IndexFromPoint(ListDragTarget.PointToClient(new Point(e.X, e.Y)));

            // Изменение текста нашего вспомогательного Label
            if (indexOfItemUnderMouseToDrop != ListBox.NoMatches)
                DropLbl.Text = "Вставить над элементом #" + (indexOfItemUnderMouseToDrop + 1);
            else
                DropLbl.Text = "Вставить в конец";
        }


ListBox DragDrop

Добавление перетаскиваемого объекта в новый список. Здесь самое важное это аргумент sender, который является нашим перетаскиваемым элементом. И здесь снова включается ваша фантазия. В данном случае, я просто получаю значение Tag из полученного объекта(помните мы добавляли в поле Tag названия номиналов?). Точно таким же образом вы можете реализовать всё, что пожелает ваша душа.
private void ListDragTarget_DragDrop(object sender, System.Windows.Forms.DragEventArgs e)
        {
            // Проверим, что перетаскиваемый не пустой
            Object item = (object)e.Data.GetData(typeof(System.Windows.Forms.ListViewItem));

            // Выбираем действием в зависимости от действия(перемещения или копирования)
            if (e.Effect == DragDropEffects.Copy ||
                e.Effect == DragDropEffects.Move)
            {

                // Вставка элемента в новый список
                if (indexOfItemUnderMouseToDrop != ListBox.NoMatches)
                    ListDragTarget.Items.Insert(indexOfItemUnderMouseToDrop, ((ListViewItem)item).Tag.ToString());
                else
                    ListDragTarget.Items.Add(((ListViewItem)item).Tag.ToString());

            }
            // Так как ничего не перетаскивается, устанавливаем значение "None"
            DropLbl.Text = "None";
        }


ListBox DragEnter,DragLeave

Наконец, самое последнее, это «сброс» нашего Label. Так как если этого не сделать, то он продолжить нам показывать в какое место списка будет добавлен файл, даже если файл уже добавлен.
        private void ListDragTarget_DragEnter(object sender, System.Windows.Forms.DragEventArgs e) {
            // Устанавливаем значение "None"
            DropLbl.Text = "None";
        }

        private void ListDragTarget_DragLeave(object sender, System.EventArgs e) {
            // Устанавливаем значение "None", когда курсор вышел за пределы объекта, в который перетаскиваем элемент
            DropLbl.Text = "None";
        }


Заключение


Остается только запустить приложение и удостовериться, что всё работает(не забудьте протестировать программу с зажатым Ctrl). Теперь, сделав всё написанное выше, вы сможете адаптировать этот код под свои цели. Добавить счетчики для подсчета объектов, перетаскивать объекты не только из списков в списки, но и многое другое теперь можно реализовать в кратчайшие сроки.

Задание на дом

1. Добавьте учет количества монет в ListView(с помощью счетчика или любым другим удобным способом). Реализуйте не удаление монеты из ListView, а уменьшение их количества.
2. Добавьте любой новый объект(какой именно вы должны определить сами) и перетащите в него изображение из ListView. Каждое новое перетаскивание должно удалять предыдущее изображение.
Примечание: Можно добавить сразу ListView и добавлять в него фото в виде новых элементов. Не забудьте учесть, что если монетка(купюра) были добавлены ранее, то необходимо не добавлять новый элемент, а увеличить количество старых элементов.

На этом я заканчиваю свое руководство, надеюсь, что оно кому-то будет полезным. Спасибо за внимание!

комментарии (3)

+9
workless ,  

facepalm.jpg

+3
+4 –1
Suvitruf ,  

2014 год на дворе)

+1
nomit ,   * (был изменён)

Стало интересно, нету ли в DragEventArgs свойства Point. открыл msdn, и понял, что тут код выдран в наглую от туда.
Даже комментарии: msdn.microsoft.com/ru-ru/library/system.windows.forms.drageventargs(v=vs.110).aspx

PS. facepalm.jpg
PPS промахнулся :(