C# Drag & Drop Between ListViews - c#

I'm trying to create a self-contained Winforms control called DragDropListView. It derives from ListView.
I have code that allows the user to sort list items within the control by dragging and dropping the items in the new location. I achieved that by overriding OnDragDrop, OnDragOver, OnDragEnter, OnItemDrag.
The issue I have is with dragging from one listview to a completely different listview. The event fires on the other list view as expected, but the method doesn't take a "sender" argument, so there's no good way to tell where the items are being dragged from, and no way I can figure out to actually grab the items being dragged. The current code works with stuff like "this.SelectedItems," but I'd like it to be "sender.SelectedItems".
I guess the reason there is no sender argument is that the control isn't supposed to responsible for knowing that much about its environment, and the host Form should handle the interaction between two controls, but I'm trying to build self contained controls that have this functionality, so letting it bleed onto the form isn't going to work.
Ideas?

I think you can know the ListView from the Items by listViewItem.ListView property, Check it.
I didn't test the code:
private void listView1_DragEnter(object sender, DragEventArgs e)
{
if (!e.Data.GetDataPresent(typeof(ListView.ListViewItemCollection)))
{
e.Effect = DragDropEffects.None;
return;
}
var items = (ListView.ListViewItemCollection)e.Data.GetData(typeof(ListView.ListViewItemCollection));
if (items.Count > 0 && items[0].ListView != listView1)
{
e.Effect = DragDropEffects.None;
return;
}
}
Check DragEventArgs , this sample in CodeProject [VB.Net]
Good luck!

Related

GridView doesn't scroll down to virtualized items

In my application, I need to select the newly created document(note) when I go back to library. After library item is selected, the Library must be scrolled to the selected item.
My library's OnLoaded method:
private async void OnLoaded(object sender, RoutedEventArgs e)
{
await this.ViewModel.InitializeAsync();
// CollectionViewSource of my GridView being filled
ViewModel.CollectionChanging = true;
GroupInfoCVS.Source = ViewModel.GroupsCollection;
ViewModel.CollectionChanging = false;
// Loading Last selected item - THIS CHANGES SELECTION
ViewModel.LoadLastSelection();
}
After I call the LoadLastSelection method, selection is changed successfuly (I've tested). This is the method that is called after that (in our GridView's extended control):
private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
this.SelectedItemsCount = this.SelectedItems.Count;
var newlySelectedItems = e.AddedItems;
if (newlySelectedItems.Any())
{
var item = newlySelectedItems.Last();
ScrollTo(item);
}
}
private void ScrollTo(object item)
{
UpdateLayout();
var itemUI = (FrameworkElement)this.ContainerFromItem(item);
if (itemUI != null)
{
_scrollViewer.UpdateLayout();
_scrollViewer.ChangeView(null, itemUI.ActualOffset.Y - itemUI.ActualHeight, null, false);
}
}
This also works for the most part. When itemUI is not null, the method scrolls successfully to the required item. The problems start when the items start to overflow the screen size. When items are completely hidden from the screen, they are virtualized. That means that ContainerFromItem returns null, so I can't take the offset properties. Keep in mind that this actually occurs before Library's OnLoaded method is finished.
Please, help me with some alternative to get such properties or other methods of scrolling, which will help me scroll successfully.
I've read a lot and tried using Dispatcher.RunAsync and ScrollIntoView methods, but I couldn't manage to produce any scrolling behavior. If you point me how to use them successfully, that would be a nice help too.
Here's what I've read (and tried):
ItemContainerGenerator.ContainerFromItem() returns null?
How to Know When a FrameworkElement Has Been Totally Rendered?
Is there a "All Children loaded" event in WPF
Let ListView scroll to selected item
Thanks in advance!
IMPORTANT: If you don't want to read all the conversation within the official answer, please read the solution in short here:
TemplatedControl's style had changed ScrollViewer's name from "ScrollViewer" to "LibraryScrollViewer" and that rendered ScrollIntoView method useless.
For GridView, the best way to achieve your needs is to call GridView.ScrollIntoView.
But you seem to have made similar attempts, and it does not to be successful, then the following points may help you:
1. Don't use GridView as a child element of ScrollViewer.
In your code, I see that you are calling the method of ScrollViewer.ChangeView to adjust the view scrolling, so it is speculated that you may put the GridView in the ScrollViewer, which is not recommended.
Because there is a ScrollViewer inside the GridView, and its ScrollIntoView method is to change the scroll area of the internal ScrollViewer. When there is a ScrollViewer outside, the ScrollViewer inside the GridView will lose the scrolling ability, thus making the ScrollIntoView method invalid.
2. Implement the Equals method of the data class.
If your data class is not a simple type (such as String, Int32, etc.), then implementing the Equals method of the data class will help the GridView to find the corresponding item.
Thanks.

Getting the actual dropped UIElement object on Drop Event in UWP

I'm converting an application I wrote in WinForms to UWP and as far as I can tell, the Drag n Drop functionality is slightly different. Here is my code from my WinForms application that I used to get the 'dragged' object, which is a Control called FunctionButton;
private void flowLayoutPanel_ActiveGroup_DragDrop(object sender, DragEventArgs e)
{
Function_Button draggedItem;
/* Check if the dragged item is one of the allowed dragged item TYPES. */
draggedItem = (Function_Button)e.Data.GetData(type);
if (draggedItem != null)
{
//DO STUFF
}
}
I'm currently setting my own StringDataFormats when the Drag Starts for the information i need, which I read using DataView.GetDataAsync(), although how can I get direct access to the dragged UIElement object in UWP?
I am not sure if this is the best way but it works.
First you need to handle the DragStarting event and store the UIElement that will be dragged inside the DataPackage that is exposed by the Data property. The DataPackage type seems to be very conditioned on files and file formats but fortunately it has a general purpose dictionary exposed by property Properties.
<local:YourElement CanDrag="True" DragStarting="dragStarting">
</local:YourElement>
private void dragStarting(UIElement sender, DragStartingEventArgs args)
{
args.Data.Properties.Add("anykeyworks", sender);
}
Next you handle the Drop event as follows:
<local:YourOtherElement AllowDrop="True" DragOver="dragOver" Drop="drop">
</<local:YourOtherElement>
private void drop(object sender, DragEventArgs e)
{
UIElement element = e.DataView.Properties["anykeyworks"] as UIElement;
}
private void dragOver(object sender, DragEventArgs e)
{
e.AcceptedOperation = DataPackageOperation.Copy;
}
If you do not implement the DragOver handler, the Drop event won't be fired.
As you've known, the drag and drop function in UWP is different from what in WinForms. In UWP apps, what we are dragging and dropping is not the UIElement but the DataPackage. So we can't get direct access to the dragged UIElement object.
I'm not sure why you want to get the dragged or dropped UIElement object. If you want to do some checks while dropping, I think you can check the content of the DataPackageView class which is exposed by DataView property.
For more information about drag and drop function in UWP, please see Drag and drop and also the official Drag and drop sample on GitHub.

Does Grid.Children.Clear() really remove all controls?

Edit:
Simple answer: Yes, it does. I found the error, which was, that another event handler was added, everytime the Combobox_SelectionChanged was fired. Hence, the collection looked fine, but the Items_CollectionChanged was fired multiple times. Once this was fixed, everything worked fine.
End Edit.
I've got a page with a combobox and a grid. The grid fills dynamically, when the selection in the combobox changes. I'm now observing a strange thing. When I select a value for the second time in the combobox, the childitems in the grid appear twice. I've checked the underlying collections, which look fine (i.e. only one record per item). When I jump out of the combobox_SelectionChanged method, after the Grid.Children.Clear() command, the screen looks fine, i.e. empty.
My guess is, that Grid.Children.Clear() only removes the controls from the visual tree, but the actual controls are still hanging around. Any Ideas?
private void combobox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
grItems.Children.Clear();
grItemsColumnDefinitions.Clear();
grItemsColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(200) });
}
private void Items_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
grItems.Children.Add(new ChildItemControl(e.NewItems[0]));
}
}
Edit: The whole thing is supposed to look like this (fictional - but hopefully understandable - example)
I would suggest you use the built in Databinding for WPF. You could use something like this:
<DataGrid x:Name="grItems" ItemsSource="{Binding comboboxItems}" />
Then, when you update comboboxItems your grid will automatically update too!
Here is a great article on Databinding with the DataGrid control:
http://www.wpftutorial.net/DataGrid.html
For more information about Databinding in general, here is a good article: https://msdn.microsoft.com/en-us/library/aa480224.aspx

Nested DataGridView Control

I am trying to create a nested DataGridView control where there will be two levels of nesting that are open at all times. It will look similar to the picture on this page: http://www.codeproject.com/Articles/12657/GridView-inline-Master-Detail-record-display. The difference will be that each subnesting will always be open and it is not necessary to have a way for the user to open/close each nesting. This control will only be used for displaying data, so there will be no need to modify the data directly from this control (even though the user will not modify the data directly it can still be changed).
If this can not be done with DataGridView, is there any other control that would allow for this.
If not does anyone know another way to do this. I can, but they would be tedious to implement. One way would be to add multiple DataGridView controls in sequence (2N DataGridControls for N categories). The other would add it all manually with static controls.
I don't know if this will give the answer, but it might make u some door.
//button to call function that looks for DatagridView control
private void button2_Click(object sender, EventArgs e)
{
scanDG(this);
}
private void scanDG(Control parent)
{
foreach (Control ctrl in parent.Controls)
{
if (ctrl.GetType().Name == "DataGridView")
{//If current Control is Datagridview then set Readonly to true
((DataGridView)ctrl).ReadOnly = true;
}
//If a control can contain control scan it and look for Datagridview control
if (ctrl.HasChildren) scanDG(ctrl);
}
}

Selecting a TreeView Item without invoking SelectedItemChanged?

In my app, I have a group of 3d objects and they're exposed to the user through a TreeView. When a user selects an item in the TreeView, an SelectedItemChanged event is fired, the corresponding 3d object is set to be selected and is highlighted in the 3d render window. This works fine.
What I'm having trouble with is the reverse. In a section of my code, I programatically set the selected 3d object in the scene. I want to reflect the currently selected object in the TreeView, so I run through the items until I find the corresponding one. But once I get to it, I can't find a way to make the item appear selected without having SelectedItemChanged being called, which is not what I want.
Is there a way to do this?
Thanks!
I take it you want to suppress the code in your event-handler? If so, a common way of doing this is with a boolean flag (or sometimes an int counter):
bool updatingSelected;
void SomeHandler(object sender, EventArgs args) { // or whatever
if(updatingSelected) return;
//...
}
void SomeCode() {
bool oldFlag = updatingSelected;
updatingSelected = true;
try {
// update the selected item
} finally {
updatingSelected = oldFlag;
}
}
Would it be appropriate to remove the TreeView's SelectedItemChanged event handler temporarily, and re-add it once you've performed the necessary operations? I haven't tried it myself, but it's the only other thing I can think of (Marc Gravell beat me to my original answer - I've done THAT before ;) ).
Good luck!

Categories