Finding a UIElement's true position when within a PivotItem - silverlight

I'd like to get the on-screen position and visibility of a TextBlock which is embedded in a PivotItem - e.g. the on-screen position and visibility of TB2 in:
<phone:Pivot>
<phone:PivotItem Header="first">
<TextBlock x:Name="TB1" Text="Hello 1"></TextBlock>
</phone:PivotItem>
<phone:PivotItem Header="second" >
<TextBlock x:Name="TB2" Text="Hello 2"></TextBlock>
</phone:PivotItem>
<phone:PivotItem Header="three" >
<TextBlock x:Name="TB3" Text="Hello 3"></TextBlock>
</phone:PivotItem>
</phone:Pivot>
For other Xaml controls I have achieved this using code like:
public static Rect Position(this FrameworkElement element)
{
if (element.Visibility == Visibility.Collapsed)
return Rect.Empty;
if (element.Opacity < 0.01)
return Rect.Empty;
// Obtain transform information based off root element
GeneralTransform gt = element.TransformToVisual(Application.Current.RootVisual);
// Find the four corners of the element
Point topLeft = gt.Transform(new Point(0, 0));
Point topRight = gt.Transform(new Point(element.RenderSize.Width, 0));
Point bottomLeft = gt.Transform(new Point(0, element.RenderSize.Height));
Point bottomRight = gt.Transform(new Point(element.RenderSize.Width, element.RenderSize.Height));
var left = Math.Min(Math.Min(Math.Min(topLeft.X, topRight.X), bottomLeft.X), bottomRight.X);
var top = Math.Min(Math.Min(Math.Min(topLeft.Y, topRight.Y), bottomLeft.Y), bottomRight.Y);
var position = new Rect(left, top, element.ActualWidth, element.ActualHeight);
return position;
}
However, for PivotItem children this calculation doesn't provide the expected answers when the PivotItem is off-screen. Instead it provides the answer of the position the item will take on-screen when the PivotItem is swiped back on-screen. During a transition, I can see that the position is correctly calculated using the above method - e.g. during the transition I will see the position correctly swipe in from the left/right of the screen.
I've looked through other properties like Visible and Opacity to see if there is some other mechanism being used here to hide the off-screen PivotItem or one of its Parent, GrandParent, etc - but all of those seem to yield Visible and Opacity == 1.0 results.
I've also tried iterating the VisualTree to see if any element with a solid background is masking the "off-screen" PivotItems - but I can't see any in the tree.
Is there some other property to consider here? How are the PivotItem contents hidden when they are "off-screen"? If I really have to, I know that I can use the SelectedIndex property of the Pivot to try to help with the PivotItem position calculations, but I'm hoping to avoid SelectedIndex if I can - I'd prefer to get the position using general Xaml and VisualTree methods if at all possible.

I looked into it, but I can't find how they are hiding the PivotItems. I guess the position is correct, but the item is just not drawn for unknown (to me) reasons. I'll look into it again, later, but for now I wanted to mention a few other potential issues I noticed.
Shouldn't you be using Math.Max for the bottom right corner? Reason being - ActualHeight and ActualWidth are not rotation-aware so to speak.
RenderSize.Width/Height and ActualWidth/Height are different. I'm not sure it's a good idea to use both of them for the calculation.
Don't you need to check everything in the visual tree above the element to see if it's actually visible?
Edit: I looked at Microsoft.Phone.dll's decompiled source (that's the dll with the Pivot control), and I couldn't find anything about how the PivotItems are hidden. There are some native method calls though.

I have made some Changes to Your code and its giving UIElement's true position
XAML:
<Grid x:Name="LayoutRoot" Background="Transparent">
<!--Pivot Control-->
<phone:Pivot x:Name="MyPivot">
<phone:PivotItem Header="first" x:Name="first">
<TextBlock x:Name="TB1" FontSize="30" Text="Hello 1" Tap="TB1_Tap"></TextBlock>
</phone:PivotItem>
<phone:PivotItem Header="second" x:Name="second">
<TextBlock x:Name="TB2" FontSize="50" Height="66" Width="155" Margin="20" Text="Hello 2" Tap="TB2_Tap"></TextBlock>
</phone:PivotItem>
<phone:PivotItem Header="three" x:Name="three">
<TextBlock x:Name="TB3" Text="Hello 3"></TextBlock>
</phone:PivotItem>
</phone:Pivot>
</Grid>
CS:
public partial class PivotPage1 : PhoneApplicationPage
{
public PivotPage1()
{
InitializeComponent();
}
private void TB1_Tap(object sender, System.Windows.Input.GestureEventArgs e)
{
Rect r = Pos.MyPosition(TB1,first);
string str = "Left" + r.Left.ToString() + "\nTop:" + r.Top.ToString() + "\nRight:" + r.Right.ToString() + "\nBottom:" + r.Bottom.ToString() + "\nHeight:" + r.Height.ToString() + "\nWidth" + r.Width.ToString();
MessageBox.Show(str);
}
private void TB2_Tap(object sender, System.Windows.Input.GestureEventArgs e)
{
Rect r = Pos.MyPosition(TB2,second);
string str = "Left" + r.Left.ToString() + "\nTop:" + r.Top.ToString() + "\nRight:" + r.Right.ToString() + "\nBottom:" + r.Bottom.ToString() + "\nHeight:" + r.Height.ToString() + "\nWidth" + r.Width.ToString();
MessageBox.Show(str);
}
}
public static class Pos
{
public static Rect MyPosition(FrameworkElement child,FrameworkElement parent)
{
if (child.Visibility == Visibility.Collapsed)
return Rect.Empty;
if (child.Opacity < 0.01)
return Rect.Empty;
// Obtain transform information based off root child
GeneralTransform gt = child.TransformToVisual(parent);
// Find the four corners of the child
Point topLeft = gt.Transform(new Point(0, 0));
Point topRight = gt.Transform(new Point(child.RenderSize.Width, 0));
Point bottomLeft = gt.Transform(new Point(0, child.RenderSize.Height));
Point bottomRight = gt.Transform(new Point(child.RenderSize.Width, child.RenderSize.Height));
var left = Math.Min(Math.Min(Math.Min(topLeft.X, topRight.X), bottomLeft.X), bottomRight.X);
var top = Math.Min(Math.Min(Math.Min(topLeft.Y, topRight.Y), bottomLeft.Y), bottomRight.Y);
var position = new Rect(left, top, child.ActualWidth, child.ActualHeight);
return position;
}
}

Related

Creating a Circular GUI

So one of my latest side projects is developing a application detection and populating assistant. Programmatically I am absolutely fine populating the backend code for what I want accomplished. But I've run into a road block on the GUI. I need a GUI that is a Quarter circle that extends from the task bar to the bottom right of a standard windows operating system. When the user doubleclicks on the application, the circle rotates into view. I can do this with a typical windows form that has a transparent background and a fancy background image. But the square properties of the form will still apply when the user has the application open. And I do not want to block the user from higher priority apps when the circle is open.
I'm not really stuck on any one specific programming language. Although, I would prefer that it not contain much 3d rendering as it is supposed to be a computing assistant and should not maintain heavy RAM/CPU consumption whilst the user is browsing around.
Secondarily, I would like the notches of the outer rings to be mobile and extend beyond the gui a mere centimeter or so.
I would not be here if I hadn't had scoured the internet for direction on this capability. But what I've found is application GUI's of this nature tend to be most used in mobile environments.
So my questions are: How can I accomplish this? What programming language can I write this in? Is this a capability currently available? Will I have to sacrifice user control for design?
I wrote out some code doing something close to what you described.
I’m not sure to understand how you do want the circle to appear, so I just let a part of it always visible.
And I didn’t get the part about the mobile outer ring.
Creating and placing the window
The XAML is very simple, it just needs a grid to host the circle’s pieces, and some attributes to remove window decorations and taskbar icon:
<Window x:Class="circle.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Circle"
Width="250"
Height="250"
AllowsTransparency="True"
Background="Transparent"
MouseDown="WindowClicked"
ShowInTaskbar="False"
WindowStyle="None">
<Grid Name="Container"/>
</Window>
To place the window in the bottom right corner, you can use SystemParameters.WorkArea in the constructor:
public MainWindow()
{
InitializeComponent();
var desktopDim = SystemParameters.WorkArea;
Left = desktopDim.Right - Width;
Top = desktopDim.Bottom - Height;
}
Creating the shape
I build the circle as a bunch of circle pieces that I generate from code behind:
private Path CreateCirclePart()
{
var circle = new CombinedGeometry
{
GeometryCombineMode = GeometryCombineMode.Exclude,
Geometry1 = new EllipseGeometry { Center = _center, RadiusX = _r2, RadiusY = _r2 },
Geometry2 = new EllipseGeometry { Center = _center, RadiusX = _r1, RadiusY = _r1 }
};
var sideLength = _r2 / Math.Cos((Math.PI/180) * (ItemAngle / 2.0));
var x = _center.X - Math.Abs(sideLength * Math.Cos(ItemAngle * Math.PI / 180));
var y = _center.Y - Math.Abs(sideLength * Math.Sin(ItemAngle * Math.PI / 180));
var triangle = new PathGeometry(
new PathFigureCollection(new List<PathFigure>{
new PathFigure(
_center,
new List<PathSegment>
{
new LineSegment(new Point(_center.X - Math.Abs(sideLength),_center.Y), true),
new LineSegment(new Point(x,y), true)
},
true)
}));
var path = new Path
{
Fill = new SolidColorBrush(Colors.Cyan),
Stroke = new SolidColorBrush(Colors.Black),
StrokeThickness = 1,
RenderTransformOrigin = new Point(1, 1),
RenderTransform = new RotateTransform(0),
Data = new CombinedGeometry
{
GeometryCombineMode = GeometryCombineMode.Intersect,
Geometry1 = circle,
Geometry2 = triangle
}
};
return path;
}
First step is to build two concentric circles and to combine them in a CombinedGeometry with CombineMode set to exclude. Then I create a triangle just tall enough to contain the section of the ring that I want, and I keep the intersection of these shapes.
Seeing it with the second CombineMode set to xor may clarify:
Building the circle
The code above uses some instance fields that make it generic: you can change the number of pieces in the circle or their radius; it will always fill the corner.
I then populate a list with the required number of shape, and add them to the grid:
private const double MenuWidth = 80;
private const int ItemCount = 6;
private const double AnimationDelayInSeconds = 0.3;
private readonly Point _center;
private readonly double _r1, _r2;
private const double ItemSpacingAngle = 2;
private const double ItemAngle = (90.0 - (ItemCount - 1) * ItemSpacingAngle) / ItemCount;
private readonly List<Path> _parts = new List<Path>();
private bool _isOpen;
public MainWindow()
{
InitializeComponent();
// window in the lower right desktop corner
var desktopDim = SystemParameters.WorkArea;
Left = desktopDim.Right - Width;
Top = desktopDim.Bottom - Height;
_center = new Point(Width, Height);
_r2 = Width;
_r1 = _r2 - MenuWidth;
Loaded += (s, e) => CreateMenu();
}
private void CreateMenu()
{
for (var i = 0; i < ItemCount; ++i)
{
var part = CreateCirclePart();
_parts.Add(part);
Container.Children.Add(part);
}
}
ItemSpacingAngle define the blank between two consecutive pieces.
Animating the circle
The final step is to unfold the circle. Using a rotateAnimation over the path rendertransform make it easy.
Remember this part of the CreateCirclePart function:
RenderTransformOrigin = new Point(1, 1),
RenderTransform = new RotateTransform(0),
The RenderTransform tells that the animation we want to perform is a rotation, and RenderTransformOrigin set the rotation origin to the lower right corner of the shape (unit is percent).
We can now animate it on click event:
private void WindowClicked(object sender, MouseButtonEventArgs e)
{
for (var i = 0; i < ItemCount; ++i)
{
if (!_isOpen)
UnfoldPart(_parts[i], i);
else
FoldPart(_parts[i], i);
}
_isOpen = !_isOpen;
}
private void UnfoldPart(Path part, int pos)
{
var newAngle = pos * (ItemAngle + ItemSpacingAngle);
var rotateAnimation = new DoubleAnimation(newAngle, TimeSpan.FromSeconds(AnimationDelayInSeconds));
var tranform = (RotateTransform)part.RenderTransform;
tranform.BeginAnimation(RotateTransform.AngleProperty, rotateAnimation);
}
private void FoldPart(Path part, int pos)
{
var rotateAnimation = new DoubleAnimation(0, TimeSpan.FromSeconds(AnimationDelayInSeconds));
var tranform = (RotateTransform)part.RenderTransform;
tranform.BeginAnimation(RotateTransform.AngleProperty, rotateAnimation);
}
Not actually answering this, but I liked your question enough that I wanted to get a minimal proof of concept together for fun and I really enjoyed doing it so i thought I'd share my xaml with you:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ed="http://schemas.microsoft.com/expression/2010/drawing" x:Class="WpfApplication1.Window2"
Title="Window2" Height="150" Width="150" Topmost="True" MouseLeftButtonDown="Window2_OnMouseLeftButtonDown"
AllowsTransparency="True"
OpacityMask="White"
WindowStyle="None"
Background="Transparent" >
<Grid>
<ed:Arc ArcThickness="40"
ArcThicknessUnit="Pixel" EndAngle="0" Fill="Blue" HorizontalAlignment="Left"
Height="232" Margin="33,34,-115,-116" Stretch="None"
StartAngle="270" VerticalAlignment="Top" Width="232" RenderTransformOrigin="0.421,0.471"/>
<Button HorizontalAlignment="Left" VerticalAlignment="Top" Width="41" Margin="51.515,71.385,0,0" Click="Button_Click" RenderTransformOrigin="0.5,0.5">
<Button.Template>
<ControlTemplate>
<Path Data="M50.466307,88.795148 L61.75233,73.463763 89.647286,102.42368 81.981422,113.07109 z"
Fill="DarkBlue" HorizontalAlignment="Left" Height="39.606"
Stretch="Fill" VerticalAlignment="Top" Width="39.181"/>
</ControlTemplate>
</Button.Template>
</Button>
</Grid>
</Window>
And it looks like this:

Click, Drag, and Scroll Canvas View

I have a ListBox with a ItemsPanelTemplate of Canvas. I know the ScrollViewer will not work with the Canvas unless its given a height and width. I DO NOT want to give the canvas a height and width because it will not always be constant. Is there any other work around or tricks anyone has gotten to work for this situation. I know I can't be the only one with this problem. Thanks in advance here is my code so far.
Another problem is I am unable to place the ScrollViewer inside the ItemsPanelTemplate because it can only have one element nested inside.
This also restricts me from placing the canvas inside a grid to get positioning.
XAML:
<!--Core Viewer-->
<ScrollViewer x:Name="scrollViewer"
VerticalScrollBarVisibility="Hidden"
HorizontalScrollBarVisibility="Hidden">
<ListBox x:Name="objCoreViewer"
ItemsSource="{Binding ItemsSource}"
Background="LightGray"
SelectionChanged="objCoreViewer_SelectionChanged"
ItemTemplateSelector="{DynamicResource CoreViewerDataTemplateSelector}"
ItemContainerStyleSelector="{DynamicResource ItemContainerStyleSelector}"
PreviewMouseWheel="objCoreViewer_PreviewMouseWheel">
<!-- Core Map Canvas -->
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<Canvas x:Name="objCoreViewerCanvas"
Background="Transparent">
<Canvas.LayoutTransform>
<ScaleTransform ScaleX="{Binding Path=Value, ElementName=ZoomSlider}"
ScaleY="{Binding Path=Value, ElementName=ZoomSlider}" />
</Canvas.LayoutTransform>
</Canvas>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
</ScrollViewer>
To get the canvas to grow based on the the child elements inside of it you will need to override the Canvas's MeasureOverride event in a custom class that inherits from Canavas. This code works great for me:
protected override System.Windows.Size MeasureOverride(System.Windows.Size constraint)
{
Size toReport = new Size();
foreach (UIElement element in this.InternalChildren)
{
//Get the left most and top most point. No using Bottom or Right in case the controls actual bottom and right most points are less then the desired height/width
var left = Canvas.GetLeft(element);
var top = Canvas.GetTop(element);
left = double.IsNaN(left) ? 0 : left;
top = double.IsNaN(top) ? 0 : top;
element.Measure(constraint);
Size desiredSize = element.DesiredSize;
if (!double.IsNaN(desiredSize.Width) && !double.IsNaN(desiredSize.Height))
{
//left += desiredSize.Width;
//top += desiredSize.Height;
toReport.Width = toReport.Width > left +desiredSize.Width ? toReport.Width : left + desiredSize.Width;
toReport.Height = toReport.Height > top+desiredSize.Height ? toReport.Height : top + desiredSize.Height;
}
}
//Make sure scroll includes the margins incase of a border or something
toReport.Width += this.Margin.Right;
toReport.Height += this.Margin.Bottom;
return toReport;
}

Silverlight 4 - Am I using TransformToVisual() correctly?

I have just started to play around with Silverlight and I decided to do a small app in Visual Studio 2010. I am trying to find the current position of a usercontrol in a Canvas. Here is the XAML layout:
<Grid x:Name="LayoutRoot" Background="#FF141313">
<Grid.RowDefinitions>
<RowDefinition Height="39"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Button Opacity="0.5" Background="{x:Null}" BorderThickness="1" FontFamily="Courier New" Content="Align Images" Cursor="Hand" Name="buttonAlignImages" Click="buttonAlignImages_Click" Margin="45,8,0,11" HorizontalAlignment="Left" Width="84" />
<Button HorizontalAlignment="Left" Width="33" Opacity="0.5" Background="{x:Null}" BorderThickness="1" FontFamily="Courier New" Content="Home" Cursor="Hand" Margin="8,8,0,11"/>
<Canvas x:Name="ImageContainer" Margin="8" Grid.Row="1" Background="Black"/>
</Grid>
My usercontrol is added to the "ImageContainer" Canvas. One of the buttons in XAML is called "buttonAlignImages". When the user clicks this I basically want the images to be aligned in a specific manner. Anyways to do this I want to first get the position of the usercontrol embedded in the "ImageContainer". So here is the code when the button is clicked:
private void buttonAlignImages_Click(object sender, RoutedEventArgs e)
{
double margin = 5.0;
Point top_left = new Point(margin, margin);
Point top_right = new Point(ActualWidth - margin, margin);
Point bottom_left = new Point(5.0, ActualHeight - margin);
Point bottom_right = new Point(ActualWidth - margin, ActualHeight - margin);
foreach (UIElement element in ImageContainer.Children)
{
Photo singlePhoto = element as Photo;
if (singlePhoto != null)
{
// get the transform for the current photo as applicable to basically this visual
GeneralTransform gt = singlePhoto.TransformToVisual(ImageContainer);
// get the position on the root visual by applying the transform to the singlePhoto
Point singlePhotoTopLeft = gt.Transform(new Point(0, 0));
// now translate the position of the singlePhoto
singlePhoto.Translate(singlePhotoTopLeft.X - top_left.X, singlePhotoTopLeft.Y - top_left.Y);
}
}
}
public void Translate(double deltaX, double deltaY)
{
translateTransform.X += deltaX;
translateTransform.Y += deltaY;
}
The embedded photo usercontrol does move around but when I call gt.Transform(new Point(0,0)) it always gives me (0,0), so the resulting translation is only by 5 pixels. Why does this happen? Am I not using TransformToVisual() correctly?
It s been a while but if u cannot live on without the answer :) will u try
Point singlePhotoTopLeft = gt.Transform(new Point());
instead ?

WPF: how to make the (0,0) in center inside a Canvas

The WPF Canvas has a coordinate system starting at (0,0) at the top-left of the control.
For example, setting the following will make my control appear on the top-left:
<Control Canvas.Left="0" Canvas.Top="0">
How can I change it to the standard cartesian coordinates?
Basically:
(0,0) at center
flip Y
I noticed this post is similar, but it does not talk about translating the coordinate system. I tried adding a TranslateTransform, but I can't make it work.
There is no need to create a custom Panel. Canvas will do just fine. Simply wrap it inside another control (such as a border), center it, give it zero size, and flip it with a RenderTransform:
<Border>
<Canvas HorizontalAlignment="Center" VerticalAlignment="Center"
Width="0" Height="0"
RenderTransform="1 0 0 -1 0 0">
...
</Canvas>
</Border>
You can do this and everything in the canvas will still appear, except (0,0) will be at the center of the containing control (in this case, the center of the Border) and +Y will be up instead of down.
Again, there is no need to create a custom panel for this.
It was very easy to do. I looked at the original Canvas's code using .NET Reflector, and noticed the implementation is actually very simple. The only thing required was to override the function ArrangeOverride(...)
public class CartesianCanvas : Canvas
{
public CartesianCanvas()
{
LayoutTransform = new ScaleTransform() { ScaleX = 1, ScaleY = -1 };
}
protected override Size ArrangeOverride( Size arrangeSize )
{
Point middle = new Point( arrangeSize.Width / 2, arrangeSize.Height / 2 );
foreach( UIElement element in base.InternalChildren )
{
if( element == null )
{
continue;
}
double x = 0.0;
double y = 0.0;
double left = GetLeft( element );
if( !double.IsNaN( left ) )
{
x = left;
}
double top = GetTop( element );
if( !double.IsNaN( top ) )
{
y = top;
}
element.Arrange( new Rect( new Point( middle.X + x, middle.Y + y ), element.DesiredSize ) );
}
return arrangeSize;
}
}
You can simply change the Origin with RenderTransformOrigin.
<Canvas Width="Auto" Height="Auto"
HorizontalAlignment="Center"
VerticalAlignment="Center"
RenderTransformOrigin="0.5,0.5">
<Canvas.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleY="-1" ScaleX="1" />
</TransformGroup>
</Canvas.RenderTransform>
</Canvas>
The best thing is to write a custom Canvas, in which you can write ArrangeOverride in such a way that it takes 0,0 as the center.
Update : I had given another comment in the below answer (#decasteljau) I wont recommend deriving from Canvas, You can derive from Panel and add two Attached Dependancy properties Top and Left and put the same code you pasted above. Also doesnt need a constructor with LayoutTransform in it, And dont use any transform on the panel code use proper measure and arrange based on the DesiredSize of the panel So that you can get nice content resize behavior too. Canvas doesn't dynamically position items when the Canvas size changes.

Grab-to-pan in Silverlight app

I have a Canvas inside a ScrollViewer. I want to have the user to be able to grab the canvas and move it around, with the thumbs on the scrollbars updating appropriately.
My initial implementation calculates the offset on each mouse move, and updates the scrollbars:
// Calculate the new drag distance
Point newOffsetPos = e.GetPosition(MapCanvas);
System.Diagnostics.Debug.WriteLine(" newOffsetPos : " + newOffsetPos.X + " " + newOffsetPos.Y);
double deltaX = newOffsetPos.X - _offsetPosition.X ;
double deltaY = newOffsetPos.Y - _offsetPosition.Y ;
System.Diagnostics.Debug.WriteLine(" delta X / Y : " + deltaX + " " + deltaY);
System.Diagnostics.Debug.WriteLine(" sv offsets X / Y : " + _scrollViewer.HorizontalOffset + " " + _scrollViewer.VerticalOffset);
_scrollViewer.ScrollToHorizontalOffset(_scrollViewer.HorizontalOffset - deltaX);
_scrollViewer.ScrollToVerticalOffset(_scrollViewer.VerticalOffset - deltaY);
_offsetPosition = newOffsetPos;
While this works, it is not very smooth.
Is there a better way to do this? If Transforms are used, will the scrollbars update automagically when the Canvas is moved?
Thanks for any tips...
Actually this sort of problem is really a matter of using the right pattern to track the mouse. I've seen this issue in variety of cases not just in Silverlight.
The best pattern is to trap the original locations of both mouse and subject, then recalculate the new offset from the fixed original values. That way the mouse stays planted solid at a single point on the image being panned. Here is my simple Repro:-
Start with a fresh Silverlight Application in visual studio. Modify MainPage.Xaml thus:-
<UserControl x:Class="SilverlightApplication1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid x:Name="LayoutRoot" Background="White">
<ScrollViewer x:Name="Scroller" HorizontalScrollBarVisibility="Auto">
<Image x:Name="Map" Source="test.jpg" Width="1600" Height="1200" />
</ScrollViewer>
</Grid>
</UserControl>
Add the following code to the MainPage.xaml.cs file:-
public MainPage()
{
InitializeComponent();
Map.MouseLeftButtonDown += new MouseButtonEventHandler(Map_MouseLeftButtonDown);
}
void Map_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Point mapOrigin = new Point(Scroller.HorizontalOffset, Scroller.VerticalOffset);
Point mouseOrigin = e.GetPosition(Application.Current.RootVisual);
MouseEventHandler moveHandler = null;
MouseButtonEventHandler upHandler = null;
moveHandler = (s, args) =>
{
Point mouseNew = args.GetPosition(Application.Current.RootVisual);
Scroller.ScrollToHorizontalOffset(mapOrigin.X - (mouseNew.X - mouseOrigin.X));
Scroller.ScrollToVerticalOffset(mapOrigin.Y - (mouseNew.Y - mouseOrigin.Y));
};
upHandler = (s, args) =>
{
Scroller.MouseMove -= moveHandler;
Scroller.MouseLeftButtonUp -= upHandler;
};
Scroller.MouseMove += moveHandler;
Scroller.MouseLeftButtonUp += upHandler;
}
}
Give it a reasonably large test.jpg (doesn't need to be 1600x1200 Image will scale it).
You'll note that when dragging the mouse remains exactly over a fixed point in the image until you hit a boundary. Move the mouse as fast as you like it always tracks, this is because it doesn't depend on deltas being accurate and up-to-date. The only variable is the current mouse position, the other values remain fixed as they were at mouse down.
You could try this (or at least take a peek at how it's implemented).

Resources