In my previous post I discussed Drag/Drop in WPF. Often drag/drop is a user gesture to exchange data between two applications, or between parts of an application. But sometimes dragging alone is used as a gesture to scroll some content that's too big to see all at once. Most online map sites let you drag the map image to reveal a different part of the map – in effect, scrolling it.
In the Family.Show reference app, Ralph Arvesen enabled the drag-to-scroll gesture to position the family genealogy graph using the ScrollViewer control and mouse events. It's a nifty approach that I've extracted out into a bare bones example.
The WPF ScrollViewer control already handles positioning content using the scrollbars and clipping of content within its viewport. All that's left is for your code to do is to handle the mouse events and position the content programmatically. Mouse events are implemented in WPF as attached events, and to make things even easier UIElement exposes an alias event for each mouse event. This sample just overrides the event handler methods on Page, which derives from UIElement.
The ScrollViewer's extent is sized to hold the content (in this example, an explicitly-sized Canvas), and the Scrollviewer's viewport is sized to fit within a window, page or some other XAML content control. When the viewport is smaller than the content, the drag-to-scroll behavior is enabled.
On PreviewMouseDown, the original scroll position of the content within the viewport is noted. Each time PreviewMouseMove fires the scroll position is updated, giving the user constant feedback.
XAML
<Page x:Class="WindowsApplication1.Page1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
<ScrollViewer x:Name="myScrollViewer"
HorizontalScrollBarVisibility="Hidden"
VerticalScrollBarVisibility="Hidden"
>
<Canvas Height="2000" Width="3000">
<Label x:Name="myLabel">Here's my label.</Label>
</Canvas>
</ScrollViewer>
</Page>
Code
public partial class Page1 : System.Windows.Controls.Page
{
private Point mouseDragStartPoint;
private Point scrollStartOffset;
public Page1()
{
InitializeComponent();
myLabel.Loaded += new RoutedEventHandler(myLabel_Loaded);
}
protected override void OnPreviewLeftMouseDown(MouseButtonEventArgs e)
{
mouseDragStartPoint = e.GetPosition(this);
scrollStartOffset.X = myScrollViewer.HorizontalOffset;
scrollStartOffset.Y = myScrollViewer.VerticalOffset;
// Update the cursor if scrolling is possible
this.Cursor = (myScrollViewer.ExtentWidth > myScrollViewer.ViewportWidth) ||
(myScrollViewer.ExtentHeight > myScrollViewer.ViewportHeight) ?
Cursors.ScrollAll : Cursors.Arrow;
this.CaptureMouse();
base.OnPreviewMouseDown(e);
}
protected override void OnPreviewMouseMove(MouseEventArgs e)
{
if (this.IsMouseCaptured)
{
// Get the new mouse position.
Point mouseDragCurrentPoint = e.GetPosition(this);
// Determine the new amount to scroll.
Point delta = new Point(
(mouseDragCurrentPoint.X > this.mouseDragStartPoint.X) ?
-(mouseDragCurrentPoint.X - this.mouseDragStartPoint.X) :
(this.mouseDragStartPoint.X - mouseDragCurrentPoint.X),
(mouseDragCurrentPoint.Y > this.mouseDragStartPoint.Y) ?
-(mouseDragCurrentPoint.Y - this.mouseDragStartPoint.Y) :
(this.mouseDragStartPoint.Y - mouseDragCurrentPoint.Y));
// Scroll to the new position.
myScrollViewer.ScrollToHorizontalOffset(this.scrollStartOffset.X + delta.X);
myScrollViewer.ScrollToVerticalOffset(this.scrollStartOffset.Y + delta.Y);
}
base.OnPreviewMouseMove(e);
}
protected override void OnPreviewMouseUp(MouseButtonEventArgs e)
{
if (this.IsMouseCaptured)
{
this.Cursor = Cursors.Arrow;
this.ReleaseMouseCapture();
}
base.OnPreviewMouseUp(e);
}
void myLabel_Loaded(object sender, RoutedEventArgs e)
{
//center the label initially
myLabel.SetValue(Canvas.LeftProperty,
((myScrollViewer.ExtentWidth / 2) - (myLabel.ActualWidth / 2)));
myLabel.SetValue(Canvas.TopProperty,
((myScrollViewer.ExtentHeight / 2) - (myLabel.ActualHeight / 2)));
myScrollViewer.ScrollToHorizontalOffset(
(myScrollViewer.ExtentWidth / 2) - (myScrollViewer.ViewportWidth / 2));
myScrollViewer.ScrollToVerticalOffset(
(myScrollViewer.ExtentHeight / 2) - (myScrollViewer.ViewportHeight / 2));
}
}
Enjoy ~ Susan