作者简介:Jeff Wilcox,微软高级软件开发工程师,不久前,调往微软Windows Azure组,负责微软云技术的开源项目。Jeff Wilcox曾负责开发了Windows Phone平台的Foursqaure客户端,并参与了无数Silverlight工具包、Windows Phone 7.0、7.1开发包,Windows Phone用户界面控制、帮助,以及许多其他重要项目。
【51CTO译文】作为沉浸于开发行业多年的老手,大家一定对这些话题并不陌生:整理页面边距、对齐图像内容以及让自己的应用程序更美观。随着软件消费者中“外貌协会”成员的比重不断上升,用户界面不够抢眼的应用几乎已经无法在市场上占得一席之地。
不久前,我有幸参加了Windows Phone设计团队举办的“美观开发空间”活动。我要由衷地赞叹,这是一次真正的创意空间交流活动,到处是有趣的参与者、舒缓的音乐以及为市场创造出更好、更漂亮的应用程序的热烈渴求。
在活动中我一直坐在两位用户体验设计师Corrina与Arturo身边,与他们讨论设计工作中的原则性取向;很偶然地,我们的话题转移到一副简洁而相当美观的网格图上,这也正是二位在Windows Phone平台上开发的主要焦点。图像由多个红色正方形构成,每个方形为25x25像素,两个方形之间相隔12像素,也就是说每个独立单元都拥有宽高为24像素的反衬背景。(还是那句话,Metro风格要求边框长度始终为12的整数)
设计将使用典型的Photoshop层来容纳这些方形,或者是在应用程序页面之上插入XAML借以完成图形对齐、网格设计及位置调整等等。
我的想法是:如果最终外观设计方案能够与Windows Phone上现有的性能计数器相似,那么肯定会大受欢迎——在调试时,我们能够将这套网格重叠覆盖在整个应用程序框架上,使其成为独立而完整的全局显示方案。就在活动过程当中,我即兴用代码将自己的想法表达了出来,下面请大家分享我的成果。
要使用这款计数器,我们只需打开App.xaml.cs文件(这里囊括了其它多款性能计数器),并将其添加进来。如果大家只是打算简单设计一下,那么我建议各位直接将其启用,这样一来我们就能够让它作用于实机及模拟器中的应用程序。之所以要把它与模拟器关联起来,是因为我们能够将最终显示效果通过截图与朋友及家人分享,并聆听他们在图像位置方面提出的意见。
- // Show graphics profiling information while debugging.
- if (System.Diagnostics.Debugger.IsAttached)
- {
- // Display the current frame rate counters.
- Application.Current.Host.Settings.EnableFrameRateCounter = true;
- // Display the metro grid helper.
- MetroGridHelper.IsVisible = true;
以下是这段简单的代码在与小型应用程序协作时显示出的效果:
在这个例子中,我遇到了Windows Phone设计中的经典“bug”:文本信息区块之一在插入过程中未能正确显示应有的Metro风格。也就是说,该区块的左侧边距为“0”,而不是Metro要求的12像素,这使得对应文字内容比其它字体更靠左。通过上图中正方形的对比,相信大家能更清晰地理解我遇到的问题,字体错位现象十分明显。
如果大家不喜欢默认的红色以及~0.15的不透明度,我还为不透明度及颜色添加了简单的静态属性设置选项,希望能让各位获得自己理想中的方形单元效果。在运行过程中,可见属性不会显示出来,但请大家注意,只要网格本身仍然存在于可视化元素列表当中,就会占用对应的性能资源(因此请务必在应用程序的发布版本中把网格去掉,否则会造成毫无意义的资源浪费)。
源代码
我已经在NuGet上发布过源文件——这应该是大家在自己的项目中使用这款小成品的最佳方式。如果今后我做出任何修正或添加某些功能,各位也将会在NuGet网站上及时找到最新版本。
◆确保自己已经安装了NuGet(http://www.nuget.org/)
◆使用控制台或软件包管理器安装该软件包,我把它命名为MetroGridHelper
PM> Install-Package MetroGridHelper
当然,大家也可以将这部分源代码在项目中整理成一个全新的文件,MetroGridHelper.cs:
- // (c) Copyright Microsoft Corporation.
- // This source is subject to the Microsoft Public License (Ms-PL).
- // Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
- // All other rights reserved.
- using System.Collections.Generic;
- using System.Diagnostics;
- using System.Windows.Controls;
- using System.Windows.Media;
- using System.Windows.Shapes;
- namespace System.Windows
- {
- /// <summary>
- /// A utility class that overlays a designer-friendly grid on top of the
- /// application frame, for use similar to the performance counters in
- /// App.xaml.cs. The color and opacity are configurable. The grid contains
- /// a number of squares that are 24x24, offset with 12px gutters, and all
- /// 24px away from the edge of the device.
- /// </summary>
- public static class MetroGridHelper
- {
- private static bool _visible;
- private static double _opacity = 0.15;
- private static Color _color = Colors.Red;
- private static List<Rectangle> _squares;
- private static Grid _grid;
- /// <summary>
- /// Gets or sets a value indicating whether the designer grid is
- /// visible on top of the application's frame.
- /// </summary>
- public static bool IsVisible
- {
- get
- {
- return _visible;
- }
- set
- {
- _visible = value;
- UpdateGrid();
- }
- }
- /// <summary>
- /// Gets or sets the color to use for the grid's squares.
- /// </summary>
- public static Color Color
- {
- get { return _color; }
- set
- {
- _color = value;
- UpdateGrid();
- }
- }
- /// <summary>
- /// Gets or sets a value indicating the opacity for the grid's squares.
- /// </summary>
- public static double Opacity
- {
- get { return _opacity; }
- set
- {
- _opacity = value;
- UpdateGrid();
- }
- }
- /// <summary>
- /// Updates the grid (if it already has been created) or initializes it
- /// otherwise.
- /// </summary>
- private static void UpdateGrid()
- {
- if (_squares != null)
- {
- var brush = new SolidColorBrush(_color);
- foreach (var square in _squares)
- {
- square.Fill = brush;
- }
- if (_grid != null)
- {
- _grid.Visibility = _visible ? Visibility.Visible : Visibility.Collapsed;
- _grid.Opacity = _opacity;
- }
- }
- else
- {
- BuildGrid();
- }
- }
- /// <summary>
- /// Builds the grid.
- /// </summary>
- private static void BuildGrid()
- {
- _squares = new List<Rectangle>();
- var frame = Application.Current.RootVisual as Frame;
- if (frame == null || VisualTreeHelper.GetChildrenCount(frame) == 0)
- {
- Deployment.Current.Dispatcher.BeginInvoke(BuildGrid);
- return;
- }
- var child = VisualTreeHelper.GetChild(frame, 0);
- var childAsBorder = child as Border;
- var childAsGrid = child as Grid;
- if (childAsBorder != null)
- {
- // Not a pretty way to control the root visual, but I did not
- // want to implement using a popup.
- var content = childAsBorder.Child;
- if (content == null)
- {
- Deployment.Current.Dispatcher.BeginInvoke(BuildGrid);
- return;
- }
- childAsBorder.Child = null;
- Deployment.Current.Dispatcher.BeginInvoke(() =>
- {
- Grid newGrid = new Grid();
- childAsBorder.Child = newGrid;
- newGrid.Children.Add(content);
- PrepareGrid(frame, newGrid);
- });
- }
- else if (childAsGrid != null)
- {
- PrepareGrid(frame, childAsGrid);
- }
- else
- {
- Debug.WriteLine("Dear developer:");
- Debug.WriteLine("Unfortunately the design overlay feature requires that the root frame visual");
- Debug.WriteLine("be a Border or a Grid. So the overlay grid just isn't going to happen.");
- return;
- }
- }
- /// <summary>
- /// Does the actual work of preparing the grid once the parent frame is
- /// in the visual tree and we have a Grid instance to work with for
- /// placing the chilren.
- /// </summary>
- /// <param name="frame">The phone application frame.</param>
- /// <param name="parent">The parent grid to insert the sub-grid into.</param>
- private static void PrepareGrid(Frame frame, Grid parent)
- {
- var brush = new SolidColorBrush(_color);
- _grid = new Grid();
- _grid.IsHitTestVisible = false;
- // To support both orientations, unfortunately more visuals need to
- // be used. An alternate implementation would be to react to the
- // orientation change event and re-draw/remove squares.
- double width = frame.ActualWidth;
- double height = frame.ActualHeight;
- double max = Math.Max(width, height);
- for (int x = 24; x < /*width*/ max; x += 37)
- {
- for (int y = 24; y < /*height*/ max; y += 37)
- {
- var rect = new Rectangle
- {
- Width = 25,
- Height = 25,
- VerticalAlignment = System.Windows.VerticalAlignment.Top,
- HorizontalAlignment = System.Windows.HorizontalAlignment.Left,
- Margin = new Thickness(x, y, 0, 0),
- IsHitTestVisible = false,
- Fill = brush,
- };
- _grid.Children.Add(rect);
- _squares.Add(rect);
- }
- }
- _grid.Visibility = _visible ? Visibility.Visible : Visibility.Collapsed;
- _grid.Opacity = _opacity;
- // For performance reasons a single surface should ideally be used
- // for the grid.
- _grid.CacheMode = new BitmapCache();
- // Places the grid into the visual tree. It is never removed once
- // being added.
- parent.Children.Add(_grid);
- }
- }
- }
原文链接:http://www.jeff.wilcox.name/2011/10/metrogridhelper/ 核子可乐 译