2

I am trying to implement Drag-and-Drop in MVVM, but when I try to drag the item, the event doesn't get triggered. However, when I drag the items from outside, it starts working.

I want the first case, make it working while dragging. Is there any special way? I am using behavior and relay command for this.

Here is the code that I am using, Please let me know where I am wrong:

XAML

<Grid x:Name="MainGrid" Width="{Binding ElementName=ProjectWindow,Path=ActualWidth}">
    <ListBox   x:Name="icTodoList"  Background="#FFF3800C"  Canvas.Top="25" Height="600" Width="{Binding ElementName=gd,Path=ActualWidth}" BorderThickness="0" BorderBrush="{x:Null}">
        <ListBox.Resources>
            <SolidColorBrush  x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="#FFF3800C"  Opacity="0.2"/>
            <SolidColorBrush x:Key="{x:Static SystemColors.InactiveSelectionHighlightBrushKey}"  Color="#FFF3800C" />
        </ListBox.Resources>
        <ListBox  Name="InnerListBox" dd:DragOverBehaviour.DragOver="{Binding DragOver}"    local2:PhasesDragDropViewModel.ListBox="{Binding ElementName=InnerListBox}" Margin="0,0,0,0" Height="550" AllowDrop="True" ScrollViewer.HorizontalScrollBarVisibility="Auto">
            <ListBox.ItemsPanel>
                <ItemsPanelTemplate>
                    <WrapPanel IsItemsHost="True" Orientation="Horizontal"  />
                </ItemsPanelTemplate>
            </ListBox.ItemsPanel>
        </ListBox>
    </ListBox>
</Grid>

Model class

public static class DragOverBehaviour
{
    public static readonly DependencyProperty DragOver = EventBehaviourFactory.CreateCommandExecutionEventBehaviour(UIElement.DragOverEvent, "DragOver", typeof(DragOverBehaviour));

    public static void SetDragOver(DependencyObject o, ICommand value)
    {
        o.SetValue(DragOver, value);
    }

    public static ICommand GetDragOver(DependencyObject o)
    {
        return o.GetValue(DragOver) as ICommand;
    }
}

View Model

#region DragOverAction
private RelayCommand<object> m_cmdDragOver;
public ICommand DragOver
{
    get { return m_cmdDragOver ?? (m_cmdDragOver = new RelayCommand<object>(DragOverAction, delegate { return true; })); }
}
private void DragOverAction(object sender)
{

}
#endregion
Gábor Bakos
  • 8,982
  • 52
  • 35
  • 52
Harjeet Singh
  • 388
  • 2
  • 6
  • 22
  • first try local2:PhasesDragDropViewModel.ListBox="{Binding RelativeSource={RelativeSoruce Self}" /> – eran otzap Jul 29 '14 at 12:45
  • @eranotzap still not working. – Harjeet Singh Jul 29 '14 at 12:52
  • You have got a problem here : local2:PhasesDragDropViewModel.ListBox="{Binding ElementName=InnerListBox}" You are not describing the path here which is the main necessity of binding. – Vishal Aug 02 '14 at 14:27
  • Also have a look at the Output Window in Visual Studio to catch the Binding Errors. – Vishal Aug 02 '14 at 14:28
  • I do not see any `DoDragDrop()` call, so wondering how you are initiating a drag. – pushpraj Aug 05 '14 at 03:34
  • You should scrap this and switch to Gong Drag and Drop for WPF. You can get it with Nuget and it is on github https://github.com/punker76/gong-wpf-dragdrop It also supports MVVM etc. very well – Alan Aug 05 '14 at 16:08

2 Answers2

4

enter image description here

It's not much fun to implement DragDrop in WPF by yourself, but it's all possible of course, and the problem with using libraries is always limitations at some point. I've attached a sample that shows how to link your behavior to the viewmodel. You need to manually initiate DoDragDrop, as noted.

What is not included in the sample: drop logic, which tells you what happens on the drop (reorder items if DragDrop was inside same listbox, add if it was outside). If you want a slightly more complex and complete sample, have a look at my answer here.

Xaml:

<UserControl x:Class="WpfApplication1.Controls.DragOverDemo"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:vm="clr-namespace:WpfApplication1.ViewModels"
             xmlns:beh="clr-namespace:WpfApplication1.Behavior"
             xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity">
    <UserControl.DataContext>
        <vm:DragOverDemoViewModel />
    </UserControl.DataContext>

    <StackPanel x:Name="MainPanel">
        <TextBlock Text="{Binding State}" Margin="5" />
        <ListBox x:Name="icTodoList" Background="#FFF3800C" Canvas.Top="25" Height="600" BorderThickness="0" BorderBrush="{x:Null}">
            <ListBox.Resources>
                <SolidColorBrush  x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="#FFF3800C"  Opacity="0.2"/>
                <SolidColorBrush x:Key="{x:Static SystemColors.InactiveSelectionHighlightBrushKey}"  Color="#FFF3800C" />
            </ListBox.Resources>

            <ListBox x:Name="InnerListBox" ItemsSource="{Binding Items}" Margin="0" Height="550" Width="250" ScrollViewer.HorizontalScrollBarVisibility="Disabled">
                <i:Interaction.Behaviors>
                    <beh:DragDropBehavior DragOverCommand="{Binding DragOverCommand}" DropCommand="{Binding DropCommand}" />
                </i:Interaction.Behaviors>
                <ListBox.ItemsPanel>
                    <ItemsPanelTemplate>
                        <WrapPanel IsItemsHost="True" Orientation="Horizontal"  />
                    </ItemsPanelTemplate>
                </ListBox.ItemsPanel>
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <Border MinHeight="30" MinWidth="100" BorderThickness="1" BorderBrush="DarkGray" Margin="2" >
                            <TextBlock Text="{Binding Name}" VerticalAlignment="Center" HorizontalAlignment="Center"/>
                        </Border>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>
        </ListBox>
    </StackPanel>
</UserControl>

DragDropBehavior:

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interactivity;
namespace WpfApplication1.Behavior
{
    public class DragDropBehavior : Behavior<ItemsControl>
    {
        private bool _isDragging;
        private IDataObject _dataObject;
        private Type _dataType;

        public ICommand DragOverCommand { get { return (ICommand)GetValue(DragOverCommandProperty); } set { SetValue(DragOverCommandProperty, value); } }
        public static readonly DependencyProperty DragOverCommandProperty = DependencyProperty.Register("DragOverCommand", typeof(ICommand), typeof(DragDropBehavior), new PropertyMetadata(null));

        public ICommand DropCommand { get { return (ICommand)GetValue(DropCommandProperty); } set { SetValue(DropCommandProperty, value); } }
        public static readonly DependencyProperty DropCommandProperty = DependencyProperty.Register("DropCommand", typeof(ICommand), typeof(DragDropBehavior), new PropertyMetadata(null));


        protected override void OnAttached()
        {
            this.AssociatedObject.AllowDrop = true;
            this.AssociatedObject.PreviewMouseLeftButtonDown += AssociatedObject_PreviewMouseLeftButtonDown;
            this.AssociatedObject.PreviewMouseMove += AssociatedObject_PreviewMouseMove;
            this.AssociatedObject.PreviewMouseLeftButtonUp += AssociatedObject_PreviewMouseLeftButtonUp;
            base.OnAttached();
        }

        void AssociatedObject_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            // get data from mouse position
            ItemsControl itemsControl = (ItemsControl)sender;
            Point p = e.GetPosition(itemsControl);
            object data = itemsControl.GetDataObjectFromPoint(p);

            if (data != null)
            {
                _dataType = data.GetType();
                _dataObject = new DataObject(_dataType, data);
                _isDragging = true; // valid data found, set dragging to true
            }
        }

        void AssociatedObject_PreviewMouseMove(object sender, MouseEventArgs e)
        {
            if (_isDragging)
            {
                // let viewmodel know of the drag
                this.DragOverCommand.Execute(_dataObject.GetData(_dataType)); 

                // execute drag
                DragDropEffects eff = DragDrop.DoDragDrop(this.AssociatedObject, _dataObject, DragDropEffects.Copy | DragDropEffects.Move);
                // thread waits for DragDrop to finish...

                Drop();
            }
        }

        void AssociatedObject_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            _isDragging = false;
        }

        private void Drop()
        {
            // let viewmodel know of the drop
            this.DropCommand.Execute(_dataObject.GetData(_dataType));

            _isDragging = false;
        }
    }
}

ViewModel:

using System;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Input;

namespace WpfApplication1.ViewModels
{
    public class DragOverDemoViewModel : NotifyBase
    {
        public class ItemModel : NotifyBase
        {
            public string Name { get { return base.GetValue(() => Name); } set { base.SetValue(() => Name, value); } }
        }

        public ObservableCollection<ItemModel> Items { get; private set; }
        public ICommand DragOverCommand { get { return base.GetValue(() => DragOverCommand); } set { base.SetValue(() => DragOverCommand, value); } }
        public ICommand DropCommand { get { return base.GetValue(() => DropCommand); } set { base.SetValue(() => DropCommand, value); } }
        public string State { get { return base.GetValue(() => State); } set { base.SetValue(() => State, value); } }

        public DragOverDemoViewModel()
        {
            this.Items = new ObservableCollection<ItemModel>() 
            {
                new ItemModel() { Name = "Item 1"}, 
                new ItemModel() { Name = "Item 2"},
                new ItemModel() { Name = "Item 3"},
                new ItemModel() { Name = "Item 4"},
                new ItemModel() { Name = "Item 5"},
                new ItemModel() { Name = "Item 6"},
                new ItemModel() { Name = "Item 7"},
            };

            this.DragOverCommand = new ActionCommand<ItemModel>(DragOver);
            this.DropCommand = new ActionCommand<ItemModel>(Drop);
        }

        private void DragOver(ItemModel item) { this.State = string.Format("Dragging {0}...", item.Name); }
        private void Drop(ItemModel item) { this.State = string.Format("Dropped {0}.", item.Name); }
    }

    public class ActionCommand<T> : ICommand
    {
        public event EventHandler CanExecuteChanged;
        private Action<T> _action;

        public ActionCommand(Action<T> action)
        {
            _action = action;
        }

        public bool CanExecute(object parameter) { return true; }

        public void Execute(object parameter)
        {
            if (_action != null)
            {
                var castParameter = (T)Convert.ChangeType(parameter, typeof(T));
                _action(castParameter);
            }
        }
    }
}
Community
  • 1
  • 1
Mike Fuchs
  • 12,081
  • 6
  • 58
  • 71
0

First of all, this is how I understand your problem (correct me if I am wrong): when you drag an object from an outside application over your list box, the DragOver event fires. However, when you drag an object from inside the application, the DragOver event does not fire.

If you want to drag objects from within your application, you need to register them as a drag source:

using System.Windows;
using System.Windows.Input;

...

dragSourceControl.MouseMove += dragSourceControl_MouseMove;

...

private static void dragSourceControl_MouseMove(object sender, MouseEventArgs e)
{
    if (e.LeftButton != MouseButtonState.Pressed)
        return;
    var data = new DataObject();
    data.SetData("some data");
    DragDrop.DoDragDrop((UIElement)sender, data, DragDropEffects.Move);
}
AJ Richardson
  • 6,610
  • 1
  • 49
  • 59