This is a migrated thread and some comments may be shown as answers.

Deny Drop when List Box A contains different objects from List Box B

7 Answers 115 Views
DragAndDrop
This is a migrated thread and some comments may be shown as answers.
This question is locked. New answers and comments are not allowed.
Scott McEachern
Top achievements
Rank 1
Scott McEachern asked on 29 Apr 2011, 07:08 PM
Hello Telerik Wizards,

I'm new to your DragAndDropManager class and have been reading up on it lately. I'm able to get the samples working without any problem and have been trying to adjust some of the sample code to meet a need I have. The need is this. I have a ListBox (A) with Bird objects within it and a ListBox (B) with Dinosaurs in it. Both list's items can be dragged within their same list to reorder them, however I want to prevent items from ListBox (A) being dropped onto ListBox (B). Now, I can determine the type of object in the destination list during the DropQuery event handler, but I can't seem to figure out how to prevent the OnDropInfo event from firing.

Additionally, I can prevent the Item from ListBox (A) from being added to ListBox (B) in the OnDropInfo event (based on some typeof() logic), however at this point the Item from ListBox (A) has already been removed from ListBox (A).

I would, ideally, like to be able to set the status of the DragDropQuery event args to DropImpossible when I try to drag an Item from a ListBox whose Items are of a different than the destination.

Does this make sense?

Thanks,
- Scott

7 Answers, 1 is accepted

Sort by
0
Milan
Telerik team
answered on 02 May 2011, 07:32 AM

Hello Scott McEachern,

You can find a wide arrange of topics on Dran and Drop on our online documentation page. For that particular problem, you could use the OnDropQuery method and cancel the drop operation is the drop is invalid. For example:

// ListBox (a) OnDropQuery
private void OnDropQuery( object sender, DragDropQueryEventArgs e )
{
      var payload = e.Options.Payload as Bird;
      if(payload == null) {
         e.QueryResult = false;
      }
      else 
      {
          e.QueryResult = true;
      }
}



Greetings,
Milan
the Telerik team
Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Public Issue Tracking system and vote to affect the priority of the items
0
Scott McEachern
Top achievements
Rank 1
answered on 02 May 2011, 06:39 PM
Hello Milan,

Thanks for the quick response. I have added the logic that checks the data type of the payload object against the data type of the objects in the destination list within the OnDropQuery event, however the OnDropInfo still fires. I have ensured that e.QueryResult get's set to false in this condition.

I guess I'm confused about the purpose of the Handled property and the QueryResult property on the DragDropQueryEventArgs object. What behavior is expected when their values change?

Setting the QueryResult property (in OnDropQuery) to false doesn't prevent the OnDropInfo method from firing when the user releases the mouse when trying to drop a Bird object (A) into ListBox of Dinosaurs (B).

Q: Is it necessary to place this same conditional logic within the OnDropInfo event to prevent the item from being added to the list? If that is true, then I would argue that it is pointless to check for this condition in the OnDropQuery method, per your suggestion.

I would like to know if it is possible to deny the Drop (and NOT remove the object from List (A)) during a failed Drop AND prevent the OnDropInfo event from firing when the object and its destination list object are not the same object. Does this make sense?

I know this is possible, but I'm just not sure how Telerik would have intended for it to happen.

Here's my XAML:

<UserControl x:Class="DragDropListBoxPrototype.MainPage3"
    xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows"
    xmlns:telerik="clr-namespace:Telerik.Windows.Controls;assembly=Telerik.Windows.Controls"
    xmlns:telerikDragDrop="clr-namespace:Telerik.Windows.Controls.DragDrop;assembly=Telerik.Windows.Controls"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">
     
    <UserControl.Resources>
         
        <Style TargetType="Control" x:Key="DraggableItem">
            <Setter Property="telerikDragDrop:RadDragAndDropManager.AllowDrag" Value="True" />
            <Setter Property="telerikDragDrop:RadDragAndDropManager.AllowDrop" Value="True" />
        </Style>
 
        <Style TargetType="ListBox" x:Key="DraggableListBox">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="ListBox">
                        <Border x:Name="ListBoxBorder" CornerRadius="2"
               BorderBrush="{TemplateBinding BorderBrush}"
               BorderThickness="{TemplateBinding BorderThickness}">
                            <vsm:VisualStateManager.VisualStateGroups>
                                <vsm:VisualStateGroup x:Name="DragCue">
                                    <vsm:VisualState x:Name="NoDrop" />
                                    <vsm:VisualState x:Name="DropPossible">
                                        <Storyboard>
                                            <ObjectAnimationUsingKeyFrames
                                   Storyboard.TargetName="DropCueElement"
                                   Storyboard.TargetProperty="(UIElement.Visibility)">
                                                <DiscreteObjectKeyFrame KeyTime="0:0:0"
                                       Value="Visible" />
                                            </ObjectAnimationUsingKeyFrames>
                                            <ObjectAnimationUsingKeyFrames
                                   Storyboard.TargetName="ListBoxBorder"
                                   Storyboard.TargetProperty="(Border.BorderBrush)">
                                                <DiscreteObjectKeyFrame KeyTime="0:0:0"
                                       Value="Orange" />
                                            </ObjectAnimationUsingKeyFrames>
                                        </Storyboard>
                                    </vsm:VisualState>
                                    <vsm:VisualState x:Name="DropImpossible" />
                                </vsm:VisualStateGroup>
                            </vsm:VisualStateManager.VisualStateGroups>
                            <Grid>
                                <ScrollViewer x:Name="ScrollViewer" Padding="{TemplateBinding Padding}"
                   Background="{TemplateBinding Background}"
                   BorderBrush="Transparent" BorderThickness="0"
                         telerik:ScrollViewerExtensions.EnableMouseWheel="True">
                                <ItemsPresenter x:Name="ItemsPresenterElement" />
                            </ScrollViewer>
                             
                                <Grid x:Name="DropCueElement" HorizontalAlignment="Stretch"
                           VerticalAlignment="Top" IsHitTestVisible="False"
                           telerikDragDrop:RadDragAndDropManager.AllowDrop="True"
                           Visibility="Collapsed" Margin="-2.">
                                    <Ellipse Width="5" Height="5" Fill="DarkSlateGray"
                               HorizontalAlignment="Left" IsHitTestVisible="False" />
                                    <Ellipse Width="5" Height="5" Fill="DarkSlateGray"
                               HorizontalAlignment="Right" />
                                    <Rectangle Height="3" VerticalAlignment="Center"
                               IsHitTestVisible="False" Fill="DarkSlateGray" />
                                </Grid>
                            </Grid>
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
            <Setter Property="telerikDragDrop:RadDragAndDropManager.AllowDrop" Value="True" />
            <Setter Property="ItemContainerStyle" Value="{StaticResource DraggableItem}" />
        </Style>
 
    </UserControl.Resources>
     
    <Grid x:Name="LayoutRoot" Background="White">
        <ListBox x:Name="birdsListBox" Margin="8,8,0,8" DisplayMemberPath="Name" Style="{StaticResource DraggableListBox}" HorizontalAlignment="Left" Width="400"/>
        <ListBox x:Name="dinoListBox" Margin="0,8,8,8" DisplayMemberPath="Name" Style="{StaticResource DraggableListBox}" HorizontalAlignment="Right" Width="400"/>
 
    </Grid>
</UserControl>


Here's my code. Its pretty straight forward.

public partial class MainPage3 : UserControl
    {
        public class Bird
        {
            public string Name { get; set; }
        }
 
        public class Dinosaur
        {
            public string Name { get; set; }
        }
 
        public MainPage3()
        {
            InitializeComponent();
            birdsListBox.ItemsSource = GetBirds();
            dinoListBox.ItemsSource = GetDinos();
 
            //- Wire up drag drop for birds
            RadDragAndDropManager.AddDropQueryHandler(birdsListBox, new EventHandler<DragDropQueryEventArgs>(OnDropQuery));
            RadDragAndDropManager.AddDragQueryHandler(birdsListBox, new EventHandler<DragDropQueryEventArgs>(OnDragQuery));
            RadDragAndDropManager.AddDropInfoHandler(birdsListBox, new EventHandler<DragDropEventArgs>(OnDropInfo));
            RadDragAndDropManager.AddDragInfoHandler(birdsListBox, new EventHandler<DragDropEventArgs>(OnDragInfo));
 
            //- Wire up drag drop for dinos
            RadDragAndDropManager.AddDropQueryHandler(dinoListBox, new EventHandler<DragDropQueryEventArgs>(OnDropQuery));
            RadDragAndDropManager.AddDragQueryHandler(dinoListBox, new EventHandler<DragDropQueryEventArgs>(OnDragQuery));
            RadDragAndDropManager.AddDropInfoHandler(dinoListBox, new EventHandler<DragDropEventArgs>(OnDropInfo));
            RadDragAndDropManager.AddDragInfoHandler(dinoListBox, new EventHandler<DragDropEventArgs>(OnDragInfo));
        }
 
        // OnDragQuery event handler
        private void OnDragQuery(object sender, DragDropQueryEventArgs e)
        {
            var listBoxItem = e.Options.Source as ContentControl;
 
            if (e.Options.Status == DragStatus.DragQuery && listBoxItem != null)
            {
                var sourceControl = e.Options.Source;
 
                var dragCue = RadDragAndDropManager.GenerateVisualCue(sourceControl);
                dragCue.HorizontalAlignment = HorizontalAlignment.Left;
                dragCue.Content = sourceControl.DataContext;
                dragCue.ContentTemplate = listBoxItem.ContentTemplate;
                 
                e.Options.DragCue = dragCue;
                e.Options.Payload = new DragDropOperation()
                {
                    Payload = sourceControl.DataContext
                };
            }
 
            e.QueryResult = true;
            e.Handled = true;
        }
 
        // OnDragInfo event handler
        private void OnDragInfo(object sender, DragDropEventArgs e)
        {
            var listBox = e.Options.Source.FindItemsControlParent() as ItemsControl;
            var operation = e.Options.Payload as DragDropOperation;
             
            if (e.Options.Status == DragStatus.DragComplete)
            {
                listBox = e.Options.Source.FindItemsControlParent() as ItemsControl;
                var itemsSource = listBox.ItemsSource as IList;
                operation = e.Options.Payload as DragDropOperation;
                itemsSource.Remove(operation.Payload);
            }
        }
 
        // OnDropQuery event handler
        private void OnDropQuery(object sender, DragDropQueryEventArgs e)
        {
            var destination = e.Options.Destination;
            var operation = e.Options.Payload as DragDropOperation;
 
            if (e.Options.Status == DragStatus.DropDestinationQuery && destination is ListBoxItem && operation != null)
            {
                var listBox = destination.FindItemsControlParent() as ListBox;
 
                // Cannot place an item relative to itself:
                if (e.Options.Source == e.Options.Destination)
                {
                    return;
                }
 
                // Get the spatial relation between the destination item and the vis. root:
                var destinationTopLeft = destination.TransformToVisual(null).Transform(new Point());
 
                // Should the new Item be moved before or after the destination item?:
                bool placeBefore = (e.Options.CurrentDragPoint.Y - destinationTopLeft.Y) < destination.ActualHeight / 2;
                operation.DropPosition = placeBefore ? DropPosition.Before : DropPosition.After;
                e.QueryResult = true;
                e.Handled = true;
            }
 
            if (e.Options.Status == DragStatus.DropDestinationQuery && destination is ListBox && operation != null)
            {
                var listBox = destination as ListBox;
                var itemsCollection = listBox.ItemsSource.Cast<object>();
 
                // Check to see if the object being dragged matches the type of objects in the destination list
                Type type = listBox.ItemsSource.GetType().GetProperty("Item").PropertyType;
 
                if (type != operation.Payload.GetType())
                {
                    // Deny drag drop here some how?
                    e.QueryResult = false;
                    e.Handled = true;
 
                    return;
                }
 
                // Cannot drop the last or only item of the list box within the same list box:
                if (listBox.ItemsSource != null && (!itemsCollection.Any() || itemsCollection.Last() != operation.Payload))
                {
                    e.QueryResult = true;
                    e.Handled = true;
                }
                else
                {
                    e.QueryResult = false;
                    e.Handled = true;
                }
            }
        }
         
        // OnDropInfo event handler
        private void OnDropInfo(object sender, DragDropEventArgs e)
        {
            var destination = e.Options.Destination;
             
            if (e.Options.Status == DragStatus.DropPossible && destination is ListBoxItem)
            {
                var listBox = destination.FindItemsControlParent() as ListBox;
                VisualStateManager.GoToState(listBox, "DropPossible", false);
 
                // Get the DropCueElement:
                var dropCueElement = (VisualTreeHelper.GetChild(listBox, 0) as FrameworkElement).FindName("DropCueElement") as FrameworkElement;
                var operation = e.Options.Payload as DragDropOperation;
 
                // Get the parent of the destination:
                var visParent = VisualTreeHelper.GetParent(destination) as UIElement;
 
                // Get the spatial relation between the destination and its parent:
                var destinationStackTopLeft = destination.TransformToVisual(visParent).Transform(new Point());
                var yTranslateValue = operation.DropPosition == DropPosition.Before ? destinationStackTopLeft.Y : destinationStackTopLeft.Y + destination.ActualHeight;
                dropCueElement.RenderTransform = new TranslateTransform()
                {
                    Y = yTranslateValue
                };
 
                e.Handled = true;
            }
 
            if (e.Options.Status == DragStatus.DropPossible && destination is ListBox)
            {
                var listBox = destination as ListBox;
                VisualStateManager.GoToState(listBox, "DropPossible", false);
 
                // Get the DropCueElement:
                var dropCueElement = (VisualTreeHelper.GetChild(listBox, 0) as FrameworkElement).FindName("DropCueElement") as FrameworkElement;
                var operation = e.Options.Payload as DragDropOperation;
 
                // Get the size of the items:
                var itemsPresenter = listBox.GetTemplateChild<FrameworkElement>("ItemsPresenterElement");
                var panel = VisualTreeHelper.GetChild(itemsPresenter, 0) as Panel;
 
                if (panel != null)
                {
                    var yTranslateValue = panel.ActualHeight;
                    dropCueElement.RenderTransform = new TranslateTransform()
                    {
                        Y = yTranslateValue
                    };
                }
            }
 
            // Hide the DropCue:
            if (e.Options.Status == DragStatus.DropImpossible || e.Options.Status == DragStatus.DropCancel || e.Options.Status == DragStatus.DropComplete)
            {
                var listBox = destination as ListBox;
                if (listBox == null)
                {
                    listBox = e.Options.Destination.FindItemsControlParent() as ListBox;
                }
 
                VisualStateManager.GoToState(listBox, "DropImpossible", false);
            }
 
            // Place the item:
            if (e.Options.Status == DragStatus.DropComplete && destination is ListBoxItem)
            {
                var listBox = e.Options.Destination.FindItemsControlParent() as ListBox;
                var itemsSource = listBox.ItemsSource as IList;
                var destinationIndex = itemsSource.IndexOf(e.Options.Destination.DataContext);
                var operation = e.Options.Payload as DragDropOperation;
                var insertIndex = operation.DropPosition == DropPosition.Before ? destinationIndex : destinationIndex + 1;
 
                itemsSource.Insert(insertIndex, operation.Payload);
                listBox.Dispatcher.BeginInvoke(() =>
                {
                    listBox.SelectedIndex = insertIndex;
                });
            }
 
            if (e.Options.Status == DragStatus.DropComplete && destination is ListBox)
            {
                var listBox = destination as ListBox;
                var itemsSource = listBox.ItemsSource as IList;
                var operation = e.Options.Payload as DragDropOperation;
                 
                itemsSource.Add(operation.Payload);
                listBox.Dispatcher.BeginInvoke(() =>
                {
                    listBox.SelectedIndex = itemsSource.Count-1;
                });
            }
        }
         
         
 
        public static ObservableCollection<Bird> GetBirds()
        {
            ObservableCollection<Bird> items = new ObservableCollection<Bird>();
            items.Add(new Bird() { Name = "Meadow Lark"});
            items.Add(new Bird() { Name = "Crow"});
            items.Add(new Bird() { Name = "Sparrow"});
            items.Add(new Bird() { Name = "Canary"});
            items.Add(new Bird() { Name = "Robin"});
            items.Add(new Bird() { Name = "Oriole"});
            items.Add(new Bird() { Name = "Pheasant"});
            items.Add(new Bird() { Name = "Magpie"});
            items.Add(new Bird() { Name = "Cardinal"});
            items.Add(new Bird() { Name = "Parrot"});
            items.Add(new Bird() { Name = "Finch"});
            items.Add(new Bird() { Name = "Raven"});
            items.Add(new Bird() { Name = "French Hen"});
            items.Add(new Bird() { Name = "Duck"});
            items.Add(new Bird() { Name = "Cornish Game Hen"});
            items.Add(new Bird() { Name = "Chicken"});
            items.Add(new Bird() { Name = "Ostrich"});
            items.Add(new Bird() { Name = "Roadrunner" });
 
            return items;
        }
 
        public static ObservableCollection<Dinosaur> GetDinos()
        {
            ObservableCollection<Dinosaur> items = new ObservableCollection<Dinosaur>();
            items.Add(new Dinosaur() { Name = "T-Rex" });
            items.Add(new Dinosaur() { Name = "Triceritops"});
            items.Add(new Dinosaur() { Name = "Brontosarus"});
            items.Add(new Dinosaur() { Name = "Velociraptor"});
            items.Add(new Dinosaur() { Name = "Alligator"});
            items.Add(new Dinosaur() { Name = "Brachiasaurus"});
            items.Add(new Dinosaur() { Name = "Stegasauraus"});
            items.Add(new Dinosaur() { Name = "Pteridactyl"});
             
            return items;
        }
0
Scott McEachern
Top achievements
Rank 1
answered on 02 May 2011, 07:24 PM
Additionally, and for what it is worth, I think it would be helpful if, from a development perspective we could set the DragDropEventArgs Options' Status property. Right now it is read only.

In this event handler I can access the destination of the Drop (meaning, the place where the user intends to drop their object). I can then check to see if this destination object and the payload object are the same object and can then deny the drag / drop process from here. However, the DragDropEventArgs object doesn't contain a QueryResult object that can be set to "false" (I'm still not sure what this does anyway), and I can't explicitly set the DragStatus to DropImpossible as I would like to.

// OnDragInfo event handler
private void OnDragInfo( object sender, DragDropEventArgs e )
{
   // if we are dropping on the appropriate listbox, then remove the item from the first listbox.
   if ( e.Options.Status == DragStatus.DragComplete )
   {
       var itemsControl = e.Options.Source.FindItemsConrolParent() as ItemsControl;
       var itemsSource = itemsControl.ItemsSource as IList;
       itemsSource.Remove( e.Options.Payload );
   }
}

I know that I am probably going about this in a manner that wasn't intended, but the object structure and the pattern employed are confusing to me. The documentation doesn't offer much, either in the way of explaining the nature of the properties of the EventArgs objects.

Please let me know if what I am asking for is prohibited and if there is a proper way of going about this.

Thanks
0
Scott McEachern
Top achievements
Rank 1
answered on 02 May 2011, 07:40 PM
One more thing:

DragCancel - the source is notified that the DragDrop has been cancelled. For example, the user may press Esc while dragging to cancel the event or the object may be released over a target that does not accept it.

I should add that I'd like to know I can have the target list "not accept" an object of another type. I think this might be the best approach. I see no documentation explaining how or when this two-way handshake process occurrs.

Thanks,
- Scott
0
Tsvyatko
Telerik team
answered on 03 May 2011, 08:53 AM
Hello Scott McEachern,

 I have checked the code sent and found that indeed the drop operation is executed when dragging between the two listboxes. The reason for that is the code in the DropQuery - it check for payload type only when destination is Listbox, but not when destination is Listboxitem. Adding the code in this case result in correct behavior.

About the other questions I will try to summarize the drag drop mechanism in few words:

 - dragquery and dropquery  are raised before draginfo and dropinfo to check whether the current operation is allowed or not. This is done by using QueryResult. If the result is false the corresponding info method is not called.

 - The progress of the drag operations can be tracked through Options.Status property. It cannot be changed from outside, since it reflects the final result from the mouse position, state and query results in the drag/drop query methods. As an example, I have added status textblock that displays the dragstatus in DragInfo method. it will display DropPossible,DropImpossible,DragComplete,DragCancel depending on current drag state.

More information about the events and statuses can be found in these help articles:

http://www.telerik.com/help/silverlight/raddraganddrop-events-overview.html
http://www.telerik.com/help/silverlight/raddragdrop-events-eventargs.html

Also, you can check the attached project using the code posted earlier in the thread.

Let us know if you have any further questions.

Kind regards,
Tsvyatko
the Telerik team
Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Public Issue Tracking system and vote to affect the priority of the items
0
Scott McEachern
Top achievements
Rank 1
answered on 03 May 2011, 04:31 PM
Hello Tsvyatk,

That adjustment made a big difference. I didn't realize that I had to check for both ListBox and ListBoxItems. It seems as though checking for a ListBox only should be sufficient, but now it seems that ListBoxItems are almost always found to be the drop destination in this example. In what cases would the ListBox be found? When there are no items in the list?

Thanks again, this is working now.

Thanks,
- Scott
0
Milan
Telerik team
answered on 04 May 2011, 07:01 AM
Hi Scott McEachern,

I am glad that all is fine now. You are correct, the ListBox will be found when you are dropping on empty area.


Regards,
Milan
the Telerik team
Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Public Issue Tracking system and vote to affect the priority of the items
Tags
DragAndDrop
Asked by
Scott McEachern
Top achievements
Rank 1
Answers by
Milan
Telerik team
Scott McEachern
Top achievements
Rank 1
Tsvyatko
Telerik team
Share this question
or