WPF 中的双向功能概述

与其他任何开发平台不同的是,WPF 具有许多支持快速开发双向内容(例如,同一文档中混合了从左到右和从右到左的数据)的功能。 同时,WPF 也为需要双向功能的用户(如阿拉伯语和希伯来语用户)带来了绝佳的体验。

以下各部分结合一些示例阐释如何获得双向内容的最佳显示效果,并对许多双向功能进行了说明。 其中多数示例使用的是 XAML,但您可以轻松地将这些概念应用于 C# 或 Microsoft Visual Basic 代码中。

本主题包括下列各节。

  • FlowDirection
  • FlowDocument
  • Span 元素
  • 针对非文本元素使用 FlowDirection
  • 数字替换

FlowDirection

FlowDirection 是在 WPF 应用程序中定义内容流方向的基本属性。 此属性可设置为以下两个枚举值之一:LeftToRightRightToLeft。 此属性可用于从 FrameworkElement 继承的所有 WPF 元素。

下面几个示例设置 TextBox 元素的流方向。

从左到右的流方向

<TextBlock Background="DarkBlue" Foreground="LightBlue" 
   FontSize="20" FlowDirection="LeftToRight">
        This is a left-to-right TextBlock
</TextBlock>

从右到左的流方向

<TextBlock Background="LightBlue" Foreground="DarkBlue"
   FontSize="20" FlowDirection="RightToLeft">
        This is a right-to-left TextBlock
</TextBlock>

下图显示了如何呈现上述代码。

阐释 FlowDirection 的图形

TextBlock 对齐

user interface (UI) 树中的元素将从其容器中继承 FlowDirection。 在下面的示例中,TextBlock 位于 Grid 中,而后者位于 Window 中。 为 Window 设置 FlowDirection 属性意味着同时也会为 GridTextBlock 设置该属性。

下面的示例演示如何设置 FlowDirection

<Window
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
    x:Class="FlowDirectionApp.Window1"
    Title="BidiFeatures" Height="200" Width="700" 
    FlowDirection="RightToLeft">

    <Grid>
      <Grid.ColumnDefinitions>
        <ColumnDefinition/>
        <ColumnDefinition/>
      </Grid.ColumnDefinitions>
      <TextBlock Grid.Column="0" >
          This is a right-to-left TextBlock
      </TextBlock>

      <TextBlock Grid.Column="1" FlowDirection="LeftToRight">
          This is a left-to-right TextBlock
      </TextBlock>
    </Grid>
</Window>

顶级 Window 具有 RightToLeft FlowDirection 属性,因此它所包含的所有元素也都继承同一 FlowDirection。 对于要重写指定 FlowDirection 的元素,它必须添加显式方向更改,如上一示例中的第二个 TextBlock,该控件将更改为 LeftToRight。 如果没有定义 FlowDirection,则会应用默认的 LeftToRight

下图显示了上一示例的输出。

阐释显式分配了 FlowDirection 的图形

流方向图

FlowDocument

许多开发平台(如 HTML、Win32 和 Java)都特意支持双向内容开发。 诸如 HTML 的标记语言为内容编写器提供了在任意所需方向显示文本时必需的标记,如 HTML 4.0 标记、采用“rtl”或“ltr”作为值的“dir”等。 此标记与 FlowDirection 属性类似,但 FlowDirection 属性使用了一种更高级的方法来设置文本内容的布局,并可用于除文本以外的内容。

在 WPF 中,FlowDocument 是一种多功能 UI 元素,可承载文本、表、图像和其他元素的组合。 以下各部分中的示例将使用此元素。

可以使用多种方法将文本添加到 FlowDocument 中。 其中一种简单方法就是使用 Paragraph,它是用于对文本等内容进行分组的块级元素。 为了将文本添加到内联级元素,这些示例使用了 SpanRunSpan 是用于对其他内联元素进行分组的内联级流内容元素,而 Run 是用于包含一系列无格式文本的内联级流内容元素。 Span 可包含多个 Run 元素。

第一个文档示例包含的文档具有很多个网络共享名;例如 \\server1\folder\file.ext。 无论此网络链接是包含在阿拉伯语文档还是英语文档中,您始终希望用相同的方式显示它。 下图显示了阿拉伯语 RightToLeft 文档中的该链接。

阐释如何使用 Span 元素的图形

从右向左显示的文档

由于文本是 RightToLeft,因此所有特殊字符(如“\”)都按从右到左的顺序分隔文本。 这会导致无法按正确顺序显示该链接。若要解决此问题,必须嵌入文本以保留单独的按 LeftToRight 流动的 Run。 除了为每种语言提供单独的 Run 之外,还可使用一种更好的方法来解决此问题,就是将不常用的英语文本嵌入到较大的阿拉伯语 Span 中。

下图阐释了这一点。

阐释如何使用 Span 元素中嵌入的 Run 元素的图形

XamlPad 屏幕快照

下面的示例演示如何在文档中使用 RunSpan 元素。

<Page
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
    FlowDirection="RightToLeft">

  <FlowDocument>
    <Paragraph>
      <Span FlowDirection="RightToLeft" >
        ستجد الملف هنا:
        <Run FlowDirection="LeftToRight">
           \\server1\filename\filename1.txt</Run>
        ثم باقى النص!
      </Span>
    </Paragraph>
  </FlowDocument>
</Page>

Span 元素

Span 元素用作具有不同流方向的文本之间的边界分隔符。 即使具有相同流方向的 Span 元素被视为具有不同的双向范围(这意味着 Span 元素按容器的 FlowDirection 排序),也只有 Span 元素中的内容才遵循 SpanFlowDirection

下图显示了若干 TextBlock 元素的流方向。

阐释若干 TextBlock 元素中的 FlowDirection 的图形

具有不同流方向的文本块

下面的示例演示如何使用 SpanRun 元素生成上图中显示的结果。

<Page xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation">
  <StackPanel >

    <TextBlock FontSize="20" FlowDirection="RightToLeft">
      <Run FlowDirection="LeftToRight">العالم</Run>
      <Run FlowDirection="LeftToRight" Foreground="Red" >فى سلام</Run>
    </TextBlock>

    <TextBlock FontSize="20" FlowDirection="LeftToRight">
      <Run FlowDirection="RightToLeft">العالم</Run>
      <Run FlowDirection="RightToLeft" Foreground="Red" >فى سلام</Run>
    </TextBlock>

    <TextBlock FontSize="20" Foreground="Blue">العالم فى سلام</TextBlock>

    <Separator/>

    <TextBlock FontSize="20" FlowDirection="RightToLeft">
      <Span Foreground="Red" FlowDirection="LeftToRight">Hello</Span>
      <Span FlowDirection="LeftToRight">World</Span>
    </TextBlock>

    <TextBlock FontSize="20" FlowDirection="LeftToRight">
      <Span Foreground="Red" FlowDirection="RightToLeft">Hello</Span>
      <Span FlowDirection="RightToLeft">World</Span>
    </TextBlock>

    <TextBlock FontSize="20" Foreground="Blue">Hello World</TextBlock>

  </StackPanel>

</Page>

在该示例的 TextBlock 元素中,Span 元素按父级的 FlowDirection 进行布局,而每个 Span 元素中的文本则按其自己的 FlowDirection 流动。 这适用于拉丁语和阿拉伯语,也适用于任何其他语言。

添加 xml:lang

下图显示了另一个示例,此示例使用数字和算术表达式,如 "200.0+21.4=221.4"。 请注意,此示例仅设置了 FlowDirection

仅使用 FlowDirection 显示数字的图形

从右向左显示的数字

此应用程序的用户会对输出感到失望,因为即使 FlowDirection 正确,这些数字的形状也不同于正常的阿拉伯数字。

XAML 元素可包含 XML 特性(xml:lang),该特性指定每个元素的语言。 XAML 还支持 XML 语言原则,供子元素使用应用于树中父元素的 xml:lang 值。 在上一示例中,由于没有为 Run 元素或其任何顶级元素定义语言,因此使用了默认语言 xml:lang,即 XML 中的 en-US。 Windows Presentation Foundation (WPF) 的内部数字形状算法用相应的语言(在此示例中为英语)选择数字。 若要正确呈现阿拉伯数字,需要设置 xml:lang。

下图显示了添加了 xml:lang 的示例。

阐释如何使用 xml:lang 特性的图形

从右向左显示的阿拉伯数字

下面的示例将 xml:lang 添加到应用程序中。

<Page
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
    FlowDirection="RightToLeft">
      <FlowDocument>
         <Paragraph>
            <Span FlowDirection="RightToLeft" Language="ar-SA">
              العملية الحسابية: "200.0+21.4=221.4"
            </Span>
         </Paragraph>
      </FlowDocument>
</Page>

请注意,许多语言根据目标区域会有不同的 xml:lang 值;例如,"ar-SA" 和 "ar-EG" 表示阿拉伯语的两个变体。 上面几个示例阐释需要同时定义 xml:lang 和 FlowDirection 值。

针对非文本元素使用 FlowDirection

FlowDirection 不仅定义文本在文本元素中流动的方式,而且还定义了几乎所有其他 UI 元素的流方向。 下图显示了一个 ToolBar,该控件可使用水平 LinearGradientBrush 绘制其背景。

显示使用从左到右渐变的工具栏的图形

渐变屏幕快照

在将 FlowDirection 设置为 RightToLeft 后,不仅 ToolBar 按钮是从右到左排列的,甚至 LinearGradientBrush 也将其偏移量重新调整为从右到左流动。

下图显示了重新调整 LinearGradientBrush 后的结果。

显示使用从右到左渐变的工具栏的图形

从右向左显示的渐变

下面的示例绘制一个 RightToLeft ToolBar。 (若要从左到右绘制它,请移除 ToolBar 上的 FlowDirection 特性。)

<Page
  xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation" 
  xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml">

  <ToolBar FlowDirection="RightToLeft" Height="50" DockPanel.Dock="Top">
    <ToolBar.Background>
      <LinearGradientBrush StartPoint="0,0.5" EndPoint="1,1">
        <LinearGradientBrush.GradientStops>
          <GradientStop Color="DarkRed" Offset="0" />
          <GradientStop Color="DarkBlue" Offset="0.3" />
          <GradientStop Color="LightBlue" Offset="0.6" />
          <GradientStop Color="White" Offset="1" />
        </LinearGradientBrush.GradientStops>
      </LinearGradientBrush>
    </ToolBar.Background>

    <Button FontSize="12" Foreground="White">Button1</Button>
    <Rectangle Width="20"/>
    <Button FontSize="12" Foreground="White">Button2</Button>
    <Rectangle Width="20"/>
    <Button FontSize="12" Foreground="White">Button3</Button>
    <Rectangle Width="20"/>
    <Button FontSize="12" Foreground="White">Button4</Button>
    <Rectangle Width="20"/>
  </ToolBar>
</Page>

FlowDirection 异常

在有些情况下,FlowDirection 可能会发生意外行为。 本部分介绍了其中的两种异常。

Image

Image 表示显示图像的控件。 在 XAML 中,该控件可与 Source 属性一起使用,而该属性定义要显示的 Image 的uniform resource identifier (URI)。

与其他 UI 元素不同,Image 不从容器继承 FlowDirection。 但如果将 FlowDirection 显式设置为 RightToLeft,则会以水平翻转的方式显示 Image。 这可作为一种便捷功能提供给双向内容的开发人员,因为在某些情况下,水平翻转图像会达到所需的效果。

下图显示了一个翻转的 Image

阐释翻转图像的图形

XamlPad 屏幕快照

下面的示例演示 Image 无法从包含它的 StackPanel 中继承 FlowDirection注意   若要运行此示例,C:\ 驱动器上必须存在一个名为 ms_logo.jpg 的文件。

<StackPanel 
  xmlns='https://schemas.microsoft.com/winfx/2006/xaml/presentation' 
  FlowDirection="RightToLeft">

  <Image Source="file://c:/ms_logo.jpg" 
         Width="147" Height="50"/>
  <Separator Height="10"/>
  <Image Source="file://c:/ms_logo.jpg" 
         Width="147" Height="50" FlowDirection="LeftToRight" />
  <Separator Height="10"/>
  <Image Source="file://c:/ms_logo.jpg" 
         Width="147" Height="50" FlowDirection="RightToLeft"/>
</StackPanel>

注意 下载文件中包含 ms_logo.jpg 文件。 此代码假定 .jpg 文件不在您的项目中,而是位于 C:\ 驱动器下的某个位置。 您必须将项目文件中的 .jpg 文件复制到 C:\ 驱动器下,或者更改代码以查找项目中的文件。 为此,需要将 Source="file://c:/ms_logo.jpg" 更改为 Source="ms_logo.jpg"。

Path

除了 Image 之外,还有一个值得关注的元素 Path。 Path 是一个对象,可用于绘制一系列相互连接的直线和曲线。 就其 FlowDirection 而言,它的行为方式与 Image 类似;例如,它的 RightToLeft FlowDirection 是它的 LeftToRight 图像的水平镜像。 但与 Image 不同的是,Path 从容器继承其 FlowDirection,因此用户无需显式指定它。

下面的示例使用 3 个线条绘制简单箭头。 第一个箭头从 StackPanel 继承 RightToLeft 流方向,以便从右侧的根处测量其起点和终点。 具有显式 RightToLeft FlowDirection 的第二个箭头也从右侧开头。 但第三个箭头的起始根位于左侧。 有关绘制的更多信息,请参见 LineGeometryGeometryGroup

<StackPanel 
  xmlns='https://schemas.microsoft.com/winfx/2006/xaml/presentation' 
  FlowDirection="RightToLeft">

  <Path Stroke="Blue" StrokeThickness="4">
    <Path.Data>
      <GeometryGroup >
        <LineGeometry StartPoint="300,10" EndPoint="350,30" />
        <LineGeometry StartPoint="10,30" EndPoint="352,30" />
        <LineGeometry StartPoint="300,50" EndPoint="350,30" />
      </GeometryGroup>
    </Path.Data>
  </Path>

  <Path Stroke="Red" StrokeThickness="4" FlowDirection="RightToLeft">
    <Path.Data>
      <GeometryGroup >
        <LineGeometry StartPoint="300,10" EndPoint="350,30" />
        <LineGeometry StartPoint="10,30" EndPoint="352,30" />
        <LineGeometry StartPoint="300,50" EndPoint="350,30" />
      </GeometryGroup>
    </Path.Data>
  </Path>

  <Path Stroke="Green" StrokeThickness="4" FlowDirection="LeftToRight">
    <Path.Data>
      <GeometryGroup >
        <LineGeometry StartPoint="300,10" EndPoint="350,30" />
        <LineGeometry StartPoint="10,30" EndPoint="352,30" />
        <LineGeometry StartPoint="300,50" EndPoint="350,30" />
      </GeometryGroup>
    </Path.Data>
  </Path>
</StackPanel>

下图显示了上一示例的输出。

阐释使用 Path 元素绘制的箭头的图形

路径

ImagePath 这两个示例演示 Windows Presentation Foundation (WPF) 如何使用 FlowDirection。 除了在容器中按特定方向对 UI 元素进行布局之外,FlowDirection 还可用于 InkPresenter(在图面上呈现墨迹)、LinearGradientBrushRadialGradientBrush 之类的元素。 每当模拟从左到右行为的内容需要从右到左行为(反之亦然)时,Windows Presentation Foundation (WPF) 就会提供该功能。

数字替换

一直以来,Windows 始终通过以下方式支持数字替换:允许对相同数字使用不同区域性形状的表示形式,但同时使这些数字的内部存储形式在不同区域设置之间保持统一;例如,数字虽然以其常见的十六进制值(如 0x40、0x41)存储,但却根据所选的语言进行显示。

这使应用程序无需将数值从一种语言转换为另一种语言就可对它们进行处理;例如,用户可以在本地化的阿拉伯文 Windows 中打开 Microsoft Excel 电子表格,并会看到阿拉伯文形状的数字;而在 Windows 的欧洲版本中打开它,则会看到相同数字的欧洲表示形式。 这对其他符号(如逗号分隔符和百分比符号)来说也是必需的,因为在同一文档中它们通常随数字一起出现。

Windows Presentation Foundation (WPF) 沿承了这一传统,并为此功能提供了进一步支持,以允许更多的用户对使用替换的时间和方式进行控制。 虽然此功能适用于任何语言,但它对双向内容尤其有用;由于应用程序可能会在各种区域性下运行,因此针对特定语言来设置数字形状通常是应用程序开发人员所面临的难题。

Substitution 依赖项属性是用来控制在 Windows Presentation Foundation (WPF) 中如何执行数字替换的核心属性。 NumberSubstitution 类指定文本中数字的显示方式, 它有三个定义其行为的公共属性。 下面概括了其中的每个属性。

CultureSource:

此属性指定确定数字区域性的方式。 它采用以下三个 NumberCultureSource 枚举值之一。

CultureOverride

仅当 CultureSource 属性设置为 Override 时,才使用 CultureOverride 属性;否则后一种属性会被忽略。 该属性指定数字区域性。 默认值为 null,它将被解释为 en-US。

Substitution

此属性指定要执行的数字替换的类型。 它采用下列 NumberSubstitutionMethod 枚举值之一。

  • AsCulture:根据数字区域性的 NumberFormatInfo.DigitSubstitution 属性确定替换方法。 这是默认值。

  • Context:如果数字区域性为阿拉伯语或波斯语区域性,它将指定数字取决于上下文。

  • European:数字始终呈现为欧洲数字。

  • NativeNational:使用数字区域性的民族数字(由区域性的 NumberFormat 指定)呈现数字。

  • Traditional:使用数字区域性的传统数字呈现数字。 对于大多数区域性,其效果与 NativeNational 相同。 但是,NativeNational 对某些阿拉伯语区域性会产生拉丁数字,而此值对所有阿拉伯语区域性产生阿拉伯数字。

这些值对双向内容开发人员意味着什么呢? 在多数情况下,开发人员可能仅需要定义 FlowDirection 以及每个文本 UI 元素的语言(例如,Language="ar-SA"),而 NumberSubstitution 逻辑将负责根据正确的 UI 显示这些数字。 下面的示例演示如何在阿拉伯文版本的 Windows 中运行的 Windows Presentation Foundation (WPF) 应用程序内使用阿拉伯数字和英文数字。

<Page 
  xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation" 
  xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml" >
  <StackPanel>
   <TextBlock Background="LightGreen" FontSize="32" 
      Language="ar-SA" FlowDirection="RightToLeft">1+2=3</TextBlock>
   <TextBox Background="LightGreen" FontSize="32" 
      Language="ar-SA" FlowDirection="RightToLeft">1+2=3</TextBox>
   <TextBlock Background="LightBlue" FontSize="32">1+2=3</TextBlock>
   <TextBox Background="LightBlue" FontSize="32">1+2=3</TextBox>
 </StackPanel>
</Page>

下图显示了在阿拉伯语版本的 Windows 中运行时上一示例的输出。

演示所显示的阿拉伯数字和英文数字的图形

具有数字的 XamlPad 屏幕快照

在此示例中,FlowDirection 十分重要,因为将 FlowDirection 改为设置成 LeftToRight 时会产生欧洲数字。 以下各部分讨论了如何在整个文档内统一显示数字。 如果此示例未在 Windows 的阿拉伯文版本中运行,则所有数字都会显示为欧洲数字。

定义替换规则

在实际的应用程序中,可能需要以编程方式设置 Language。 例如,希望将 xml:lang 特性设置为与系统的 UI 所使用的特性相同,或者可能根据应用程序状态更改 Language。

如果要根据应用程序的状态进行更改,请使用 Windows Presentation Foundation (WPF) 所提供的其他功能。

首先,设置应用程序组件的 NumberSubstitution.CultureSource="Text"。 使用此设置可确保对于将 "User" 用作默认值的文本元素(如 TextBlock),设置不会来自 UI。

例如:

<TextBlock

Name="text1" NumberSubstitution.CultureSource="Text">

1234+5679=6913

</TextBlock>

在相应的 C# 代码中,设置 Language 属性;例如,将该属性设置为 "ar-SA"。

text1.Language =

System.Windows.Markup.XmlLanguage.GetLanguage("ar-SA");

如果需要将 Language 属性设置为当前用户的 UI 语言,请使用以下代码。

text1.Language =

System.Windows.Markup.XmlLanguage.GetLanguage(

System.Globalization.CultureInfo.CurrentUICulture.IetfLanguageTag);

CurrentCulture 表示当前线程在运行时所使用的当前区域性。

最后一个 XAML 示例应与下面的示例类似。

<Page x:Class="WindowsApplication.Window1"
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
    Title="Code Sample" Height="300" Width="300"
>
    <StackPanel>
      <TextBlock Language="ar-SA" 
         FlowDirection="RightToLeft">عربى: 1+2=3
      </TextBlock>
      <TextBlock Language="ar-SA" 
         FlowDirection="RightToLeft" 
         NumberSubstitution.Substitution="European">عربى: 1+2=3 
      </TextBlock>
    </StackPanel>
</Page>

最后一个 C# 示例应与以下类似。

namespace BidiTest
{
    public partial class Window1 : Window
    {

        public Window1()
        {
            InitializeComponent();

            string currentLanguage = 
                System.Globalization.CultureInfo.CurrentCulture.IetfLanguageTag;

            text1.Language = System.Windows.Markup.XmlLanguage.GetLanguage(currentLanguage);

            if (currentLanguage.ToLower().StartsWith("ar"))
            {
                text1.FlowDirection = FlowDirection.RightToLeft;
            }
            else
            {
                text1.FlowDirection = FlowDirection.LeftToRight;
            }
        }
    }
}

下图针对任一编程语言显示了窗口的外观。

显示阿拉伯数字的图形

阿拉伯数字

使用 Substitution 属性

数字替换在 Windows Presentation Foundation (WPF) 中的工作方式取决于文本元素的 Language 及其 FlowDirection。 如果 FlowDirection 为从左到右,则会呈现欧洲数字。 但如果数字的前面为阿拉伯语文本或者具有设置为 "ar" 的 Language,并且 FlowDirectionRightToLeft,则会改为呈现阿拉伯数字。

但在某些情况下,可能需要创建统一的应用;例如,对所有用户呈现欧洲数字。 或者,在具有特定 StyleTable 单元格中呈现阿拉伯数字。 执行此操作的一种简单方法是使用 Substitution 属性。

在下面的示例中,第一个 TextBlock 没有设置 Substitution 属性,因此算法会正常显示阿拉伯数字。 但在第二个 TextBlock 中,Substitution 设置为 European,这会重写呈现阿拉伯数字的默认 Substitution,并且会显示欧洲数字。

<Page x:Class="WindowsApplication.Window1"
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
    Title="Code Sample" Height="300" Width="300"
>
    <StackPanel>
      <TextBlock Language="ar-SA" 
         FlowDirection="RightToLeft">عربى: 1+2=3
      </TextBlock>
      <TextBlock Language="ar-SA" 
         FlowDirection="RightToLeft" 
         NumberSubstitution.Substitution="European">عربى: 1+2=3 
      </TextBlock>
    </StackPanel>
</Page>