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

Farsi 库 FX

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.78/5 (35投票s)

2007年8月27日

MIT

6分钟阅读

viewsIcon

132859

downloadIcon

4692

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

Screenshot - Themes

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。在为控件创建外观时,我们必须为控件创建一个新样式。请注意这里的 KeyTargetType 属性

<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,我们需要在运行时绑定一些数据(月份集合中的天数)到它。有一些指导原则要遵循

  1. 将控件的关键部分命名为以 PART_ 开头,这样在自定义模板/样式时,就可以知道哪些部分可以/不应该从默认模板中移除
    <Win:FXMonthViewButton x:Name="PART_TodayDateButton" ... />
  2. 使用 TemplatePart 属性指定控件的部件,并指定部件的名称和类型(名称应与步骤 1 中使用的名称相同)。如果您将与其他设计器(如 Expression Blend)一起设计控件的外观,使用 TemplatePart 属性是一种好习惯
    [TemplatePart(Name = "PART_PreviousMonthButton", Type = 
            typeof(RepeatButton))]
    public class FXMonthView : Control
    {
       ...
    }
  3. 重写 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>
Culture Support

历史

版本 1.1.0.0

  • 已修复:将 WeekOfDay 枚举转换为整数值的问题,该问题导致在使用公历时显示错误的星期几

版本 1.0.0.0

  • 提供基本的 FXMonthView FXDatePicker 控件
  • 所有系统主题(Luna 变体、Royale、Aero、Classic)均已实现
  • 月份/年份更改时触发基本淡入动画。应改进
  • 将应用程序文化设置为阿拉伯语变体时出现问题
  • MinDate MaxDate 部分实现
  • MultiSelection 仅在 UI 上可用,应完全实现
© . All rights reserved.