WPF Drag-to-scroll doesn't work correctly - wpf

I am tying to realize a drag-to-scroll functionality in my application and have problems on my way. Can anybody help me?
I have a ScrollViewer and inside it an ItemsControl and within ItemsTemplate I have a UserControl. I want to drag that UserControl within ItemsControl. I want the ScrollViewer to scroll down, when I am dragging to the boundaries of the ItemsControl.
protected override void OnPreviewMouseMove(System.Windows.Input.MouseEventArgs e)
{
if (this.IsMouseCaptured)
{
// Get the new mouse position.
Point mouseDragCurrentPoint = e.GetPosition(this);
if (Math.Abs(mouseDragCurrentPoint.Y - this.ActualHeight) <= 50)
{
this._scrollStartOffset.Y += 5;
_containingScrollViewer.ScrollToVerticalOffset(this._scrollStartOffset.Y);
}
if (mouseDragCurrentPoint.Y <= 50)
{
this._scrollStartOffset.Y -= 5;
_containingScrollViewer.ScrollToVerticalOffset(this._scrollStartOffset.Y);
}
}
base.OnPreviewMouseMove(e);
}
When I start dragging by calling DragDrop.DoDragDrop() scrolling don't happens. But when I disable dragging, the ScrollViewer scrolls down dependong on mouse position.
Maybe there's something that I don't take into accont about dragging and Capturing the mouse?
Thanks for attention.
Garegin

When using DragDrop.DoDragDrop(), I use a Sub that Handles the Me.DragOver event (in VB) so it looks as follows. Mind you, my control has a ListBox wrapped in a ScrollViewer.
Private Sub ListBox_Items_DragOver(ByVal sender As System.Object, ByVal e As System.Windows.DragEventArgs) Handles Me.DragOver
Dim currentMousePoint As Point = e.GetPosition(_containtingScrollViewer)
If Math.Abs(currentMousePoint.Y - _containtingScrollViewer.ActualHeight) <= 50 Then
_containtingScrollViewer.ScrollToVerticalOffset(currentMousePoint.Y + 5)
End If
If currentMousePoint.Y <= 50 Then
_containtingScrollViewer.ScrollToVerticalOffset(currentMousePoint.Y - 5)
End If
End Sub
This gives me the ability to scroll whilst dragging items. You can tweak the tolerances to get better/smoother scrolling as needed.

Related

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!

How to prevent ScrollViewer from using MouseWheel event

I'm building SL application for zooming and panning across the layout. Everything is working fine, except that when I zoom in using mouse wheel , after some zoom scrollbars start to use mouse wheel so after that I can scroll not zoom. I only can zoom again if I put scrollbars at the end or begining. How to prevent scrollviewer from using mouse wheel? I want that zoom only be operated by wheel. Thank you in advance!
Here is my code of MouseWheel method when I'm zooming content :
protected override void OnMouseWheel(MouseWheelEventArgs e)
{
base.OnMouseWheel(e);
if (e.Delta > 0)
{
this.aniScaleX.To += 0.2;
this.aniScaleY.To += 0.2;
this.sbScale.Begin();
}
else if (e.Delta < 0 && (this.aniScaleX.To > 1 && this.aniScaleY.To > 1))
{
this.aniScaleX.To -= 0.2;
this.aniScaleY.To -= 0.2;
this.sbScale.Begin();
}
Sizer.Width = Board.ActualWidth * (double)this.aniScaleX.To;
Sizer.Height = Board.ActualHeight * (double)this.aniScaleY.To;
Try to set:
e.Handled=true;
The MouseWheel event is a bubbling
event. This means that if multiple
MouseWheel event handlers are
registered for a sequence of objects
connected by parent-child
relationships in the object tree, the
event is potentially received by each
object in that relationship. The
bubbling metaphor indicates that the
event starts at the source and works
its way up the object tree. For a
bubbling event, the sender available
to the event handler identifies the
object where the event is handled, not
necessarily the object that actually
received the input condition that
initiated the event. To get the object
that initiated the event, use the
OriginalSource value of the event
data. http://msdn.microsoft.com/en-us/library/system.windows.uielement.mousewheel(VS.95).aspx
In my case,ScrollViewer always received event before because he is on the top of the visual tree. So I just registered event handler in scrollviewer on mouse wheel event and always when It happens, I simply redirect him to my "original" mousewheel function which do zoom.
I hope so that this will help somebody who is "stuck" like me here. Thank you all on your answers and suggestions..
This took me awhile (WPF), but basically you should use AddHandler in whatever parent UIElement that has the event.
For me, I did this in my MainWindow. So my code looked like:
public MainWindow()
{
InitializeComponent();
this.AddHandler(MainWindow.MouseWheelEvent, new RoutedEventHandler(this.MouseWheel_1), true);
}
This will also mean not overriding OnMouseWheelDown, but instead creating a method that matches the RoutedEventHandler delegate and casting e (which will be a RoutedEventArg) as MouseWheelEventArgs to get access to the properties required to determining zoom.
I hope this helps for your situation.

WinForms ListView empty rows glitch

I have a glitch in a WinForms C# ListView (with custom modifications to sort and filters on all columns, but it happened also in a standard ListView).
I modify the ListView Items with this (fairly standard) pattern:
BeginUpdate();
// add some items
// remove some other items
Sort();
EndUpdate();
But if I call this code when the ListView is already scrolled, then I get some empty (non selectable) rows before the real items, and 2 scrollabars even if they are not needed.
It looks like a graphic glitch, because when I scroll the list then the empty items disappear.
Have anyone met this problem before?
Ok, I found the problem. A call to set a column Width = -2 during Resize was messing the owner-draw filters...
This ListView graphic bug. A similar problem can be reproduced, if while changing the ListView size, change the width of its columns. As a solution, the method proposes to change the width in a separate thread.
private void ListView_SizeChanged(object sender, EventArgs e)
{
var widthChangedThread = new Thread(() => SetNewColumnSize()) {IsBackground = true};
widthChangedThread.Start();
}
private void SetNewColumnSize()
{
Invoke(new MethodInvoker(() =>_columnHeader.Width += 10));
}
This control behave in a strange way but setting up the scrollabe
property to false in the resize event fixed completly the problem as:
With DirectCast(sender, ListView)
'do not allow scrolling in the resize event
'ortherwise there is a condition where the control
'stop showing the row data
IsScrollEnabled = .Scrollable
.Scrollable = False
ThisColumnHeader = .Columns("colMessage")
If ThisColumnHeader IsNot Nothing Then
.BeginUpdate()
'With .Columns("colMessage")
' .Width = -2
'End With
If .Items.Count > 0 Then
'If MyListDownloadMessage.Count > 0 Then
If ToolStripSerialRxFillDown.Checked Then
.EnsureVisible(.Items.Count - 1)
Else
If .TopItem IsNot Nothing Then
.EnsureVisible(.TopItem.Index)
End If
End If
End If
.EndUpdate()
.Refresh()
End If
.Scrollable = IsScrollEnabled
End With

How to add a ContextMenu depending on which WPF DataGrid row is right-clicked?

I need to display different options in a ContextMenu depending on which row of a WPF DataGrid is right-clicked. My initial ideas were to accomplish this through either binding or handling a mouse click event, but I haven't had success with either strategy so far. Any help would be most appreciated!
Thank you!
Denise
You can handle the DataGrid's ContextMenuOpening event and based on the original source of the routed event you adjust your context menu.
Below is a sample where I show a context menu if the data context of the original source is of type Inventory otherwise I do not show the context menu by handling the event.
Private Sub InventoriesDataGrid_ContextMenuOpening( _
ByVal sender As Object, _
ByVal e As System.Windows.Controls.ContextMenuEventArgs) Handles _
InventoriesDataGrid.ContextMenuOpening
Dim context = DirectCast(e.OriginalSource, System.Windows.FrameworkElement).DataContext
If TypeOf context Is Inventory Then
InventoriesDataGrid.ContextMenu = InventoriesDataGrid.Resources("DefaultContextMenu")
Else
e.Handled = True 'Do not show context menu.
End If
End Sub
I'm sure it is too late to help you now, but in case it is not too late and for anyone else who comes across this.
You can try the OriginalSource from the ContextMenuEventArgs argument in the ContextMenuOpening event :
DataGridResults.ContextMenuOpening += (sender, args) =>
{
var frameworkElement = args.OriginalSource as FrameworkElement;
var gridRow = frameworkElement != null ? frameworkElement.TemplatedParent as DataGridRow : null;
}
Note however that the use of TemplatedParent depends on how the datagrid items were bound

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