C# 和 WPF 中的二进制时钟






4.93/5 (16投票s)
在本文中,我们将使用 C# 和 WPF 创建一个简单的二进制时钟。该项目本身将用于展示一些特殊之处,例如 Task 的使用、如何操作 WPF 页面的 UI 以及基本的数据转换。
前言
在本文中,我们将使用 C# 和 WPF 创建一个简单的二进制时钟。该项目本身将用于展示一些特殊之处,例如 Task 的使用、如何操作 WPF 页面的 UI 以及基本的数据转换。
引言
二进制时钟是一种以二进制格式显示当前时间的时钟。在下面的示例中,我们将创建一组图形 LED,每个 LED 代表一个二进制数字。每个 LED 可以有两种状态:亮(表示值为 1)或灭(表示值为零)。从右到左,我们的 LED 将代表值 1、2、4、8、16、32,因为我们将基于 24 小时格式的时间进行转换,并且需要足够多的位数来表示最大值为 60(用于分钟和秒)。
当前时间的每个部分(小时、分钟、秒)都将有自己的六个 LED 行,以表示十进制值的二进制转换。例如,如果我们想显示 10:33:42 的时间,我们的 LED 必须按照以下模式点亮:
XAML 中的二进制时钟
上述概念的 XAML 渲染相当简单。在一个新的 XAML 页面中,我们需要创建三行矩形,其边框半径将设置为 50 以赋予它们圆形形状。其他设置将涉及填充颜色、形状阴影等,以按照我们的期望绘制 LED。在我们的示例中,LED 将根据以下 XAML 代码进行阴影处理和着色:
<Rectangle HorizontalAlignment="Left" Height="35" Margin="211,40,0,0"
Stroke="#FF033805" VerticalAlignment="Top"
Width="38" RadiusX="50" RadiusY="50">
<Rectangle.Effect>
<DropShadowEffect BlurRadius="10" ShadowDepth="10"/>
</Rectangle.Effect>
<Rectangle.Fill>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FFFFFF1B" Offset="0"/>
<GradientStop Color="#FF29B413" Offset="0.568"/>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
完成 UI 的每一行创建并美化完所有元素后,XAML 页面将如下所示:
您可以参考文章末尾提供的下载部分,获取上述代码片段的完整参考。
源代码
下一节将解释如何控制我们的矩形以显示当前时间的二进制表示。
在我们的 XAML 窗口中,我们声明了一个事件调用 - 更准确地说,一个必须在页面加载时触发的事件(Loaded Event)。在 `Window_Loaded` 例程的代码隐藏中,我们执行两项主要操作:第一项纯粹是图形操作,即设置每个矩形的透明度为 `0.35`,以给人一种 LED 关闭的印象。第二项是执行将进行计算并更新 UI 的任务。稍后将详细介绍。首先,让我们看看如何识别 XAML 页面上声明的控件。
让我们看看将所有矩形透明度设置为 `0.35` 的循环:
// Sets all rectangles opacity to 0.35
foreach (var r in LogicalTreeHelper.GetChildren(MainGrid))
{
if (r is Rectangle) (r as Rectangle).Fill.Opacity = 0.35;
}
说到识别控件,WinForms 和 WPF 之间的主要区别在于我们无法通过 `Controls()` 属性来引用容器的控件。WPF 处理此类操作的方式是通过 `LogicalTreeHelper` 类。通过它,我们可以调用 `GetChildren` 方法,指定要检索子控件的主控件的名称。在我们的例子中,我们在 `MainGrid` 控件(标识我们 XAML 页面 `Grid` 对象的名称)上执行了 `LogicalTreeHelper.GetChildren`。然后,在遍历控件数组时,我们检查该特定控件是否为 `Rectangle`,如果是,则继续设置其不透明度为所需值。
`Window_Loaded` 事件中的第二组指令是执行一个辅助任务,用于计算每个时间部分的二进制表示,并更新 UI。代码如下:
Task.Factory.StartNew(() =>
{
// while the thread is running...
while (true)
{
// ...get the current system time
DateTime _now = System.DateTime.Now;
// Convert each part of the system time (i.e.: hour, minutes, seconds) to
// binary, filling with 0s up to a length of 6 char each
String _binHour = Convert.ToString(_now.Hour, 2).PadLeft(6, '0');
String _binMinute = Convert.ToString(_now.Minute, 2).PadLeft(6, '0');
String _binSeconds = Convert.ToString(_now.Second, 2).PadLeft(6, '0');
// For each digit of the binary hour representation
for (int i = 0; i <= _binHour.Length - 1; i++)
{
// Dispatcher invoke to refresh the UI, which belongs to the main thread
H0.Dispatcher.Invoke(() =>
{
// Update the contents of the labels which use decimal h/m/s representation
lbHour.Content = _now.Hour.ToString("00");
lbMinute.Content = _now.Minute.ToString("00");
lbSeconds.Content = _now.Second.ToString("00");
// Search for a rectangle which name corresponds to the _binHour current char index.
// Then, set its opacity to 1 if the current _binHour digit is 1, or to 0.35 otherwise
(MainGrid.FindName("H" + i.ToString()) as Rectangle).Fill.Opacity =
_binHour.Substring(i, 1).CompareTo("1") == 0 ? 1 : 0.35;
(MainGrid.FindName("M" + i.ToString()) as Rectangle).Fill.Opacity =
_binMinute.Substring(i, 1).CompareTo("1") == 0 ? 1 : 0.35;
(MainGrid.FindName("S" + i.ToString()) as Rectangle).Fill.Opacity =
_binSeconds.Substring(i, 1).CompareTo("1") == 0 ? 1 : 0.35;
});
}
}
});
这段代码非常直观,任务包含一个永无止境的循环,该循环不断检索当前系统时间。然后,将其分解为三个主要部分(小时、分钟、秒),并使用 `Convert.ToString()` 函数将其转换为二进制表示,我们将在此函数中传递转换的数字基数(在本例中为 `2`)。由于我们需要三个长度为六的 `string`(每行有六个 LED),因此我们需要将每个 `string` 填充到六个字符。因此,例如,如果我们转换 5 的值,该函数将产生 101 作为输出 - 我们将其填充到 000101。
第二个循环工作到与小时相关的二进制 `string` 的长度(值永远是六),它将使用 `Dispatcher` 属性来调用在另一个线程上运行的对象的 `update` 方法来提供 UI 更新(有关 `Invoke` 方法的更多详细信息,请参考“VB.NET:从辅助线程调用方法更新 UI”)。对于我们 `string` 中的每个数字,我们需要识别正确的 `Rectangle` 来更新其不透明度值。
我们可以通过 `FindName()` 函数来完成这类任务:给定一个父对象(在本例中为 `MainGrid`),如果传入的参数对应于一个存在的控件名称,`FindName` 将引用该 UI 控件。由于我们为每个时间部分的矩形命名了一个递增的数字(从 0 到 5,以 H 代表小时,M 代表分钟,S 代表秒),因此我们可以要求该函数检索名称以特定字母开头并后跟当前二进制字符串索引的索引的控件。
为了清晰起见,让我们看其中一行:使用以下行:
(MainGrid.FindName("H" + i.ToString()) as Rectangle).Fill.Opacity =
_binHour.Substring(i, 1).CompareTo("1") == 0 ? 1 : 0.35;
我们要求:从 `MainGrid` 中检索一个名称等于“H”+当前循环索引的控件。将其视为一个 `Rectangle`,然后根据以下规则设置其 `Opacity`:如果二进制 `string` 中索引的字符是 1,则 `Opacity` 必须为 1,否则必须设置为 0.35。执行程序将产生如下视频中所示的结果。
演示视频
下载
本文示例的完整源代码可以在 https://code.msdn.microsoft.com/Binary-Clock-in-C-and-WPF-f954c9a5 下载。