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
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
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;
}
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
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
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.
Tsvyatko
the Telerik team
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
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