65.9K
CodeProject 正在变化。 阅读更多。
Home

WPF 中的二进制时钟 3D 屏幕保护程序

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.88/5 (18投票s)

2008 年 12 月 22 日

CPOL

6分钟阅读

viewsIcon

72094

downloadIcon

2184

3D 二进制时钟屏保 - 展示 WPF 的布局可能性、样式/模板化和数据绑定

BinaryClock_Overview.png

目录

引言

虽然您可能在网上找到许多二进制时钟项目,但本文将不仅向您展示如何创建二进制时钟,还将展示如何将其托管在太空中并用作 3D 旋转屏保 - 所有这些都利用了 Windows Presentation Foundation 及其强大的概念,如数据绑定、数据模板、样式、自动布局、动画和 3D 支持。

这个二进制时钟项目最初只是我为了帮助自己理解上述 WPF 概念而进行的一次实验。因此,您在这里看到的也是我 WPF 学习过程的一部分,我认为这对于该技术和其他爱好者也可能很有用。

背景

也许您已经知道 二进制时钟 的概念:它是一种时钟,您将当前时间的 6 位数字读取为 6 列,每列由一些点亮的和未点亮的 LED 组成。实际上,我们最好称之为伪二进制时钟,因为它以 BCD(二进制编码的十进制)表示法显示数字。如果您喜欢这个概念,但还没有在您的桌面上拥有它,那么至少可以通过下载附件中的二进制文件将其作为您的屏保。

核心 - 二进制时钟

首先,二进制时钟有一个简单的任务:逐一显示其六位数字。其次,数字本身甚至不需要了解它们的视觉表示,因此,要表示一个 BCD 数字,只需有一个纯数据类就足够了。显示数据类的想法是应用一个 DataTemplate。这样,我们就可以简单地将 Digit 类的六个实例放入我们窗口的某个项目容器中,并将其余的外观工作留给我们的 DataTemplate 来完成。

这看起来是该模板的基本结构

<DataTemplate DataType="{x:Type local:Digit}">

    <DataTemplate.Resources>
        ...
    </DataTemplate.Resources>

    <!-- BCD Digits consist of 4 bits -->
    <StackPanel Orientation="Vertical">

        <Rectangle Style="{StaticResource fourthBit}" />
        <Rectangle Style="{StaticResource thirdBit}" />
        <Rectangle Style="{StaticResource secondBit}" />
        <Rectangle Style="{StaticResource firstBit}" />

    </StackPanel>
</DataTemplate> 

每个 BCD 数字包含四个二进制数字/位。它们堆叠在一起,最低有效位位于底部,最高有效位位于顶部。

应用于特定位的样式会告诉该位在特定时间是否需要点亮或熄灭。例如,最低有效位需要点亮,如果 BCD 数字的模 2 余数非零(也就是说,每隔一秒)。同样,最低有效位上方的位需要点亮,如果 BCD 数字的模 4 余数大于等于 2,依此类推。这个逻辑可以通过使用 WPF 值转换器来概括,该转换器内部包含一些简单的模运算。DigitModuloConverter 类将告诉我们任何整数值的指定模数是否大于提供的参数。

class DigitModuloConverter : DependencyObject, IValueConverter
{
    public static DependencyProperty ModuloModeProperty = ...
    public int ModuloMode { ... }
    public object Convert(object value, Type targetType, object parameter,
                          System.Globalization.CultureInfo culture)
    {
        int source = (int)value;
        int param = 0;
        Int32.TryParse(parameter as string, out param);
        return (source % this.ModuloMode > param);
    }
    ...
}

有了这个转换器,我们就可以直接将 BCD 数字的值转换为其位的开/关状态。这是通过 WPF 的数据触发器实现的:每当指定的 DigitModuloConverter 告知时,每个位将使用高亮填充和高亮描边颜色。否则,它将保持或切换回基本的暗色。

<DataTemplate.Resources>

    <!-- Digit converters -->    
    <local:DigitModuloConverter x:Key="moduloTwoConverter" ModuloMode="2" />
    <local:DigitModuloConverter x:Key="moduloFourConverter" ModuloMode="4" />
    <local:DigitModuloConverter x:Key="moduloEightConverter" ModuloMode="8" />
    <local:DigitModuloConverter x:Key="moduloSixteenConverter" ModuloMode="16" />

        ...
    <!-- Appearance of 1st bit in Digit (the least significant one) -->
    <Style TargetType="Rectangle" x:Key="firstBit"
                                  BasedOn="{StaticResource bitsCommonStyle}">
        <Style.Triggers>

            <DataTrigger Binding="{Binding Path=Value,
                                   Converter={StaticResource moduloTwoConverter}}"
                                   Value="True">
                <Setter Property="Stroke" 
			Value="{DynamicResource fullRectBorderBrush}" />

                <Setter Property="Fill" Value="{DynamicResource fullRectBrush}" />
            </DataTrigger>
        </Style.Triggers>
    </Style>

    ...
</DataTemplate.Resources>

设置好所有这些之后,只需要一个定时器即可每秒更新我们的 BCD 数字值。BCD 数字 - 即 Digit 类的实例 - 在 XAML 中声明,并在 setTimeToDigits() 函数中进行更新。

private void setTimeToDigits()
{
    System.DateTime now = System.DateTime.Now;

    this.hourUpper.Value = (int)now.Hour / 10;
    this.hourLower.Value = now.Hour % 10;

    this.minUpper.Value = (int)now.Minute / 10;
    this.minLower.Value = now.Minute % 10;

    this.secUpper.Value = (int)now.Second / 10;
    this.secLower.Value = now.Second % 10;
}

Digit 实例需要正确设置其 IsUpperDigitIsHourDigit 属性。位的样式会读取这些属性并根据它们隐藏一些不必要的位。例如,“小时”的“十位”BCD 数字将隐藏最高两位,因为一天只有 24 小时。同样,“分钟”的“十位”BCD 数字将隐藏最高有效位,因为即使显示 59 分钟,我们只需要 3 位来表示“十位”。

<StackPanel ...>
    <StackPanel ...>
        <ContentControl>
            <local:Digit x:Name="hourUpper" IsUpperDigit="True" IsHourDigit="True"/>

        </ContentControl>

        <ContentControl>
            <local:Digit x:Name="hourLower" IsHourDigit="True" />
        </ContentControl>

    </StackPanel>
    <StackPanel ...>
        <ContentControl>

            <local:Digit x:Name="minUpper" IsUpperDigit="True" />

        </ContentControl>
        <ContentControl>
            <local:Digit x:Name="minLower" />
        </ContentControl>
    </StackPanel>

    <StackPanel ...>
        <ContentControl>
            <local:Digit x:Name="secUpper" IsUpperDigit="True" />
        </ContentControl>

        <ContentControl>

            <local:Digit x:Name="secLower" />
        </ContentControl>
    </StackPanel>
</StackPanel>

3D 屏保和带实时预览的配置

Windows 屏保基本上需要一个接受一些定义良好的命令行参数的 *.exe 模块。屏保主要需要显示一个全屏窗口,但也应该支持预览模式并提供一个可选的设置窗口进行自定义。

本项目更进一步:它不仅支持简单的预览模式,还在设置窗口中提供了实时预览,以便交互式地显示每个更改将如何影响输出。

BinaryClock_Settings_Video.png 

点击观看演示实时预览的视频。

这可以通过重用 BinaryClockPage 类来实现,该类提供了二进制时钟的核心功能(请参阅上一节)。

时钟的 3D 效果是通过将 Digit 类实例添加到 ViewPort3D 对象来实现的。这样,我们将 2D Rectangles 投影到空间中的 3D 曲面上。

<Viewport3D>

    ...
    <!-- 2D elements on 3D surface -->

    <Viewport2DVisual3D>
        <!-- Give the plane a slight rotation -->
        <Viewport2DVisual3D.Transform>
            <RotateTransform3D>
                <RotateTransform3D.Rotation>

                    <AxisAngleRotation3D x:Name="planeRotation"
                                         Angle="0"
                                         Axis="0, -1, -0.17" />

                </RotateTransform3D.Rotation>

            </RotateTransform3D>
        </Viewport2DVisual3D.Transform>

        <!-- The Geometry, Material, and Visual for the Viewport2DVisual3D -->
        <Viewport2DVisual3D.Geometry>

            <MeshGeometry3D ... />
        </Viewport2DVisual3D.Geometry>

        <Viewport2DVisual3D.Material>
            <DiffuseMaterial Viewport2DVisual3D.IsVisualHostMaterial="True"
                             Brush="White"/>
        </Viewport2DVisual3D.Material>
        ...
    </Viewport2DVisual3D>

    ...
</Viewport3D>

二进制时钟绕垂直轴进行旋转动画。可以通过设置窗口中的简单滑块调整此旋转速度。另一个滑块用于设置相机距离,从而使二进制时钟看起来更近或更远。

每次移动旋转速度滑块时,都需要重新启动旋转动画以应用新的动画持续时间。

多显示器支持

屏保将以非常简单的方式处理您系统中的多个显示器:它将在每个显示器上复制相同的内容。如果您注意到这些不同实例中的时钟并不总是同步,那么是的,您是完全正确的。这种视觉上的副作用是由于每个时钟实例都使用自己的计时器,该计时器仅在创建前一个实例后启动。这个问题将来可能会在文章的更新中得到解决。

皮肤支持

BinaryClock 屏保的皮肤化利用了 WPF 的 ResourceDictionary 概念。这允许我们在运行时更改皮肤。每个“皮肤”都放置在一个单独的 XAML 文件中,其中包含一个 ResourceDictionary,因此我们可以在运行时切换使用其中任何一个 XAML 文件。每个 ResourceDictionary 定义了相同的画笔对象,但颜色设置不同。因此,通过在运行时加载不同的 ResourceDictionary,我们的二进制时钟颜色将自动切换到新皮肤(这对我们来说是完全透明的,因为我们将这些画笔引用为 DynamicResource 对象)。

定义“蓝色”皮肤的 XAML 文件代码片段

<ResourceDictionary ...>
    <!-- Unlit LED border color -->
    <SolidColorBrush x:Key="emptyRectBorderBrush" Color="#FF00087F" />
	...
</ResourceDictionary>

BinaryClock 如何使用皮肤元素

<!-- Basic appearance of all Digits -->
<Style TargetType="Rectangle" x:Key="bitsCommonStyle">
	<Setter Property="Stroke" Value="{DynamicResource emptyRectBorderBrush}" />
	...
</Style>

未来的增强

如果您喜欢这个屏保并对改进有一些建议,请随时告知我,因为我仍然计划在未来添加一些新功能。这些功能可能包括用于调整 3D 效果的新设置、显示可读十进制提示的可能性,以及错误修复等。

历史 

  • 2009 年 1 月 18 日:皮肤支持
  • 2008 年 12 月 23 日:根据读者评论,对多显示器系统的小错误修复
  • 2008 年 12 月 22 日:初始修订
© . All rights reserved.