对每个Windows Phone的使用者来说,给他们的第一印象就是大大小小的磁贴——Metro,本篇介绍的是Windows Phone的磁贴,提到的有开始菜单的磁贴,也有在App里面的磁贴。
开始菜单的磁贴
首先介绍了一下每个磁贴的构造,每个磁贴分正反两面,正反两面都有图标,而正面有一个标题和统计数量(一般用作消息推送的时候用),在背面就有一个描述性的内容,下图就摘自MSDN上的图片,图中黑色字体其实就是每个磁贴数据类的属性,这个稍后会提到
对于一个磁贴来说,他的图片像素建议是173*173像素的,占用空间控制在80KB以内,它各个部分更详尽的数据大小如下图。
在开始菜单中的磁贴分两类,一类是App本身启动用的,通过在应用程序列表中创建的磁贴,叫应用程序磁贴;另一类是由App创建的,那个叫次要磁贴。
在控制开始菜单上的磁贴,主要有两个类,一个是StandandTileData,另一个是ShellTile。前者则是之前提过有一个类存储磁贴上的数据信息那个类,后者是负责管理开始菜单中的磁贴(包括了增删改查),但只局限于本App的磁贴。
下面的代码则磁贴创建的代码
1 StandardTileData tileData = new StandardTileData() 2 3 { 4 5 Title = "Test Tile", 6 7 BackContent = "The " + ShellTile.ActiveTiles.Count() + " One", 8 9 BackTitle = ShellTile.ActiveTiles.Count().ToString(), 10 11 Count = ShellTile.ActiveTiles.Count() 12 13 }; 14 15 ShellTile.Create(new Uri(NavigationService.Source.ToString()+"?key="+Guid.NewGuid().ToString(), UriKind.Relative), tileData);
添加磁贴就是ShellTile的静态方法Create,传入的是点击磁贴后要跳转到的页面的URI还有这个磁贴的数据类,对于上面的代码,如果创建了一次磁贴之后再次执行则会抛出异常,原因在于对一个App的次要磁贴来说,它们的URI不允许重复,那遇到创建多个磁贴都是跳转到相同的页面时,可以给URI上面加上不同是QueryString来使得各个URI不一样。
ShellTile的ActiveTiles静态属性是获取这个App所有开始菜单上磁贴的枚举,是一个ShellTile的泛型集合。要获取这个App中的某一个磁贴只能遍历这个集合。有个特别之处就是不管这个App有没有放应用程序磁贴到开始菜单中,第一个元素绝对是应用程序磁贴,次要磁贴则是从第二个元素开始。
更新磁贴只是从ActiveTiles获取了相应的磁贴类之后,然后用一个StandandTileData赋上新的值,通过该磁贴的ShellTile实例的Update方法把StandandTileData传过去就可以了。
删除磁贴也是通过ActiveTiles获取了相应的磁贴类ShellTile实例,再调用它的Delete方法,但注意的一点是这里的删除只能删除次要磁贴,应用程序磁贴是不允许删除的。
应用程序内部的磁贴
类似开始菜单中的磁贴也可以添加到App内部。但它就不是ShellTile了,是HubTile,这个控件并非单纯从工具箱可以拖到页面中去,这个需要引用Toolkit,在以前WP7时使用Toolkit相对简单,但是WP8的话则需要联机获取dll了。
在vs中打开"扩展与更新"窗口,搜索"Nuget";
搜索出来了"Nuget Package Manager"便安装,安装完毕后就记得重启VS;在"扩展与更新"窗口中重启"Nuget Package Manager"。
现在就可以在引用文件夹中添加dll了。选的是"管理NuGet程序包"。
搜索"windows phone toolkit"进行安装,
最后在包管理器控制台中输入命令"Install-Package WPToolkit"就可以完成dll的添加了。包管理控制台打开方式如下图。
在需要使用该dll的xaml页面肯要添加对应的xml命名空间
Xmlns:toolkit="clr-namespace;Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Toolkit"
在xaml中添加下面语句则可以往页面成功添加一个磁贴
<toolkit:HubTile Grid.Row="1" Grid.Column="1" Background="Red" Source="Assets/Tiles/FlipCycleTileSmall.png" Title="Metro" Message="This is Metro in App"/>
一个HubTile一共有下面五种状态,这个这个磁贴用到的属性其实在上面一条语句中都可以看出来,Background是磁贴的背景色;Source是磁贴中图片,这个图片就只有一面才有,反面就没有了,Title则是那个很醒目的磁贴的标题,在磁贴的背面也有;Message是在磁贴背面
运行的时候会发现磁贴是贴在了页面上了,但是手点击上去就没有了开始菜单中的那种倾斜效果,这个磁贴的倾斜效果是这个Toolkit的另外一个附加属性 TiltEffect.IsEnable。它是一个布尔类型,True表示使用倾斜效果。还需要在隐藏文件的构造函数中加入这个控件的类型
<toolkit:HubTile toolkit:TiltEffect.IsTiltEnabled="True" Grid.Row="1" Grid.Column="1" Background="Red" Source="Assets/Tiles/FlipCycleTileSmall.png" Title="Metro" Message="This is Metro in App"/>
public TileTestPage() { InitializeComponent(); ControlTiltEffect.TiltEffect.TiltableItems.Add(typeof(HubTile)); }
但是用Toolkit的效果不是很明显,而且有限制,有一些控件虽然用上了但也没有倾斜的效果。在网上查看资料时发现有个老外也写了一个倾斜效果,效果比微软提供的要明显,而且还可以调节倾斜的角度。两个类的代码如下
1 using System; 2 using System.Windows; 3 using System.Windows.Controls; 4 using System.Windows.Input; 5 using System.Windows.Media; 6 using System.Windows.Media.Animation; 7 using System.Collections.Generic; 8 using System.Windows.Controls.Primitives; 9 10 11 #if WINDOWS_PHONE 12 using Microsoft.Phone.Controls; 13 #endif 14 15 namespace ControlTiltEffect 16 { 17 /// <summary> 18 /// This code provides attached properties for adding a ‘tilt‘ effect to all controls within a container. 19 /// </summary> 20 public class TiltEffect : DependencyObject 21 { 22 23 #region Constructor and Static Constructor 24 /// <summary> 25 /// This is not a constructable class, but it cannot be static because it derives from DependencyObject. 26 /// </summary> 27 private TiltEffect() 28 { 29 } 30 31 /// <summary> 32 /// Initialize the static properties 33 /// </summary> 34 static TiltEffect() 35 { 36 // The tiltable items list. 37 TiltableItems = new List<Type>() { typeof(ButtonBase), typeof(ListBoxItem) }; 38 UseLogarithmicEase = false; 39 } 40 41 #endregion 42 43 44 #region Fields and simple properties 45 46 // These constants are the same as the built-in effects 47 /// <summary> 48 /// Maximum amount of tilt, in radians 49 /// </summary> 50 const double MaxAngle = 0.3; 51 52 /// <summary> 53 /// Maximum amount of depression, in pixels 54 /// </summary> 55 const double MaxDepression = 25; 56 57 /// <summary> 58 /// Delay between releasing an element and the tilt release animation playing 59 /// </summary> 60 static readonly TimeSpan TiltReturnAnimationDelay = TimeSpan.FromMilliseconds(200); 61 62 /// <summary> 63 /// Duration of tilt release animation 64 /// </summary> 65 static readonly TimeSpan TiltReturnAnimationDuration = TimeSpan.FromMilliseconds(100); 66 67 /// <summary> 68 /// The control that is currently being tilted 69 /// </summary> 70 static FrameworkElement currentTiltElement; 71 72 /// <summary> 73 /// The single instance of a storyboard used for all tilts 74 /// </summary> 75 static Storyboard tiltReturnStoryboard; 76 77 /// <summary> 78 /// The single instance of an X rotation used for all tilts 79 /// </summary> 80 static DoubleAnimation tiltReturnXAnimation; 81 82 /// <summary> 83 /// The single instance of a Y rotation used for all tilts 84 /// </summary> 85 static DoubleAnimation tiltReturnYAnimation; 86 87 /// <summary> 88 /// The single instance of a Z depression used for all tilts 89 /// </summary> 90 static DoubleAnimation tiltReturnZAnimation; 91 92 /// <summary> 93 /// The center of the tilt element 94 /// </summary> 95 static Point currentTiltElementCenter; 96 97 /// <summary> 98 /// Whether the animation just completed was for a ‘pause‘ or not 99 /// </summary> 100 static bool wasPauseAnimation = false; 101 102 /// <summary> 103 /// Whether to use a slightly more accurate (but slightly slower) tilt animation easing function 104 /// </summary> 105 public static bool UseLogarithmicEase { get; set; } 106 107 /// <summary> 108 /// Default list of items that are tiltable 109 /// </summary> 110 public static List<Type> TiltableItems { get; private set; } 111 112 #endregion 113 114 115 #region Dependency properties 116 117 /// <summary> 118 /// Whether the tilt effect is enabled on a container (and all its children) 119 /// </summary> 120 public static readonly DependencyProperty IsTiltEnabledProperty = DependencyProperty.RegisterAttached( 121 "IsTiltEnabled", 122 typeof(bool), 123 typeof(TiltEffect), 124 new PropertyMetadata(OnIsTiltEnabledChanged) 125 ); 126 127 /// <summary> 128 /// Gets the IsTiltEnabled dependency property from an object 129 /// </summary> 130 /// <param name="source">The object to get the property from</param> 131 /// <returns>The property‘s value</returns> 132 public static bool GetIsTiltEnabled(DependencyObject source) { return (bool)source.GetValue(IsTiltEnabledProperty); } 133 134 /// <summary> 135 /// Sets the IsTiltEnabled dependency property on an object 136 /// </summary> 137 /// <param name="source">The object to set the property on</param> 138 /// <param name="value">The value to set</param> 139 public static void SetIsTiltEnabled(DependencyObject source, bool value) { source.SetValue(IsTiltEnabledProperty, value); } 140 141 /// <summary> 142 /// Suppresses the tilt effect on a single control that would otherwise be tilted 143 /// </summary> 144 public static readonly DependencyProperty SuppressTiltProperty = DependencyProperty.RegisterAttached( 145 "SuppressTilt", 146 typeof(bool), 147 typeof(TiltEffect), 148 null 149 ); 150 151 /// <summary> 152 /// Gets the SuppressTilt dependency property from an object 153 /// </summary> 154 /// <param name="source">The object to get the property from</param> 155 /// <returns>The property‘s value</returns> 156 public static bool GetSuppressTilt(DependencyObject source) { return (bool)source.GetValue(SuppressTiltProperty); } 157 158 /// <summary> 159 /// Sets the SuppressTilt dependency property from an object 160 /// </summary> 161 /// <param name="source">The object to get the property from</param> 162 /// <returns>The property‘s value</returns> 163 public static void SetSuppressTilt(DependencyObject source, bool value) { source.SetValue(SuppressTiltProperty, value); } 164 165 166 /// <summary> 167 /// Property change handler for the IsTiltEnabled dependency property 168 /// </summary> 169 /// <param name="target">The element that the property is atteched to</param> 170 /// <param name="args">Event args</param> 171 /// <remarks> 172 /// Adds or removes event handlers from the element that has been (un)registered for tilting 173 /// </remarks> 174 static void OnIsTiltEnabledChanged(DependencyObject target, DependencyPropertyChangedEventArgs args) 175 { 176 if (target is FrameworkElement) 177 { 178 // Add / remove the event handler if necessary 179 if ((bool)args.NewValue == true) 180 { 181 (target as FrameworkElement).ManipulationStarted += TiltEffect_ManipulationStarted; 182 } 183 else 184 { 185 (target as FrameworkElement).ManipulationStarted -= TiltEffect_ManipulationStarted; 186 } 187 } 188 } 189 190 #endregion 191 192 193 #region Top-level manipulation event handlers 194 195 /// <summary> 196 /// Event handler for ManipulationStarted 197 /// </summary> 198 /// <param name="sender">sender of the event - this will be the tilt container (eg, entire page)</param> 199 /// <param name="e">event args</param> 200 static void TiltEffect_ManipulationStarted(object sender, ManipulationStartedEventArgs e) 201 { 202 203 TryStartTiltEffect(sender as FrameworkElement, e); 204 } 205 206 /// <summary> 207 /// Event handler for ManipulationDelta 208 /// </summary> 209 /// <param name="sender">sender of the event - this will be the tilting object (eg a button)</param> 210 /// <param name="e">event args</param> 211 static void TiltEffect_ManipulationDelta(object sender, ManipulationDeltaEventArgs e) 212 { 213 214 ContinueTiltEffect(sender as FrameworkElement, e); 215 } 216 217 /// <summary> 218 /// Event handler for ManipulationCompleted 219 /// </summary> 220 /// <param name="sender">sender of the event - this will be the tilting object (eg a button)</param> 221 /// <param name="e">event args</param> 222 static void TiltEffect_ManipulationCompleted(object sender, ManipulationCompletedEventArgs e) 223 { 224 225 EndTiltEffect(currentTiltElement); 226 } 227 228 #endregion 229 230 231 #region Core tilt logic 232 233 /// <summary> 234 /// Checks if the manipulation should cause a tilt, and if so starts the tilt effect 235 /// </summary> 236 /// <param name="source">The source of the manipulation (the tilt container, eg entire page)</param> 237 /// <param name="e">The args from the ManipulationStarted event</param> 238 static void TryStartTiltEffect(FrameworkElement source, ManipulationStartedEventArgs e) 239 { 240 foreach (FrameworkElement ancestor in (e.OriginalSource as FrameworkElement).GetVisualAncestors()) 241 { 242 foreach (Type t in TiltableItems) 243 { 244 if (t.IsAssignableFrom(ancestor.GetType())) 245 { 246 if ((bool)ancestor.GetValue(SuppressTiltProperty) != true) 247 { 248 // Use first child of the control, so that you can add transforms and not 249 // impact any transforms on the control itself 250 FrameworkElement element = VisualTreeHelper.GetChild(ancestor, 0) as FrameworkElement; 251 FrameworkElement container = e.ManipulationContainer as FrameworkElement; 252 253 if (element == null || container == null) 254 return; 255 256 // Touch point relative to the element being tilted 257 Point tiltTouchPoint = container.TransformToVisual(element).Transform(e.ManipulationOrigin); 258 259 // Center of the element being tilted 260 Point elementCenter = new Point(element.ActualWidth / 2, element.ActualHeight / 2); 261 262 // Camera adjustment 263 Point centerToCenterDelta = GetCenterToCenterDelta(element, source); 264 265 BeginTiltEffect(element, tiltTouchPoint, elementCenter, centerToCenterDelta); 266 return; 267 } 268 } 269 } 270 } 271 } 272 273 /// <summary> 274 /// Computes the delta between the centre of an element and its container 275 /// </summary> 276 /// <param name="element">The element to compare</param> 277 /// <param name="container">The element to compare against</param> 278 /// <returns>A point that represents the delta between the two centers</returns> 279 static Point GetCenterToCenterDelta(FrameworkElement element, FrameworkElement container) 280 { 281 Point elementCenter = new Point(element.ActualWidth / 2, element.ActualHeight / 2); 282 Point containerCenter; 283 284 #if WINDOWS_PHONE 285 286 // Need to special-case the frame to handle different orientations 287 if (container is PhoneApplicationFrame) 288 { 289 PhoneApplicationFrame frame = container as PhoneApplicationFrame; 290 291 // Switch width and height in landscape mode 292 if ((frame.Orientation & PageOrientation.Landscape) == PageOrientation.Landscape) 293 { 294 295 containerCenter = new Point(container.ActualHeight / 2, container.ActualWidth / 2); 296 } 297 else 298 containerCenter = new Point(container.ActualWidth / 2, container.ActualHeight / 2); 299 } 300 else 301 containerCenter = new Point(container.ActualWidth / 2, container.ActualHeight / 2); 302 #else 303 304 containerCenter = new Point(container.ActualWidth / 2, container.ActualHeight / 2); 305 306 #endif 307 308 Point transformedElementCenter = element.TransformToVisual(container).Transform(elementCenter); 309 Point result = new Point(containerCenter.X - transformedElementCenter.X, containerCenter.Y - transformedElementCenter.Y); 310 311 return result; 312 } 313 314 /// <summary> 315 /// Begins the tilt effect by preparing the control and doing the initial animation 316 /// </summary> 317 /// <param name="element">The element to tilt </param> 318 /// <param name="touchPoint">The touch point, in element coordinates</param> 319 /// <param name="centerPoint">The center point of the element in element coordinates</param> 320 /// <param name="centerDelta">The delta between the <paramref name="element"/>‘s center and 321 /// the container‘s center</param> 322 static void BeginTiltEffect(FrameworkElement element, Point touchPoint, Point centerPoint, Point centerDelta) 323 { 324 325 326 if (tiltReturnStoryboard != null) 327 StopTiltReturnStoryboardAndCleanup(); 328 329 if (PrepareControlForTilt(element, centerDelta) == false) 330 return; 331 332 currentTiltElement = element; 333 currentTiltElementCenter = centerPoint; 334 PrepareTiltReturnStoryboard(element); 335 336 ApplyTiltEffect(currentTiltElement, touchPoint, currentTiltElementCenter); 337 } 338 339 /// <summary> 340 /// Prepares a control to be tilted by setting up a plane projection and some event handlers 341 /// </summary> 342 /// <param name="element">The control that is to be tilted</param> 343 /// <param name="centerDelta">Delta between the element‘s center and the tilt container‘s</param> 344 /// <returns>true if successful; false otherwise</returns> 345 /// <remarks> 346 /// This method is conservative; it will fail any attempt to tilt a control that already 347 /// has a projection on it 348 /// </remarks> 349 static bool PrepareControlForTilt(FrameworkElement element, Point centerDelta) 350 { 351 // Prevents interference with any existing transforms 352 if (element.Projection != null || (element.RenderTransform != null && element.RenderTransform.GetType() != typeof(MatrixTransform))) 353 return false; 354 355 TranslateTransform transform = new TranslateTransform(); 356 transform.X = centerDelta.X; 357 transform.Y = centerDelta.Y; 358 element.RenderTransform = transform; 359 360 PlaneProjection projection = new PlaneProjection(); 361 projection.GlobalOffsetX = -1 * centerDelta.X; 362 projection.GlobalOffsetY = -1 * centerDelta.Y; 363 element.Projection = projection; 364 365 element.ManipulationDelta += TiltEffect_ManipulationDelta; 366 element.ManipulationCompleted += TiltEffect_ManipulationCompleted; 367 368 return true; 369 } 370 371 /// <summary> 372 /// Removes modifications made by PrepareControlForTilt 373 /// </summary> 374 /// <param name="element">THe control to be un-prepared</param> 375 /// <remarks> 376 /// This method is basic; it does not do anything to detect if the control being un-prepared 377 /// was previously prepared 378 /// </remarks> 379 static void RevertPrepareControlForTilt(FrameworkElement element) 380 { 381 element.ManipulationDelta -= TiltEffect_ManipulationDelta; 382 element.ManipulationCompleted -= TiltEffect_ManipulationCompleted; 383 element.Projection = null; 384 element.RenderTransform = null; 385 } 386 387 /// <summary> 388 /// Creates the tilt return storyboard (if not already created) and targets it to the projection 389 /// </summary> 390 /// <param name="projection">the projection that should be the target of the animation</param> 391 static void PrepareTiltReturnStoryboard(FrameworkElement element) 392 { 393 394 if (tiltReturnStoryboard == null) 395 { 396 tiltReturnStoryboard = new Storyboard(); 397 tiltReturnStoryboard.Completed += TiltReturnStoryboard_Completed; 398 399 tiltReturnXAnimation = new DoubleAnimation(); 400 Storyboard.SetTargetProperty(tiltReturnXAnimation, new PropertyPath(PlaneProjection.RotationXProperty)); 401 tiltReturnXAnimation.BeginTime = TiltReturnAnimationDelay; 402 tiltReturnXAnimation.To = 0; 403 tiltReturnXAnimation.Duration = TiltReturnAnimationDuration; 404 405 tiltReturnYAnimation = new DoubleAnimation(); 406 Storyboard.SetTargetProperty(tiltReturnYAnimation, new PropertyPath(PlaneProjection.RotationYProperty)); 407 tiltReturnYAnimation.BeginTime = TiltReturnAnimationDelay; 408 tiltReturnYAnimation.To = 0; 409 tiltReturnYAnimation.Duration = TiltReturnAnimationDuration; 410 411 tiltReturnZAnimation = new DoubleAnimation(); 412 Storyboard.SetTargetProperty(tiltReturnZAnimation, new PropertyPath(PlaneProjection.GlobalOffsetZProperty)); 413 tiltReturnZAnimation.BeginTime = TiltReturnAnimationDelay; 414 tiltReturnZAnimation.To = 0; 415 tiltReturnZAnimation.Duration = TiltReturnAnimationDuration; 416 417 if (UseLogarithmicEase) 418 { 419 tiltReturnXAnimation.EasingFunction = new LogarithmicEase(); 420 tiltReturnYAnimation.EasingFunction = new LogarithmicEase(); 421 tiltReturnZAnimation.EasingFunction = new LogarithmicEase(); 422 } 423 424 tiltReturnStoryboard.Children.Add(tiltReturnXAnimation); 425 tiltReturnStoryboard.Children.Add(tiltReturnYAnimation); 426 tiltReturnStoryboard.Children.Add(tiltReturnZAnimation); 427 } 428 429 Storyboard.SetTarget(tiltReturnXAnimation, element.Projection); 430 Storyboard.SetTarget(tiltReturnYAnimation, element.Projection); 431 Storyboard.SetTarget(tiltReturnZAnimation, element.Projection); 432 } 433 434 435 /// <summary> 436 /// Continues a tilt effect that is currently applied to an element, presumably because 437 /// the user moved their finger 438 /// </summary> 439 /// <param name="element">The element being tilted</param> 440 /// <param name="e">The manipulation event args</param> 441 static void ContinueTiltEffect(FrameworkElement element, ManipulationDeltaEventArgs e) 442 { 443 FrameworkElement container = e.ManipulationContainer as FrameworkElement; 444 if (container == null || element == null) 445 return; 446 447 Point tiltTouchPoint = container.TransformToVisual(element).Transform(e.ManipulationOrigin); 448 449 // If touch moved outside bounds of element, then pause the tilt (but don‘t cancel it) 450 if (new Rect(0, 0, currentTiltElement.ActualWidth, currentTiltElement.ActualHeight).Contains(tiltTouchPoint) != true) 451 { 452 453 PauseTiltEffect(); 454 return; 455 } 456 457 // Apply the updated tilt effect 458 ApplyTiltEffect(currentTiltElement, e.ManipulationOrigin, currentTiltElementCenter); 459 } 460 461 /// <summary> 462 /// Ends the tilt effect by playing the animation 463 /// </summary> 464 /// <param name="element">The element being tilted</param> 465 static void EndTiltEffect(FrameworkElement element) 466 { 467 if (element != null) 468 { 469 element.ManipulationCompleted -= TiltEffect_ManipulationCompleted; 470 element.ManipulationDelta -= TiltEffect_ManipulationDelta; 471 } 472 473 if (tiltReturnStoryboard != null) 474 { 475 wasPauseAnimation = false; 476 if (tiltReturnStoryboard.GetCurrentState() != ClockState.Active) 477 tiltReturnStoryboard.Begin(); 478 } 479 else 480 StopTiltReturnStoryboardAndCleanup(); 481 } 482 483 /// <summary> 484 /// Handler for the storyboard complete event 485 /// </summary> 486 /// <param name="sender">sender of the event</param> 487 /// <param name="e">event args</param> 488 static void TiltReturnStoryboard_Completed(object sender, EventArgs e) 489 { 490 if (wasPauseAnimation) 491 ResetTiltEffect(currentTiltElement); 492 else 493 StopTiltReturnStoryboardAndCleanup(); 494 } 495 496 /// <summary> 497 /// Resets the tilt effect on the control, making it appear ‘normal‘ again 498 /// </summary> 499 /// <param name="element">The element to reset the tilt on</param> 500 /// <remarks> 501 /// This method doesn‘t turn off the tilt effect or cancel any current 502 /// manipulation; it just temporarily cancels the effect 503 /// </remarks> 504 static void ResetTiltEffect(FrameworkElement element) 505 { 506 PlaneProjection projection = element.Projection as PlaneProjection; 507 projection.RotationY = 0; 508 projection.RotationX = 0; 509 projection.GlobalOffsetZ = 0; 510 } 511 512 /// <summary> 513 /// Stops the tilt effect and release resources applied to the currently-tilted control 514 /// </summary> 515 static void StopTiltReturnStoryboardAndCleanup() 516 { 517 if (tiltReturnStoryboard != null) 518 tiltReturnStoryboard.Stop(); 519 520 RevertPrepareControlForTilt(currentTiltElement); 521 } 522 523 /// <summary> 524 /// Pauses the tilt effect so that the control returns to the ‘at rest‘ position, but doesn‘t 525 /// stop the tilt effect (handlers are still attached, etc.) 526 /// </summary> 527 static void PauseTiltEffect() 528 { 529 if ((tiltReturnStoryboard != null) && !wasPauseAnimation) 530 { 531 tiltReturnStoryboard.Stop(); 532 wasPauseAnimation = true; 533 tiltReturnStoryboard.Begin(); 534 } 535 } 536 537 /// <summary> 538 /// Resets the storyboard to not running 539 /// </summary> 540 private static void ResetTiltReturnStoryboard() 541 { 542 tiltReturnStoryboard.Stop(); 543 wasPauseAnimation = false; 544 } 545 546 /// <summary> 547 /// Applies the tilt effect to the control 548 /// </summary> 549 /// <param name="element">the control to tilt</param> 550 /// <param name="touchPoint">The touch point, in the container‘s coordinates</param> 551 /// <param name="centerPoint">The center point of the container</param> 552 static void ApplyTiltEffect(FrameworkElement element, Point touchPoint, Point centerPoint) 553 { 554 // Stop any active animation 555 ResetTiltReturnStoryboard(); 556 557 // Get relative point of the touch in percentage of container size 558 Point normalizedPoint = new Point( 559 Math.Min(Math.Max(touchPoint.X / (centerPoint.X * 2), 0), 1), 560 Math.Min(Math.Max(touchPoint.Y / (centerPoint.Y * 2), 0), 1)); 561 562 // Shell values 563 double xMagnitude = Math.Abs(normalizedPoint.X - 0.5); 564 double yMagnitude = Math.Abs(normalizedPoint.Y - 0.5); 565 double xDirection = -Math.Sign(normalizedPoint.X - 0.5); 566 double yDirection = Math.Sign(normalizedPoint.Y - 0.5); 567 double angleMagnitude = xMagnitude + yMagnitude; 568 double xAngleContribution = xMagnitude + yMagnitude > 0 ? xMagnitude / (xMagnitude + yMagnitude) : 0; 569 570 double angle = angleMagnitude * MaxAngle * 180 / Math.PI; 571 double depression = (1 - angleMagnitude) * MaxDepression; 572 573 // RotationX and RotationY are the angles of rotations about the x- or y-*axis*; 574 // to achieve a rotation in the x- or y-*direction*, we need to swap the two. 575 // That is, a rotation to the left about the y-axis is a rotation to the left in the x-direction, 576 // and a rotation up about the x-axis is a rotation up in the y-direction. 577 PlaneProjection projection = element.Projection as PlaneProjection; 578 projection.RotationY = angle * xAngleContribution * xDirection; 579 projection.RotationX = angle * (1 - xAngleContribution) * yDirection; 580 projection.GlobalOffsetZ = -depression; 581 } 582 583 #endregion 584 585 586 #region Custom easing function 587 588 /// <summary> 589 /// Provides an easing function for the tilt return 590 /// </summary> 591 private class LogarithmicEase : EasingFunctionBase 592 { 593 /// <summary> 594 /// Computes the easing function 595 /// </summary> 596 /// <param name="normalizedTime">The time</param> 597 /// <returns>The eased value</returns> 598 protected override double EaseInCore(double normalizedTime) 599 { 600 return Math.Log(normalizedTime + 1) / 0.693147181; // ln(t + 1) / ln(2) 601 } 602 } 603 604 #endregion 605 } 606 607 /// <summary> 608 /// Couple of simple helpers for walking the visual tree 609 /// </summary> 610 static class TreeHelpers 611 { 612 /// <summary> 613 /// Gets the ancestors of the element, up to the root 614 /// </summary> 615 /// <param name="node">The element to start from</param> 616 /// <returns>An enumerator of the ancestors</returns> 617 public static IEnumerable<FrameworkElement> GetVisualAncestors(this FrameworkElement node) 618 { 619 FrameworkElement parent = node.GetVisualParent(); 620 while (parent != null) 621 { 622 yield return parent; 623 parent = parent.GetVisualParent(); 624 } 625 } 626 627 /// <summary> 628 /// Gets the visual parent of the element 629 /// </summary> 630 /// <param name="node">The element to check</param> 631 /// <returns>The visual parent</returns> 632 public static FrameworkElement GetVisualParent(this FrameworkElement node) 633 { 634 return VisualTreeHelper.GetParent(node) as FrameworkElement; 635 } 636 } 637 }
1 public static class MetroInMotion 2 { 3 #region AnimationLevel 4 5 public static int GetAnimationLevel(DependencyObject obj) 6 { 7 return (int)obj.GetValue(AnimationLevelProperty); 8 } 9 10 public static void SetAnimationLevel(DependencyObject obj, int value) 11 { 12 obj.SetValue(AnimationLevelProperty, value); 13 } 14 15 16 public static readonly DependencyProperty AnimationLevelProperty = 17 DependencyProperty.RegisterAttached("AnimationLevel", typeof(int), 18 typeof(MetroInMotion), new PropertyMetadata(-1)); 19 20 #endregion 21 22 #region Tilt 23 24 public static double GetTilt(DependencyObject obj) 25 { 26 return (double)obj.GetValue(TiltProperty); 27 } 28 29 public static void SetTilt(DependencyObject obj, double value) 30 { 31 obj.SetValue(TiltProperty, value); 32 } 33 34 35 public static readonly DependencyProperty TiltProperty = 36 DependencyProperty.RegisterAttached("Tilt", typeof(double), 37 typeof(MetroInMotion), new PropertyMetadata(2.0, OnTiltChanged)); 38 39 /// <summary> 40 /// The extent of the tilt action, the larger the number, the bigger the tilt 41 /// </summary> 42 private static double TiltAngleFactor = 4; 43 44 /// <summary> 45 /// The extent of the scaling action, the smaller the number, the greater the scaling. 46 /// </summary> 47 private static double ScaleFactor = 100; 48 49 private static void OnTiltChanged(DependencyObject d, 50 DependencyPropertyChangedEventArgs args) 51 { 52 FrameworkElement targetElement = d as FrameworkElement; 53 54 double tiltFactor = GetTilt(d); 55 56 // create the required transformations 57 var projection = new PlaneProjection(); 58 var scale = new ScaleTransform(); 59 var translate = new TranslateTransform(); 60 61 var transGroup = new TransformGroup(); 62 transGroup.Children.Add(scale); 63 transGroup.Children.Add(translate); 64 65 // associate with the target element 66 targetElement.Projection = projection; 67 targetElement.RenderTransform = transGroup; 68 targetElement.RenderTransformOrigin = new Point(0.5, 0.5); 69 70 targetElement.MouseLeftButtonDown += (s, e) => 71 { 72 var clickPosition = e.GetPosition(targetElement); 73 74 // find the maximum of width / height 75 double maxDimension = Math.Max(targetElement.ActualWidth, targetElement.ActualHeight); 76 77 // compute the normalised horizontal distance from the centre 78 double distanceFromCenterX = targetElement.ActualWidth / 2 - clickPosition.X; 79 double normalisedDistanceX = 2 * distanceFromCenterX / maxDimension; 80 81 // rotate around the Y axis 82 projection.RotationY = normalisedDistanceX * TiltAngleFactor * tiltFactor; 83 84 // compute the normalised vertical distance from the centre 85 double distanceFromCenterY = targetElement.ActualHeight / 2 - clickPosition.Y; 86 double normalisedDistanceY = 2 * distanceFromCenterY / maxDimension; 87 88 // rotate around the X axis, 89 projection.RotationX = -normalisedDistanceY * TiltAngleFactor * tiltFactor; 90 91 // find the distance to centre 92 double distanceToCentre = Math.Sqrt(normalisedDistanceX * normalisedDistanceX 93 + normalisedDistanceY * normalisedDistanceY); 94 95 // scale accordingly 96 double scaleVal = tiltFactor * (1 - distanceToCentre) / ScaleFactor; 97 scale.ScaleX = 1 - scaleVal; 98 scale.ScaleY = 1 - scaleVal; 99 100 // offset the plane transform 101 var rootElement = Application.Current.RootVisual as FrameworkElement; 102 var relativeToCentre = (targetElement.GetRelativePosition(rootElement).Y - rootElement.ActualHeight / 2) / 2; 103 translate.Y = -relativeToCentre; 104 projection.LocalOffsetY = +relativeToCentre; 105 106 }; 107 108 targetElement.ManipulationCompleted += (s, e) => 109 { 110 var sb = new Storyboard(); 111 sb.Children.Add(CreateAnimation(null, 0, 0.1, "RotationY", projection)); 112 sb.Children.Add(CreateAnimation(null, 0, 0.1, "RotationX", projection)); 113 sb.Children.Add(CreateAnimation(null, 1, 0.1, "ScaleX", scale)); 114 sb.Children.Add(CreateAnimation(null, 1, 0.1, "ScaleY", scale)); 115 sb.Begin(); 116 117 translate.Y = 0; 118 projection.LocalOffsetY = 0; 119 }; 120 121 } 122 123 124 #endregion 125 126 #region IsPivotAnimated 127 128 public static bool GetIsPivotAnimated(DependencyObject obj) 129 { 130 return (bool)obj.GetValue(IsPivotAnimatedProperty); 131 } 132 133 public static void SetIsPivotAnimated(DependencyObject obj, bool value) 134 { 135 obj.SetValue(IsPivotAnimatedProperty, value); 136 } 137 138 public static readonly DependencyProperty IsPivotAnimatedProperty = 139 DependencyProperty.RegisterAttached("IsPivotAnimated", typeof(bool), 140 typeof(MetroInMotion), new PropertyMetadata(false, OnIsPivotAnimatedChanged)); 141 142 private static void OnIsPivotAnimatedChanged(DependencyObject d, DependencyPropertyChangedEventArgs args) 143 { 144 ItemsControl list = d as ItemsControl; 145 146 list.Loaded += (s2, e2) => 147 { 148 // locate the pivot control that this list is within 149 Pivot pivot = list.Ancestors<Pivot>().Single() as Pivot; 150 151 // and its index within the pivot 152 int pivotIndex = pivot.Items.IndexOf(list.Ancestors<PivotItem>().Single()); 153 154 bool selectionChanged = false; 155 156 pivot.SelectionChanged += (s3, e3) => 157 { 158 selectionChanged = true; 159 }; 160 161 // handle manipulation events which occur when the user 162 // moves between pivot items 163 pivot.ManipulationCompleted += (s, e) => 164 { 165 if (!selectionChanged) 166 return; 167 168 selectionChanged = false; 169 170 if (pivotIndex != pivot.SelectedIndex) 171 return; 172 173 // determine which direction this tab will be scrolling in from 174 bool fromRight = e.TotalManipulation.Translation.X <= 0; 175 176 177 // iterate over each of the items in view 178 var items = list.GetItemsInView().ToList(); 179 for (int index = 0; index < items.Count; index++) 180 { 181 var lbi = items[index]; 182 183 list.Dispatcher.BeginInvoke(() => 184 { 185 var animationTargets = lbi.Descendants() 186 .Where(p => MetroInMotion.GetAnimationLevel(p) > -1); 187 foreach (FrameworkElement target in animationTargets) 188 { 189 // trigger the required animation 190 GetSlideAnimation(target, fromRight).Begin(); 191 } 192 }); 193 }; 194 195 }; 196 }; 197 } 198 199 200 #endregion 201 202 /// <summary> 203 /// Animates each element in order, creating a ‘peel‘ effect. The supplied action 204 /// is invoked when the animation ends. 205 /// </summary> 206 public static void Peel(this IEnumerable<FrameworkElement> elements, Action endAction) 207 { 208 var elementList = elements.ToList(); 209 var lastElement = elementList.Last(); 210 211 // iterate over all the elements, animating each of them 212 double delay = 0; 213 foreach (FrameworkElement element in elementList) 214 { 215 var sb = GetPeelAnimation(element, delay); 216 217 // add a Completed event handler to the last element 218 if (element.Equals(lastElement)) 219 { 220 sb.Completed += (s, e) => 221 { 222 endAction(); 223 }; 224 } 225 226 sb.Begin(); 227 delay += 50; 228 } 229 } 230 231 232 /// <summary> 233 /// Enumerates all the items that are currently visible in am ItemsControl. This implementation assumes 234 /// that a VirtualizingStackPanel is being used as the ItemsPanel. 235 /// </summary> 236 public static IEnumerable<FrameworkElement> GetItemsInView(this ItemsControl itemsControl) 237 { 238 // locate the stack panel that hosts the items 239 VirtualizingStackPanel vsp = itemsControl.Descendants<VirtualizingStackPanel>().First() as VirtualizingStackPanel; 240 241 // iterate over each of the items in view 242 int firstVisibleItem = (int)vsp.VerticalOffset; 243 int visibleItemCount = (int)vsp.ViewportHeight; 244 for (int index = firstVisibleItem; index <= firstVisibleItem + visibleItemCount + 1; index++) 245 { 246 var item = itemsControl.ItemContainerGenerator.ContainerFromIndex(index) as FrameworkElement; 247 if (item == null) 248 continue; 249 250 yield return item; 251 } 252 } 253 254 /// <summary> 255 /// Creates a PlaneProjection and associates it with the given element, returning 256 /// a Storyboard which will animate the PlaneProjection to ‘peel‘ the item 257 /// from the screen. 258 /// </summary> 259 private static Storyboard GetPeelAnimation(FrameworkElement element, double delay) 260 { 261 Storyboard sb; 262 263 var projection = new PlaneProjection() 264 { 265 CenterOfRotationX = -0.1 266 }; 267 element.Projection = projection; 268 269 // compute the angle of rotation required to make this element appear 270 // at a 90 degree angle at the edge of the screen. 271 var width = element.ActualWidth; 272 var targetAngle = Math.Atan(1000 / (width / 2)); 273 targetAngle = targetAngle * 180 / Math.PI; 274 275 // animate the projection 276 sb = new Storyboard(); 277 sb.BeginTime = TimeSpan.FromMilliseconds(delay); 278 sb.Children.Add(CreateAnimation(0, -(180 - targetAngle), 0.3, "RotationY", projection)); 279 sb.Children.Add(CreateAnimation(0, 23, 0.3, "RotationZ", projection)); 280 sb.Children.Add(CreateAnimation(0, -23, 0.3, "GlobalOffsetZ", projection)); 281 return sb; 282 } 283 284 private static DoubleAnimation CreateAnimation(double? from, double? to, double duration, 285 string targetProperty, DependencyObject target) 286 { 287 var db = new DoubleAnimation(); 288 db.To = to; 289 db.From = from; 290 db.EasingFunction = new SineEase(); 291 db.Duration = TimeSpan.FromSeconds(duration); 292 Storyboard.SetTarget(db, target); 293 Storyboard.SetTargetProperty(db, new PropertyPath(targetProperty)); 294 return db; 295 } 296 297 /// <summary> 298 /// Creates a TranslateTransform and associates it with the given element, returning 299 /// a Storyboard which will animate the TranslateTransform with a SineEase function 300 /// </summary> 301 private static Storyboard GetSlideAnimation(FrameworkElement element, bool fromRight) 302 { 303 double from = fromRight ? 80 : -80; 304 305 Storyboard sb; 306 double delay = (MetroInMotion.GetAnimationLevel(element)) * 0.1 + 0.1; 307 308 TranslateTransform trans = new TranslateTransform() { X = from }; 309 element.RenderTransform = trans; 310 311 sb = new Storyboard(); 312 sb.BeginTime = TimeSpan.FromSeconds(delay); 313 sb.Children.Add(CreateAnimation(from, 0, 0.8, "X", trans)); 314 return sb; 315 } 316 317 } 318 319 public static class ExtensionMethods 320 { 321 public static Point GetRelativePosition(this UIElement element, UIElement other) 322 { 323 return element.TransformToVisual(other) 324 .Transform(new Point(0, 0)); 325 } 326 } 327 328 public class ItemFlyInAndOutAnimations 329 { 330 private Popup _popup; 331 332 private Canvas _popupCanvas; 333 334 private FrameworkElement _targetElement; 335 336 private Point _targetElementPosition; 337 338 private Image _targetElementClone; 339 340 private Rectangle _backgroundMask; 341 342 private static TimeSpan _flyInSpeed = TimeSpan.FromMilliseconds(200); 343 344 private static TimeSpan _flyOutSpeed = TimeSpan.FromMilliseconds(300); 345 346 public ItemFlyInAndOutAnimations() 347 { 348 // construct a popup, with a Canvas as its child 349 _popup = new Popup(); 350 _popupCanvas = new Canvas(); 351 _popup.Child = _popupCanvas; 352 } 353 354 public static void TitleFlyIn(FrameworkElement title) 355 { 356 TranslateTransform trans = new TranslateTransform(); 357 trans.X = 300; 358 trans.Y = -50; 359 title.RenderTransform = trans; 360 361 var sb = new Storyboard(); 362 363 // animate the X position 364 var db = CreateDoubleAnimation(300, 0, 365 new SineEase(), trans, TranslateTransform.XProperty, _flyInSpeed); 366 sb.Children.Add(db); 367 368 // animate the Y position 369 db = CreateDoubleAnimation(-100, 0, 370 new SineEase(), trans, TranslateTransform.YProperty, _flyInSpeed); 371 sb.Children.Add(db); 372 373 sb.Begin(); 374 } 375 376 /// <summary> 377 /// Animate the previously ‘flown-out‘ element back to its original location. 378 /// </summary> 379 public void ItemFlyIn() 380 { 381 if (_popupCanvas.Children.Count != 2) 382 return; 383 384 _popup.IsOpen = true; 385 _backgroundMask.Opacity = 0.0; 386 387 Image animatedImage = _popupCanvas.Children[1] as Image; 388 389 var sb = new Storyboard(); 390 391 // animate the X position 392 var db = CreateDoubleAnimation(_targetElementPosition.X - 100, _targetElementPosition.X, 393 new SineEase(), 394 _targetElementClone, Canvas.LeftProperty, _flyInSpeed); 395 sb.Children.Add(db); 396 397 // animate the Y position 398 db = CreateDoubleAnimation(_targetElementPosition.Y - 50, _targetElementPosition.Y, 399 new SineEase(), 400 _targetElementClone, Canvas.TopProperty, _flyInSpeed); 401 sb.Children.Add(db); 402 403 sb.Completed += (s, e) => 404 { 405 // when the animation has finished, hide the popup once more 406 _popup.IsOpen = false; 407 408 // restore the element we have animated 409 _targetElement.Opacity = 1.0; 410 411 // and get rid of our clone 412 _popupCanvas.Children.Clear(); 413 }; 414 415 sb.Begin(); 416 } 417 418 419 /// <summary> 420 /// Animate the given element so that it flies off screen, fading 421 /// everything else that is on screen. 422 /// </summary> 423 public void ItemFlyOut(FrameworkElement element, Action action) 424 { 425 _targetElement = element; 426 var rootElement = Application.Current.RootVisual as FrameworkElement; 427 428 _backgroundMask = new Rectangle() 429 { 430 Fill = new SolidColorBrush(Colors.Black), 431 Opacity = 0.0, 432 Width = rootElement.ActualWidth, 433 Height = rootElement.ActualHeight 434 }; 435 _popupCanvas.Children.Add(_backgroundMask); 436 437 _targetElementClone = new Image() 438 { 439 Source = new WriteableBitmap(element, null) 440 }; 441 _popupCanvas.Children.Add(_targetElementClone); 442 443 _targetElementPosition = element.GetRelativePosition(rootElement); 444 Canvas.SetTop(_targetElementClone, _targetElementPosition.Y); 445 Canvas.SetLeft(_targetElementClone, _targetElementPosition.X); 446 447 var sb = new Storyboard(); 448 449 // animate the X position 450 var db = CreateDoubleAnimation(_targetElementPosition.X, _targetElementPosition.X + 500, 451 new SineEase() { EasingMode = EasingMode.EaseIn }, 452 _targetElementClone, Canvas.LeftProperty, _flyOutSpeed); 453 sb.Children.Add(db); 454 455 // animate the Y position 456 db = CreateDoubleAnimation(_targetElementPosition.Y, _targetElementPosition.Y + 50, 457 new SineEase() { EasingMode = EasingMode.EaseOut }, 458 _targetElementClone, Canvas.TopProperty, _flyOutSpeed); 459 sb.Children.Add(db); 460 461 // fade out the other elements 462 db = CreateDoubleAnimation(0, 1, 463 null, _backgroundMask, UIElement.OpacityProperty, _flyOutSpeed); 464 sb.Children.Add(db); 465 466 sb.Completed += (s, e2) => 467 { 468 action(); 469 470 // hide the popup, by placing a task on the dispatcher queue, this 471 // should be executed after the navigation has occurred 472 element.Dispatcher.BeginInvoke(() => 473 { 474 _popup.IsOpen = false; 475 }); 476 }; 477 478 // hide the element we have ‘cloned‘ into the popup 479 element.Opacity = 0.0; 480 481 // open the popup 482 _popup.IsOpen = true; 483 484 // begin the animation 485 sb.Begin(); 486 } 487 488 public static DoubleAnimation CreateDoubleAnimation(double from, double to, IEasingFunction easing, 489 DependencyObject target, object propertyPath, TimeSpan duration) 490 { 491 var db = new DoubleAnimation(); 492 db.To = to; 493 db.From = from; 494 db.EasingFunction = easing; 495 db.Duration = duration; 496 Storyboard.SetTarget(db, target); 497 Storyboard.SetTargetProperty(db, new PropertyPath(propertyPath)); 498 return db; 499 } 500 } 501 502 503 public class VisualTreeAdapter : ILinqTree<DependencyObject> 504 { 505 private DependencyObject _item; 506 507 public VisualTreeAdapter(DependencyObject item) 508 { 509 _item = item; 510 } 511 512 public IEnumerable<DependencyObject> Children() 513 { 514 int childrenCount = VisualTreeHelper.GetChildrenCount(_item); 515 for (int i = 0; i < childrenCount; i++) 516 { 517 yield return VisualTreeHelper.GetChild(_item, i); 518 } 519 } 520 521 public DependencyObject Parent 522 { 523 get 524 { 525 return VisualTreeHelper.GetParent(_item); 526 } 527 } 528 } 529 530 public interface ILinqTree<T> 531 { 532 IEnumerable<T> Children(); 533 534 T Parent { get; } 535 } 536 537 public static class TreeExtensions 538 { 539 /// <summary> 540 /// Returns a collection of descendant elements. 541 /// </summary> 542 public static IEnumerable<DependencyObject> Descendants(this DependencyObject item) 543 { 544 ILinqTree<DependencyObject> adapter = new VisualTreeAdapter(item); 545 foreach (var child in adapter.Children()) 546 { 547 yield return child; 548 549 foreach (var grandChild in child.Descendants()) 550 { 551 yield return grandChild; 552 } 553 } 554 } 555 556 /// <summary> 557 /// Returns a collection containing this element and all descendant elements. 558 /// </summary> 559 public static IEnumerable<DependencyObject> DescendantsAndSelf(this DependencyObject item) 560 { 561 yield return item; 562 563 foreach (var child in item.Descendants()) 564 { 565 yield return child; 566 } 567 } 568 569 /// <summary> 570 /// Returns a collection of ancestor elements. 571 /// </summary> 572 public static IEnumerable<DependencyObject> Ancestors(this DependencyObject item) 573 { 574 ILinqTree<DependencyObject> adapter = new VisualTreeAdapter(item); 575 576 var parent = adapter.Parent; 577 while (parent != null) 578 { 579 yield return parent; 580 adapter = new VisualTreeAdapter(parent); 581 parent = adapter.Parent; 582 } 583 } 584 585 /// <summary> 586 /// Returns a collection containing this element and all ancestor elements. 587 /// </summary> 588 public static IEnumerable<DependencyObject> AncestorsAndSelf(this DependencyObject item) 589 { 590 yield return item; 591 592 foreach (var ancestor in item.Ancestors()) 593 { 594 yield return ancestor; 595 } 596 } 597 598 /// <summary> 599 /// Returns a collection of child elements. 600 /// </summary> 601 public static IEnumerable<DependencyObject> Elements(this DependencyObject item) 602 { 603 ILinqTree<DependencyObject> adapter = new VisualTreeAdapter(item); 604 foreach (var child in adapter.Children()) 605 { 606 yield return child; 607 } 608 } 609 610 /// <summary> 611 /// Returns a collection of the sibling elements before this node, in document order. 612 /// </summary> 613 public static IEnumerable<DependencyObject> ElementsBeforeSelf(this DependencyObject item) 614 { 615 if (item.Ancestors().FirstOrDefault() == null) 616 yield break; 617 foreach (var child in item.Ancestors().First().Elements()) 618 { 619 if (child.Equals(item)) 620 break; 621 yield return child; 622 } 623 } 624 625 /// <summary> 626 /// Returns a collection of the after elements after this node, in document order. 627 /// </summary> 628 public static IEnumerable<DependencyObject> ElementsAfterSelf(this DependencyObject item) 629 { 630 if (item.Ancestors().FirstOrDefault() == null) 631 yield break; 632 bool afterSelf = false; 633 foreach (var child in item.Ancestors().First().Elements()) 634 { 635 if (afterSelf) 636 yield return child; 637 638 if (child.Equals(item)) 639 afterSelf = true; 640 } 641 } 642 643 /// <summary> 644 /// Returns a collection containing this element and all child elements. 645 /// </summary> 646 public static IEnumerable<DependencyObject> ElementsAndSelf(this DependencyObject item) 647 { 648 yield return item; 649 650 foreach (var child in item.Elements()) 651 { 652 yield return child; 653 } 654 } 655 656 /// <summary> 657 /// Returns a collection of descendant elements which match the given type. 658 /// </summary> 659 public static IEnumerable<DependencyObject> Descendants<T>(this DependencyObject item) 660 { 661 return item.Descendants().Where(i => i is T).Cast<DependencyObject>(); 662 } 663 664 /// <summary> 665 /// Returns a collection of the sibling elements before this node, in document order 666 /// which match the given type. 667 /// </summary> 668 public static IEnumerable<DependencyObject> ElementsBeforeSelf<T>(this DependencyObject item) 669 { 670 return item.ElementsBeforeSelf().Where(i => i is T).Cast<DependencyObject>(); 671 } 672 673 /// <summary> 674 /// Returns a collection of the after elements after this node, in document order 675 /// which match the given type. 676 /// </summary> 677 public static IEnumerable<DependencyObject> ElementsAfterSelf<T>(this DependencyObject item) 678 { 679 return item.ElementsAfterSelf().Where(i => i is T).Cast<DependencyObject>(); 680 } 681 682 /// <summary> 683 /// Returns a collection containing this element and all descendant elements 684 /// which match the given type. 685 /// </summary> 686 public static IEnumerable<DependencyObject> DescendantsAndSelf<T>(this DependencyObject item) 687 { 688 return item.DescendantsAndSelf().Where(i => i is T).Cast<DependencyObject>(); 689 } 690 691 /// <summary> 692 /// Returns a collection of ancestor elements which match the given type. 693 /// </summary> 694 public static IEnumerable<DependencyObject> Ancestors<T>(this DependencyObject item) 695 { 696 return item.Ancestors().Where(i => i is T).Cast<DependencyObject>(); 697 } 698 699 /// <summary> 700 /// Returns a collection containing this element and all ancestor elements 701 /// which match the given type. 702 /// </summary> 703 public static IEnumerable<DependencyObject> AncestorsAndSelf<T>(this DependencyObject item) 704 { 705 return item.AncestorsAndSelf().Where(i => i is T).Cast<DependencyObject>(); 706 } 707 708 /// <summary> 709 /// Returns a collection of child elements which match the given type. 710 /// </summary> 711 public static IEnumerable<DependencyObject> Elements<T>(this DependencyObject item) 712 { 713 return item.Elements().Where(i => i is T).Cast<DependencyObject>(); 714 } 715 716 /// <summary> 717 /// Returns a collection containing this element and all child elements. 718 /// which match the given type. 719 /// </summary> 720 public static IEnumerable<DependencyObject> ElementsAndSelf<T>(this DependencyObject item) 721 { 722 return item.ElementsAndSelf().Where(i => i is T).Cast<DependencyObject>(); 723 } 724 725 } 726 727 public static class EnumerableTreeExtensions 728 { 729 /// <summary> 730 /// Applies the given function to each of the items in the supplied 731 /// IEnumerable. 732 /// </summary> 733 private static IEnumerable<DependencyObject> DrillDown(this IEnumerable<DependencyObject> items, 734 Func<DependencyObject, IEnumerable<DependencyObject>> function) 735 { 736 foreach (var item in items) 737 { 738 foreach (var itemChild in function(item)) 739 { 740 yield return itemChild; 741 } 742 } 743 } 744 745 /// <summary> 746 /// Applies the given function to each of the items in the supplied 747 /// IEnumerable, which match the given type. 748 /// </summary> 749 public static IEnumerable<DependencyObject> DrillDown<T>(this IEnumerable<DependencyObject> items, 750 Func<DependencyObject, IEnumerable<DependencyObject>> function) 751 where T : DependencyObject 752 { 753 foreach (var item in items) 754 { 755 foreach (var itemChild in function(item)) 756 { 757 if (itemChild is T) 758 { 759 yield return (T)itemChild; 760 } 761 } 762 } 763 } 764 765 /// <summary> 766 /// Returns a collection of descendant elements. 767 /// </summary> 768 public static IEnumerable<DependencyObject> Descendants(this IEnumerable<DependencyObject> items) 769 { 770 return items.DrillDown(i => i.Descendants()); 771 } 772 773 /// <summary> 774 /// Returns a collection containing this element and all descendant elements. 775 /// </summary> 776 public static IEnumerable<DependencyObject> DescendantsAndSelf(this IEnumerable<DependencyObject> items) 777 { 778 return items.DrillDown(i => i.DescendantsAndSelf()); 779 } 780 781 /// <summary> 782 /// Returns a collection of ancestor elements. 783 /// </summary> 784 public static IEnumerable<DependencyObject> Ancestors(this IEnumerable<DependencyObject> items) 785 { 786 return items.DrillDown(i => i.Ancestors()); 787 } 788 789 /// <summary> 790 /// Returns a collection containing this element and all ancestor elements. 791 /// </summary> 792 public static IEnumerable<DependencyObject> AncestorsAndSelf(this IEnumerable<DependencyObject> items) 793 { 794 return items.DrillDown(i => i.AncestorsAndSelf()); 795 } 796 797 /// <summary> 798 /// Returns a collection of child elements. 799 /// </summary> 800 public static IEnumerable<DependencyObject> Elements(this IEnumerable<DependencyObject> items) 801 { 802 return items.DrillDown(i => i.Elements()); 803 } 804 805 /// <summary> 806 /// Returns a collection containing this element and all child elements. 807 /// </summary> 808 public static IEnumerable<DependencyObject> ElementsAndSelf(this IEnumerable<DependencyObject> items) 809 { 810 return items.DrillDown(i => i.ElementsAndSelf()); 811 } 812 813 /// <summary> 814 /// Returns a collection of descendant elements which match the given type. 815 /// </summary> 816 public static IEnumerable<DependencyObject> Descendants<T>(this IEnumerable<DependencyObject> items) 817 where T : DependencyObject 818 { 819 return items.DrillDown<T>(i => i.Descendants()); 820 } 821 822 /// <summary> 823 /// Returns a collection containing this element and all descendant elements. 824 /// which match the given type. 825 /// </summary> 826 public static IEnumerable<DependencyObject> DescendantsAndSelf<T>(this IEnumerable<DependencyObject> items) 827 where T : DependencyObject 828 { 829 return items.DrillDown<T>(i => i.DescendantsAndSelf()); 830 } 831 832 /// <summary> 833 /// Returns a collection of ancestor elements which match the given type. 834 /// </summary> 835 public static IEnumerable<DependencyObject> Ancestors<T>(this IEnumerable<DependencyObject> items) 836 where T : DependencyObject 837 { 838 return items.DrillDown<T>(i => i.Ancestors()); 839 } 840 841 /// <summary> 842 /// Returns a collection containing this element and all ancestor elements. 843 /// which match the given type. 844 /// </summary> 845 public static IEnumerable<DependencyObject> AncestorsAndSelf<T>(this IEnumerable<DependencyObject> items) 846 where T : DependencyObject 847 { 848 return items.DrillDown<T>(i => i.AncestorsAndSelf()); 849 } 850 851 /// <summary> 852 /// Returns a collection of child elements which match the given type. 853 /// </summary> 854 public static IEnumerable<DependencyObject> Elements<T>(this IEnumerable<DependencyObject> items) 855 where T : DependencyObject 856 { 857 return items.DrillDown<T>(i => i.Elements()); 858 } 859 860 /// <summary> 861 /// Returns a collection containing this element and all child elements. 862 /// which match the given type. 863 /// </summary> 864 public static IEnumerable<DependencyObject> ElementsAndSelf<T>(this IEnumerable<DependencyObject> items) 865 where T : DependencyObject 866 { 867 return items.DrillDown<T>(i => i.ElementsAndSelf()); 868 } 869 }
使用的时候只需要这样
<toolkit:HubTile local:MetroInMotion.Tilt="1" Grid.Row="1" Grid.Column="1" Background="Red" Source="Assets/Tiles/FlipCycleTileSmall.png" Title="Metro" Message="This is Metro in App"/>
高级的磁贴
现在又说回开始菜单中的磁贴,在StandardTileData的属性中有一个Count专门表达这个App存在的通知数量,但是这个属性在后来的动态磁贴出现后而变得很少用(如下图左1),这部分内容并不是取代Count属性的动态磁贴的实现,而是关注另外个内置应用的磁贴——人脉(下图中间和右2)
这里的人脉磁贴需要用到之前提过的动画效果,源码不是我编写的,我只是在网上找到老外写了那么的一个控件,cs部分的注释已经加了,xmal的由于基础不好现在还没看得明白,代码全部都铺上来,效果如下图
1 public class PeopleHubTileData : INotifyPropertyChanged 2 { 3 private ImageSource _ImageFront; 4 public ImageSource ImageFront 5 { 6 get 7 { 8 return _ImageFront; 9 } 10 set 11 { 12 if (value != _ImageFront) 13 { 14 _ImageFront = value; 15 NotifyPropertyChanged("ImageFront"); 16 } 17 } 18 } 19 20 private ImageSource _ImageBack; 21 public ImageSource ImageBack 22 { 23 get 24 { 25 return _ImageBack; 26 } 27 set 28 { 29 if (value != _ImageBack) 30 { 31 _ImageBack = value; 32 NotifyPropertyChanged("ImageBack"); 33 } 34 } 35 } 36 37 private Stretch _ImageStretch; 38 public Stretch ImageStretch 39 { 40 get 41 { 42 return _ImageStretch; 43 } 44 set 45 { 46 if (value != _ImageStretch) 47 { 48 _ImageStretch = value; 49 NotifyPropertyChanged("ImageStretch"); 50 } 51 } 52 } 53 54 55 56 57 public event PropertyChangedEventHandler PropertyChanged; 58 private void NotifyPropertyChanged(String propertyName) 59 { 60 PropertyChangedEventHandler handler = PropertyChanged; 61 if (null != handler) 62 { 63 handler(this, new PropertyChangedEventArgs(propertyName)); 64 } 65 } 66 }
1 public class Tiles : DependencyObject 2 { 3 public Tiles() 4 { 5 this.CenterOfRotationY = 0.5; 6 } 7 public Tiles(object item) 8 : this() 9 { 10 this.TileData = item; 11 } 12 public object TileData { get; set; } 13 14 public double CenterOfRotationY { get; set; } 15 public double ZIndex 16 { 17 get { return (int)GetValue(ZIndexProperty); } 18 set { SetValue(ZIndexProperty, value); } 19 } 20 public static DependencyProperty ZIndexProperty = DependencyProperty.Register("ZIndex", typeof(int), typeof(Tiles), new PropertyMetadata(0)); 21 public double RotationX 22 { 23 get { return (double)GetValue(RotationXProperty); } 24 set { SetValue(RotationXProperty, value); } 25 } 26 public static DependencyProperty RotationXProperty = DependencyProperty.Register("RotationX", typeof(double), typeof(Tiles), new PropertyMetadata(0.0)); 27 28 29 30 }
1 public class PeopleHubTile : ContentControl 2 { 3 #region Member variables 4 private int LastAnimatedTile = 0; 5 /// <summary> 6 /// 大磁贴起始位置选择完毕,可以开始制造大磁贴 7 /// </summary> 8 private bool isBigTileAnimationStarted = false; 9 /// <summary> 10 /// 表明给大磁贴选择了图片 11 /// </summary> 12 private bool isBitImageSelected = false; 13 /// <summary> 14 /// 大磁贴图片的索引 15 /// </summary> 16 private int BitImageSelectedIndex = 0; 17 /// <summary> 18 /// 累计翻动大磁贴时已经翻动了小磁贴的数目 19 /// </summary> 20 private int TileAnimateIndex = 0; 21 private int TileAnimationCount = 0; 22 /// <summary> 23 /// 所有磁贴进入就绪状态,可以开始选取大磁贴的起始位置 24 /// </summary> 25 private bool isReadyForBigTile = false; 26 private Random RandomTile = new Random(); 27 private DispatcherTimer dispatcherTimer = new DispatcherTimer(); 28 private List<String> ImageUrl = new List<string>() 29 { 30 "/Themes/Images/1.jpg", 31 "/Themes/Images/13.jpg", 32 "/Themes/Images/14.jpg", 33 "/Themes/Images/15.jpg", 34 "/Themes/Images/16.jpg", 35 "/Themes/Images/17.jpg", 36 "/Themes/Images/18.jpg", 37 "/Themes/Images/19.jpg", 38 "/Themes/Images/2.jpg", 39 "/Themes/Images/20.jpg", 40 "/Themes/Images/21.jpg", 41 "/Themes/Images/3.jpg", 42 43 }; 44 45 46 47 private ObservableCollection<Tiles> dataItems = new ObservableCollection<Tiles>() 48 { 49 new Tiles(new PeopleHubTileData(){ ImageFront = new BitmapImage(new Uri("/Themes/Images/1.jpg", UriKind.RelativeOrAbsolute)) }), 50 new Tiles(new PeopleHubTileData(){ ImageFront = new BitmapImage(new Uri("/Themes/Images/2.jpg", UriKind.RelativeOrAbsolute)) }), 51 new Tiles(new PeopleHubTileData(){ ImageFront = new BitmapImage(new Uri("/Themes/Images/13.jpg", UriKind.RelativeOrAbsolute)) }), 52 new Tiles(new PeopleHubTileData(){ ImageFront = new BitmapImage(new Uri("/Themes/Images/14.jpg", UriKind.RelativeOrAbsolute)) }), 53 new Tiles(new PeopleHubTileData(){ ImageFront = new BitmapImage(new Uri("/Themes/Images/15.jpg", UriKind.RelativeOrAbsolute)) }), 54 new Tiles(new PeopleHubTileData(){ ImageFront = new BitmapImage(new Uri("/Themes/Images/16.jpg", UriKind.RelativeOrAbsolute)) }), 55 new Tiles(new PeopleHubTileData(){ ImageFront = new BitmapImage(new Uri("/Themes/Images/17.jpg", UriKind.RelativeOrAbsolute)) }), 56 new Tiles(new PeopleHubTileData(){ ImageFront = new BitmapImage(new Uri("/Themes/Images/18.jpg", UriKind.RelativeOrAbsolute)) }), 57 new Tiles(new PeopleHubTileData(){ ImageFront = new BitmapImage(new Uri("/Themes/Images/19.jpg", UriKind.RelativeOrAbsolute)) }), 58 }; 59 #endregion 60 61 #region Constructor 62 public PeopleHubTile() 63 { 64 DefaultStyleKey = typeof(PeopleHubTile); 65 Loaded += PeopleHubTile_Loaded; 66 67 } 68 #endregion 69 70 #region Methods 71 ListBox ItemsListBox; 72 void PeopleHubTile_Loaded(object sender, RoutedEventArgs e) 73 { 74 ///在generic中获取ListBox 附上各个Tilt在ListBox中 75 ItemsListBox = this.GetTemplateChild("ItemsListBox") as ListBox; 76 this.ItemsListBox.ItemsSource = dataItems; 77 //开启定时更换tile的任务 78 dispatcherTimer.Interval = TimeSpan.FromSeconds(2); 79 dispatcherTimer.Tick += dispatcherTimer_Tick; 80 dispatcherTimer.Start(); 81 } 82 83 void dispatcherTimer_Tick(object sender, EventArgs e) 84 { 85 //计数,如果是9个则尝试启动大磁贴 86 TileAnimationCount++; 87 if (TileAnimationCount > 9 && isReadyForBigTile == false) 88 { 89 TileAnimationCount = 0; 90 isReadyForBigTile = true; 91 92 } 93 94 95 int AnimateItem = 0; 96 Tiles AnimateTile = null; 97 //未启动大磁贴的操作 98 if (!isBigTileAnimationStarted) 99 { 100 AnimateItem = RandomTile.Next(this.dataItems.Count); 101 AnimateTile = this.dataItems[AnimateItem]; 102 103 ///尝试启动大磁贴 并且当前是抽到变换的磁贴是允许作为大磁贴的第一个磁贴 104 ///它变换大磁贴时是从大磁贴的左上 右上 左下 右下来变换 105 if (isReadyForBigTile && (AnimateItem == 0 || AnimateItem == 1 || AnimateItem == 3 || AnimateItem == 4)) 106 { 107 LastAnimatedTile = AnimateItem; 108 isBigTileAnimationStarted = true; 109 TileAnimateIndex = 0; 110 111 } 112 113 ///用ZIndex来区分正面和反面 114 /// Animate small tiles 115 if (AnimateTile.ZIndex == 0) 116 { 117 //back tile 118 PeopleHubTileData ItemData = AnimateTile.TileData as PeopleHubTileData; 119 120 int newImage = RandomTile.Next(ImageUrl.Count); 121 if (RandomTile.Next(20) > 5) 122 ItemData.ImageBack = new BitmapImage(new Uri(ImageUrl[newImage], UriKind.RelativeOrAbsolute)); 123 else 124 ItemData.ImageBack = new BitmapImage(new Uri("", UriKind.RelativeOrAbsolute)); 125 } 126 else if (AnimateTile.ZIndex != 0) 127 { 128 //front tile 129 PeopleHubTileData ItemData = AnimateTile.TileData as PeopleHubTileData; 130 131 int newImage = RandomTile.Next(ImageUrl.Count); 132 if (RandomTile.Next(20) > 5) 133 ItemData.ImageFront = new BitmapImage(new Uri(ImageUrl[newImage], UriKind.RelativeOrAbsolute)); 134 else 135 ItemData.ImageFront = new BitmapImage(new Uri("", UriKind.RelativeOrAbsolute)); 136 137 138 } 139 140 141 } 142 143 144 ///已经启用大磁贴 145 else if (isBigTileAnimationStarted && TileAnimateIndex < 4) 146 { 147 148 int[] LastTiles = new int[4]; 149 //按照大磁贴其实位置来选出大磁贴所占用的小磁贴的索引 150 switch (LastAnimatedTile) 151 { 152 case 0: 153 LastTiles = new int[4] { 0, 1, 3, 4 }; 154 break; 155 case 1: 156 LastTiles = new int[4] { 1, 2, 4, 5 }; 157 break; 158 case 3: 159 LastTiles = new int[4] { 3, 4, 6, 7 }; 160 break; 161 case 4: 162 LastTiles = new int[4] { 4, 5, 7, 8 }; 163 break; 164 default: 165 break; 166 167 } 168 169 170 171 AnimateTile = this.dataItems[LastTiles[TileAnimateIndex]]; 172 ///还没有生成大磁贴所用的图片时 173 if (!isBitImageSelected) 174 { 175 isBitImageSelected = true; 176 BitImageSelectedIndex = RandomTile.Next(ImageUrl.Count); 177 } 178 ///bmpWB是直接从资源列表中拿的图片 179 BitmapImage bmpWB = new BitmapImage(new Uri(ImageUrl[BitImageSelectedIndex], UriKind.RelativeOrAbsolute)); 180 ///最终写到磁贴上的部分图片 181 WriteableBitmap ImageWB = new WriteableBitmap(bmpWB.PixelWidth, bmpWB.PixelHeight); 182 bmpWB.CreateOptions = BitmapCreateOptions.None; 183 ///整幅大磁贴的图片 184 WriteableBitmap imageBitMap = new WriteableBitmap(bmpWB); 185 186 switch (TileAnimateIndex) 187 { 188 case 0: 189 190 ImageWB = GetCropImage(imageBitMap, 0, 0, imageBitMap.PixelWidth / 2, imageBitMap.PixelHeight / 2); 191 192 break; 193 case 1: 194 195 ImageWB = GetCropImage(imageBitMap, imageBitMap.PixelWidth / 2, 0, imageBitMap.PixelWidth / 2, imageBitMap.PixelHeight / 2); 196 break; 197 case 2: 198 199 ImageWB = GetCropImage(imageBitMap, 0, imageBitMap.PixelHeight / 2, imageBitMap.PixelWidth / 2, imageBitMap.PixelHeight / 2); 200 201 202 203 break; 204 case 3: 205 206 ImageWB = GetCropImage(imageBitMap, imageBitMap.PixelWidth / 2, imageBitMap.PixelHeight / 2, imageBitMap.PixelWidth / 2, imageBitMap.PixelHeight / 2); 207 break; 208 default: 209 break; 210 211 } 212 ///通过累计数目来判断大磁贴是否完成 213 TileAnimateIndex++; 214 if (TileAnimateIndex > 3) 215 { 216 isBigTileAnimationStarted = false; 217 isReadyForBigTile = false; 218 isBitImageSelected = false; 219 } 220 221 222 //animate part of big tiles 223 if (AnimateTile.ZIndex == 0) 224 { 225 //back tile 226 PeopleHubTileData ItemData = AnimateTile.TileData as PeopleHubTileData; 227 228 ItemData.ImageBack = ImageWB; 229 230 } 231 else if (AnimateTile.ZIndex != 0) 232 { 233 //front tile 234 PeopleHubTileData ItemData = AnimateTile.TileData as PeopleHubTileData; 235 236 ItemData.ImageFront = ImageWB; 237 } 238 239 240 } 241 //tile animation 242 Storyboard MyStory = new Storyboard(); 243 DoubleAnimation MyDouble = new DoubleAnimation(); 244 MyDouble.From = AnimateTile.RotationX; 245 MyDouble.To = AnimateTile.RotationX + 180; 246 MyDouble.Duration = TimeSpan.FromSeconds(0.5); 247 Storyboard.SetTarget(MyDouble, AnimateTile); 248 Storyboard.SetTargetProperty(MyDouble, new PropertyPath(Tiles.RotationXProperty)); 249 MyStory.Children.Add(MyDouble); 250 251 ObjectAnimationUsingKeyFrames MyObject = new ObjectAnimationUsingKeyFrames(); 252 DiscreteObjectKeyFrame MyKeyFrame = new DiscreteObjectKeyFrame(); 253 MyKeyFrame.KeyTime = TimeSpan.FromSeconds(0); 254 MyKeyFrame.Value = AnimateTile.ZIndex; 255 MyObject.KeyFrames.Add(MyKeyFrame); 256 257 MyKeyFrame = new DiscreteObjectKeyFrame(); 258 MyKeyFrame.KeyTime = TimeSpan.FromSeconds(0.3); 259 MyKeyFrame.Value = (AnimateTile.ZIndex == 0) ? 1 : 0; 260 MyObject.KeyFrames.Add(MyKeyFrame); 261 Storyboard.SetTarget(MyObject, AnimateTile); 262 Storyboard.SetTargetProperty(MyObject, new PropertyPath(Tiles.ZIndexProperty)); 263 MyStory.Children.Add(MyObject); 264 MyStory.Begin(); 265 266 267 268 } 269 270 /// <summary> 271 /// 利用数组copy,通过计算位图的位置来切割出部分的图片 272 /// </summary> 273 /// <param name="aBitmapSource"></param> 274 /// <param name="XPoint"></param> 275 /// <param name="YPoint"></param> 276 /// <param name="aWidth"></param> 277 /// <param name="aHeight"></param> 278 /// <returns></returns> 279 private static WriteableBitmap GetCropImage(WriteableBitmap aBitmapSource, int XPoint, int YPoint, int aWidth, int aHeight) 280 { 281 var SourceWidth = aBitmapSource.PixelWidth; 282 var result = new WriteableBitmap(aWidth, aHeight); 283 for (var x = 0; x < aHeight - 1; x++) 284 { 285 var Index = XPoint + (YPoint + x) * SourceWidth; 286 var FinalIndex = x * aWidth; 287 Array.Copy(aBitmapSource.Pixels, Index, result.Pixels, FinalIndex, aWidth); 288 289 290 } 291 return result; 292 } 293 #endregion 294 295 #region OnApplyTemplate 296 public override void OnApplyTemplate() 297 { 298 base.OnApplyTemplate(); 299 } 300 #endregion 301 }
1 <ResourceDictionary 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows" 5 xmlns:toolkit="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Toolkit" 6 xmlns:local="clr-namespace:PeopleHubTileEx"> 7 8 <DataTemplate x:Key="DataTemplatePeopleHubTile"> 9 <Grid x:Name="TileGrid" Height="80" Width="80" > 10 <Grid.Projection> 11 <PlaneProjection RotationX="{Binding RotationX}" CenterOfRotationY="{Binding CenterOfRotationX}"> 12 </PlaneProjection> 13 </Grid.Projection> 14 <Grid x:Name="BackGrid" Canvas.ZIndex="{Binding ZIndex}" RenderTransformOrigin="0.5,0.5"> 15 <Grid.RenderTransform> 16 <CompositeTransform ScaleY="-1"/> 17 </Grid.RenderTransform> 18 <Grid.Background> 19 <SolidColorBrush Color="Green"></SolidColorBrush> 20 </Grid.Background> 21 <Image Source="{Binding TileData.ImageBack}" Stretch="Fill" /> 22 </Grid> 23 <Grid x:Name="FrontGrid"> 24 <Grid.Background> 25 <SolidColorBrush Color="Green"></SolidColorBrush> 26 </Grid.Background> 27 <Image Source="{Binding TileData.ImageFront}" Stretch="Fill" > 28 29 </Image> 30 </Grid> 31 </Grid> 32 </DataTemplate> 33 34 <Style TargetType="local:PeopleHubTile"> 35 36 <Setter Property="Template"> 37 <Setter.Value> 38 <ControlTemplate TargetType="local:PeopleHubTile"> 39 <Grid> 40 <ListBox Name="ItemsListBox" Width="240" Height="240" 41 ScrollViewer.HorizontalScrollBarVisibility="Disabled" 42 ScrollViewer.VerticalScrollBarVisibility="Disabled" 43 ItemTemplate="{StaticResource DataTemplatePeopleHubTile}"> 44 <ListBox.ItemsPanel> 45 <ItemsPanelTemplate> 46 <toolkit:WrapPanel ItemHeight="80" ItemWidth="80" Orientation="Horizontal"> 47 48 </toolkit:WrapPanel> 49 </ItemsPanelTemplate> 50 </ListBox.ItemsPanel> 51 </ListBox> 52 53 </Grid> 54 55 </ControlTemplate> 56 </Setter.Value> 57 </Setter> 58 </Style> 59 </ResourceDictionary>
使用的时候只需要以下面的形式则可。
<PeopleHubTileControl:PeopleHubTile VerticalAlignment="Center"> </PeopleHubTileControl:PeopleHubTile>