WPF 中的双向功能概述

不同于任何其他开发平台,WPF 独特地拥有许多功能,支持快速开发双向内容,例如在同一文档中混合使用从左到右和从右到左的内容。 同时,WPF 为需要双向功能(如阿拉伯语和希伯来语用户)的用户创造了出色的体验。

以下部分将介绍许多双向功能,以及演示如何实现双向内容的最佳显示的示例。 大多数示例都使用 XAML,不过可以轻松地将概念应用于 C# 或 Microsoft Visual Basic 代码。

FlowDirection

定义 WPF 应用程序中的内容流方向的基本属性是 FlowDirection。 此属性可以设置为两个枚举值之一, 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>

下图显示了上一个代码的呈现方式。

说明不同流方向的图形。

用户界面(UI)树中的元素将从其容器继承FlowDirection。 在下面的示例中,TextBlock 位于 Grid 内,而 Grid 又位于 Window 中。 设置FlowDirectionWindow意味着也设置GridTextBlock

下面的示例演示了设置 FlowDirection

<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://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 有一个 RightToLeftFlowDirection,因此包含的所有元素也继承相同的 FlowDirection元素。 为了使元素覆盖指定的FlowDirection,必须添加显式的方向变化,例如在前面的示例中,第二个TextBlock元素更改为LeftToRight。 如果未 FlowDirection 定义,则 LeftToRight 默认适用。

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

说明显式流方向更改的图形。

FlowDocument

许多开发平台(如 HTML、Win32 和 Java)都为双向内容开发提供特殊支持。 标记语言(如 HTML)为内容撰写者提供所需的标记,可将文本显示为任何所需的方向,例如 HTML 4.0 的 “dir” 属性,其值为 “rtl” 或 “ltr”。 此标记类似于 FlowDirection 属性,但该 FlowDirection 属性以更高级的方式布局文本内容,可用于文本以外的内容。

在 UI 元素中,可以托管文本、表、图像和其他元素的组合。 以下部分中的示例使用此元素。

可以通过多种方式向文本 FlowDocument 添加文本。 执行此作的一种简单方法是, Paragraph 它是用于对内容(如文本)进行分组的块级元素。 若要向内联级元素添加文本,示例使用 SpanRunSpan 是用于对其他内联元素进行分组的内联级别流内容元素,而 Run 是旨在包含一段未格式化文本的内联级别流内容元素。 A Span 可以包含多个 Run 元素。

第一个文档示例包含一个具有多个网络共享名称的文档;例如 \\server1\folder\file.ext。 无论你在阿拉伯语或英语文档中是否具有此网络链接,你始终希望它以相同的方式显示。 下图演示了如何使用 Span 元素并显示阿拉伯语 RightToLeft 文档中的链接:

说明使用 Span 元素的图形。

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

下图演示了如何使用 Span 元素中嵌入的 Run 元素:

显示 Span 元素中嵌入的 Run 元素的图形。

以下示例展示如何在文档中使用 RunSpan 元素。

<Page
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://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 元素的流方向。

说明具有不同流方向的文本块的图形。

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

<Page xmlns="http://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 ,即 en-US XAML。 Windows Presentation Foundation 的内部数字整形算法(WPF)选择相应语言的数字 - 在本例中为英语。 若要使阿拉伯数字正确渲染,需要设置 xml:lang

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

显示从右向左流动的阿拉伯数字的图形。

以下示例将 xml:lang 添加到应用程序。

<Page
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://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:langFlowDirection 值。

包含非文本元素的 FlowDirection

FlowDirection 不仅定义文本元素中的文本流动方式,还定义几乎所有其他 UI 元素的流方向。 下图展示了一个 ToolBar,该图形使用水平 LinearGradientBrush 绘制其背景,并实现从左到右的渐变效果。

显示具有从左到右渐变的工具栏的图形。

FlowDirection 设置为 RightToLeft 后,不仅 ToolBar 按钮从右到左排列,甚至 LinearGradientBrush 也重新调整其偏移量,使它们从右到左对齐。

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

显示具有从右到左渐变的工具栏的图形。

下面的示例绘制了一个 RightToLeftToolBar。 若要从左到右绘制,请删除在 ToolBar 上的 FlowDirection 属性。

<Page
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
  xmlns:x="http://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 代表一个显示图像的控件。 在 XAML 中,它可用于一个 Source 属性,该属性定义了要显示的 Image 的统一资源标识符 (URI)。

与其他 UI 元素不同,a Image 不会从容器继承 FlowDirection 。 将 FlowDirection 显式设置为 RightToLeft 时,一个 Image 会被水平翻转展示。 这是作为双向内容的开发人员的便捷功能实现的;因为在某些情况下,水平翻转图像会产生所需的效果。

下图显示了 Image 被翻转。

展示翻转图像的图形。

以下示例演示Image无法从包含它的StackPanel中继承FlowDirection

注释

必须在 C:\ 驱动器上具有名为 ms_logo.jpg 的文件才能运行此示例。

<StackPanel 
  xmlns='http://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"

路径

除了Image,另一个有趣的元素是Path。 Path 是一个对象,可以绘制一系列连接的线条和曲线。 它的行为方式类似于Image在其FlowDirection方面;例如,它的RightToLeftFlowDirection是其LeftToRight的水平镜像。 但是,与Image不同的是,Path从容器继承FlowDirection,不需要显式指定。

以下示例使用 3 行绘制一个简单的箭头。 第一个箭头从StackPanel继承了RightToLeft流动方向,因此其起点和终点是从右侧的根进行测量的。 具有显式 RightToLeftFlowDirection 的第二个箭头也从右侧开始。 但是,第三个箭头的起始根位于左侧。 有关绘图的详细信息,请参阅 LineGeometryGeometryGroup

<StackPanel 
  xmlns='http://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 元素绘制的箭头:

显示使用 Path 元素绘制的箭头的图形。

ImagePath是WPF如何使用FlowDirection的两个示例。 除了在容器中以特定方向布局 UI 元素外,FlowDirection还可以与InkPresenter元素一起使用,例如在图面上呈现墨迹的LinearGradientBrushRadialGradientBrush等。 每当您需要为内容设置模拟从左到右或从右到左的行为时,Windows Presentation Foundation(WPF)都提供这种功能。

数字替换

历史上,Windows 通过允许对相同数字的不同文化形状进行表示来支持数字替换,同时在不同区域设置之间保持这些数字的内部存储统一。例如,数字如0x40、0x41存储在其已知的十六进制值中,但根据所选语言进行显示。

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

Windows Presentation Foundation(WPF)延续了相同的传统,并添加了对此功能的进一步支持,使用户能够更好地控制使用替换的时间和方式。 虽然此功能适用于任何语言,但在双向内容中特别有用,因为应用程序可能运行的各种区域性,因此,为特定语言调整数字通常是应用程序开发人员的挑战。

控制数字替换在 Windows Presentation Foundation 中的工作原理的核心属性是 Substitution 依赖属性。 该 NumberSubstitution 类指定如何显示文本中的数字。 它具有三个定义其行为的公共属性。 下面是每个属性的摘要:

CultureSource:

此属性指定如何确定数字格式的文化设置。 它采用三 NumberCultureSource 个枚举值之一。

CultureOverride

CultureOverride仅当CultureSource属性设置为Override且被忽略时,才会使用该属性。 它指定数字文化。 默认值为null时,解释为en-US。

替换

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

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

  • Context:如果数字文化是阿拉伯或波斯文化,这意味着数字取决于上下文。

  • European:数字始终显示为欧洲数字。

  • NativeNational:数字按照文化的要求使用本国数字呈现,由文化 NumberFormat 指定。

  • Traditional:数字按照数字文化的传统格式呈现。 对于大多数文化,这与 NativeNational 是一样的。 但是,NativeNational 在某些阿拉伯文化中可能会生成拉丁数字,而此值则会在所有阿拉伯文化中生成阿拉伯数字。

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

<Page 
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
  xmlns:x="http://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,并且用阿拉伯语和英语数字显示,那么下图将展示上一示例的输出:

显示阿拉伯语和英语数字的图形。

在这种情况下,FlowDirection 很重要,因为设置 FlowDirectionLeftToRight 将会导致使用欧洲式数字。 以下部分讨论如何在整个文档中统一显示数字。 如果此示例未在阿拉伯语 Windows 上运行,则所有数字都显示为欧洲数字。

定义替换规则

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

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

首先,设置应用程序组件的NumberSubstitution.CultureSource="Text"。 使用此设置可确保设置不来自默认值为“用户”的文本元素的 UI,例如 TextBlock

例如:

<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);

CultureInfo.CurrentCulture 表示当前线程在运行时使用的当前文化设置。

最终的 XAML 示例应类似于以下示例。

<Page x:Class="WindowsApplication.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://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;
            }
        }
    }
}

以下图形显示在任一编程语言中窗口的外观,显示阿拉伯数字:

显示阿拉伯语数字的图形。

使用替换属性

在 Windows Presentation Foundation 中,数字替换的工作方式取决于文本元素的语言及其 FlowDirection。 如果从 FlowDirection 左到右,则显示欧洲数字。 但是,如果它前面是阿拉伯语文本,或者语言设置为“ar”并且FlowDirectionRightToLeft,就会呈现阿拉伯数字。

但在某些情况下,你可能想要创建统一的应用程序,例如所有用户的欧洲数字。 或具有特定Style单元格的Table阿拉伯文数字。 执行此作的一种简单方法是使用 Substitution 属性。

在下面的示例中,第一个 TextBlock 没有 Substitution 属性集,因此算法按预期显示阿拉伯语数字。 但是,在第二个TextBlock中,将替换设置为欧洲数字,覆盖了默认的阿拉伯数字替换,因此显示的是欧洲数字。

<Page x:Class="WindowsApplication.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://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>