Farsi 库 FX






4.78/5 (35投票s)
WPF 自定义控件,可使用特定文化日历中的日期,支持波斯语(赫吉拉 Shamsi)、阿拉伯语(农历赫吉拉)和公历。

WPF 是 .NET Framework 3 最新的图形引擎,在我看来,它是迄今为止最好的 UI 框架之一,因为它内置了对从右到左语言的丰富支持。由于它是一项全新的技术,关于如何构建自定义控件、如何启用主题和皮肤以及需要考虑哪些因素的资源非常少。在本文中,我将创建一个 MonthView
和 DatePicker
自定义控件,以处理各种文化中的日期。这些控件将在所有已知系统主题(Classic、Aero、Luna、Royale)下正确呈现,并且有一个示例向您展示如何完全更改组件的外观。请注意,要运行和使用这些组件,您需要安装 .NET Framework 3,它只能安装在 Windows Vista/Windows XP/Windows Server 2003 上。
要了解支持文化的组件的概念,请参阅我的另一篇文章此处。
注意: 我在此项目中使用 Visual Studio 2008 Beta 2 (Orcas)。IDE/框架的后续版本可能与提供的控件兼容或不兼容。
设计时集成
由于 Cider(WPF 应用程序的 Visual Studio 设计器)仅为版本 1,因此可以集成到自定义组件中的设计时功能有限。对于当前版本,设计时体验仅限于 VS.NET 的属性窗格。希望在即将发布的版本中我能添加更多功能。
首先,选择一个基类
在 WPF 中创建新控件时,首先需要选择一个基类。与 .NET 2 / WinForms 中的控件开发不同,您不必通过子类化控件来更改其外观。在 WPF 中,控件的外观在 XAML 文件中定义,其逻辑在控件本身(代码隐藏文件)中定义。因此,如果您需要更改控件的使用方式,您可以轻松更改控件的 Style,或者为了进一步自定义,您可以更改其 Template,或某些部分的 Templates(我稍后会向您展示如何操作)。如果您从头开始创建控件,您可能应该使用 Control(不要与 WinForms 命名空间中的对应项混淆),它位于 System.Windows.Controls
命名空间中。创建自定义控件时,重要的一点是覆盖控件的默认(基)样式以创建新的外观。您可以通过将以下代码添加到您的 static
构造函数来实现此目的
static FXMonthView()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(FXMonthView),
new FrameworkPropertyMetadata(typeof(FXMonthView)));
}
您还可以覆盖从其基类继承的其他设置,例如 TabStop、键盘焦点、点击测试可见性等。此处的情况是,我们还覆盖了 TabStop 属性。
static FXMonthView
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(FXMonthView),
new FrameworkPropertyMetadata(typeof(FXMonthView)));
IsTabStopProperty.OverrideMetadata(typeof(FXMonthView),
new FrameworkPropertyMetadata(false));
}
创建默认外观
尽管有人说 WPF 中的所有控件都是无外观的,但开发人员应该为新创建的控件提供默认外观。您可以在 ResourceDictionary (XAML 文件) 中执行此操作,在创建自定义控件库时,默认名称为 Generic.Xaml。在为控件创建外观时,我们必须为控件创建一个新样式。请注意这里的 Key
和 TargetType
属性
<Style x:Key="{x:Type Win:FXMonthView}" TargetType="{x:Type Win:FXMonthView}">
在我们的日历控件中,主要布局是一个 Grid 控件。在标题部分,我们有一些标签来显示月份/年份信息,以及重复按钮以导航到上一个月和下一年。在中间,我们有一个 ListBox 控件,其布局经过修改,用于显示月份中的天数。在页脚部分,我们有“今天”和“清空”按钮。为了使自定义任务更容易一些,在 FXMonthView
控件中为各个部分定义了一些 Style 属性。通过这种方式,您可以通过将新样式分配给相关属性来自定义每个部分。例如,为了更改页脚按钮(例如“今天”、“清空”)的样式,您可以设置 FXMonthView
控件的 ButtonStyle
属性。
使用命令
在 WPF 中,为了拥有松散耦合的事件处理系统,您会更喜欢使用命令。我也建议您使用单独的 static
类并将您的控件命令放在那里。以下是我们的命令的样子
public static class FXMonthViewCommands
{
public static readonly RoutedCommand ChangeToNextMonth =
new RoutedCommand("FXMonthViewCommands.ChangeToNextMonth",
typeof(FXMonthViewCommands));
public static readonly RoutedCommand ChangeToNextYear =
new RoutedCommand("FXMonthViewCommands.ChangeToNextYear",
typeof(FXMonthViewCommands));
}
要使这些命令实际执行某些操作,您应该将绑定添加到命令,并将实际代码放入您的代码隐藏类中。这会告诉命令框架在命令激活时运行您的代码
public FXMonthView()
{
CommandBindings.Add(new CommandBinding
(FXMonthViewCommands.ChangeToNextMonth, OnChangeToNextMonth));
CommandBindings.Add(new CommandBinding
(FXMonthViewCommands.ChangeToNextYear, OnChangeToNextYear));
}
现在,唯一缺少的部分是决定命令应该在哪里执行。我们应该向应该执行每个特定命令的控件添加绑定。在这里,我们的 TodayButton
应该执行 FXMonthViewCommands.SelectTodayDate
命令,所以我们应该将以下内容添加到我们的模板中
<Win:FXMonthViewButton x:Name="PART_TodayDateButton"
Command="Win:FXMonthViewCommands.SelectTodayDate" ... />
连接 XAML 到代码隐藏
我们的控件中有一些必要的细节。这些细节(即 Parts),有时背后有逻辑,应该写在我们的代码隐藏文件中。那么,我们如何将 XAML 中定义的 Parts 连接到我们代码隐藏中的局部变量呢?假设我们有一个 Listbox,我们需要在运行时绑定一些数据(月份集合中的天数)到它。有一些指导原则要遵循
- 将控件的关键部分命名为以
PART_
开头,这样在自定义模板/样式时,就可以知道哪些部分可以/不应该从默认模板中移除
<Win:FXMonthViewButton x:Name="PART_TodayDateButton" ... />
- 使用
TemplatePart
属性指定控件的部件,并指定部件的名称和类型(名称应与步骤 1 中使用的名称相同)。如果您将与其他设计器(如 Expression Blend)一起设计控件的外观,使用TemplatePart
属性是一种好习惯
[TemplatePart(Name = "PART_PreviousMonthButton", Type = typeof(RepeatButton))] public class FXMonthView : Control { ... }
- 重写
OnApplyTemplate
方法并使用GetTemplateChild
来获取您的部分。如果该部分缺失,您可以抛出异常,或者根据您的控件性质执行其他操作。
private FXMonthViewContainer container = null; public override void OnApplyTemplate() { container = GetTemplateChild("PART_MonthDays") as FXMonthViewContainer; if(container == null) throw new ArgumentException(); container.SelectionChanged += OnContainerSelectionChanged; container.LayoutUpdated += OnContainerLayoutUpdated; }
设置文化和流向
如果您需要使用其他日历,您应该在启动应用程序时将应用程序的 Culture 和 UICulture 设置为您的要求
public class DemoApplication : Application
{
protected override void OnStartup(StartupEventArgs e)
{
//Decide which language should be used on the UI
System.Threading.Thread.CurrentThread.CurrentCulture =
new System.Globalization.CultureInfo("fa-IR");
System.Threading.Thread.CurrentThread.CurrentUICulture =
new System.Globalization.CultureInfo("fa-IR");
base.OnStartup(e);
}
}
要使控件以镜像顺序显示,您可以简单地将控件的 FlowDirection
设置为从右到左布局(默认是从左到右)。在创建将在 RTL 和 LTR 方向使用的自定义控件时,几乎所有部分都通过 WPF 布局引擎自动镜像,您需要做的很少。在我们的示例中,只是在 RTL 模式下,将 DatePicker
的阴影显示在控件的左下方而不是右下方进行了修复。我通过使用一个简单的 Trigger,在 FlowDirection
属性具有 RTL 值时,使用 DropDown 的 Shadow 的 Margin 属性实现了这一点
<ControlTemplate TargetType="{x:Type Win:FXDatePicker}">
...
<ControlTemplate.Triggers>
<Trigger Property="HasDropShadow" SourceName="PART_Popup" Value="true">
<Setter Property="Margin" TargetName="Shdw" Value="0,0,5,5"/>
<Setter Property="Color" TargetName="Shdw" Value="#71000000"/>
</Trigger>
<Trigger Property="FlowDirection" Value="RightToLeft">
<Setter Property="Margin" TargetName="Shdw" Value="5,0,0,5"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>

历史
版本 1.1.0.0
- 已修复:将
WeekOfDay
枚举转换为整数值的问题,该问题导致在使用公历时显示错误的星期几
版本 1.0.0.0
- 提供基本的
FXMonthView
和FXDatePicker
控件 - 所有系统主题(Luna 变体、Royale、Aero、Classic)均已实现
- 月份/年份更改时触发基本淡入动画。应改进
- 将应用程序文化设置为阿拉伯语变体时出现问题
MinDate
和MaxDate
部分实现MultiSelection
仅在 UI 上可用,应完全实现