这是《我佩服——WPF深入研究》的第三章,包含14个示例的研究。这一章比较怪,都是以FlowDocument为主题,进行布局设计,同时对TextBlock进行了详尽的分析。很多不常见的类出现在这一章的例子中。
这一章介绍Flow流。
本章共计27个示例,全都在VS2008下.NET3.5测试通过,点击这里下载:Flow.rar
本章的所有示例都使用了FlowDocument来对文本进行布局。
为此需要先介绍FlowDocumentReader——这是一个提供用来查看流内容并内置有对多种查看模式的支持的控件。
FlowDocumentReader 包括一些功能,使用户能够在各种查看模式之间动态选择,其中包括单页(一次一页)查看模式、一次两页(书本阅读格式)查看模式和连续滚动(无限)查看模式。 如果不需要在不同的查看模式之间动态切换的功能,则选择一些使用某种固定的查看模式的轻量流内容查看器。FlowDocumentPageViewer 以单页查看模式显示流内容,而 FlowDocumentScrollViewer 以连续滚动模式显示流内容。 有关可用的显示模式的更多信息,请参见 FlowDocumentReaderViewingMode。
此外还要介绍FlowDocumentScrollViewer:以连续滚动模式显示内容。默认情况下,总是显示垂直滚动条,而水平滚动条则根据需要显示,而且它的默认UI不包括工具栏。
最后介绍FlowDocumentPageViewer:以“一次一页”的查看模式显示内容
总结:
FlowDocumentPageViewer 和 FlowDocumentScrollViewer 都固定采用某种特定查看模式。另一方面,FlowDocumentReader 包含若干功能,允许用户在各种查看模式(由 FlowDocumentReaderViewingMode 枚举提供)之间进行动态选择,但其性能不如 FlowDocumentPageViewer 或 FlowDocumentScrollViewer。
这三个控件都能且只能承载一个 FlowDocument,
1.FlowDirectionLayout
这个实例展现了FlowDirection的两个枚举值的效果,指定文本和用户界面 (UI) 元素的内容流动方向,其中:
LeftToRight 指示内容应从左向右流动
RightToLeft 指示内容应从右向左流动
注:内容的流动方向通常与所表示语言的固有流动方向相对应。例如,希伯来语和阿拉伯语是从右向左流动的语言,英语、德语和俄语是从左向右流动的语言。
XAML中使用如下:
<FlowDocumentReader>
<FlowDocument FontFamily="Arial" Name="tf1" >
<Paragraph FlowDirection="LeftToRight">
Lorem ipsum dolorctetuer adipiscing elit, sed diam nonummy
</Paragraph>
</FlowDocument>
</FlowDocumentReader>
在C#代码中:
Paragraph par = new Paragraph(new Run("This paragraph will flow from left to right."));
par.FlowDirection = FlowDirection.LeftToRight;
2.TextTrimmingLayout
这个实例展现了TextTrimming的三个枚举值的效果,描述当文本溢出其包含框的边缘时如何修整文本:默认为None
<TextBlock TextTrimming="None"… />
None 不修整文本
CharacterEllipsis 在字符边界处修整文本。将绘制省略号 (...) 来替代剩余的文本
WordEllipsis 在单词边界处修整文本。将绘制省略号 (...) 来替代剩余的文本
3.TextWrapProperty
这个实例展现了TextWrapping的三个枚举值的效果,指定文本在到达包含框的边缘时是否换行:默认为NoWrap。
<TextBlock Name="txt2" TextWrapping="Wrap"… />
NoWrap 不换行
Wrap 如果行溢出可用块宽度则进行换行
WrapWithOverflow 如果行溢出可用块宽度则进行换行,但是有例外,如下
后两个枚举值效果基本上一样,只是对于非常长的单词会有特殊处理。对于WrapWithOverflow,如果换行算法无法确定换行时机(比如说,被限制在不允许滚动的固定宽度容器中的非常长的单词的情况),则行可能会溢出块宽度。而对于Wrap,则没有例外——不管多长的单词都会换行而不会超过块的宽度。
4.FlowDocument_LoadSave
这个例子演示了如何加载以FlowDocument开头的XAML到当前的FlowDocumentReader中,以及如何将一个FlowDocument片段保存为一个XAML文件。
XAML中有一个空的FlowDocumentReader:
<FlowDocumentReader Name="FlowDocRdr" Grid.Row="1" />
主要的逻辑在后台代码中:为此使用了XAML中的XamlReader和XamlWriter
先看如何加载一个XAML,并设置给FlowDocumentReader,这里先打开OpenFileDialog携带的加载路径,并将其装换为FileStream:
OpenFileDialog openFile = new OpenFileDialog();
FileStream xamlFile = openFile.OpenFile() as FileStream;
FlowDocument content = XamlReader.Load(xamlFile) as FlowDocument;
FlowDocRdr.Document = content;
再看如何将当前FlowDocumentReader中的FlowDocument保存到外部的XAML,仍然是先打开SaveFileDialog携带的保存路径,并将其装换为FileStream:
SaveFileDialog saveFile = new SaveFileDialog();
FileStream xamlFile = saveFile.OpenFile() as FileStream;
XamlWriter.Save(FlowDocRdr.Document, xamlFile);
xamlFile.Close();
注:这个例子的扩展性极大。例如可以变型为加载/保存任何以DockPanel开始的XAML。
5.FlowDoc_OptimalParagraph
这个例子演示了FlowDocument的5个属性:
IsHyphenationEnabled
获取或设置一个值,该值指示是否启用文字的自动断字和添加连字符功能。
自动断字功能使 FlowDocument 能够根据当前布局情况自动分断文字并添加连字符。这使得在一行中开始的某个较长的单词能够继续到下一行,从而使空白在两端对齐的文本中能够更为均匀地分布。单词根据标准语法规则进行分断和添加连字符。自动断字当与最佳段落布局(由 IsOptimalParagraphEnabled 属性表示)结合使用时尤其有效。
IsOptimalParagraphEnabled
获取或设置一个值,该值指示是否启用最佳段落布局功能
最佳段落布局功能即按照使空白尽可能均匀分布的方式来布局 FlowDocument 中的段落。从理论上而言,这消除了因行对齐文本及其他布局程序而产生的混乱的空白,从而可提供最佳的阅读体验。
在本例中,IsHyphenationEnabled和IsOptimalParagraphEnabled同时为true,可以获得比较好的视觉效果。
IsColumnWidthFlexible获取或设置一个值,该值指示 ColumnWidth 值是可变的(true)还是固定的(false)。
IsColumnWidthFlexible 属性确定任何多余的内容区域宽度(即页宽与内容在布局后的宽度之间的差)在各列之间的分配方式。如果设置为 true,则表示将额外的空间平均分配给每一列。在这种情况下,调整后的列宽可能会大于 ColumnWidth 属性所指定的宽度。如果设置为 false,则表示将额外的空间分配给页面右侧的边距。在这种情况下,列宽始终为 ColumnWidth 属性所指定的宽度(只要该宽度小于页宽减去所有 PagePadding)。
ColumnWidth获取或设置 FlowDocument 中列的所需最小宽度。
默认值为 Double.NaN,导致无论页面宽度如何,都只显示一列。
ColumnGap获取或设置列间隔值,该值指示 FlowDocument 中各列之间的间距。
注:列间隔不能超过当前 PageWidth 减去所有 PagePadding。如果 ColumnGap 属性的值超过此限制,则会减小有效列间隔以遵从此限制
6.FlowFormatCatalog
这个例子演示了各种流内容元素:
Bold 元素
BreakPageBefore 属性
FontSize 属性
Italic 元素
LineBreak 元素
List 元素
ListItem 元素
Paragraph 元素
un 元素
Section 元素
Span 元素
Variants 属性(上标和下标)
Underline 元素
另外,在FlowDocument中,使用Paragraph标签作为文本的容器。使用LineBreak可以换行:
<Paragraph>
<LineBreak/>
</Paragraph>
还有就是,一加载这个FlowDocument,就会自动使用来生成FlowDocumentPageViewer内容。因为这个例子的XAML以FlowDocument开始,而FlowDocumentPageViewer无论是否显示包在FlowDocument的外面,都会被调用来生成内容。
7.FontFamilySample
这个例子演示了如何动态改变文本的FontSize和FontFamily。同时,我们还可以改变它的FontStretch、FontWeight、以及FontStyle
注:这里FontStyle指的是“Normal”、“Italic”或“Oblique
8.TableCsharpSample
这个例子完全用C#构造出一个Table来
一个Table中可以有多个TableRowGroup:
currentRow = table1.RowGroups[0].Rows[4];
9.TableElementSample
这个例子演示了如何在XAML中使用Table。
首先要定义列,方式同Grid一样(当然也可以不设置):
<Table>
<Table.Columns>
<TableColumn />
<TableColumn />
<TableColumn />
<TableColumn />
</Table.Columns>
有几列就定义几列——这里是一个4列的表格。
随后要定义TableRowGroup,这是存放TableRow的容器,TableRow中放的是TableCell:
<TableRowGroup>
<TableRow>
<TableCell ColumnSpan="4"><Paragraph FontWeight="Bold">Planetary</Paragraph>
</TableCell>
</TableRow>
TableCell 元素可承载从 Block 派生的一个或多个流内容元素。 TableCell 有效的内容元素包括:
BlockUIContainer
List
Paragraph
Section
Table
注:没有 TableCell 内容的内置数据绑定。
另外,只可以在TableCell中设置ColumnSpan和RowSpan:
<TableCell ColumnSpan="4" RowSpan="4">
10.TableAddContent
这个例子演示了如何在C#中为已有Table添加一个TableRow。
在WPF中,是先把TableRow添加到已知的TableRowGroup中:然后在这个TableRow中添加带有内容的TableCell:
TableRow row = new TableRow();
trg1.Rows.Add(row);
Paragraph para = new Paragraph();
para.Inlines.Add("A new Row and Cell have been Added to the Table");
TableCell cell = new TableCell(para);
row.Cells.Add(cell);
注意到:Paragraph、TableCell、TableRow都是位于System.Windows.Documents命名空间的
11.TableVsGridSample
这个例子用一个3x4的表格模拟一个Grid。
注意,这是一个Layout,而不是控件。
12.FlowDocumentNews
这个示例的关键是document这个XAML底部的三个图片缩略图,对应鼠标左键单击事件,从而为Frame1重新设定数据源。
再有就是两个布局控件Figure和Floater的区别。
Figure 或 Floater 元素通常用于突出显示或强调内容的某些部分,承载主内容流中的支持图像或其他内容,或者用于插入松散相关的内容(如广告)。
Figure 和 Floater 在多个方面存在差异,它们用于不同的方案。
Figure:
l 可定位:可以设置其水平和垂直定位点,以便相对于页、内容、栏或段落进行停靠。也可使用其 HorizontalOffset 和 VerticalOffset 属性指定任意偏移。
l 可将其大小调整为多个栏大小:可以设置 Figure 的高度和宽度,使其是页、内容或栏的高度或宽度的倍数。注意,对于页和内容,倍数不能大于 1。例如,可以将 Figure 的宽度设置为“0.5 page”、“0.25 content”或“2 Column”。还可以将高度和宽度设置为绝对像素值。
l 不分页:如果 Figure 中的内容无法容纳在 Figure 内部,它会呈现能够容纳的内容部分,其余内容将丢失。
Floater:
l 无法定位;可利用能够使用的所有空间进行呈现。不能设置偏移或将 Floater 锚定。
l 不能将其大小设置为多个栏大小:默认情况下,Floater 的大小设置为一个栏宽。它有一个可设置为绝对像素值的 Width 属性,但是如果此值大于一个栏宽,会将其忽略并将浮标的大小设置为一个栏宽。您可以通过设置正确的像素宽度将其大小设置为小于一个栏宽,但其大小与栏无关,因此“0.5 倍栏宽”并不是 Floater 宽度的有效表达。Floater 没有高度属性,无法设置其高度;其高度取决于内容。
l Floater在以下情况下分页:具有指定宽度的内容超出了一个栏的高度,浮标会断开并分页到下一栏、下一页,等等。
Figure 适合放置希望控制其大小和定位的独立内容,并且可以确信内容将适合指定的大小。Floater 适合放置流动更加自由的内容,其流动方式与主页内容类似,但与主页内容相分离。
13.textblockPropsSamp
这个例子有很多地方值得研究,都是基于TextBlock的各种属性。
首先是Visibility枚举的三个值:Visible、Hidden、Collapsed。其中Hidden表示不显示元素,但为元素保留布局空间;而Collapsed则表示不显示元素,且不为其保留布局空间。
窗体中所有以make开头的按钮都使用了同样的逻辑:先将所有的元素都设置为Collapsed,然后将具体一个控件设为Visible。
其次是TextBlock的BaselineOffset属性,获取或设置每个文本行相对于基线的偏移量。
基线是一条假想的水平线,文本行中每个字符的底部都与基线进行对齐。改变TextBlock中的这个值,则TextBlock后面的文本都会重新设定基线偏移量。
BaselineOffset可以设置为负值,从而向相反的方向偏移。
然后是BreakBefore属性,指示内容应当如何在当前元素之前进行分行。而BreakAfter属性,指示内容应当如何在当前元素之后进行分行。
注:尾部换行符不起作用的控件将忽略这两个属性。
这两个属性总是返回LineBreakCondition的BreakDesired。
这就牵扯到了LineBreakCondition枚举——围绕内联对象描述换行条件。其中BreakDesired表示如果另一个对象不禁止则换行。更多细节参见LineBreakCondition枚举的详细介绍。
然后是设置文本上的画线效果。
TextDecorationCollection myCollection = new TextDecorationCollection();
TextDecoration myStrikethrough = new TextDecoration();
myStrikethrough.Location = TextDecorationLocation.Strikethrough;
myStrikethrough.Pen = new Pen(Brushes.Red, 1);
myStrikethrough.PenThicknessUnit = TextDecorationUnit.FontRecommended;
myCollection.Add(myStrikethrough);
tb1.TextDecorations = myCollection;
上述代码为TextBlock设置了myCollection文本修饰器集合,其中包括Strikethrough线条。
最后一个有趣的效果是“Set TextEffects”按钮,从而可以激发teTranslate、teScale、teRotate这三个方法,产生不同的效果:移动、缩放、旋转。
因为这三个效果是独立的,所以都具有相同的逻辑流程:先执行DisableTextEffects方法,将先前的效果都销毁;然后建立TextEffect实例myEffect,携带着不同的效果;最后是执行EnableTextEffects方法:
EnableTextEffects(tb1, myEffect);
将myEffect效果应用到TextBlock上。
注意这个EnableTextEffects方法:
private void EnableTextEffects(TextBlock tb, TextEffect effect)
{
_textEffectTargets = TextEffectResolver.Resolve(tb.ContentStart, tb.ContentEnd, effect);
foreach (TextEffectTarget target in _textEffectTargets)
target.Enable();
}
获取TextBlock中从起始到结束所有的内容,将其解析为一个TextEffectTarget数组,对其进行遍历,使每个效果都生效。
14.FlowDocumentPropsSamp
这个例子是示例-13的延伸,是对全章的技术总结。
其中有两个值得探讨的技术:
首先是DynamicDocumentPaginator:这是一个抽象基类,该类除了支持其自己基类的方法和属性之外,还支持自动后台分页和在重新分页期间跟踪内容位置。
DynamicDocumentPaginator dynPaginator;
dynPaginator = ((IDocumentPaginatorSource)fd1).DocumentPaginator as DynamicDocumentPaginator;
dynPaginator.IsBackgroundPaginationEnabled = false;
先说自动后台分页。当IsBackgroundPaginationEnabled = true时,调整文本大小等动作会导致页数变化并显示出来。反之设为false,会禁用自动分页,从而不会有变化。
再说分页期间的跟踪。完成分页后会触发PaginationProgress事件,我们可以实现这个事件的方法。
其次是Typography:提供对一组丰富的 OpenType 版式属性的访问。
Typography对象公开OpenType字体支持的一组功能。通过使用标记或代码设置 Typography 属性,您可以轻松地编写利用 OpenType 功能的文档。这是一个非常复杂的类,有太多的属性和方法,我们仅就示例中的3条语句进行分析:这里fd1为一个FlowDocument实例。
Typography.SetHistoricalForms(fd1, false);
确定是否启用历史记录格式,这里false是不启用。
历史记录格式在过去是常用的版式约定。
Typography.SetCapitals(fd1, FontCapitals.Normal);
指定fd1中的文本的大小写形式。默认为Normal(正常)。SmallCaps表示将小写字母都替换为大写字母。其它值详见FontCapitals枚举。
Typography.SetVariants(fd1, FontVariants.Subscript);
指定fd1中的文本的版式变体。默认为Normal(正常)。Subscript表示将默认标志符号替换为下标标志符号。其它值详见FontVariants枚举。