为 X11 和 Windows 编写 XAML 7 段 LCD 显示器 UserControl





5.00/5 (4投票s)
目前,没有哪个主要的 Linux/Unix (X11) GUI 应用程序框架(GTK+、KDE)支持基于 XAML 的应用程序开发。Moonlight 项目(包括 XAML 支持)已于 2012 年 5 月 29 日被放弃。本文介绍了一个基于 XAML 的 7 段 LCD 显示器,它利用了一个 WPF UserControl。
下载 XamlSevenSegment_X11_32.zip Mono 解决方案,包含完整的源代码和可执行文件
下载 XamlSevenSegment_X11_64.zip Mono 解决方案,包含完整的源代码和可执行文件
下载 XamlSevenSegment_Win8.zip Visual Studio 2013 解决方案,包含完整的源代码和可执行文件
引言
本文是一个案例研究,介绍如何使用 **Roma Widget Set** (Xrw) 编写一个基于 MVVM (Model View ViewModel) 设计模式的 X11/Windows(跨平台)7 段 LCD 显示器(利用 WPF UserControls)并使用 XAML。 **Roma Widget Set** 是一个零依赖的 X11 GUI 应用程序框架(它只需要免费的 Mono 标准安装程序集和免费的 X11 发行版库;它不特别需要 GNOME、KDE、cairo、pango 或商业库),并且完全用 C# 实现。
本文是接续之前的文章 《为 X11 编写 XAML 对话框应用程序》、《为 X11 编写 XAML 应用程序》、《为 X11 编写 XAML 功能区应用程序》、《使用大量数据绑定和零代码为 X11 编写 XAML 应用程序》、《为 X11 编写 XAML 计算器应用程序》、《编写带有几何对象(形状)的 XAML 应用程序以用于 X11》 和 《使用 UserControls 为 X11 编写 XAML 应用程序》 等作品。据我所知,这是在 Moonlight 被放弃后,利用 Xrw 进行 X11 应用程序开发的首次尝试。
Roma Widget Set 和 XAML 实现都未完成。这个示例应用程序旨在作为另一个“概念验证”,并检查是否以及如何能够使用 XAML 为 X11/Windows(跨平台)应用程序创建基于 MVVM 设计模式的应用程序。
由于这是第七次尝试使用 XAML 进行 X11 应用程序开发,并且已经成功,因此关于使用 Roma Widget Set 在 X11 上进行 XAML 的后续文章肯定会接踵而至。
背景
在 《为 X11 编写 XAML 对话框应用程序》 文章中已经解释了使用 XAML 进行 X11 应用程序开发的 **动机** 和总体 **概念**。
XAML 解释器已为 Xrw 版本 0.9 进行了几乎完全的重构,以便更通用地工作。XAML 预处理器现在可以处理项目子文件夹。这两个更改都使使用 WPF UserControls 更加舒适。
焦点
由于之前的六篇文章《编写 XAML...》已经演示了多种 MVVM 技术,因此本文将演示使用 XAML 为 X11
- 可以轻松创建兼容 Windows 的、外观漂亮的 7 段 LCD 显示器 UserControls。
使用代码
UserControl 和示例应用程序是用 Mono Develop 2.4.1 为 Mono 2.8.1 在 OPEN SUSE 11.3 Linux 32 位 EN 和 GNOME 桌面上编写的。移植到任何旧版本或新版本都不应该存在问题。示例应用程序的解决方案是用 MONO/.NET 3.5 构建的。它包含两个项目(提供完整的源代码供下载)
- **XamlSevenSegment** 包含 UserControl 和示例应用程序的源代码。
- XamlPreprocessor 包含 XAML 预处理器的源代码。
UserControl 和示例应用程序也已在 OPEN SUSE 12.3 Linux 64 位 DE 和 GNOME 桌面、IceWM、TWM 和 Xfce 上使用 Mono Develop 3.0.6 为 Mono 3.0.4 进行了测试。
32 位和 64 位解决方案之间唯一的区别是某些 X11 特定数据类型的定义,这已在 《使用 Mono Develop 编写 Xlib - 第一部分:底层(概念验证)》 文章中有所描述。
Xlib/X11 窗口处理基于 **X11Wrapper** 程序集版本 0.9(此解决方案中包含一个版本 0.9 的预览版库),它为 libX11.so 的 Xlib/X11 调用定义了函数原型、结构和类型。该程序集是为 《使用 Mono Develop 编写 Xlib - 第一部分:底层(概念验证)》 项目开发的,并在 《编程 Roma Widget Set (C# X11) - 一个零依赖的 GUI 应用程序框架 - 基础》 项目中得到了改进。
GUI 框架基于 **Xrw** 程序集版本 0.9(此解决方案中包含一个版本 0.9 的预览版库),它定义了 XAML 代码中使用的控件/小工具及其包装类(这些代码应尽可能接近 Microsoft® 的原始代码)。该程序集是在 《编程 Roma Widget Set (C# X11) - 一个零依赖的 GUI 应用程序框架 - 基础》 项目中开发的。
建议:要使用 MonoDevelop 的类库文档快捷键 (F1),必须安装“mono-tools”软件包。
示例应用程序的灵感来自 Liu Xia 的精彩文章 《一款外观精美的分段 LED 控件》 (这篇文章非常好且详细,控件提供了很高的灵活性,但使用了 Windows.Forms)和 Mohammad Dayyan 的文章 《WPF 中的七段 LCD 控件》。
示例应用程序的图片显示了一个可调整大小的数字时钟,带有自动缩放的 7 段 LCD 显示屏。
第一张图片显示了示例应用程序在 OPEN SUSE 11.3 Linux 32 位 EN 和 GNOME 桌面上的样子。
第二张图片显示了示例应用程序在 OPEN SUSE 12.3 Linux 64 位 DE 和 Xfce 上的样子。
第三张图片显示了示例应用程序在 Windows® 8.1 64 位版本上的运行效果。
应用程序窗口使用 System.Windows.Controls.Grid
作为其根布局管理器。它实现了六个 System.Windows.ExtControls.SimpleSevenSegment
UserControls 和两个 System.Windows.ExtControls.ColumnForSevenSegment
UserControls 的边距和位置。所有 UserControls 都能自动调整大小。一个 System.Windows.Forms.Timer
每 50 毫秒更新一次时钟。
X11 和 Windows 8.1 版本的示例应用程序在功能上没有任何区别,它们完全共享相同的代码。
逐步演示
主视图文件上下文
XAML (MainWindow.xaml)
这是带有对八个用户控件(6 个 SimpleSevenSegment
和 2 个 ColumnForSevenSegment
)引用的 XAML 文件。
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ext="clr-namespace:System.Windows.ExtControls"
x:Class="XamlSevenSegment.MainWindow"
Name="DigiClock" Title="Digital clock"
Height="208" Width="780" Icon="XrwIcon16.bmp"
Background="#FFDDDDDD">
<Grid Name="gridDigiClock" Background="#FFDDDDDD" Grid.Row="1" Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="10*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="3*"/>
<ColumnDefinition Width="3*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="3*"/>
<ColumnDefinition Width="3*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="3*"/>
<ColumnDefinition Width="3*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ext:SimpleSevenSegment x:Name="TenHours" Grid.Row="1" Grid.Column="1" DisplayCharacter="0"
SegmentStroke="#FFAADDAA" SegmentFillOn="#FF22CC22"
SegmentFillOff="#FFD8E1D8"/>
<ext:SimpleSevenSegment x:Name="SingleHours" Grid.Row="1" Grid.Column="2" DisplayCharacter="0"
SegmentStroke="#FFAADDAA" SegmentFillOn="#FF22CC22"
SegmentFillOff="#FFD8E1D8"/>
<ext:ColumnForSevenSegment x:Name="HoursMinutesDelimiter" Grid.Row="1" Grid.Column="3"
Background="#FFDDDDDD" />
<ext:SimpleSevenSegment x:Name="TenMinutes" Grid.Row="1" Grid.Column="4" DisplayCharacter="0"
SegmentStroke="#FFAADDAA" SegmentFillOn="#FF22CC22"
SegmentFillOff="#FFD8E1D8"/>
<ext:SimpleSevenSegment x:Name="SingleMinutes" Grid.Row="1" Grid.Column="5" DisplayCharacter="0"
SegmentStroke="#FFAADDAA" SegmentFillOn="#FF22CC22"
SegmentFillOff="#FFD8E1D8"/>
<ext:ColumnForSevenSegment x:Name="MinutesSecondsDelimiter" Grid.Row="1" Grid.Column="6"
Background="#FFDDDDDD" />
<ext:SimpleSevenSegment x:Name="TenSeconds" Grid.Row="1" Grid.Column="7" DisplayCharacter="0"
SegmentStroke="#FFAADDAA" SegmentFillOn="#FF22CC22"
SegmentFillOff="#FFD8E1D8"/>
<ext:SimpleSevenSegment x:Name="SingleSeconds" Grid.Row="1" Grid.Column="8" DisplayCharacter="0"
SegmentStroke="#FFAADDAA" SegmentFillOn="#FF22CC22"
SegmentFillOff="#FFD8E1D8"/>
</Grid>
</Window>
完整的 XAML 代码与 Microsoft® 完全兼容。
与 《使用 UserControls 为 X11 编写 XAML 应用程序》 及之前的文章相比,没有新的改进或区别需要讨论。
后端代码 (MainWindow.xaml.cs)
主视图对应的 C# 代码文件是 MainWindow.xaml.cs
,内容如下:
using System;
using System.Windows;
using System.Windows.Controls;
using X11;
using Xrw;
using XrwXAML;
namespace XamlSevenSegment
{
/// <summary>Interaction logic for MainWindow.xaml</summary>
public partial class MainWindow : XrwXAML.Window
{
public MainWindow()
: base (-1, -1)
{
InitializeComponent();
System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer();
timer.Interval = 50;
timer.Tick += timer_Tick;
timer.Start ();
}
void timer_Tick(object sender, EventArgs e)
{
DateTime now = DateTime.Now;
int hrs = now.Hour;
int mns = now.Minute;
int scs = now.Second;
TenHours.DisplayCharacter = (char)('0' + (hrs / 10));
SingleHours.DisplayCharacter = (char)('0' + (hrs % 10));
TenMinutes.DisplayCharacter = (char)('0' + (mns / 10));
SingleMinutes.DisplayCharacter = (char)('0' + (mns % 10));
TenSeconds.DisplayCharacter = (char)('0' + (scs / 10));
SingleSeconds.DisplayCharacter = (char)('0' + (scs % 10));
gridDigiClock.InvalidateVisual ();
}
}
}
主视图模型文件上下文
主视图没有 ModelView,因为不需要 Model。
主模型文件上下文
主视图没有 Model,因为没有数据需要处理。
SimpleSevenSegment UserControl
XAML (SimpleSevenSegment.xaml)
UserControl 的 XAML 文件是:
<UserControl x:Class="System.Windows.ExtControls.SimpleSevenSegment"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:my="clr-namespace:System.Windows.ExtControls">
<Canvas Name="canvasSimpleSevenSegment" Background="#FFDDDDDD" Grid.Row="0" Grid.Column="0"
Clip="{Binding ElementName=canvasSimpleSevenSegment, Path=DefiningGeometry}"/>
</UserControl>
UserControl 只包含一个 System.Windows.Controls.Canvas
。所有七段形状都由代码隐藏动态创建。
代码隐藏 (SimpleSevenSegment.xaml.cs)
UserControl 对应的 C# 代码文件是 SimpleSevenSegment.xaml.cs
。它包含几个实用方法:
CreateSegments()
用于创建显示在System.Windows.Controls.Canvas
顶部的七个形状(System.Windows.Shapes.Polygon
)。UpdateSegmentGeometry()
根据父System.Windows.Controls.Canvas
的大小动态重新计算七个形状的大小。UpdateSegmentCharacter()
根据由DisplayCharacter
控件属性设置的当前要显示的字符来配置七个形状的开/关标志。UpdateSegmentColors()
根据当前- 基于要显示的字符设置的开/关标志(由
DisplayCharacter
控件属性设置),以及 - 由
SegmentStroke
、SegmentFillOn
和SegmentFillOff
控件属性设置的颜色,来配置七个形状的描边颜色和填充颜色。
- 基于要显示的字符设置的开/关标志(由
UpdateSegmentColors()
方法始终调用 UpdateSegmentCharacter()
,以确保七个形状的开/关标志是最新的。
SimpleSevenSegment
UserControl 支持的依赖属性是:
/// <summary>Identify the Mines.SimpleSevenSegment.SegmentStroke dependency property.</summary>
/// <returns>The identifier for the Mines.SimpleSevenSegment.SegmentStroke dependency property.</returns>
public static readonly DependencyProperty SegmentStrokeProperty;
/// <summary>Identify the Mines.SimpleSevenSegment.SegmentFillOn dependency property.</summary>
/// <returns>The identifier for the Mines.SimpleSevenSegment.SegmentFillOn dependency property.</returns>
public static readonly DependencyProperty SegmentFillOnProperty;
/// <summary>Identify the Mines.SimpleSevenSegment.SegmentFillOff dependency property.</summary>
/// <returns>The identifier for the Mines.SimpleSevenSegment.SegmentFillOff dependency property.</returns>
public static readonly DependencyProperty SegmentFillOffProperty;
/// <summary>Identify the Mines.SimpleSevenSegment.DisplayCharacter dependency property.</summary>
/// <returns>The identifier for the Mines.SimpleSevenSegment.DisplayCharacter dependency property.</returns>
public static readonly DependencyProperty DisplayCharacterProperty;
相应的属性实现如下:
/// <summary>The segment's outline stroke color.</summary>
[Browsable(true)]
[Bindable(true)]
[Category("Appearence")]
[Description("The segment's outline stroke color.")]
public System.Windows.Media.Color SegmentStroke
{
get
{
return ((System.Windows.Media.Color)GetValue(SegmentStrokeProperty));
}
set
{
SetValue(SegmentStrokeProperty, value);
}
}
/// <summary>The segment's fill color.</summary>
[Browsable(true)]
[Bindable(true)]
[Category("Appearence")]
[Description("The segment's fill color for on segments.")]
public System.Windows.Media.Color SegmentFillOn
{
get
{
return ((System.Windows.Media.Color)GetValue(SegmentFillOnProperty));
}
set
{
SetValue(SegmentFillOnProperty, value);
}
}
/// <summary>The segment's fill color.</summary>
[Browsable(true)]
[Bindable(true)]
[Category("Appearence")]
[Description("The segment's fill color for off segments.")]
public System.Windows.Media.Color SegmentFillOff
{
get
{
return ((System.Windows.Media.Color)GetValue(SegmentFillOffProperty));
}
set
{
SetValue(SegmentFillOffProperty, value);
}
}
/// <summary>The segment's character to display.</summary>
[Browsable(true)]
[Bindable(true)]
[Category("Appearence")]
[Description("The character to display.")]
public System.Char DisplayCharacter
{
get
{
return ((System.Char)GetValue(DisplayCharacterProperty));
}
set
{
SetValue(DisplayCharacterProperty, value);
}
}
ColumnForSevenSegment UserControl
XAML (ColumnForSevenSegment.xaml)
UserControl 的 XAML 文件是:
<UserControl x:Class="System.Windows.ExtControls.ColumnForSevenSegment"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="30">
<Grid Name="gridColumnForSevenSegment">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="10*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="3*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="2*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="3*"/>
</Grid.RowDefinitions>
<Viewbox Name="viewboxUpperDot" Grid.Row="1" Grid.Column="1" >
<Ellipse Fill="#FF22CC22" Stroke="#FF22CC22" />
</Viewbox>
<Viewbox Name="viewboxLowerDot" Grid.Row="3" Grid.Column="1" >
<Ellipse Fill="#FF22CC22" Stroke="#FF22CC22"/>
</Viewbox>
</Grid>
</UserControl>
UserControl 包含一个 System.Windows.Controls.Grid
作为其布局管理器。两个 System.Windows.Controls.ViewBox
控件实现了两个椭圆(x 半径 == y 半径)的二次大小/缩放。两个 System.Windows.Shapes.Ellipse
形状实现了列点。
代码隐藏 (ColumnForSevenSegment.xaml.cs)
UserControl 对应的 C# 代码文件是 ColumnForSevenSegment.xaml.cs
,它尽可能地精简。
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Shapes;
namespace System.Windows.ExtControls
{
/// <summary>
/// Interaction logic for ColumnForSevenSegment.xaml
/// </summary>
public partial class ColumnForSevenSegment : UserControl
{
public ColumnForSevenSegment()
{
InitializeComponent();
}
}
}
关注点
这是另一个用于 X11 的 XAML 应用程序,完全兼容 Microsoft®,展示了这种方法的 M M M (compared to an implementation with GTK+ or KDE):100% 跨平台兼容的 GUI 定义以及在创建 GUI 时节省的代码行数。
使用用户控件可以将复杂的 GUI 分解为更简单、更易于维护的块,节省代码重复,并且可以应用于 X11 和 Windows(跨平台)。
历史
本文的第一个版本发布于 2015 年 9 月 30 日。