В этой статье я разберу работу с drag & drop на примере вот такого приложения:
Идея в том, чтобы из списка всех пользователей мы могли собственноручно (используя drag & drop) рассортировать пользователей по двум группам. Причем, в белый список нельзя будет добавить пользователей с отрицательной кармой.
Но ближе к делу. Прошу под кат.
«Из коробки» мы не можем сразу взять и начать использовать функциональность drag & drop (придется все реализовывать самостоятельно), но, чтобы облегчить нам жизнь, специально обученные парни из Silverlight Team в Microsoft уже все реализовали в Silverlight Toolkit.
Представлена эта функциональность в виде оберток для некоторых основных элементов управления (User Controls) с именем элемента управления + DragDropTarget (для ListBox это ListBoxDragDropTarget). Именно на примере ListBox'а, а точнее нескольких ListBox'ов, будет продемонстрирована работа с drag & drop.
Я назвал его DragAndDropTestApp, что, собственно, значения-то вовсе и не имеет.
В следующем диалоговом окне все оставляем как есть и нажимаем ОК.
Сразу добавим ссылку на Toolkit:
Предполагается, что он уже скачан и установлен.
Добавим в MainPage.xaml следующие пространства имен:
На Toolkit. И на пространство имен, в котором лежит наше приложение.
Набросаем xaml.
Каждый из трех списков представлен следующей разметкой:
TextBox с заголовком для списка элементов (со всеми стилями я баловался в App.xaml). И, собственно, сам ListBox, обернутый в ListBoxDragDropTarget. Для отображения пользователей переопределен ItemTemplate. UCUser — мой собственный элемент управления. Внутри нет ничего сверхъестественного, смотрите сами.
Задан источник (Source) для картинки и прописана привязка к данным (Binding).
Кстати, для самого пользователя есть отдельный класс. Вот его представление:
Теперь создадим по коллекции для каждого списка и сымитируем загрузку пользователей:
В качестве DataContext для MainPage установим сам MainPage. Сделал я это для того, чтобы можно было легко привязаться к данным. Выглядит это так:
Для работы Drag & Drop не хватает последнего штриха:
Установив этому свойству значение true, мы разрешаем элементу принимать данные с использованием drag & drop. Причем, установить его можно как у ListBox'а, так и у ListBoxDragDropTarget'а.
Вот что получилось:
Сейчас можно перетащить пользователя с отрицательной кармой в любой из списков.
По плану, нужно запретить перетаскивать такового в White List.
Сначала разберемся с событиями, которые генерируются при Drag-n-Drop'е:
DragEnter происходит, когда перетаскиваемый контейнер пересекает границу элемента назначения (срабатывает до события DragOver).
DragLeave — перетаскиваемый контейнер вытащили из элемента-источника.
DragOver — срабатывает, когда перетаскиваемый контейнер перемещается над элементом назначения.
Drop — перетаскиваемый контейнер был «дропнут» в элемент назначения.
Добавим обработчик события DragEnter для белого списка.
И присвоим ListBox'у имя, оно нам пригодится.
Сам обработчик:
DragEventArgs.Data возвращает объект IDataObject, который содержит в себе данные, связанные с соответствующим drag event'ом. Но напрямую к данным обратиться не получится, они помечены как Non-Public:
Для получения этих данных используется метод GetData(), который возвращает данные в формате, переданном в качестве параметра (формат можно задать строкой или типом). А узнать формат можно с помощью метода GetFormats().
В результате получился экземпляр класса ItemDragEventArgs. Этот класс содержит информацию с описанием события перетаскивания на UIElement. У него есть свойство Data, которое уже напрямую относится к перетаскиваемому контейнеру. Приводим этот объект к SelectionCollection.
Коллекция здесь используется потому что мы можем перетаскивать сразу несколько элементов
(проверить это можно очень просто, у ListBox'a свойство SelectionMode устанавливаем в Multiple).
Осталось пробежать по всей коллекции и проверить есть ли среди перетаскиваемых пользователей ребята с отрицательной кармой. Если есть, то просто запрещаем их «дропать» в белый список.
Вернуть AllowDrop в прежнее значение (в True) можно в обработчике события MouseLeave.
И последнее, для реализации возможности сортировать элементы в пределах одного списка c помощью drag-n-drop'а достаточно просто переопределить шаблон для ItemsPanel:
Скачать проект можно здесь.
Идея в том, чтобы из списка всех пользователей мы могли собственноручно (используя drag & drop) рассортировать пользователей по двум группам. Причем, в белый список нельзя будет добавить пользователей с отрицательной кармой.
Но ближе к делу. Прошу под кат.
Итак, вступление.
«Из коробки» мы не можем сразу взять и начать использовать функциональность drag & drop (придется все реализовывать самостоятельно), но, чтобы облегчить нам жизнь, специально обученные парни из Silverlight Team в Microsoft уже все реализовали в Silverlight Toolkit.
Представлена эта функциональность в виде оберток для некоторых основных элементов управления (User Controls) с именем элемента управления + DragDropTarget (для ListBox это ListBoxDragDropTarget). Именно на примере ListBox'а, а точнее нескольких ListBox'ов, будет продемонстрирована работа с drag & drop.
Создадим новое Silverlight-приложение.
Я назвал его DragAndDropTestApp, что, собственно, значения-то вовсе и не имеет.
В следующем диалоговом окне все оставляем как есть и нажимаем ОК.
Сразу добавим ссылку на Toolkit:
Предполагается, что он уже скачан и установлен.
Добавим в MainPage.xaml следующие пространства имен:
xmlns:toolkit="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Toolkit"
xmlns:my="clr-namespace:DragAndDropTestApp"
* All source code (under and below) was highlighted with Source Code Highlighter.
На Toolkit. И на пространство имен, в котором лежит наше приложение.
Набросаем xaml.
Каждый из трех списков представлен следующей разметкой:
<TextBlock
Text="Users:"
Style="{StaticResource Header}"/>
<toolkit:ListBoxDragDropTarget
Grid.Row="1"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch">
<ListBox>
<ListBox.ItemTemplate>
<DataTemplate>
<my:UCUser />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</toolkit:ListBoxDragDropTarget>
TextBox с заголовком для списка элементов (со всеми стилями я баловался в App.xaml). И, собственно, сам ListBox, обернутый в ListBoxDragDropTarget. Для отображения пользователей переопределен ItemTemplate. UCUser — мой собственный элемент управления. Внутри нет ничего сверхъестественного, смотрите сами.
- <Grid
- x:Name="LayoutRoot"
- Background="Transparent">
- <Grid.ColumnDefinitions>
- <ColumnDefinition Width="Auto" />
- <ColumnDefinition Width="4*"/>
- <ColumnDefinition Width="6*"/>
- </Grid.ColumnDefinitions>
- <Grid.RowDefinitions>
- <RowDefinition Height="Auto" />
- <RowDefinition Height="Auto" />
- </Grid.RowDefinitions>
- <Image
- Source="../Images/gnome_face_devilish.png"
- Width="48"
- Height="48"
- Margin="15, 0, 20, 0"
- Grid.RowSpan="2"/>
- <TextBlock
- Text="Nickname:"
- Grid.Column="1"/>
- <TextBlock
- Text="{Binding Path=NickName, Mode=OneWay}"
- Grid.Column="2"
- FontWeight="Bold"/>
- <TextBlock
- Text="Karma:"
- Grid.Row="1"
- Grid.Column="1" />
- <TextBlock
- Text="{Binding Path=Karma, Mode=OneWay}"
- Grid.Row="1"
- Grid.Column="2"
- FontWeight="Bold" />
- </Grid>
Задан источник (Source) для картинки и прописана привязка к данным (Binding).
Кстати, для самого пользователя есть отдельный класс. Вот его представление:
public class User
{
public string NickName { get; set; }
public int Karma { get; set; }
}
Теперь создадим по коллекции для каждого списка и сымитируем загрузку пользователей:
- public partial class MainPage : UserControl
- {
- public MainPage()
- {
- InitializeComponent();
- this.DataContext = this;
- InitializeCollections();
- LoadUsers();
- }
-
- //Объявление коллекций
- public ObservableCollection<User> Users { get; set; }
- public ObservableCollection<User> WhiteList { get; set; }
- public ObservableCollection<User> BlackList { get; set; }
-
- //Инициализация коллекций
- private void InitializeCollections()
- {
- Users = new ObservableCollection<User>();
- WhiteList = new ObservableCollection<User>();
- BlackList = new ObservableCollection<User>();
- }
-
- //Метод, имитирующий загрузку данных в коллекцию Users
- private void LoadUsers()
- {
- var r = new Random();
-
- //Добавление 20 пользователей со случайным значением кармы.
- for (int i = 1; i < 21; i++)
- {
- var newUser = new User
- {
- NickName = "Username" + i,
- Karma = r.Next(200) - 100
- };
-
- Users.Add(newUser);
- }
- }
- }
В качестве DataContext для MainPage установим сам MainPage. Сделал я это для того, чтобы можно было легко привязаться к данным. Выглядит это так:
<ListBox ItemsSource="{Binding Path=Users, Mode=TwoWay}">
Для работы Drag & Drop не хватает последнего штриха:
AllowDrop="True"
Установив этому свойству значение true, мы разрешаем элементу принимать данные с использованием drag & drop. Причем, установить его можно как у ListBox'а, так и у ListBoxDragDropTarget'а.
Вот что получилось:
Сейчас можно перетащить пользователя с отрицательной кармой в любой из списков.
По плану, нужно запретить перетаскивать такового в White List.
Сначала разберемся с событиями, которые генерируются при Drag-n-Drop'е:
DragEnter происходит, когда перетаскиваемый контейнер пересекает границу элемента назначения (срабатывает до события DragOver).
DragLeave — перетаскиваемый контейнер вытащили из элемента-источника.
DragOver — срабатывает, когда перетаскиваемый контейнер перемещается над элементом назначения.
Drop — перетаскиваемый контейнер был «дропнут» в элемент назначения.
Добавим обработчик события DragEnter для белого списка.
- <toolkit:ListBoxDragDropTarget
- AllowDrop="True"
- Grid.Row="1"
- Grid.Column="1"
- HorizontalContentAlignment="Stretch"
- VerticalContentAlignment="Stretch"
- DragEnter="ListBoxDragDropTarget_DragEnter">
- <ListBox
- ItemsSource="{Binding Path=WhiteList, Mode=TwoWay}"
- x:Name="lbWhiteList">
- <ListBox.ItemTemplate>
- <DataTemplate>
- <my:UCUser />
- </DataTemplate>
- </ListBox.ItemTemplate>
- </ListBox>
- </toolkit:ListBoxDragDropTarget>
И присвоим ListBox'у имя, оно нам пригодится.
Сам обработчик:
- private void ListBoxDragDropTarget_DragEnter(object sender, Microsoft.Windows.DragEventArgs e)
- {
- //Узнали формат
- var dataFormat = e.Data.GetFormats()[0];
-
- //Получили объект в формате ItemDragEventArgs
- var dragEventArgs = e.Data.GetData(dataFormat) as ItemDragEventArgs;
-
- //Получили коллекцию перетаскиваемых элементов
- SelectionCollection sc = dragEventArgs.Data as SelectionCollection;
-
- //Проверка каждого элемента коллекции
- foreach (var item in sc)
- {
- //Если хоть один из перетаскиваемых элементов (пользователей) имеет отрицательную карму
- //запрещаем добавлять эти элементы в белый список.
- if ((item.Item as User).Karma < 0)
- {
- lbWhiteList.AllowDrop = false;
- break;
- }
- }
- }
DragEventArgs.Data возвращает объект IDataObject, который содержит в себе данные, связанные с соответствующим drag event'ом. Но напрямую к данным обратиться не получится, они помечены как Non-Public:
Для получения этих данных используется метод GetData(), который возвращает данные в формате, переданном в качестве параметра (формат можно задать строкой или типом). А узнать формат можно с помощью метода GetFormats().
В результате получился экземпляр класса ItemDragEventArgs. Этот класс содержит информацию с описанием события перетаскивания на UIElement. У него есть свойство Data, которое уже напрямую относится к перетаскиваемому контейнеру. Приводим этот объект к SelectionCollection.
Коллекция здесь используется потому что мы можем перетаскивать сразу несколько элементов
(проверить это можно очень просто, у ListBox'a свойство SelectionMode устанавливаем в Multiple).
Осталось пробежать по всей коллекции и проверить есть ли среди перетаскиваемых пользователей ребята с отрицательной кармой. Если есть, то просто запрещаем их «дропать» в белый список.
Вернуть AllowDrop в прежнее значение (в True) можно в обработчике события MouseLeave.
И последнее, для реализации возможности сортировать элементы в пределах одного списка c помощью drag-n-drop'а достаточно просто переопределить шаблон для ItemsPanel:
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
Скачать проект можно здесь.