I am trying to handle some drag and drop functionality for values in my TreeView.
I am specifically targeting the Drop method. I want to be able to access the Parent object of the element being dragged.
The
TreeView.SelectedItem
is the property of the TreeView that I suspect I will need. However, this property is of type string, so accessing the Parent object is proposing a challenge.
I tried using the
VisualTreeHelper.GetParent(DependencyObject)
method but there doesn't seem to exist a conversion from string to DependencyObject and vice versa.
I am able to access the ParentControl of the target, but not the source and I can't understand why.
This is my Drop Method, as well as the MouseMove. Any help would be appreciated.
private void TreeViewItem_Drop(object sender, DragEventArgs e)
{
if (sender is TreeViewItem target)
{
string source = e.Data.GetData(typeof(string)) as string;
string dropTarget = target.DataContext as string;
var parentControl = VisualTreeHelper.GetParent((TreeViewItem)(sender));
var parentElement = ((VirtualizingStackPanel)(parentControl)).DataContext;
if (parentElement is GroupDecision decision)
{
if (decision.Decisions.Contains(source) && (decision.Decisions.Contains(dropTarget)))
{
int sourceIndex = decision.Decisions.IndexOf(source);
int targetIndex = decision.Decisions.IndexOf(dropTarget);
SwapElements(sourceIndex, targetIndex, decision.Decisions);
}
else
{
if (!decision.Decisions.Contains(source))
{
decision.Decisions.Add(source);
// Remove from old
}
}
}
}
private void TreeViewItem_MouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
{
if (TreeView.SelectedItem != null)
{
DragDrop.DoDragDrop(this, TreeView.SelectedItem, DragDropEffects.Move);
}
}
}
The treeview.selecteditem in your dodragdrop there is what ends up in your data when you do your e.Data.GetData. Since you cast it to a string, it's just a string floating about in memory. Adrift on an ocean of not-in-the-visual-tree. It has no parent control, because it's a string.
The sender is whatever control you dropped it on, so that's not the thing you dragged from. It's what you're dragging to.
But... what you pass as data can be whatever you give it.
That can be a simple string or a complicated business object with many properties on it.
Whatever you want when you drop, is best stashed away there in that data.
In the answer here:
WPF drag and drop and data types
That's a viewmodel.
I don't really follow what you want out of this but instead of
DragDrop.DoDragDrop(this, TreeView.SelectedItem, DragDropEffects.Move);
You should put whatever is required as that second parameter.
DragDrop.DoDragDrop(this, AmoreComplexObjectWithMoreData, DragDropEffects.Move);
Define a class which has properties matching whatever you need. New it up and set those as you start dragging. When you drop, you have everything you need then. Cast your drag data back to said class.
A string has no visual ancestor. Maybe you want to pass the TreeViewItem container of the selected item to the DoDragDrop method?:
private void TreeViewItem_MouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
{
if (TreeView.SelectedItem != null)
{
DragDrop.DoDragDrop(this,
TreeView.ItemContainerGenerator.ContainerFromItem(TreeView.SelectedItem) as TreeViewItem,
DragDropEffects.Move);
}
}
}
You could then get a reference to the visual container in the Drop event handler and traverse the visual tree as you like:
TreeViewItem source = e.Data.GetData(typeof(TreeViewItem)) as TreeViewItem;
string sourceObject = source.DataContext as string;
//...
Related
I‘ve one ContextMenuStrip attached to two controls (DataGridView).
In the ToolStripMenuItem click event, currently I’ve used:this.ActiveControl.Name to get the active GridView control name;
This is fine if I first select the GridView cell and than Rt. click on it to invoke the ContextMenu
Case: sometime if GridView control is not a active control and cell is pre-selected, than context menu Item click not worked accordingly.
Is there any way to get the owner name that initiate the context menu Item click event?
Currently, In the ToolStripMenuItem click event, I've manage to get the original caller (i.e. DataGridView) with this code:
private void CopytoolStripMenuItem1_Click(object sender, EventArgs e)
{
var grid = new DataGridView();
switch (this.ActiveControl.Name)
{
case "dGVEL1":
{
grid=dGVEL1;
break;
}
case "dGVEL2":
{
grid=dGVEL2;
break;
}
}
if (grid == null) return;
DataObject data = grid.GetClipboardContent();
Clipboard.SetDataObject(data);
}
Finally I've Resolved the issue..
The complete solution is
private void CopytoolStripMenuItem1_Click(object sender, EventArgs e)
{
ToolStripDropDownItem item = sender as ToolStripDropDownItem;
if (item == null) // Error
return;
ContextMenuStrip strip = item.Owner as ContextMenuStrip;
var grid = strip.SourceControl as DataGridView;
if (grid == null) // Control wasn't a DGV
return;
switch (grid.Name)
{
case "dGVEL1":
{
grid=dGVEL1;
break;
}
case "dGVEL2":
{
grid=dGVEL2;
break;
}
}
if (grid == null) return;
DataObject data = grid.GetClipboardContent();
Clipboard.SetDataObject(data);
}
Is there any way to get the owner name that initiate the context menu Item click event?
I worked off of the solution you found and simplified it down. Hopefully, this can help some future readers who are looking for a more general solution.
It looks like you go through quite a bit to get ahold of the ContextMenuStrip which is already being passed as sender (granted you will still need to cast it). The following is a more general solution to this issue and it's a bit more simple.
private void contextMenuStrip_ItemClicked(object sender, ToolStripItemClickedEventArgs e) {
//Cast the parent as a context menu strip so we can access 'sourceControl' attribute
ContextMenuStrip strip = (ContextMenuStrip) sender;
var stripParent = strip.SourceControl;
//print the name of the 'parent' (Control that called the context menu)
System.Diagnostics.Debug.WriteLine("Called From: " + stripParent.Name);
}
Hy!
I would like to create an ObjectListView, where you can delete items with a ContextMenu.
So basicly I used to delete it by getting OLV.SelectedIndex, and then deleting from the list OLV is based on, and re-setting the OLV objects.
Then I realized, if I sort the OLV, then delete an item, it deletes another item, since the selected item index does not equals the index in the list.
With OLV CellRightClick event I can get the object behind the clicked item (e.Model),but I dont know how to pass it to the ContextMenu click event handler.
Subjects is a List.
private void subjectListView_CellRightClick(object sender, BrightIdeasSoftware.CellRightClickEventArgs e)
{
if (subjectsListView.SelectedIndex != -1)
{
ContextMenu cm = new ContextMenu();
cm.MenuItems.Add("Delete", new EventHandler(DeleteItem));
subjectsListView.ContextMenu = cm;
}
}
void DeleteItem(object sender, EventArgs e)
{
//get the Subject object, which was clicked on
Subjects.RemoveAt(subjectsListView.SelectedIndex);
subjectsListView.SetObjects(Subjects);
}
So basically I want to get the object (not the index) when the ContextMenus "Delete" item is clicked.
Also, I feel like there is an easier way to do this.
Thanks for the answer.
I would just assign an appropriate ContextMenuStrip from the designer to the ObjectListView.ContextMenuStrip property and then handle the click of the corresponding "Delete" click like this:
private void deleteToolStripMenuItem_Click(object sender, EventArgs e) {
if (objectListView1.SelectedObject != null) {
objectListView1.RemoveObject(objectListView1.SelectedObject);
}
}
Or is there a requirement I am missing from your question?
I am trying to make a method where i can get the element that was clicked. In App.xaml.cs i have method OnPreviewMouseDown that is activated for each click in application.
Now i need some help with getting element name from sender (if this is even possible)
static void OnPreviewMouseDown(object sender, MouseButtonEventArgs e)
{
if (e.RightButton == MouseButtonState.Pressed)
{
Control control = (Control)sender; // Sender gives you which control is clicked.
string name = control.Name.ToString(); //returns main window name, not element....
string typee = sender.GetType().ToString(); //returns PPPMain.Views.MainWindow
}
}
I tried this and some other suggestions from internet but didn't find any solutions...
Thanks in advance!
Use the OriginalSource property of the MouseButtonEventArgs:
var element = e.OriginalSource as FrameworkElement;
var name = element?.Name;
You could try using this code inside your event:
VisualTreeHelper.HitTest(this, e.GetPosition(this));
you can find more in this other topic: WPF Get Element(s) under mouse
I am trying write Drag and Drop functionality using MVVM which will allow me to drag PersonModel objects from one ListView to another.
This is almost working but I need to be able to get the ItemsSource of the source ListView from the DragEventArgs which I cant figure out how to do.
private void OnHandleDrop(DragEventArgs e)
{
if (e.Data != null && e.Data.GetDataPresent("myFormat"))
{
var person = e.Data.GetData("myFormat") as PersonModel;
//Gets the ItemsSource of the source ListView
..
//Gets the ItemsSource of the target ListView and Adds the person to it
((ObservableCollection<PersonModel>)(((ListView)e.Source).ItemsSource)).Add(person);
}
}
Any help would be greatly appreciated.
Thanks!
I found the answer in another question
The way to do it is to pass the source ListView into the DragDrow.DoDragDrop method ie.
In the method which handles the PreviewMouseMove for the ListView do-
private static void List_MouseMove(MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
{
if (e.Source != null)
{
DragDrop.DoDragDrop((ListView)e.Source, (ListView)e.Source, DragDropEffects.Move);
}
}
}
and then in the OnHandleDrop method change the code to
private static void OnHandleDrop(DragEventArgs e)
{
if (e.Data != null && e.Data.GetDataPresent("System.Windows.Controls.ListView"))
{
//var person = e.Data.GetData("myFormat") as PersonModel;
//Gets the ItemsSource of the source ListView and removes the person
var source = e.Data.GetData("System.Windows.Controls.ListView") as ListView;
if (source != null)
{
var person = source.SelectedItem as PersonModel;
((ObservableCollection<PersonModel>)source.ItemsSource).Remove(person);
//Gets the ItemsSource of the target ListView
((ObservableCollection<PersonModel>)(((ListView)e.Source).ItemsSource)).Add(person);
}
}
}
I am getting some erratic behavior from a ContextMenuStip:
private void lstModules_MouseMove(object sender , MouseEventArgs e)
{ mouse = e.Location; }
private void lstModules_MouseDown(object sender , MouseEventArgs e)
{
ListViewItem item = null;
if((hitTest = lstModules.HitTest(mouse)) != null)
item = hitTest.Item;
switch (e.Button)
{
case MouseButtons.Right:
if (item != null)
{
// valid item selection
ShowModuleDetails(item.Name);
lstModules.ContextMenuStrip = mnuContext_Module;
}
else
{
// right-click - no item selection
lblModuleDetails.Text = string.Empty;
lstModules.ContextMenuStrip = mnuContext_Desktop;
}
lstModules.ContextMenuStrip.Show(lstModules , mouse);
break;
case MouseButtons.Left:
if (item != null)
{ ShowModuleDetails(item.Name); }
break;
}
}
private void ShowModuleDetails(string modName)
{
// get module details from dictionary
lblModuleDetails.Text = Modules[modName].Details;
}
The item in the list view is not properly selected when the context menu is showing. In other words, when the item is selected, a detail string value is displayed in a label control.
If a context menu is visible, and an item is selected, the item details do not change.
Context menu location briefly appears at the old mouse location then moves to the new mouse location.
Is there something I'm doing wrong with the context menus?
I tried to reproduce your problem as far as I could. I think I can help you out with at least two of the three issues you've listed.
1. The item in the list view is not always properly selected. In other words, when the item is selected, a detail string value is displayed in a label control.
You can be notified when an item has been selected via the ListView.ItemSelectionChanged event:
//
// this handler's only responsibility is updating the item info label:
//
void lstModules_ItemSelectionChanged(object sender,
ListViewItemSelectionChangedEventArgs e)
{
if (e.IsSelected)
{
// an item has been selected; update the label, e.g.:
lblModuleDetails.Text = e.Item.Text;
}
else
{
// some item has been de-selected; clear the label:
lblModuleDetails.Text = string.Empty;
}
}
3. Context menu location briefly appears at the old mouse location then moves to the new mouse location.
I believe you try to do too much. Let the framework handle the displaying of the context menu which you have specified via the ListView.ContextMenuStrip property. The effect you experience is caused by your manually calling ContextMenuStrip.Show(...), which results in the displaying of the context menu by the framework, and then you doing the same thing a second time, at another location.
Therefore, try not to call this function; the context menu should still appear.
//
// this handler's only responsibility is setting the correct context menu:
//
void lstModules_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Right)
{
var hitTest = lstModules.HitTest(e.Location);
if (hitTest != null && hitTest.Item != null)
{
lstModules.ContextMenuStrip = mnuContext_Module;
}
else
{
lstModules.ContextMenuStrip = mnuContext_Desktop;
}
}
}
Btw., if that works, you can also get rid of your lstModules_MouseMove event handler and the mouse location object.