Ignore parent in PreviewMouseButtonDown on TreeviewItem - c#

I have a treeview and im trying to implement a drag and drop functionallity.
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<EventSetter Event="TreeViewItem.PreviewMouseLeftButtonDown" Handler="PreviewMouseLeftButtonDown" />
</Style>
</TreeView.ItemContainerStyle>
Then i have an event in the code behind to handle the click, and start the drag:
void PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (sender is TreeViewItem)
{
//clone the tree item, create an adorner, apply adorner
//doDragDrop
}
}
My problem is since PreviewMouseLftButtonDown is a routed event using the tunneling strategy, when the mouse is clicked on a treenode item, I get the root node first. I am not interested in the root node, only the leaf nodes. My structure is like this:
HeaderNodeObject
|_LeafnodeObject
|_LeafNodeOject
So in my event I need to basically not do anything and just proceed if the sender treeViewItem is a HeaderNodeObject. But how can i tell what it is? the sender comes in as a treeViewItem, im not sure how to tell what the object it holds is.

You should be able to use e.Source as TreeViewItem to retrieve the item that was clicked. You can check it against sender to confirm that you only process the event when it has reached the source.
This should result in the event only being processed for the clicked item:
void PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (e.Source is TreeViewItem item && sender == item)
/* do something */;
}
Note, however, that using the event so bluntly means that you'll receive the event if any part of the item receives a mouse down event, including the expansion button. Thus, be careful to *not* set e.Handled = true, lest you prevent the user from toggling expansion, among other things.

The above solution only works for leafs, and I had to do this for a TreeView where I wanted to process the PreviewMouseLeftDown event on any node in the tree. I finally solved it by ignoring the event for all nodes where a child had mouse over. Like this:
var treeViewItem = sender as TreeViewItem;
if (treeViewItem.Items.OfType<TreeViewItem>().All(item => !item.IsMouseOver))
{
//do my stuff
}
Sort of a hackish solution, but I couldn't find any better, and at least it gets the job done...

I solved this fairly simply by just checking if the itemsource on the sender is null. If its null that means its a leaf node:
if (sender is TreeViewItem)
{
Item = sender as TreeViewItem;
//make sure we have a leaf node, if we dont just move on.
if (Item.ItemsSource == null)
{
//do my stuff
}
}

Will not call this a duplicate answer but will simply mention that Øystein Kolsrud's answer is as correct as this can be without data binding. I don't have enough reputation or else I would simply comment and up-vote, but "User's" answer will only work for leafs on the tree, while Øystein's answer works for all nodes on the tree, including all the leaf nodes.
My case involved going through a fairly shallow (yet heavily populated) tree of recursively populated 'work items.' I was using a TreeView to display the work flow hierarchy, where a selected node would raise an event that captured the Work ID text from the header of the TreeViewItem, so I could then be able to use that WorkID in a meaningful way. When I wanted this to happen for more than just the root or just the first level of children (leaving out the root), Øystein's answer is the way I had to go. Definitely hackish, but definitely effective.
All of the following code I had (besides his conditional check), was there previously. All I had to do was include his check and everything worked as I initially intended.
Just wanted to share a real-world example of this working and verify his work. This condition works like a charm and saved my butt. Thanks again, Øystein Kolsrud!!
private void TreeItem_Selected(object sender, RoutedEventArgs e)
{
TreeViewItem workItem = sender as TreeViewItem;
if(workItem.Items.OfType<TreeViewItem>().All(item => !item.IsMouseOver))
{
string workItemHeader = workItem.Header.ToString();
string stopAt = "-";
currentWorkIDToEdit = workItemHeader.Substring(0, workItemHeader.IndexOf(stopAt));
workItemToEdit.Content = "Work Item To Edit: " + currentWorkIDToEdit + " ";
}
}

Related

Get the Selected Tree View Item

I am using a wpf tree in a .net form. So, I don't have any xaml. I simply do everything in code. I am using Hierarchical Data Template to bind my data to the wpftree.
I am trying to find a way to get the TreeViewItem for the Node selected in the tree. I tried registering a EventHandler on the SelectedItemChanged event on the TreeView, but in that handler I only get the associated data object. Since my tree is virtual, ItemContainerGenerator.ContainerFromItem doesn't work.
When I searched on StackOverflow, one suggestion was to listen to the TreeViewItem.Selected event.
But I couldn't find a way to do this in code. ( I don't have a xaml).
Any help is greatly appreciated.
thank you.
What you could do is attach the handler to each control every time you add it
void AddTreeViewItem()
{
TreeView t = new TreeView();
TreeViewItem treeItem = new TreeViewItem();
t.Items.Add(treeItem);
treeItem.Selected += DoSomethingHere;
}
private void DoSomethingHere(object sender, RoutedEventArgs e)
{
Console.WriteLine("Tree Item Selected");
}

LongListSelector: Item tap?

I am using LongListSelector control on Windows Phone 8 and can't figure out the best way to handle a tap on an item.
The few examples I've found rely on the SelectionChanged event. However, this solution is buggy because if I tap an item that opens a new page, hit back, and then tap the same item again, it won't work because this item is already selected, so SelectionChanged is not triggered.
I tried to register to the tap event and use the current selected item as the tapped one, but some times the current selected item is not the one I expect.
I could wrap my ItemTemplate in a button and handle the tap for each item but I need to reskin the button to make it look like a simple list item.
Finally, I don't understand why it is so complicated to achieve such a basic thing. Is there a simple and standard way I missed?
My second wish is to get an effect on the item when it is tapped. Is there any standard way to do it?
You could null your LongListSelector's SelectedItem at the end of each SelectionChanged event. I.e.
<phone:LongListSelector x:Name="LLS" SelectionChanged="LLS_SelectionChanged">
And the event handler:
private void LLS_SelectionChanged(object sender, SelectionChangedEventArgs e) {
// If selected item is null, do nothing
if (LLS.SelectedItem == null)
return;
// Navigate to the next page
NavigationService.Navigate(new Uri("/nextpage.xaml", UriKind.Relative));
// Reset selected item to null
LLS.SelectedItem = null;
}
You'll fire the SelectionChanged event twice, but nothing's going to happen the second time round and you should get the behaviour that you're looking for - (i.e Setting SelectedItem to null will trigger a new SelectionChanged event, but this second event gets caught in the if-statement)
As for the second part of your question, you might be better posting a new question.
I done it with the Tap event handling.
I prefer not to use Selected property, but get tapped item this way (and I haven't noticed any bugs):
MyListItemClass item = ((FrameworkElement)e.OriginalSource).DataContext
as MyListItemClass;
Also, you could get the original item ContentPresenter simple by navigating up through VisualTree from e.OriginalSource. That way:
ContentPresenter itemPresenter = SomeHelperClass
.FindParent<ContentPresenter>(e.OriginalSource,"");
Where FindParent is similar to find child in this question: How can I find WPF controls by name or type?
ContentPresenter is that object what you need to manually change the item template if you want to (to set "selected" state for example).
private void Item_tap(object sender, RoutedEventArgs e)
{
var element = (FrameworkElement)sender;
DataSource data = (DataSource)element.DataContext;
}
My second wish is to get an effect on the item when it is tapped. Is
there any standard way to do it?
For this the only thing you need to do add this to your control (or stackpanel where you want to have this effect):
<StackPanel toolkit:TiltEffect.IsTiltEnabled="True">
first add this to *.xaml page inside the
LongListSelectorSelectionChanged="listBox_SelectionChanged"
so that it looks like this :
<toolkit:LongListSelector x:Name="listBox" SelectionChanged="listBox_SelectionChanged">
then in the *.xaml.cs file in the event handler
private void listBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// Write your logic on what you want to do with the selected item
}
In addition to halil´s answer:
First of all you need to install the Windows Phone Toolkit (WPtoolkit) by NuGet.
Than add the namespace declaration on the PhoneApplicationPage.
xmlns:toolkit="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Toolkit"
After this you can add toolkit:TiltEffect.IsTiltEnabled="True" to the control definition.
It is nice documneted by NOKIA:
http://developer.nokia.com/community/wiki/Tilt_Effect_for_Windows_Phone
Oliver

C# Drag & Drop Between ListViews

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!

Windows Forms: Highlight destination node in TreeView during Drag&Drop

I created Drag&Drop mechanism for my TreeView. I added DragEnter, DragDrop and ItemDrag methods and everything works fine.
But when you are doing D&D with standard Windows controls, destination node is highlighted.
Image is worth 1000 words, video probably even more:
http://www.youtube.com/watch?v=PlltSiihHPo
I mean such highlight effect like you can see in this video on Recycle Bin.
That's not a TreeView, it's a ListView with View = LargeIcons. TreeView isn't a great control as a drop target since it hides sub-nodes. But you can solve both problems by implementing the DragOver event. Test where the mouse is at and expand and select the node:
void treeView1_DragOver(object sender, DragEventArgs e) {
var pos = treeView1.PointToClient(new Point(e.X, e.Y));
var hit = treeView1.HitTest(pos);
if (hit.Node != null) {
hit.Node.Expand();
treeView1.SelectedNode = hit.Node;
}
}

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