专栏首页汪宇杰博客打造一把UWP像素尺

打造一把UWP像素尺

在特定应用里,我们需要用标尺来标识屏幕上的像素。然而唯一内置的尺是在InkToolbar控件里的,我们没法拿出来用。今天我就教大家如何自己打造一把UWP引用里随处可用的像素尺。

样例应用

新建一个名为PixelRulerUwp的UWP应用,版本设置为Windows 10, version 1803 (10.0; Build 17134)。

// 不用1809是因为这个版本实在是人类历史上最可怕的药丸啊,微软别打我,这行注释了你们看不见……

Win2D

官方文档描述

Win2D is an easy-to-use Windows Runtime API for immediate mode 2D graphics rendering with GPU acceleration. It is available to C#, C++ and VB developers writing apps for the Windows Universal Platform (UWP). It utilizes the power of Direct2D, and integrates seamlessly with XAML and CoreWindow.

我们将使用Win2D绘制标尺。使用NuGet将Win2D安装到我们的工程里:

Install-Package Win2D.uwp

创建PixelRuler用户控件

在工程里添加一个名为“PixelRuler.xaml”的用户控件

Win2D能够在CanvasControl上绘制图形,因此我们需要添加一个名为“RulerCanvas”的CanvasControl,之后我们会在它上面绘制像素尺。

UserControl属性里加入一个新的命名空间

xmlns:xaml="using:Microsoft.Graphics.Canvas.UI.Xaml"

然后添加CanvasControl

<xaml:CanvasControl x:Name="RulerCanvas"                     VerticalAlignment="Top" />

这不是最终的XAML代码,我们将继续完成它。

创建绑定属性

这把尺至少需要一个宽度和一个背景色,在PixelRuler.xaml.cs中加入它们。

实现INotifyPropertyChanged接口来进行数据绑定

using System.ComponentModel;

然后

public sealed partial class PixelRuler : UserControl, INotifyPropertyChanged

实现接口

public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

然后创建RulerWidth属性

public static readonly DependencyProperty RulerWidthProperty = DependencyProperty.Register(
    nameof(RulerWidth),
    typeof(int),
    typeof(PixelRuler),
    new PropertyMetadata(null)
);
public int RulerWidth
{
    get => (int)GetValue(RulerWidthProperty);
    set => SetValue(RulerWidthProperty, value);
}

DependencyProperty通常用于UserControl里,这样使用者能够对你的UserControl进行属性的设定。

类似的,添加BackgroundColor属性。

加入命名空间

using Windows.UI;

加入属性

public static readonly DependencyProperty BackgroundColorProperty = DependencyProperty.Register(
    nameof(BackgroundColor),
    typeof(Color),
    typeof(PixelRuler),
    new PropertyMetadata(null)
);
public Color BackgroundColor
{
    get => (Color)GetValue(BackgroundColorProperty);
    set { SetValue(BackgroundColorProperty, value); OnPropertyChanged(); }
}

在构造函数里给它们设置默认值

public PixelRuler()
{
    this.InitializeComponent();
    RulerWidth = 50;
    BackgroundColor = Color.FromArgb(128, 238, 238, 238);
}

现在,修改PixelRuler.xaml,绑定这两个属性

<xaml:CanvasControl x:Name="RulerCanvas" 
                    Height="{x:Bind RulerWidth, Mode=OneWay}"
                    ClearColor="{x:Bind BackgroundColor, Mode=OneWay}"
                    VerticalAlignment="Top" />

PixelRuler用户控件添加到MainPage.xaml里

<local:PixelRuler HorizontalAlignment="Stretch"
                  VerticalAlignment="Top" />

绘制尺

要在CanvasControl上绘制图形,使用Draw事件,处理函数命名为RulerCanvas_OnDraw

<xaml:CanvasControl x:Name="RulerCanvas" 
                    VerticalAlignment="Top" 
                    Height="{x:Bind RulerWidth, Mode=OneWay}"
                    ClearColor="{x:Bind BackgroundColor, Mode=OneWay}"
                    Draw="RulerCanvas_OnDraw" />

为了教程目的,我暂时使用硬编码来写

private void RulerCanvas_OnDraw(CanvasControl sender, CanvasDrawEventArgs args)
{
    var session = args.DrawingSession;
    session.DrawLine(0, 0, 1920, 0, Colors.Black, 1);
    session.DrawLine(0, RulerWidth, 1920, RulerWidth, Colors.Black, 1);
}

方法签名是这样的

public void DrawLine(float x0, float y0, float x1, float y1, Color color, float strokeWidth);

这里,1920是屏幕的宽度,(x0, y0)是线条起点坐标,(x1, y1)是线条终点坐标,0是画布的左上角位置

运行这个工程,你会得到一个带上下边和背景色的空尺子:

现在你了解了如何使用Win2D在CanvasControl上绘制图形并在一个应用页面里使用的过程,让我们来更深入的完成这把尺子。

绘制刻度

一把尺有小刻度和大刻度,我们允许用户自定义刻度的步长。

加入代表这两种刻度的属性

public static readonly DependencyProperty LargeStepsProperty = DependencyProperty.Register(
    nameof(LargeSteps),
    typeof(int),
    typeof(PixelRuler),
    new PropertyMetadata(null)
);
public int LargeSteps
{
    get => (int)GetValue(LargeStepsProperty);
    set => SetValue(LargeStepsProperty, value);
}
public static readonly DependencyProperty SmallStepsProperty = DependencyProperty.Register(
    nameof(SmallSteps),
    typeof(int),
    typeof(PixelRuler),
    new PropertyMetadata(null)
);
public int SmallSteps
{
    get => (int)GetValue(SmallStepsProperty);
    set => SetValue(SmallStepsProperty, value);
}

在构造函数里设置默认值

public PixelRuler()
{
    // …
    LargeSteps = 50;
    SmallSteps = 10;
}

为了教程目的,我依然暂时使用硬编码,现在RulerCanvas_OnDraw 的代码为:

private void RulerCanvas_OnDraw(CanvasControl sender, CanvasDrawEventArgs args)
{
    var session = args.DrawingSession;
    session.DrawLine(0, 0, 1920, 0, Colors.Black, 1);
    session.DrawLine(0, RulerWidth, 1920, RulerWidth, Colors.Black, 1);
    for (int x = 0; x < 1920; x += LargeSteps)
    {
        for (int x1 = x + SmallSteps; x1 < x + LargeSteps; x1 += SmallSteps)
        {
            session.DrawLine(x1, 0, x1, 10, Colors.Black, 1);
        }
        session.DrawLine(x, 0, x, 20, Colors.Black, 1);
    }
}

这里10代表小刻度的长度,20代表大刻度的长度。

运行工程,你能看见一把带有大、小刻度的尺

绘制数值文本

一把尺也需要在大刻度上标明数值,将下面代码添加到RulerCanvas_OnDraw事件处理函数里:

for (int x = 0; x < 1920; x += LargeSteps)
{
    session.DrawText(x.ToString(), x, 30, Colors.Black, new CanvasTextFormat()
    {
        FontSize = (float)FontSize,
        FontFamily = FontFamily.Source,
        HorizontalAlignment = CanvasHorizontalAlignment.Center,
        VerticalAlignment = CanvasVerticalAlignment.Center
    });
}

其中30代表尺的顶端到文本的距离,等于大刻度的长度加10个像素。

FontSizeFontFamily不需要额外创建两个属性,它们继承于UserControl本身,所以用户已经可以控制这两者的值了,例如在MainPage.xaml里:

<local:PixelRuler HorizontalAlignment="Stretch"

VerticalAlignment="Top" FontSize="12" FontFamily="Consolas" />

运行工程,你会得到一个带有刻度和数字的尺

现在你已经完成了像素尺的基本功能,我们来让它更加完善。

更完美的功能

我们的UserControl需要在不同场景下使用,因此我们要让用户能尽可能自定义每一处设置,而不是硬编码进程序里。

例如,关于屏幕宽度,我之前硬编码了1920。我们把它设置为用户屏幕分辨率的大边。获取屏幕分辨率的简单方法可以安装我的UWP助手库获得:

Install-Package Edi.UWP.Helpers

现在你可以把每一处1920都改成largePixel了:

var pixelW = Edi.UWP.Helpers.UI.GetScreenWidth();

var pixelH = Edi.UWP.Helpers.UI.GetScreenHeight();

var largePixel = pixelW > pixelH ? pixelW : pixelH;

session.DrawLine(0, 0, (int)largePixel, 0, Colors.Black, 1);

session.DrawLine(0, RulerWidth, (int)largePixel, RulerWidth, Colors.Black, 1);

我也添加了如下的属性帮助自定义这把尺

  • DrawBorder (是否绘制边框)
  • ScaleMarkPosition (刻度位置:左、右、两者都要)
  • ScaleMarkLength (刻度长度)
  • ScaleMarkColor (刻度颜色)
  • TextColor (数字颜色)
  • TextMargin (数字边距)

你可以深度自定义像素尺

<local:PixelRuler HorizontalAlignment="Stretch"
                  VerticalAlignment="Top"
                  RulerWidth="80"
                  LargeSteps="50"
                  SmallSteps="10"
                  ScaleMarkLength="20"
                  FontSize="12"
                  ScaleMarkPosition="Both"
                  ScaleMarkColor="#CCC"
                  TextColor="#CCC"
                  FontFamily="Segoe UI"
                  TextMargin="10"
                  BackgroundColor="#333" 
                  DrawBorder="True"
                  Height="80" />

完整的案例代码在我的GitHub上:

https://github.com/EdiWang/PixelRulerUwp

你也可以在微软应用商店找到样例APP:

https://www.microsoft.com/en-us/p/pixelruleruwp-demo/9nblggh5fjhn

本文分享自微信公众号 - 汪宇杰博客(ediwangblog)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-11-05

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • [设计模式] 责任链模式

    使多个对象都有机会处理请求,从而避免请求的发送者与接受者之间的耦合关系. 将多个接受者连成一条链,沿着该链处理请求,直到请求被处理为止.

    呼延十
  • JSON的简单认识

    JSON(JavaScript Object Notation):JavaScript 对象表示法。其是一种轻量级的数据交换格式,简洁和清晰的层次结构使得其成为...

    正念君
  • Linux Command(二)

    命令格式: more [-dlfpcsu ] [-num ] [+/ pattern] [+ linenum] [file ... ]

    呼延十
  • 字符串查找(kmp)

    对于一个给定的 source 字符串和一个 target 字符串,你应该在 source 字符串中找出 target 字符串出现的第一个位置(从0开始)。如果不...

    呼延十
  • [设计模式] 模板方法模式

    模板方法模式比较简单,或者说比较常用.在开发过程中,许多人在不知不觉的情况下就会使用,只要他具有良好的面对对象思维.

    呼延十
  • 【C语言笔记】ASCII码可见字符与不可见字符

    ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)是基于拉丁字母的一套电脑...

    正念君
  • String Stringbuilder Stringbuffer异同

    字符串在编程中使用的非常频繁,同时又是面试中的常见题型,那么我们的对字符串相关类String,StringBuilder,StringBuffer的理解真的正确...

    呼延十
  • 【C语言笔记】带参宏定义(二)

    其中参数列表中的参数之间用逗号分隔,字符序列中应包含参数表中的参数。在定义带参数的宏时,宏名标识符与左圆括号之间不允许有空白符,应紧接在一起,否则变成了无参数的...

    正念君
  • 【RT-Thread笔记】裸机系统与多线程系统

    轮询系统即是在裸机编程的时候,先初始化好相关的硬件,然后让主程序在一个死循环里面不断循环,顺序地做各种事情,大概的伪代码具体如代码清单 1-1所示:

    正念君
  • 【C语言笔记】时间日期函数

    time.h是C/C++中的日期和时间头文件。用于需要时间方面的函数。下面分享time.h头文件中几个常用函数的用法:

    正念君

扫码关注云+社区

领取腾讯云代金券