Adding ToolTip to ViewPort3D child elements - wpf

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.

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: 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.

WPF Losing caret position when moving mouse over adorner in richtextbox

I have an adorner layer in my richtextbox. Whenever a word is misspelled I add an adorner to the richtextbox's adornerlayer. I want to be able to right click on the adorner and capture that event but I don't want to lose the caretposition of the richtextbox when the mouse is over the adorned element.
I've tried setting IsHitTestVisible=False. This keeps the mouse from changing to a pointer when I'm over the adorned element like I want so that I can click on the underlying richtextbox, but it complicates the rightmousebutton click capturing.
Protected Sub Editor_PreviewRightMouseButtonUp(ByVal sender As Object, ByVal e As MouseEventArgs)
' Retreive the coordinates of the mouse button event.
Dim pt As Point = e.GetPosition(CType(sender, UIElement))
' Initiate the hit test by setting up a hit test result callback method.
VisualTreeHelper.HitTest(Me, Nothing, New HitTestResultCallback(AddressOf HitTestCallBack_PreviewRightMouseButtonUp), New PointHitTestParameters(pt))
e.Handled = True
End Sub
Public Function HitTestCallBack_PreviewRightMouseButtonUp(ByVal result As HitTestResult) As HitTestResultBehavior
If result.VisualHit.GetType() Is GetType(HighLightAdorner) Then
Dim adornerControl As HighLightAdorner = DirectCast(result.VisualHit, HighLightAdorner)
Dim e = New MouseButtonEventArgs(Mouse.PrimaryDevice, Environment.TickCount, MouseButton.Right)
e.RoutedEvent = Mouse.PreviewMouseUpEvent
adornerControl.RaiseEvent(e)
Return HitTestResultBehavior.Stop
End If
Return HitTestResultBehavior.Continue
End Function
This allows me to capture the rightmousebutton click but it only seems to work when I click right at the bottom of the adornedElement. If I try to click in the body of the adornedElement it doesn't find it in the hitTesting.
Am I going about this correctly or is there a better way to allow an underlying control to maintain it cursor when an element above it needs to be able to accept mouse events.
Thanks for the help!

Scrolling to the end of a single line WPF TextBox

This seems like such a simple thing but I just can't get it to work.
I have a single line text box that has a lot of text. What I want to happen is that whenever the text box receives focus, it scrolls to the end of the text so that it comes into view and the cursor is at the end ready to accept new text.
In the text box's GotFocus event I call textBox.ScrollToEnd(). It looks like all this does is move the cursor to the end of the text box but not actually bring the end into view.
What am I missing?
You should be able to do it using these members:
TextBoxBase.ScrollToEnd
TextBox.CaretIndex
EDIT: not sure why ScrollToEnd isn't working... Anyway, this code works:
textBox.CaretIndex = textBox.Text.Length;
var rect = textBox.GetRectFromCharacterIndex(textBox.CaretIndex);
textBox.ScrollToHorizontalOffset(rect.Right);
It is true - setting the caret property from code doesn't affect the view; and the caret can go outside the visible part.
kb_target_box.Focus(); // just for sure
Rect rect = kb_target_box.GetRectFromCharacterIndex(kb_target_box.CaretIndex);
kb_target_box.ScrollToHorizontalOffset(Math.Max((kb_target_box.HorizontalOffset + rect.Left - (kb_target_box.ActualWidth - 40)), 0.0));
h-scroll will follow the caret after it comes closer than 40 to the right TextBox border.
This worked for me.
textBox.CaretIndex = txt.Text.Length;
textBox.ScrollToEnd();
In my case, my textbox was inside a scrollviewer and I had to call ScrollToEnd() for scrollviewer instead textbox.
XAML
<ScrollViewer x:Name="scrollviewer">
<TextBox TextBoxBase.TextChanged="TextChanged"/>
</ScrollViewer>
CODE
private void TextChanged(object sender, TextChangedEventArgs e)
{
scrollviewer.ScrollToEnd();
}

WPF ListView ScrollViewer Double-Click Event

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.

Resources