WPF ListView ScrollViewer Double-Click Event - wpf

Doing the below will reproduce my problem:
New WPF Project
Add ListView
Name the listview: x:Name="lvList"
Add enough ListViewItems to the ListView to fill the list completely so a vertical scroll-bar appears during run-time.
Put this code in the lvList.MouseDoubleClick event
Debug.Print("Double-Click happened")
Run the application
Double-click on the LargeChange area of the scroll-bar (Not the scroll "bar" itself)
Notice the Immediate window printing the double-click happened message for the ListView
How do I change this behavior so MouseDoubleClick only happens when the mouse is "over" the ListViewItems and not when continually clicking the ScrollViewer to scroll down/up in the list?

You can't change the behaviour, because the MouseDoubleClick handler is attached to the ListView control, so it has to occur whenever the ListView is clicked -- anywhere. What you can do it detect which element of the ListView first detected the double-click, and figure out from there whether it was a ListViewItem or not. Here's a simple example (omitting error checking):
private void lv_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
DependencyObject src = (DependencyObject)(e.OriginalSource);
while (!(src is Control))
src = VisualTreeHelper.GetParent(src);
Debug.WriteLine("*** Double clicked on a " + src.GetType().Name);
}
Note the use of e.OriginalSource to find the actual element that was double-clicked. This will typically be something really low level like a Rectangle or TextBlock, so we use VisualTreeHelper to walk up to the containing control. In my trivial example, I've assumed that the first Control we hit will be the ListViewItem, which may not be the case if you're dealing with CellTemplates that contain e.g. text boxes or check boxes. But you can easily refine the test to look only for ListViewItems -- but in that case don't forget to handle the case there the click is outside any ListViewItem and the search eventually hits the ListView itself.

Maybe this helps?
Private Sub LstView_MouseDoubleClick(ByVal sender As Object, ByVal e As System.Windows.Input.MouseButtonEventArgs) Handles LstView.MouseDoubleClick
Dim source As FrameworkElement = TryCast(e.OriginalSource, FrameworkElement)
If IsNothing(source) Then Return
Dim TmplParent As DependencyObject = TryCast(source.TemplatedParent, DependencyObject)
If IsNothing(TmplParent) Then Return
If Not TmplParent.GetType.Equals(GetType(System.Windows.Controls.ListViewItem)) Then e.Handled = True
End Sub

I don't have VS handy to test if this works, but have you tried handling the double-click event on the ListViewItems rather than the ListView itself?
<ListView ListViewItem.MouseDoubleClick="lv_MouseDoubleClick" ... />
That should handle the MouseDoubleClick event on any child ListViewItem controls inside the ListView. Let us know if it works!

<Style TargetType="{x:Type ListViewItem}">
<EventSetter Event="MouseDoubleClick" Handler="OnListViewDoubleClick" />
</Style>
If you apply this style, it works. Just double click on item in the listview will work.
also, you have to remove the double click from the listview.

Related

How to get name of element with focus in WPF

I'm building a Mahjong game that has more buttons than Sgt. Pepper's band. The movement of the tile is simulated by showing the background of the button (tile) in question. I'd like to leverage the use of x:Name="button"and have only one click event, than 200 but the problem is in getting the name of the focused button. I can get the element with the focus but can not access the name property, if I could I would save a lot of inelegant drudgery. Here's what I'd like to do;
Private Sub b15_Click(sender As Object, e As RoutedEventArgs) Handles b15.Click
Dim brush As Brush
Dim vButton As Button
Dim InputElement As IInputElement = Keyboard.FocusedElement
vButton.Name = InputElement.name '!! here's the problem !!
If vTog = 0 Then 'background brush transferred from
brush = vButton.Background
vButton.Background = Nothing
vTog = 1
Else 'background brush transferred to
vButton.Background = brush
vTog = 0
End If
End Sub
Maybe I've missed an easy way to get the button's name so I can use it directly in the code behind. Thank you.
I recommend learning more about the basics of WPF.
As well as "regular" events WPF has routed events. See the signature of your click handler? Notice Routedeventargs rather than eventargs?
That click event is a routed event.
Routed events bubble ( up the visual tree ) and or tunnel (down the visual tree).
https://learn.microsoft.com/en-us/dotnet/framework/wpf/advanced/routed-events-overview
Because of this mechanism, you can handle a click event of buttons at a container further up the visual tree. That could be a grid, stackpanel or window.
In the handler, cast the originalsource of the event to button and that will have a name property.
I do c# but there is very little code to this and if you have trouble following it then you could put it through an online code converter.
Markup:
Title="MainWindow"
ButtonBase.Click="Window_Button_Click"
>
<StackPanel>
<Button Name="b1" Content="b1"/>
<Button Name="b2" Content="b2"/>
<Button Name="b3" Content="b3"/>
</StackPanel>
</Window>
Handler in code behind:
private void Window_Button_Click(object sender, RoutedEventArgs e)
{
Button btn = e.OriginalSource as Button;
if(btn!=null)
{
Debug.WriteLine(btn.Name ?? "No Name for this button");
}
}
If I spin that up with an f5, I see the name of each button clicked output to my output window when I click on them.
As an aside.
I'm not clear what the player clicks and what that does.
Maybe this could work in a generic way and the datacontext of each button would be a viewmodel holding x,y location or some such.

WPF: how to auto scroll to my first ListViewItem when form load

So i am build simple Clipboard manager.
Every ListViewItem come from Clipboard.GetText and my application minimize to Tray and when double click on its Icon the application jump and i want to focus become on the first ListViewItem in order to be able to navigate with Up & Down arrows.
This is my ListView:
ListView myListView;
Model List:
public ObservableCollection<string> Clipboards
Window Loaded event:
private void Window_Loaded(object sender, RoutedEventArgs e)
{
if (viewModel.Clipboards.Count != 0)
myListView.ScrollIntoView(myListView.Items[0]);
myListView.Focus();
}
So currently when i open int the first time the application, the Focus is not on any ListViewItem and in the next time the focus is on the last selected/click ListViewItem and not on the first one.
It looks like you have your ViewModel in the code behind. This is not good MVVM standard.
Maybe this could be helpful
How can I set the focus to a ListBox properly on load if it uses databinding?
From what I see you are not focusing any ListViewItem but the ListView itself. I think this is your mistake. To focus the item you have to get it's container. The objects in the ItemsSource are actually the data itself and no the UIElement to render. To draw this data or add it to the visual tree for rendering, the ItemsControl will generate a container for the data e.g. a ListViewItem. Only the UIElement can receive focus, that's why the UIElement exposes the Focus() method. You have to use the ItemContainerGenarator to retrieve this container for your data:
ListView myListView;
(myListView.ItemsPanel as VirtualizingPanel)?.BringIndexIntoViewPublic(0);
myListView.Dispatcher.Invoke(new Action(
() =>
{
ListBoxItem dataContainer = myListView.ItemContainerGenerator.ContainerFromIndex(0) as ListBoxItem;
dataContainer?.Focus();
}), DispatcherPriority.ContextIdle);
This example will move the focus to the first element in the ListView.

WPF: ListBoxItem with expander cannot expand after adding PreviewMouseLeftButtonDown event handler to parent ListBox

I have a ListBox, where child items are expanders. I need realize DragDrop event for this. If I write in XAML
<ListBox PreviewMouseLeftButtonDown="StartDragDrop">
, StartDragDrop method is works good, but child expanders are cannot be expanded.
If I write
<ListBox MouseLeftButtonDown="StartDragDrop">
, child expanders are works correct, but StartDragDrop method is not works.
I think the problem is relates with bubble and tunnel events, but I dont know clear solution.
I need both, StartDragDrop method and ListBox child expanders Expand method, are work correct. What should I do?
You're partially right supposing, that it has to do something with tunneling and bubbling. The tunneling (with preview) event for the outer control is executed before the bubbling (without preview) event. But it doesn't prevent the latter from being executed. This only holds true, when somewhere in the whole event chain e.Handled is set to true. See this example:
XAML:
<Border Background="Red" PreviewMouseMove="OnPreviewMouseMove">
<Border Background="Blue" MouseMove="OnMouseMove" />
</Border>
C#
private void OnPreviewMouseMove(object sender, MouseEventArgs e)
{
Debug.WriteLine("Preview outer");
e.Handled = true; // this prevents OnMouseMove from being executed
}
private void OnMouseMove(object sender, MouseEventArgs e)
{
Debug.WriteLine("NoPreview inner");
}
If you delete the line "e.Handled = true;", OnMouseMove will be hit. If you don't set this yourself, consider, that a call to base."event-name" might do it.
The main idea is call DoDragDrop mathod at PreviewMouseMove() event, when moving offset is larger than somehow value.
1) Here the list box:
<ListBox AllowDrop="True" Drop=" ListBox_Drop" PreviewMouseLeftButtonDown="ListBox_PreviewMouseLeftButtonDown" PreviewMouseMove="ListBox_PreviewMouseMove">
ListBoxItems are Expanders, that cannot expand if we implement DragAndDrop.
2) Now we must add 2 variables (I use VB.NET):
Private isDragging As Boolean = False 'flag: is drag operation in process?'
Private dragStartPoint As Point 'coords of dragging start.'
3) Remember start point coords by preview mouse click:
Private Sub ListBox_PreviewMouseLeftButtonDown(sender As Object, e As MouseButtonEventArgs)
dragStartPoint = e.GetPosition(Me)
End Sub
4) On PreviewMouseMove get moving start point to current moving point offset. If offset is larger than some value, we initiate DragAndDrop operation and set flag isDragging to remember this.
Private Sub ListBox_PreviewMouseMove(sender As System.Object, e As MouseEventArgs)
If e.LeftButton = MouseButtonState.Pressed Then
     Dim diff As Vector = Point.Subtract(dragStartPoint, e.GetPosition(Me))
        If (Math.Abs(diff.X) > SystemParameters.MinimumHorizontalDragDistance) OrElse (Math.Abs(diff.Y) > SystemParameters.MinimumVerticalDragDistance) Then
If Not isDragging Then
             isDragging = True 'the flag is active until drop event raises.'
                Dim lstBox As ListBox = TryCast(sender, ListBox) 'get sender ListBox'
                If lstBox IsNot Nothing Then
                 Dim data As Object = GetDataFromListBox(lstBox, e.GetPosition(lstBox)) 'get data for drag-and-drop; need to be realized; there are some realizations at Stackoverflow.com presented.'
Dim effects As DragDropEffects = DragDrop.DoDragDrop(lstBox, data, DragDropEffects.Move) 'initiate drag-and-drop.'
                 End If
             End If
         End If
     End If
End Sub
5) Proccessing drop operation:
Private Sub ListBox_Drop(sender As Object, e As DragEventArgs)
     isDragging = False 'reset isDragging flag.'
        Dim lstBox As ListBox = TryCast(sender, ListBox) 'get sender ListBox.'
        If lstBox IsNot Nothing Then
         Dim myObj As MyClass = TryCast(e.Data.GetData(GetType(MyClass)), MyClass)
'...some actions'
        End If
End Sub
I've realized this idea and it's works exactly I was need:
on MouseLeftButtonClick ListBoxItems with Expanders are expands and
collapses,
on MouseMove with pressed left button DragAndDrop operation is
works, ListBoxItems are able to be sorted.

MouseDoubleClick gets wrong TreeViewItem

This one is really weird unless I'm missing something really basic.
I have attached an event handler to my TreeViewItem's MouseDoubleClick event through ItemContainerStyle:
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<EventSetter Event="MouseDoubleClick" Handler="TreeViewItem_MouseDoubleClick" />
</Style>
</TreeView.ItemContainerStyle>
Here's the event handler:
Private Sub TreeViewItem_MouseDoubleClick(sender As Object, e As MouseButtonEventArgs)
If TypeOf sender Is TreeViewItem Then
Dim TVI = DirectCast(sender, TreeViewItem)
MsgBox(TVI.Header)
End If
End Sub
The problem is that MsgBox always shows the header text of the root node, not the node on which I double-clicked. I can't see any obvious mistake here. Can anyone point me in the right direction?
Yup, you're missing one of the basic weird things about a TreeView :). Not sure how I can illustrate it easily in a post, so I'll try to explain it. A TreeViewItem has sub items. When you expand the root TreeViewItem to show the sub items, all those sub TreeViewItems are inside of the parent TreeViewItem. So when you double click on a child TreeViewItem the event will bubble up to the top most item. If you look at e.OriginalSource, you'll get the actual item... sort of... you'll actually get the object you double clicked on (for example the TextBlock). You can use the well known GetVisualAncestor<T>() extension method to chase up to the correct TreeViewItem:
((FrameworkElement)e.OriginalSource).GetVisualAncestor<TreeViewItem>()
Yeah, it's ugly :)...
Here is a link to a C# implementation, you'll have to find a VB version for yourself :), but its pretty trivial to port.
https://code.google.com/p/gong-wpf-dragdrop/source/browse/branches/jon/GongSolutions.Wpf.DragDrop/Utilities/VisualTreeExtensions.cs?r=29

Adding ToolTip to ViewPort3D child elements

Hi i would like to add Tool Tip to ViewPort3D child elements when i put my mouse over on it, Only viewPort3D has a tooltip property but not for their childs. Any way to work around it?
I was able to get a partial solution by adding a canvas with a textblock inside to hold my text. Like this...
<Grid>
<Canvas>
<TextBlock Name="txtblkTip" TextAlignment="Center" Padding="2" />
</Canvas>
<Viewport3d ...
...
</Viewport3d>
</Grid>
Then as the user moves the mouse over an object in viewport3d I use the following mouse event handler to redraw the tooltip at the required location, based on the HitTest method.
Private Sub viewport_PreviewMouseMove(ByVal sender As Object, ByVal e As System.Windows.Input.MouseEventArgs) Handles viewport.PreviewMouseMove
Dim ptMouse As Point = e.GetPosition(viewport)
Dim result As HitTestResult = VisualTreeHelper.HitTest(viewport, ptMouse)
If TypeOf result Is RayMeshGeometry3DHitTestResult Then
Dim result3d As RayMeshGeometry3DHitTestResult = CType(result, RayMeshGeometry3DHitTestResult)
If TypeOf result3d.VisualHit Is Sphere Then
If CType(result3d.VisualHit, Sphere).Name <> "" Then
'Position the Canvas near the mouse pointer
Canvas.SetLeft(txtblkTip, ptMouse.X + 12)
Canvas.SetTop(txtblkTip, ptMouse.Y + 12)
txtblkTip.Text = CType(result3d.VisualHit, Sphere).Name
End If
End If
End If
End Sub
One thing I have not been able to get is an event when the mouse moves off all objects in the Viewport, to remove the tooltip, but I suspect this could be done with a storyboard.
Hope this helps you along the way.
XamTrix's answer works with the addition of a MouseLeave event handler that sets the visibility of the textblock to Visibility.Collapsed (the visibility of the textblock
must also be reset to Visible in the PreviewMouseMove event handler).
Also, if the Canvas is placed after the Viewport3d instead of before it, the textblock
will appear above the Viewport3d elements. In this case the Canvas.SetLeft statement
should be changed to: ptMouse.X + 12 - viewport3d.actualWidth.

Resources