WPF Losing caret position when moving mouse over adorner in richtextbox - wpf

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!

Related

Define Click event on programmatically defined WPF GridViewColumnHeader

I am programmatically creating a GridViewColumn in WPF as follows:
Dim oGVCol As GridViewColumn = New GridViewColumn
Dim oHeaderTemplate As DataTemplate
oHeaderTemplate = New DataTemplate
Dim oGridFactory As FrameworkElementFactory = New FrameworkElementFactory(GetType(Grid))
oHeaderTemplate.VisualTree = oGridFactory
oGridFactory.SetValue(Grid.BackgroundProperty, Brushes.Transparent)
oGVCol.HeaderTemplate = oHeaderTemplate
(I have removed irrelevant code to set the content of the grid)
What I can't figure out is how to add a "click" event for the GridViewColumnHeader itself. I can add events to the Grid and any other Controls I added through the Factory objects, no problem. But I'm stuck on how to add an event to the header itself.
If you have a solution in VB.NET, great, but C# is fine too.
One (failed) attempt:
AddHandler TryCast(oGVCol.Header, GridViewColumnHeader).Click, AddressOf HeaderClick
Sadly it turns out that I cannot cast oGVCol.Header to a GridViewColumnHeader.
Ok, it may not be pretty, but I found a pretty decent solution to the problem.
Firstly, when I create the Grid Factory for the root element in the header's Visual Tree, I give it a name
oGridFactory.SetValue(Grid.NameProperty, <column name here>))
(Please note that the Names must have only letters, numbers and underscores so if your data contains column names that don't have those, you'll need to deal with that both here, to convert invalid names to valid ones, and below, to revert them back to their original names if necessary.... I won't detail that functionality here)
Also, I add an event handler to the Root "Grid" in the Template for the column header:
oGridFactory.AddHandler(Grid.SizeChangedEvent,
New SizeChangedEventHandler(AddressOf ColumnHeaderSizeChanged))
The "magic" happens in the procedure ColumnHeaderSizeChanged. This procedure is called both when the grid is Rendered the first time, but also when the user is manually resizing columns.
Signature:
Private Sub ColumnHeaderSizeChanged(sender As Object, e As SizeChangedEventArgs)
I keep a List(Of GridViewColumnHeaders) which is reset to an empty list when I need to replace the Grid with a different one. In the ColumnHeaderSizeChanged event I then do the following:
The first thing we need to do is get to the Root of the controls in the Column Header. For example, your column header may contain a TextBlock to show a column name, and icons to indicate it's been sorted up or down. That sort of thing. When the user clicks on the header they may be clicking on any of those controls, so:
Dim oParent As Object
Dim oColHeader As GridViewColumnHeader = Nothing
Dim sColHeaderName As String = String.Empty
Dim oGWH As Grid = Nothing
oParent = e.OriginalSource 'This may be any of the controls in the header.
If Not oParent Is Nothing Then
Try
While Not oParent.Parent Is Nothing
'So we keep going down the Tree until we hit the Root Parent
'which will be the main Grid created in the Grid Factory
oParent = oParent.Parent
End While
Catch
End Try
End If
'But at this point, if we still have a control, it will be the main Grid
If oParent Is Nothing Then
Exit Sub
End If
If TryCast(oParent, Grid) Is Nothing Then
'what the heck is this? This SHOULD be the Grid at the root of the Visual Tree,
'so if, for whatever reason, this is NOT a Grid, get outta here.
Exit Sub
End If
By this point we're on the main Grid, but now we need to get the GridViewColumnHeader into which this Grid has been created. So now we go to the TemplatedParent
While Not oParent.TemplatedParent Is Nothing
oParent = oParent.TemplatedParent
oColHeader = TryCast(oParent, GridViewColumnHeader)
If Not oColHeader Is Nothing Then
'This procedure is called both when the Grid View is first rendered,
'and when the user is dragging the column width.
If Mouse.LeftButton = MouseButtonState.Pressed Then
'Do something appropriate to when the user is resizing the column
Else
'Do something appropriate to when the grid
'is first Rendered or re-displayed
End If
Exit While
End If
End While
At this point we have the GridViewColumnHeader we need, so we can add it to the List and add a Handler for its Click event. moColHeaders is the List(Of GridViewColumnHeaders)
If Not oColHeader Is Nothing Then
If Not moColHeaders.Contains(oColHeader) Then
oColHeader.Name = <column name here>
moColHeaders.Add(oColHeader) 'Only need to add it once!
AddHandler oColHeader.Click, AddressOf HeaderClick
End If
End If
Now you can code your HeaderClick procedure to handle the GridViewColumnHeader.Click event.
Signature:
Private Sub HeaderClick(sender As Object, e As RoutedEventArgs)

How to write out button event without using WPF controls?

I do not know how to specify in the title, this is in WPF visual basic. I want to know how do I write a code so that when a button is clicked, the tabcontrol selection will be = 1
Here is what I have in my MainWindow, RightWindowCommands:
<Button Content="Information" Cursor="Hand" Click="InformationButton_OnClick"
ToolTip="View the information"
x:Name="InformationView"/>
However, I did not use the WPF tools' Button, as this is a GUI that I have to place the button at RightWindowCommands, I want to know how to come out with the code so that InformationButton_OnClick gives me tabControl.SelectedIndex = 1. Please guide me on writing this code out
Here is a nice example for onclick event handling a picture, you can change it to a number instead of picture.
`AddHandler pic.Click, AddressOf pic_Click'
Private Sub pic_Click(ByVal sender As Object, ByVal e As EventArgs)
Dim pic As PictureBox = DirectCast(sender, PictureBox)
' etc...
End Sub
Source : add on click event to picturebox vb.net

Handling events from user control containing a WPF textbox

In order to take advantage of the spell checking ability of WPF textboxes, I have added one to a user control (with the use of elementhost). This user control is used in various window forms. My current problem is trying to handle keyup events from this textbox but the windows form is unable to "get" any event from the control. I can access the properties of the textbox just fine (i.e. text, length, etc.) but keyboard events don't seem to work.
I have found, however, that the following will bring back events from the WPF textbox:
Public Class MyUserControl
Private _elementHost As New ElementHost
Private _wpfTextbox As New System.Windows.Controls.Textbox
Private Sub MyUserControl_Load(...) Handles Me.Load
Me.Controls.Add(_elementHost)
_elementHost.Dock = DockStyle.Fill
_elementHost.Child = _wpfTextbox
Dim MyEventInfo As EventInfo
Dim MyMethodInfo As MethodInfo
MyMethodInfo = Me.GetType().GetMethod("WPFTextbox_KeyUp")
MyEventInfo = _wpfTextBox.GetType().GetEvent("PreviewKeyUp")
Dim dlg As [Delegate] = [Delegate].CreateDelegate(MyEventInfo.EventHandlerType, Me, MyMethodInfo)
MyEventInfo.AddEventHandler(_wpfTextBox, dlg)
End Sub
Public Sub WPFTextbox_KeyUp(ByVal sender As Object, ByVal e As RoutedEventArgs)
' something goes here
End Sub
End Class
The user control is now able to do something after the PreviewKeyUp event is fired in the WPF textbox. Now, I'm not completely sure how to have the window form containing this user control to work with this.
Im a C# person not VB so please bear with me.. Basically you could assign the event from your Window rather than within your UserControl.. So in the constructor of your Window assign the PreviewKeyUp:
this.myUserContorl.PreviewKeyUp += new System.Windows.Input.KeyEventHandler(WPFTextbox_KeyUp);
then place the event handler in your Window:
private void WPFTextbox_KeyUp(object sender, System.Windows.Input.KeyEventArgs e)
{
}
Incidentally you needn't go through the hassle of capturing the event in your UserControl as you can still access your TextBox within you UserControl directly from your Window (if you make it public), again from your constructor in your Window:
this.myUserContorl.wpfTextbox.PreviewKeyUp += new System.Windows.Input.KeyEventHandler(WPFTextbox_KeyUp);
I imagine it would look like this in VB (at a guess):
AddHandler myUserContorl.wpfTextbox.PreviewKeyUp, AddressOf WPFTextbox_KeyUp
ElementHost has a static method called EnableModelessKeyboardInterop(). Try calling it?
ElementHost.EnableModelessKeyboardInterop();
Read more here
Seems basic, but did you set the KeyPreview to TRUE on your form?

Drag window from a grid

I have created a custom window set to windowStyle="none", (no title or maximize - minimize buttons) and i am trying to implement a DragMove operation when the user clicks and drags on a grid. (this is wired up by calling DragMove on a MouseLeftButtonDown Handler)
First weird issue that this event nevers gets fired if the grid has no backround.
Adding some background color does make the event to get fired, but after the first drag i get this error:
"Can only call DragMove when primary mouse button is down"
Code Snipet:
Private Sub Grid1_MouseLeftButtonDown(ByVal sender As System.Object, ByVal e As System.Windows.Input.MouseButtonEventArgs) Handles Grid1.MouseLeftButtonDown
DragMove()
End Sub
I know that this would work fine for a label, but isnt there a way to make it work for a grid?
OK, I found the answer..
I used a border to wrap the grid and then caught the Border1_MouseLeftButtonDown event.
I also had to set the borders Background to "Transparent", and now everything works like a charm.
Private Sub Border1_MouseLeftButtonDown(ByVal sender As System.Object, ByVal e As System.Windows.Input.MouseButtonEventArgs) Handles Border1.MouseLeftButtonDown
DragMove()
End Sub

WPF ListView Databound Drag/Drop Auto Scroll

I've been working with Bea's solution here for a while and finding it very helpful. Problem now I'm having is when I drag-n-drop items within or to another ListView control and I want to scroll up/down "during" the drag (moving an item from index 30 to index 1), it's not happening. I would have to drag to the top of the visual items in the ListView, manually scroll up, then drag again, eventually ending at the position I want. This isn't very user friendly.
Now I found the function (DragDropHelper.DropTarget_PreviewDragOver) that I would want to do the testing of which item is being dragged over, and I'm getting that.
Dim pt As Point = e.GetPosition(DirectCast(Me.targetItemsControl, UIElement))
' Perform the hit test against a given portion of the visual object tree.
Dim result As HitTestResult = VisualTreeHelper.HitTest(Me.targetItemsControl, pt)
Now from there I can get the DependencyProperty of this visual hit
Dim lvi As ListViewItem = TryCast(GetDependencyObjectFromVisualTree(TryCast(result.VisualHit, DependencyObject), GetType(ListViewItem)), ListViewItem)
Which is of a ListViewItem. Now in the function DropTarget_PreviewDragOver I have the "DraggedItem" which is of type Picture in Bea's example, but that can change depending on the ObservableCollection you have bound to the ListView. Now, I want to drag the ListView up or down depending on where the mouse is on the control. I've attempted with the below un-finished non-working code
If lvi IsNot Nothing Then
If pt.Y <= 25 Then
Dim lv As ListView = TryCast(targetItemsControl, ListView)
If lv IsNot Nothing Then
Dim index As Integer = lv.Items.IndexOf(lvi)
If index > 1 Then
lv.ScrollIntoView(lv.Items(index - 1))
End If
End If
Else
If pt.Y >= Me.targetItemsControl.ActualHeight - 25 Then
Debug.Print("Scroll Down")
End If
End If
End If
Can someone point me in the right direction to get this ItemsControl or ListView to scroll when dragging over the items??
Thanks!
I'm still messing around with this exact same issue too. I'm using a slightly modified version of Bea's Drag and Drop found here, which is in VB instead of C#. When I used ScrollIntoView as described above, I could scroll down but not up. So I messed around and came up with this as my DropTarget_PreviewDragOver:
Private Sub DropTarget_PreviewDragOver(ByVal sender As Object, ByVal e As DragEventArgs)
Dim draggedItem As Object = e.Data.GetData(Me.m_format.Name)
Me.DecideDropTarget(e)
If (Not draggedItem Is Nothing) Then
If (TypeOf m_targetItemsControl Is ListBox) Then
Dim lb As ListBox = CType(m_targetItemsControl, ListBox)
Dim temp As Integer = m_insertionIndex
Dim scroll As ScrollViewer = Utilities.GetScrollViewer(lb)
If scroll.VerticalOffset = temp Then
temp -= 1
End If
If temp >= 0 And temp <= (lb.Items.Count - 1) Then
lb.ScrollIntoView(lb.Items(temp))
End If
End If
Me.ShowDraggedAdorner(e.GetPosition(Me.m_topWindow))
Me.UpdateInsertionAdornerPosition()
End If
e.Handled = True
End Sub
and I had to include this utility function, taken from here
Public Shared Function GetScrollViewer(ByVal listBox As ListBox)
Dim scroll_border As Decorator = CType(VisualTreeHelper.GetChild(listBox, 0), Decorator)
If (TypeOf scroll_border Is Decorator) Then
Dim scroll As ScrollViewer = CType(scroll_border.Child, ScrollViewer)
If (TypeOf scroll Is ScrollViewer) Then
Return scroll
Else
Return Nothing
End If
Else
Return Nothing
End If
End Function
which is great and all. Then running out what theuberk mentioned above with the adorner moving, and in the spirit of making this easy for someone else later, I added a variable to the DragDropAdorner class:
Private m_mouseDelta As Point
Added this to the last line of DragSource_PreviewMouseLeftButtonDown:
Me.m_mouseDelta = e.GetPosition(m_sourceItemContainer)
And turned ShowDraggedAdorner into:
Private Sub ShowDraggedAdorner(ByVal currentPosition As Point)
If (Me.m_draggedAdorner Is Nothing) Then
Dim adornerLayer As AdornerLayer = adornerLayer.GetAdornerLayer(Me.m_topWindow.Content)
Me.m_draggedAdorner = New DraggedAdorner(Me.m_draggedData, DragDropBehavior.GetDragTemplate(Me.m_sourceItemsControl), m_topWindow.Content, adornerLayer)
End If
Me.m_draggedAdorner.SetPosition((currentPosition.X - m_mouseDelta.X), (currentPosition.Y - m_mouseDelta.Y))
End Sub
What I did was took advantage of the ListBox.ScrollIntoView method. Basically, when you update your drop target, you can just call this method on it and wpf will do all the work. All you need to know is the index of the drop target item. This handles both vertical and horizontal scrolling.
this.listView.ScrollIntoView(this.listView.Items[index]);
When you use this method, your adorner might move with the scrolling ListBox. To fix this, I just set my adorner parent and adorner layer parent to the content of the window at the top of the visual tree (i.e. topWindow.Content).
Another possibility to scroll is to use the ScrollBar-Commands. You can do this without climbing down the VisualTree. If you have a styles ListBox with no border the GetScrollViewer()-Method would not work anymore.
I use the first Element of the ItemContainerGenerator as CommandTarget for the ScrollBar.LineXXXCommand:
Point p = e.GetPosition(itemsControl);
IInputElement commandTarget = itemsControl.ItemContainerGenerator.ContainerFromIndex(0) as IInputElement;
if (commandTarget != null)
{
if (p.Y < OFFSET_TO_SCROLL)
ScrollBar.LineUpCommand.Execute(null, commandTarget);
else if (p.Y > itemsControl.ActualHeight - OFFSET_TO_SCROLL)
ScrollBar.LineDownCommand.Execute(null, commandTarget);
if (p.X < OFFSET_TO_SCROLL)
ScrollBar.LineLeftCommand.Execute(null, commandTarget);
else if (p.X > itemsControl.ActualWidth - OFFSET_TO_SCROLL)
ScrollBar.LineRightCommand.Execute(null, commandTarget);
}
Calling the LineXXXCommands is similiar to clicking the Arrow-Buttons of a ScrollBar: The ScrollViewer scrolles by a certain ammount which you can configure by setting the 'SmallAmount' Property of the ScrollBar.

Resources