在微軟員工撰寫的文章 [好的 Windows Phone App 通常具備的特點] 文中提到
http://blogs.msdn.com/b/hermanwu/archive/2012/11/24/windows-phone-app.aspx
1. 資料載入時要有載入中畫面
2. Tilt Effect
本文針對這兩個好的特點說明如何實作。
本文刊載於
http://www.dotblogs.com.tw/chou/archive/2013/10/07/123394.aspx
http://www.dotblogs.com.tw/chou/archive/2013/09/20/119096.aspx
在這篇文章您可以學到:
資料載入時要有載入中畫面
想要達成這樣的功能,可以操作 SystemTray 搭配 ProgressIndicator 達成。
http://msdn.microsoft.com/en-us/library/windowsphone/develop/ff626537%28v=vs.105%29.aspx
新增專案,在 MainPage.xaml 中,加入 CheckBox 控制項。
切換至程式碼,我們搭配 ProgressIndicator 提供用於與應用程式的 SystemTray 做進度指示的方法和屬性。
http://msdn.microsoft.com/en-US/library/windowsphone/develop/microsoft.phone.shell.progressindicator%28v=vs.105%29.aspx
先引用 Microsoft.Phone.Shell
using Microsoft.Phone.Shell;
建立 ProgressIndicator 並設定給 SystemTray 程式碼
// 建立 ProgressIndicator 並設定給 SystemTray
ProgressIndicator pi = new ProgressIndicator();
pi.Text = "Loading...";
pi.IsIndeterminate = true;
pi.IsVisible = true;
SystemTray.SetProgressIndicator(this, pi);
以下是程式完整程式碼,其中 CheckBox 包含兩個事件 Checked 和 Unchecked,當 Checked 觸發時,顯示 System Tray,當 Unchecked 事件觸發時,隱藏 System Tray。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;
namespace SystemTrayApp
{
public partial class MainPage : PhoneApplicationPage
{
// 建構函式
public MainPage()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(MainPage_Loaded);
}
private void MainPage_Loaded(object sender, RoutedEventArgs e)
{
// 建立 ProgressIndicator 並設定給 SystemTray
ProgressIndicator pi = new ProgressIndicator();
pi.Text = "Loading...";
pi.IsIndeterminate = true;
pi.IsVisible = true;
SystemTray.SetProgressIndicator(this, pi);
// Checkbox 屬性與事件
this.cbShowSystemTray.IsChecked = true;
this.cbShowSystemTray.Checked += cbShowSystemTray_Checked;
this.cbShowSystemTray.Unchecked += cbShowSystemTray_Unchecked;
}
void cbShowSystemTray_Checked(object sender, RoutedEventArgs e)
{
// SystemTray 顯示
SystemTray.IsVisible = true;
}
void cbShowSystemTray_Unchecked(object sender, RoutedEventArgs e)
{
// SystemTray 隱藏
SystemTray.IsVisible = false;
}
}
}
執行程式,一開始就會顯示 Loading...
取消勾選 CheckBox 後 Loading ... 消失
Tilt Effect
應用程式中需要讓所有可按的連結實作 Tilt Effect,Tilt Effect 主要是讓控制項按下時,會有下凹傾斜的效果,讓使用者使用上更加明顯。
接著進行實作,新增專案,新增類別 TiltEffect.cs,程式碼如下,檔案來源來自微軟網站 Tilt Effect Sample。
http://code.msdn.microsoft.com/wpapps/Tilt-Effect-Sample-fab3b035
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Collections.Generic;
using System.Windows.Controls.Primitives;
#if WINDOWS_PHONE
using Microsoft.Phone.Controls;
#endif
namespace ControlTiltEffect
{
/// <summary>
/// This code provides attached properties for adding a 'tilt' effect to all controls within a container.
/// </summary>
public class TiltEffect : DependencyObject
{
#region Constructor and Static Constructor
/// <summary>
/// This is not a constructable class, but it cannot be static because it derives from DependencyObject.
/// </summary>
private TiltEffect()
{
}
/// <summary>
/// Initialize the static properties
/// </summary>
static TiltEffect()
{
// The tiltable items list.
TiltableItems = new List<Type>() { typeof(ButtonBase), typeof(ListBoxItem), };
UseLogarithmicEase = false;
}
#endregion
#region Fields and simple properties
// These constants are the same as the built-in effects
/// <summary>
/// Maximum amount of tilt, in radians
/// </summary>
const double MaxAngle = 0.3;
/// <summary>
/// Maximum amount of depression, in pixels
/// </summary>
const double MaxDepression = 25;
/// <summary>
/// Delay between releasing an element and the tilt release animation playing
/// </summary>
static readonly TimeSpan TiltReturnAnimationDelay = TimeSpan.FromMilliseconds(200);
/// <summary>
/// Duration of tilt release animation
/// </summary>
static readonly TimeSpan TiltReturnAnimationDuration = TimeSpan.FromMilliseconds(100);
/// <summary>
/// The control that is currently being tilted
/// </summary>
static FrameworkElement currentTiltElement;
/// <summary>
/// The single instance of a storyboard used for all tilts
/// </summary>
static Storyboard tiltReturnStoryboard;
/// <summary>
/// The single instance of an X rotation used for all tilts
/// </summary>
static DoubleAnimation tiltReturnXAnimation;
/// <summary>
/// The single instance of a Y rotation used for all tilts
/// </summary>
static DoubleAnimation tiltReturnYAnimation;
/// <summary>
/// The single instance of a Z depression used for all tilts
/// </summary>
static DoubleAnimation tiltReturnZAnimation;
/// <summary>
/// The center of the tilt element
/// </summary>
static Point currentTiltElementCenter;
/// <summary>
/// Whether the animation just completed was for a 'pause' or not
/// </summary>
static bool wasPauseAnimation = false;
/// <summary>
/// Whether to use a slightly more accurate (but slightly slower) tilt animation easing function
/// </summary>
public static bool UseLogarithmicEase { get; set; }
/// <summary>
/// Default list of items that are tiltable
/// </summary>
public static List<Type> TiltableItems { get; private set; }
#endregion
#region Dependency properties
/// <summary>
/// Whether the tilt effect is enabled on a container (and all its children)
/// </summary>
public static readonly DependencyProperty IsTiltEnabledProperty = DependencyProperty.RegisterAttached(
"IsTiltEnabled",
typeof(bool),
typeof(TiltEffect),
new PropertyMetadata(OnIsTiltEnabledChanged)
);
/// <summary>
/// Gets the IsTiltEnabled dependency property from an object
/// </summary>
/// <param name="source">The object to get the property from</param>
/// <returns>The property's value</returns>
public static bool GetIsTiltEnabled(DependencyObject source) { return (bool)source.GetValue(IsTiltEnabledProperty); }
/// <summary>
/// Sets the IsTiltEnabled dependency property on an object
/// </summary>
/// <param name="source">The object to set the property on</param>
/// <param name="value">The value to set</param>
public static void SetIsTiltEnabled(DependencyObject source, bool value) { source.SetValue(IsTiltEnabledProperty, value); }
/// <summary>
/// Suppresses the tilt effect on a single control that would otherwise be tilted
/// </summary>
public static readonly DependencyProperty SuppressTiltProperty = DependencyProperty.RegisterAttached(
"SuppressTilt",
typeof(bool),
typeof(TiltEffect),
null
);
/// <summary>
/// Gets the SuppressTilt dependency property from an object
/// </summary>
/// <param name="source">The object to get the property from</param>
/// <returns>The property's value</returns>
public static bool GetSuppressTilt(DependencyObject source) { return (bool)source.GetValue(SuppressTiltProperty); }
/// <summary>
/// Sets the SuppressTilt dependency property from an object
/// </summary>
/// <param name="source">The object to get the property from</param>
/// <returns>The property's value</returns>
public static void SetSuppressTilt(DependencyObject source, bool value) { source.SetValue(SuppressTiltProperty, value); }
/// <summary>
/// Property change handler for the IsTiltEnabled dependency property
/// </summary>
/// <param name="target">The element that the property is atteched to</param>
/// <param name="args">Event args</param>
/// <remarks>
/// Adds or removes event handlers from the element that has been (un)registered for tilting
/// </remarks>
static void OnIsTiltEnabledChanged(DependencyObject target, DependencyPropertyChangedEventArgs args)
{
if (target is FrameworkElement)
{
// Add / remove the event handler if necessary
if ((bool)args.NewValue == true)
{
(target as FrameworkElement).ManipulationStarted += TiltEffect_ManipulationStarted;
}
else
{
(target as FrameworkElement).ManipulationStarted -= TiltEffect_ManipulationStarted;
}
}
}
#endregion
#region Top-level manipulation event handlers
/// <summary>
/// Event handler for ManipulationStarted
/// </summary>
/// <param name="sender">sender of the event - this will be the tilt container (eg, entire page)</param>
/// <param name="e">event args</param>
static void TiltEffect_ManipulationStarted(object sender, ManipulationStartedEventArgs e)
{
TryStartTiltEffect(sender as FrameworkElement, e);
}
/// <summary>
/// Event handler for ManipulationDelta
/// </summary>
/// <param name="sender">sender of the event - this will be the tilting object (eg a button)</param>
/// <param name="e">event args</param>
static void TiltEffect_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
{
ContinueTiltEffect(sender as FrameworkElement, e);
}
/// <summary>
/// Event handler for ManipulationCompleted
/// </summary>
/// <param name="sender">sender of the event - this will be the tilting object (eg a button)</param>
/// <param name="e">event args</param>
static void TiltEffect_ManipulationCompleted(object sender, ManipulationCompletedEventArgs e)
{
EndTiltEffect(currentTiltElement);
}
#endregion
#region Core tilt logic
/// <summary>
/// Checks if the manipulation should cause a tilt, and if so starts the tilt effect
/// </summary>
/// <param name="source">The source of the manipulation (the tilt container, eg entire page)</param>
/// <param name="e">The args from the ManipulationStarted event</param>
static void TryStartTiltEffect(FrameworkElement source, ManipulationStartedEventArgs e)
{
foreach (FrameworkElement ancestor in (e.OriginalSource as FrameworkElement).GetVisualAncestors())
{
foreach (Type t in TiltableItems)
{
if (t.IsAssignableFrom(ancestor.GetType()))
{
if ((bool)ancestor.GetValue(SuppressTiltProperty) != true)
{
// Use first child of the control, so that you can add transforms and not
// impact any transforms on the control itself
FrameworkElement element = VisualTreeHelper.GetChild(ancestor, 0) as FrameworkElement;
FrameworkElement container = e.ManipulationContainer as FrameworkElement;
if (element == null || container == null)
return;
// Touch point relative to the element being tilted
Point tiltTouchPoint = container.TransformToVisual(element).Transform(e.ManipulationOrigin);
// Center of the element being tilted
Point elementCenter = new Point(element.ActualWidth / 2, element.ActualHeight / 2);
// Camera adjustment
Point centerToCenterDelta = GetCenterToCenterDelta(element, source);
BeginTiltEffect(element, tiltTouchPoint, elementCenter, centerToCenterDelta);
return;
}
}
}
}
}
/// <summary>
/// Computes the delta between the centre of an element and its container
/// </summary>
/// <param name="element">The element to compare</param>
/// <param name="container">The element to compare against</param>
/// <returns>A point that represents the delta between the two centers</returns>
static Point GetCenterToCenterDelta(FrameworkElement element, FrameworkElement container)
{
Point elementCenter = new Point(element.ActualWidth / 2, element.ActualHeight / 2);
Point containerCenter;
#if WINDOWS_PHONE
// Need to special-case the frame to handle different orientations
if (container is PhoneApplicationFrame)
{
PhoneApplicationFrame frame = container as PhoneApplicationFrame;
// Switch width and height in landscape mode
if ((frame.Orientation & PageOrientation.Landscape) == PageOrientation.Landscape)
{
containerCenter = new Point(container.ActualHeight / 2, container.ActualWidth / 2);
}
else
containerCenter = new Point(container.ActualWidth / 2, container.ActualHeight / 2);
}
else
containerCenter = new Point(container.ActualWidth / 2, container.ActualHeight / 2);
#else
containerCenter = new Point(container.ActualWidth / 2, container.ActualHeight / 2);
#endif
Point transformedElementCenter = element.TransformToVisual(container).Transform(elementCenter);
Point result = new Point(containerCenter.X - transformedElementCenter.X, containerCenter.Y - transformedElementCenter.Y);
return result;
}
/// <summary>
/// Begins the tilt effect by preparing the control and doing the initial animation
/// </summary>
/// <param name="element">The element to tilt </param>
/// <param name="touchPoint">The touch point, in element coordinates</param>
/// <param name="centerPoint">The center point of the element in element coordinates</param>
/// <param name="centerDelta">The delta between the <paramref name="element"/>'s center and
/// the container's center</param>
static void BeginTiltEffect(FrameworkElement element, Point touchPoint, Point centerPoint, Point centerDelta)
{
if (tiltReturnStoryboard != null)
StopTiltReturnStoryboardAndCleanup();
if (PrepareControlForTilt(element, centerDelta) == false)
return;
currentTiltElement = element;
currentTiltElementCenter = centerPoint;
PrepareTiltReturnStoryboard(element);
ApplyTiltEffect(currentTiltElement, touchPoint, currentTiltElementCenter);
}
/// <summary>
/// Prepares a control to be tilted by setting up a plane projection and some event handlers
/// </summary>
/// <param name="element">The control that is to be tilted</param>
/// <param name="centerDelta">Delta between the element's center and the tilt container's</param>
/// <returns>true if successful; false otherwise</returns>
/// <remarks>
/// This method is conservative; it will fail any attempt to tilt a control that already
/// has a projection on it
/// </remarks>
static bool PrepareControlForTilt(FrameworkElement element, Point centerDelta)
{
// Prevents interference with any existing transforms
if (element.Projection != null || (element.RenderTransform != null && element.RenderTransform.GetType() != typeof(MatrixTransform)))
return false;
TranslateTransform transform = new TranslateTransform();
transform.X = centerDelta.X;
transform.Y = centerDelta.Y;
element.RenderTransform = transform;
PlaneProjection projection = new PlaneProjection();
projection.GlobalOffsetX = -1 * centerDelta.X;
projection.GlobalOffsetY = -1 * centerDelta.Y;
element.Projection = projection;
element.ManipulationDelta += TiltEffect_ManipulationDelta;
element.ManipulationCompleted += TiltEffect_ManipulationCompleted;
return true;
}
/// <summary>
/// Removes modifications made by PrepareControlForTilt
/// </summary>
/// <param name="element">THe control to be un-prepared</param>
/// <remarks>
/// This method is basic; it does not do anything to detect if the control being un-prepared
/// was previously prepared
/// </remarks>
static void RevertPrepareControlForTilt(FrameworkElement element)
{
element.ManipulationDelta -= TiltEffect_ManipulationDelta;
element.ManipulationCompleted -= TiltEffect_ManipulationCompleted;
element.Projection = null;
element.RenderTransform = null;
}
/// <summary>
/// Creates the tilt return storyboard (if not already created) and targets it to the projection
/// </summary>
/// <param name="projection">the projection that should be the target of the animation</param>
static void PrepareTiltReturnStoryboard(FrameworkElement element)
{
if (tiltReturnStoryboard == null)
{
tiltReturnStoryboard = new Storyboard();
tiltReturnStoryboard.Completed += TiltReturnStoryboard_Completed;
tiltReturnXAnimation = new DoubleAnimation();
Storyboard.SetTargetProperty(tiltReturnXAnimation, new PropertyPath(PlaneProjection.RotationXProperty));
tiltReturnXAnimation.BeginTime = TiltReturnAnimationDelay;
tiltReturnXAnimation.To = 0;
tiltReturnXAnimation.Duration = TiltReturnAnimationDuration;
tiltReturnYAnimation = new DoubleAnimation();
Storyboard.SetTargetProperty(tiltReturnYAnimation, new PropertyPath(PlaneProjection.RotationYProperty));
tiltReturnYAnimation.BeginTime = TiltReturnAnimationDelay;
tiltReturnYAnimation.To = 0;
tiltReturnYAnimation.Duration = TiltReturnAnimationDuration;
tiltReturnZAnimation = new DoubleAnimation();
Storyboard.SetTargetProperty(tiltReturnZAnimation, new PropertyPath(PlaneProjection.GlobalOffsetZProperty));
tiltReturnZAnimation.BeginTime = TiltReturnAnimationDelay;
tiltReturnZAnimation.To = 0;
tiltReturnZAnimation.Duration = TiltReturnAnimationDuration;
if (UseLogarithmicEase)
{
tiltReturnXAnimation.EasingFunction = new LogarithmicEase();
tiltReturnYAnimation.EasingFunction = new LogarithmicEase();
tiltReturnZAnimation.EasingFunction = new LogarithmicEase();
}
tiltReturnStoryboard.Children.Add(tiltReturnXAnimation);
tiltReturnStoryboard.Children.Add(tiltReturnYAnimation);
tiltReturnStoryboard.Children.Add(tiltReturnZAnimation);
}
Storyboard.SetTarget(tiltReturnXAnimation, element.Projection);
Storyboard.SetTarget(tiltReturnYAnimation, element.Projection);
Storyboard.SetTarget(tiltReturnZAnimation, element.Projection);
}
/// <summary>
/// Continues a tilt effect that is currently applied to an element, presumably because
/// the user moved their finger
/// </summary>
/// <param name="element">The element being tilted</param>
/// <param name="e">The manipulation event args</param>
static void ContinueTiltEffect(FrameworkElement element, ManipulationDeltaEventArgs e)
{
FrameworkElement container = e.ManipulationContainer as FrameworkElement;
if (container == null || element == null)
return;
Point tiltTouchPoint = container.TransformToVisual(element).Transform(e.ManipulationOrigin);
// If touch moved outside bounds of element, then pause the tilt (but don't cancel it)
if (new Rect(0, 0, currentTiltElement.ActualWidth, currentTiltElement.ActualHeight).Contains(tiltTouchPoint) != true)
{
PauseTiltEffect();
return;
}
// Apply the updated tilt effect
ApplyTiltEffect(currentTiltElement, e.ManipulationOrigin, currentTiltElementCenter);
}
/// <summary>
/// Ends the tilt effect by playing the animation
/// </summary>
/// <param name="element">The element being tilted</param>
static void EndTiltEffect(FrameworkElement element)
{
if (element != null)
{
element.ManipulationCompleted -= TiltEffect_ManipulationCompleted;
element.ManipulationDelta -= TiltEffect_ManipulationDelta;
}
if (tiltReturnStoryboard != null)
{
wasPauseAnimation = false;
if (tiltReturnStoryboard.GetCurrentState() != ClockState.Active)
tiltReturnStoryboard.Begin();
}
else
StopTiltReturnStoryboardAndCleanup();
}
/// <summary>
/// Handler for the storyboard complete event
/// </summary>
/// <param name="sender">sender of the event</param>
/// <param name="e">event args</param>
static void TiltReturnStoryboard_Completed(object sender, EventArgs e)
{
if (wasPauseAnimation)
ResetTiltEffect(currentTiltElement);
else
StopTiltReturnStoryboardAndCleanup();
}
/// <summary>
/// Resets the tilt effect on the control, making it appear 'normal' again
/// </summary>
/// <param name="element">The element to reset the tilt on</param>
/// <remarks>
/// This method doesn't turn off the tilt effect or cancel any current
/// manipulation; it just temporarily cancels the effect
/// </remarks>
static void ResetTiltEffect(FrameworkElement element)
{
PlaneProjection projection = element.Projection as PlaneProjection;
projection.RotationY = 0;
projection.RotationX = 0;
projection.GlobalOffsetZ = 0;
}
/// <summary>
/// Stops the tilt effect and release resources applied to the currently-tilted control
/// </summary>
static void StopTiltReturnStoryboardAndCleanup()
{
if (tiltReturnStoryboard != null)
tiltReturnStoryboard.Stop();
RevertPrepareControlForTilt(currentTiltElement);
}
/// <summary>
/// Pauses the tilt effect so that the control returns to the 'at rest' position, but doesn't
/// stop the tilt effect (handlers are still attached, etc.)
/// </summary>
static void PauseTiltEffect()
{
if ((tiltReturnStoryboard != null) && !wasPauseAnimation)
{
tiltReturnStoryboard.Stop();
wasPauseAnimation = true;
tiltReturnStoryboard.Begin();
}
}
/// <summary>
/// Resets the storyboard to not running
/// </summary>
private static void ResetTiltReturnStoryboard()
{
tiltReturnStoryboard.Stop();
wasPauseAnimation = false;
}
/// <summary>
/// Applies the tilt effect to the control
/// </summary>
/// <param name="element">the control to tilt</param>
/// <param name="touchPoint">The touch point, in the container's coordinates</param>
/// <param name="centerPoint">The center point of the container</param>
static void ApplyTiltEffect(FrameworkElement element, Point touchPoint, Point centerPoint)
{
// Stop any active animation
ResetTiltReturnStoryboard();
// Get relative point of the touch in percentage of container size
Point normalizedPoint = new Point(
Math.Min(Math.Max(touchPoint.X / (centerPoint.X * 2), 0), 1),
Math.Min(Math.Max(touchPoint.Y / (centerPoint.Y * 2), 0), 1));
// Shell values
double xMagnitude = Math.Abs(normalizedPoint.X - 0.5);
double yMagnitude = Math.Abs(normalizedPoint.Y - 0.5);
double xDirection = -Math.Sign(normalizedPoint.X - 0.5);
double yDirection = Math.Sign(normalizedPoint.Y - 0.5);
double angleMagnitude = xMagnitude + yMagnitude;
double xAngleContribution = xMagnitude + yMagnitude > 0 ? xMagnitude / (xMagnitude + yMagnitude) : 0;
double angle = angleMagnitude * MaxAngle * 180 / Math.PI;
double depression = (1 - angleMagnitude) * MaxDepression;
// RotationX and RotationY are the angles of rotations about the x- or y-*axis*;
// to achieve a rotation in the x- or y-*direction*, we need to swap the two.
// That is, a rotation to the left about the y-axis is a rotation to the left in the x-direction,
// and a rotation up about the x-axis is a rotation up in the y-direction.
PlaneProjection projection = element.Projection as PlaneProjection;
projection.RotationY = angle * xAngleContribution * xDirection;
projection.RotationX = angle * (1 - xAngleContribution) * yDirection;
projection.GlobalOffsetZ = -depression;
}
#endregion
#region Custom easing function
/// <summary>
/// Provides an easing function for the tilt return
/// </summary>
private class LogarithmicEase : EasingFunctionBase
{
/// <summary>
/// Computes the easing function
/// </summary>
/// <param name="normalizedTime">The time</param>
/// <returns>The eased value</returns>
protected override double EaseInCore(double normalizedTime)
{
return Math.Log(normalizedTime + 1) / 0.693147181; // ln(t + 1) / ln(2)
}
}
#endregion
}
/// <summary>
/// Couple of simple helpers for walking the visual tree
/// </summary>
static class TreeHelpers
{
/// <summary>
/// Gets the ancestors of the element, up to the root
/// </summary>
/// <param name="node">The element to start from</param>
/// <returns>An enumerator of the ancestors</returns>
public static IEnumerable<FrameworkElement> GetVisualAncestors(this FrameworkElement node)
{
FrameworkElement parent = node.GetVisualParent();
while (parent != null)
{
yield return parent;
parent = parent.GetVisualParent();
}
}
/// <summary>
/// Gets the visual parent of the element
/// </summary>
/// <param name="node">The element to check</param>
/// <returns>The visual parent</returns>
public static FrameworkElement GetVisualParent(this FrameworkElement node)
{
return VisualTreeHelper.GetParent(node) as FrameworkElement;
}
}
}
切換至 MainPage.xaml 設計模式,引用並啟用 IsTiltEnabled 效果。
如果有特定控制項不想要有 Tilt Effect,可加上屬性 local:TiltEffect.SuppressTilt="True"
<Button Content="Button (無效果)" Height="150" HorizontalAlignment="Left" Margin="30,0,0,62" VerticalAlignment="Bottom" Width="380" local:TiltEffect.SuppressTilt="True"/>
執行專案,按下控制項測試 Tilt Effect。