Это довольно сложный ответ.Дайте мне знать, если какая-то часть этого не ясна.
В настоящее время я пытаюсь решить подобную проблему.В моем случае я хочу привязать мой ListBox ItemsSource к коллекции, а затем представить каждый элемент в этой коллекции как узел , то есть перетаскиваемый объект, или соединение , то есть линию между узлами.который перерисовывает себя, когда узлы перетаскиваются.Я покажу вам свой код и детали, где, я думаю, вам, возможно, понадобится внести изменения в соответствии с вашими потребностями.
Перетаскивание
Перетаскивание выполняется путем установки вложенных свойств, принадлежащих классу Dragger
,По моему мнению, это имеет преимущество перед использованием MoveThumb
для выполнения перетаскивания, так как создание перетаскиваемого объекта не требует изменения его шаблона управления.Моя первая реализация фактически использовала MoveThumb
в шаблонах управления для достижения перетаскивания, но я обнаружил, что это сделало мое приложение очень хрупким (добавление новых функций часто нарушало перетаскивание).Вот код для Dragger:
public static class Dragger
{
private static FrameworkElement currentlyDraggedElement;
private static FrameworkElement CurrentlyDraggedElement
{
get { return currentlyDraggedElement; }
set
{
currentlyDraggedElement = value;
if (CurrentlyDraggedElement != null)
{
CurrentlyDraggedElement.MouseMove += new MouseEventHandler(CurrentlyDraggedElement_MouseMove);
CurrentlyDraggedElement.MouseLeftButtonUp +=new MouseButtonEventHandler(CurrentlyDraggedElement_MouseLeftButtonUp);
}
}
}
private static ItemPreviewAdorner adornerForDraggedItem;
private static ItemPreviewAdorner AdornerForDraggedItem
{
get { return adornerForDraggedItem; }
set { adornerForDraggedItem = value; }
}
#region IsDraggable
public static readonly DependencyProperty IsDraggableProperty = DependencyProperty.RegisterAttached("IsDraggable", typeof(Boolean), typeof(Dragger),
new FrameworkPropertyMetadata(IsDraggable_PropertyChanged));
public static void SetIsDraggable(DependencyObject element, Boolean value)
{
element.SetValue(IsDraggableProperty, value);
}
public static Boolean GetIsDraggable(DependencyObject element)
{
return (Boolean)element.GetValue(IsDraggableProperty);
}
#endregion
#region IsDraggingEvent
public static readonly RoutedEvent IsDraggingEvent = EventManager.RegisterRoutedEvent("IsDragging", RoutingStrategy.Bubble,
typeof(RoutedEventHandler), typeof(Dragger));
public static event RoutedEventHandler IsDragging;
public static void AddIsDraggingHandler(DependencyObject d, RoutedEventHandler handler)
{
UIElement uie = d as UIElement;
if (uie != null)
{
uie.AddHandler(Dragger.IsDraggingEvent, handler);
}
}
public static void RemoveIsDraggingEventHandler(DependencyObject d, RoutedEventHandler handler)
{
UIElement uie = d as UIElement;
if (uie != null)
{
uie.RemoveHandler(Dragger.IsDraggingEvent, handler);
}
}
#endregion
public static void IsDraggable_PropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if ((bool)args.NewValue == true)
{
FrameworkElement element = (FrameworkElement)obj;
element.PreviewMouseLeftButtonDown += new MouseButtonEventHandler(itemToBeDragged_MouseLeftButtonDown);
}
}
private static void itemToBeDragged_MouseLeftButtonDown(object sender, MouseEventArgs e)
{
var element = sender as FrameworkElement;
if (element != null)
{
CurrentlyDraggedElement = element;
}
}
private static void CurrentlyDraggedElement_MouseMove(object sender, MouseEventArgs e)
{
var element = sender as FrameworkElement;
if (element.IsEnabled == true)
{
element.CaptureMouse();
//RaiseIsDraggingEvent();
DragObject(sender, new Point(Mouse.GetPosition(PavilionVisualTreeHelper.GetAncestor(element, typeof(CustomCanvas)) as CustomCanvas).X,
Mouse.GetPosition(PavilionVisualTreeHelper.GetAncestor(element, typeof(CustomCanvas)) as CustomCanvas).Y));
}
}
private static void CurrentlyDraggedElement_MouseLeftButtonUp(object sender, MouseEventArgs e)
{
FrameworkElement element = sender as FrameworkElement;
element.MouseMove -= new MouseEventHandler(CurrentlyDraggedElement_MouseMove);
element.ReleaseMouseCapture();
CurrentlyDraggedElement = null;
}
private static void DragObject(object sender, Point startingPoint)
{
FrameworkElement item = sender as FrameworkElement;
if (item != null)
{
var canvas = PavilionVisualTreeHelper.GetAncestor(item, typeof(CustomCanvas)) as CustomCanvas;
double horizontalPosition = Mouse.GetPosition(canvas).X - item.ActualWidth/2;
double verticalPosition = Mouse.GetPosition(canvas).Y - item.ActualHeight/2;
item.RenderTransform = ReturnTransFormGroup(horizontalPosition, verticalPosition);
item.RaiseEvent(new IsDraggingRoutedEventArgs(item, new Point(horizontalPosition, verticalPosition), IsDraggingEvent));
}
}
private static TransformGroup ReturnTransFormGroup(double mouseX, double mouseY)
{
TransformGroup transformGroup = new TransformGroup();
transformGroup.Children.Add(new TranslateTransform(mouseX, mouseY));
return transformGroup;
}
}
public class IsDraggingRoutedEventArgs : RoutedEventArgs
{
public Point LocationDraggedTo { get; set;}
public FrameworkElement ElementBeingDragged { get; set; }
public IsDraggingRoutedEventArgs(DependencyObject elementBeingDragged, Point locationDraggedTo, RoutedEvent routedEvent)
: base(routedEvent)
{
this.ElementBeingDragged = elementBeingDragged as FrameworkElement;
LocationDraggedTo = locationDraggedTo;
}
}
Я полагаю, что Dragger
требует, чтобы объект был на Canvas
или CustomCanvas
, но нет никаких веских причин, кроме лени, дляэтот.Вы можете легко изменить его для работы с любой панелью.(Это в моем отставании!).
Класс Dragger
также использует вспомогательный метод PavilionVisualTreeHelper.GetAncestor()
, который просто поднимается по дереву визуалов в поисках соответствующего элемента.Код для этого ниже.
/// <summary>
/// Gets ancestor of starting element
/// </summary>
/// <param name="parentType">Desired type of ancestor</param>
public static DependencyObject GetAncestor(DependencyObject startingElement, Type parentType)
{
if (startingElement == null || startingElement.GetType() == parentType)
return startingElement;
else
return GetAncestor(VisualTreeHelper.GetParent(startingElement), parentType);
}
Использование класса Dragger
очень просто.Просто установите Dragger.IsDraggable = true
в соответствующей разметке элемента управления xaml.При желании вы можете зарегистрироваться на событие Dragger.IsDragging
, которое всплывает от перетаскиваемого элемента, чтобы выполнить любую обработку, которая вам может понадобиться.
Обновление позиции соединения
Мой механизм информированиясоединение, которое нужно перерисовать, немного неаккуратно и определенно требует переадресации.
Соединение содержит два свойства DependencyProperty типа FrameworkElement: Start и End.В PropertyChangedCallbacks я пытаюсь привести их как DragAwareListBoxItems (мне нужно сделать это интерфейсом для лучшего повторного использования).Если приведение выполнено успешно, я регистрируюсь на событие DragAwareListBoxItem.ConnectionDragging
.(Плохое имя, не мое!).Когда это событие срабатывает, соединение перерисовывает свой путь.
DragAwareListBoxItem фактически не знает, когда его перетаскивают, поэтому кто-то должен сказать это.Из-за позиции ListBoxItem в моем визуальном дереве он никогда не слышит событие Dragger.IsDragging
.Таким образом, чтобы сообщить, что он перетаскивается, ListBox прослушивает событие и сообщает соответствующий DragAwareListBoxItem.
Собирался выложить код для Connection
, DragAwareListBoxItem
и ListBox_IsDragging
, но я думаю, что это слишком много, чтобы его можно было прочитать здесь.Вы можете проверить проект в http://code.google.com/p/pavilion/source/browse/#hg%2FPavilionDesignerTool%2FPavilion.NodeDesigner или клонировать репозиторий с помощью hg clone https://code.google.com/p/pavilion/.Это проект с открытым исходным кодом под лицензией MIT, так что вы можете адаптировать его по своему усмотрению.Как предупреждение, стабильной версии нет, поэтому она может измениться в любое время.
Возможность подключения
Как и при обновлении подключения, я не буду вставлять код.Вместо этого я расскажу, какие классы в проекте нужно изучить и что искать в каждом классе.
С точки зрения пользователя, вот как работает создание соединения.Пользователь щелкает правой кнопкой мыши по узлу.Это вызывает контекстное меню, из которого пользователь выбирает «Создать новое соединение».Эта опция создает прямую линию, начальная точка которой укоренена в выбранном узле, а конечная точка следует за мышью.Если пользователь нажимает на другой узел, то между ними создается соединение.Если пользователь щелкает где-либо еще, соединение не создается и линия исчезает.
Два классаs участвуют в этом процессе. ConnectionManager
(который фактически не управляет какими-либо соединениями) содержит присоединенные свойства. Управляющий элемент управления устанавливает для свойства ConnectionManager.IsConnectable значение true и устанавливает для свойства ConnectionManager.MenuItemInvoker пункт меню, который должен запустить процесс. Кроме того, некоторый элемент управления в вашем визуальном дереве должен прослушивать перенаправленное событие ConnectionPending. Здесь происходит фактическое создание соединения.
Когда выбран элемент меню, ConnectionManager создает LineAdorner. ConnectionManager прослушивает событие LineAdorner LeftClick. Когда это событие срабатывает, я выполняю тестирование попаданий, чтобы найти выбранный элемент управления. Затем я вызываю событие ConnectionPending, передавая в событие аргументы двух элементов управления, между которыми я хочу создать соединение. На самом деле, подписчик мероприятия должен выполнить работу.