Windows Phone 7 : Dragging and flicking UI controls
- by TechTwaddle
Who would want to flick and drag UI controls!? There might not be many use cases but I think some concepts here are worthy of a post.
So we will create a simple silverlight application for windows phone 7, containing a canvas element on which we’ll place a button control and an image and then, as the title says, drag and flick the controls. Here’s Mainpage.xaml,
<Grid x:Name="LayoutRoot" Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!--TitlePanel contains the name of the application and page title-->
<StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
<TextBlock x:Name="ApplicationTitle" Text="KINETICS" Style="{StaticResource PhoneTextNormalStyle}"/>
<TextBlock x:Name="PageTitle" Text="drag and flick" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
</StackPanel>
<!--ContentPanel - place additional content here-->
<Grid x:Name="ContentPanel" Grid.Row="1" >
<Canvas x:Name="MainCanvas" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Canvas.Background>
<LinearGradientBrush StartPoint="0 0" EndPoint="0 1">
<GradientStop Offset="0" Color="Black"/>
<GradientStop Offset="1.5" Color="BlanchedAlmond"/>
</LinearGradientBrush>
</Canvas.Background>
</Canvas>
</Grid>
</Grid>
the second row in the main grid contains a canvas element, MainCanvas, with its horizontal and vertical alignment set to stretch so that it occupies the entire grid. The canvas background is a linear gradient brush starting with Black and ending with BlanchedAlmond. We’ll add the button and image control to this canvas at run time.
Moving to Mainpage.xaml.cs the Mainpage class contains the following members,
public partial class MainPage : PhoneApplicationPage
{
Button FlickButton;
Image FlickImage;
FrameworkElement ElemToMove = null;
double ElemVelX, ElemVelY;
const double SPEED_FACTOR = 60;
DispatcherTimer timer;
FlickButton and FlickImage are the controls that we’ll add to the canvas. ElemToMove, ElemVelX and ElemVelY will be used by the timer callback to move the ui control. SPEED_FACTOR is used to scale the velocities of ui controls.
Here’s the Mainpage constructor,
// Constructor
public MainPage()
{
InitializeComponent();
AddButtonToCanvas();
AddImageToCanvas();
timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromMilliseconds(35);
timer.Tick += new EventHandler(OnTimerTick);
}
We’ll look at those AddButton and AddImage functions in a moment. The constructor initializes a timer which fires every 35 milliseconds, this timer will be started after the flick gesture completes with some inertia.
Back to AddButton and AddImage functions,
void AddButtonToCanvas()
{
LinearGradientBrush brush;
GradientStop stop1, stop2;
Random rand = new Random(DateTime.Now.Millisecond);
FlickButton = new Button();
FlickButton.Content = "";
FlickButton.Width = 100;
FlickButton.Height = 100;
brush = new LinearGradientBrush();
brush.StartPoint = new Point(0, 0);
brush.EndPoint = new Point(0, 1);
stop1 = new GradientStop();
stop1.Offset = 0;
stop1.Color = Colors.White;
stop2 = new GradientStop();
stop2.Offset = 1;
stop2.Color = (Application.Current.Resources["PhoneAccentBrush"] as SolidColorBrush).Color;
brush.GradientStops.Add(stop1);
brush.GradientStops.Add(stop2);
FlickButton.Background = brush;
Canvas.SetTop(FlickButton, rand.Next(0, 400));
Canvas.SetLeft(FlickButton, rand.Next(0, 200));
MainCanvas.Children.Add(FlickButton);
//subscribe to events
FlickButton.ManipulationDelta += new EventHandler<ManipulationDeltaEventArgs>(OnManipulationDelta);
FlickButton.ManipulationCompleted += new EventHandler<ManipulationCompletedEventArgs>(OnManipulationCompleted);
}
this function is basically glorifying a simple task. After creating the button and setting its height and width, its background is set to a linear gradient brush. The direction of the gradient is from top towards bottom and notice that the second stop color is the PhoneAccentColor, which changes along with the theme of the device. The line,
stop2.Color = (Application.Current.Resources["PhoneAccentBrush"] as SolidColorBrush).Color;
does the magic of extracting the PhoneAccentBrush from application’s resources, getting its color and assigning it to the gradient stop.
AddImage function is straight forward in comparison,
void AddImageToCanvas()
{
Random rand = new Random(DateTime.Now.Millisecond);
FlickImage = new Image();
FlickImage.Source = new BitmapImage(new Uri("/images/Marble.png", UriKind.Relative));
Canvas.SetTop(FlickImage, rand.Next(0, 400));
Canvas.SetLeft(FlickImage, rand.Next(0, 200));
MainCanvas.Children.Add(FlickImage);
//subscribe to events
FlickImage.ManipulationDelta += new EventHandler<ManipulationDeltaEventArgs>(OnManipulationDelta);
FlickImage.ManipulationCompleted += new EventHandler<ManipulationCompletedEventArgs>(OnManipulationCompleted);
}
The ManipulationDelta and ManipulationCompleted handlers are same for both the button and the image.
OnManipulationDelta() should look familiar, a similar implementation was used in the previous post,
void OnManipulationDelta(object sender, ManipulationDeltaEventArgs args)
{
FrameworkElement Elem = sender as FrameworkElement;
double Left = Canvas.GetLeft(Elem);
double Top = Canvas.GetTop(Elem);
Left += args.DeltaManipulation.Translation.X;
Top += args.DeltaManipulation.Translation.Y;
//check for bounds
if (Left < 0)
{
Left = 0;
}
else if (Left > (MainCanvas.ActualWidth - Elem.ActualWidth))
{
Left = MainCanvas.ActualWidth - Elem.ActualWidth;
}
if (Top < 0)
{
Top = 0;
}
else if (Top > (MainCanvas.ActualHeight - Elem.ActualHeight))
{
Top = MainCanvas.ActualHeight - Elem.ActualHeight;
}
Canvas.SetLeft(Elem, Left);
Canvas.SetTop(Elem, Top);
}
all it does is calculate the control’s position, check for bounds and then set the top and left of the control.
OnManipulationCompleted() is more interesting because here we need to check if the gesture completed with any inertia and if it did, start the timer and continue to move the ui control until it comes to a halt slowly,
void OnManipulationCompleted(object sender, ManipulationCompletedEventArgs args)
{
FrameworkElement Elem = sender as FrameworkElement;
if (args.IsInertial)
{
ElemToMove = Elem;
Debug.WriteLine("Linear VelX:{0:0.00} VelY:{1:0.00}", args.FinalVelocities.LinearVelocity.X,
args.FinalVelocities.LinearVelocity.Y);
ElemVelX = args.FinalVelocities.LinearVelocity.X / SPEED_FACTOR;
ElemVelY = args.FinalVelocities.LinearVelocity.Y / SPEED_FACTOR;
timer.Start();
}
}
ManipulationCompletedEventArgs contains a member, IsInertial, which is set to true if the manipulation was completed with some inertia. args.FinalVelocities.LinearVelocity.X and .Y will contain the velocities along the X and Y axis. We need to scale down these values so they can be used to increment the ui control’s position sensibly. A reference to the ui control is stored in ElemToMove and the velocities are stored as well, these will be used in the timer callback to access the ui control. And finally, we start the timer.
The timer callback function is as follows,
void OnTimerTick(object sender, EventArgs e)
{
if (null != ElemToMove)
{
double Left, Top;
Left = Canvas.GetLeft(ElemToMove);
Top = Canvas.GetTop(ElemToMove);
Left += ElemVelX;
Top += ElemVelY;
//check for bounds
if (Left < 0)
{
Left = 0;
ElemVelX *= -1;
}
else if (Left > (MainCanvas.ActualWidth - ElemToMove.ActualWidth))
{
Left = MainCanvas.ActualWidth - ElemToMove.ActualWidth;
ElemVelX *= -1;
}
if (Top < 0)
{
Top = 0;
ElemVelY *= -1;
}
else if (Top > (MainCanvas.ActualHeight - ElemToMove.ActualHeight))
{
Top = MainCanvas.ActualHeight - ElemToMove.ActualHeight;
ElemVelY *= -1;
}
Canvas.SetLeft(ElemToMove, Left);
Canvas.SetTop(ElemToMove, Top);
//reduce x,y velocities gradually
ElemVelX *= 0.9;
ElemVelY *= 0.9;
//when velocities become too low, break
if (Math.Abs(ElemVelX) < 1.0 && Math.Abs(ElemVelY) < 1.0)
{
timer.Stop();
ElemToMove = null;
}
}
}
if ElemToMove is not null, we get the top and left values of the control and increment the values with their X and Y velocities. Check for bounds, and if the control goes out of bounds we reverse its velocity. Towards the end, the velocities are reduced by 10% every time the timer callback is called, and if the velocities reach too low values the timer is stopped and ElemToMove is made null.
Here’s a short video of the program, the video is a little dodgy because my display driver refuses to run the animations smoothly. The flicks aren’t always recognised but the program should run well on an actual device (or a pc with better configuration),
You can download the source code from here: ButtonDragAndFlick.zip