WPF——TextBlock基础&高级用法

网上有很多关于TextBlock的基础教程,但对于实际使用却远远不够。加上相关资料较少,于是我写了本帖作为补充。

 基础

TextBlock常用的添加文本方式是直接设置Text属性。但需要显示不同字体或颜色时单一性的Text就远远不够了。这时就要需要用到Run这个类。每个Run对象可以设置独立的字体、字体大小、颜色等等属性。

XAML代码

<TextBlock x:Name="tb" FontSize="14" TextWrapping="Wrap">
    <Run Foreground="Blue" FontWeight="Bold">第一&#160;行</Run>
    <LineBreak/>
    <Run Text="第二 行\n" FontSize="16"/>
</TextBlock>

注意:

1、第一个Run和第二个之间的区别。上述代码以空格为例:第一个Run遵循的是xml写法,第二个则是代码风格。

2、换行除了用换行符,也可以用LineBreak。

3、TextBlock中的FontSize会令其中的所有Run的FontSize都是14(默认12),可以单独修改(如第二个Run)。

4、不建议同时使用TextBlock的Text属性和Run。

CS代码 

 相对于xaml方式,在cs代码中设置文本是一种动态设置方式。

Run run = new Run("第一行");
run.Foreground = Brushes.Blue;
run.FontWeight = FontWeights.Bold;
tb.Inlines.Add(run);    // 添加到末尾

为方便在多线程执行任务时打印进度信息,可以封装成一个函数(函数Add将Run追加到结尾处。提醒:字符串需要有转义字符 \n 才能换行):

/// <summary>
/// 追加文本到TextBlock(可用于多线程)
/// </summary>
/// <param name="text">文本内容</param>
/// <param name="color">字体颜色</param>
/// <param name="size">字体大小</param>
/// <param name="isBold">是否粗体</param>
private void addText(string text, SolidColorBrush color, int size = 14, bool isBold = false) {
    Dispatcher.Invoke(() => {
        tb.Inlines.Add(new Run(text) {
        Foreground = color,
        FontSize = size,
        FontWeight = isBold ? FontWeights.Bold : FontWeights.Normal
        });
    });
}

高级

TextBlock除了可以显示文字,还可以显示表情或图标。说是表情,实则是一种“特殊”字体——矢量图SVG。具代表性的是iconfont(网上有很多关于WPF如果使用iconfont的帖子,这里就不赘述了)。效果:

图中的“new” 是矢量图。下面两种方式都能得到图片中的效果。

XAML代码

<TextBlock x:Name="tb"  FontSize="14" TextWrapping="Wrap">
    <Run Foreground="Blue" FontWeight="Bold">第一行</Run>
    <LineBreak/>
    <Run Text="第二行" FontSize="16"/>
    <Path x:Name="newIcon" Width="32" Height="16" VerticalAlignment="Bottom" Data="{StaticResource NewIcon}" Fill="Red" Stretch="Fill"/>
</TextBlock>

CS代码

string svg = "M505.6512 39.0144c-261.2224 3.4816-470.1184 218.112-466.6368";
System.Windows.Shapes.Path path = new System.Windows.Shapes.Path {
    Width = 32,
    Height = 16,
    VerticalAlignment = VerticalAlignment.Bottom,
    Data = Geometry.Parse(svg),
    Fill = Brushes.Red,
    Stretch = Stretch.Fill    // 重要。默认None(不显示)
};
tb.Inlines.Add(path);

通过以上对TextBlock的了解,我们就可以实现一些具有实用性的功能 ^_^

 

创意玩法

有如下使用情景:当用户打开程序后,后台与服务器连接并检查是否有新版本发布。当存在新版本时我们期望有所提示能令用户察觉。例如在菜单项MenuItem的文本后面显示一个图标,效果如下(有新版本时显示“new”图标,否则不显示):

这里主要分为两部分:MenuItem.Icon(左边的绿色图标)和MenuItem.Header(右边的文字+红色图标)。代码:

<Menu Background="Transparent">
    <MenuItem Header="帮助">
        <MenuItem FontSize="14">
            <MenuItem.Icon>
                <Path Data="{StaticResource UpdateIcon}" Fill="Green" Stretch="Fill"/>
            </MenuItem.Icon>
            <MenuItem.Header>
                <TextBlock>
                    <Run Text="检查更新 "/>
                    <Path x:Name="newIcon" Width="32" Height="16" Stretch="Fill" VerticalAlignment="Bottom" Data="{StaticResource NewIcon}"/>
                </TextBlock>
            </MenuItem.Header>
        </MenuItem>
        <Separator/>
    </MenuItem>
</Menu>

于是利用前面讲到的内容就能轻松实现在菜单项MenuItem的文字后加图标的效果了。

剩下的动态显示图标的操作就简单了,只需要在cs代码中设置Path的Fill属性的颜色就可以了:

new System.Threading.Thread(() => {

    // TODO:你的检查更新的业务代码

    Dispatcher.Invoke(() => {
        // 有两种方式设置Fill。根据实际情况选择吧
        // 1、直接设置颜色
        newIcon.Fill = Brushes.Red;
        // 2、调用资源文件中的颜色
        newIcon.SetResourceReference(Shape.FillProperty, "ColorBrush");
    });
}).Start();

注意:在上面的xaml代码中并没对newIcon设置Fill,所以初始状态下“new”图标是不显示的。如果需要在cs代码中令显示的图标隐藏起来不显示(例如:热更新完成),将Fill设置为Brushes.Transparent(透明)即可。

 

 进阶

上面的创意玩法用于单一选项时还可以。但如果有多个Path(或组件)需要标记时,为了修改而命名多个控件显然不是一个好的操作。为了能够在诸如TreeView之类的容器中实现控制多个控件的效果,这里对创意玩法的代码做一些调整。

下面以“年级”TreeView为例说明:TreeView中有不同学部,学部之下有不同年级(如下)。假设现在要将高二的Path由隐藏转为可视。

P.S.. 实际应用中可以使用模版的方式实现,这里为了方便讲解就不用了。

<TreeView x:Name="tree">
    <TreeViewItem Header="小学" IsExpanded="True" IsSelected="False" FontSize="16">
    </TreeViewItem>
    <TreeViewItem Header="初中" IsExpanded="True" IsSelected="False" FontSize="16">
    </TreeViewItem>
    <TreeViewItem Header="高中" IsExpanded="True" IsSelected="False" FontSize="16">
        <TreeViewItem FontSize="14">
            <TreeViewItem.Header>
                <TextBlock>
                    <Run Text="高一"/>
                    <!-- &#9;是制表符 -->
                    <Run Text="&#9;&#9;"/>
                    <Path Data="M535.9 165.2l96.5 195.4c3.9 7.9 11.4 13.3 20.1 14.6l215.7 31.3c21.9 3.2 30.6 30 14.8 45.5L726.8 604.1c-6.3 6.1-9.1 14.9-7.7 23.6L756 842.5c3.7 21.8-19.1 38.4-38.7 28.1L524.4 769.2c-7.8-4.1-17-4.1-24.8 0L306.7 870.6c-19.6 10.3-42.4-6.3-38.7-28.1l36.8-214.8c1.5-8.6-1.4-17.5-7.7-23.6L141.1 452c-15.8-15.4-7.1-42.3 14.8-45.5l215.7-31.3c8.7-1.3 16.2-6.7 20.1-14.6l96.5-195.4c9.7-19.9 37.9-19.9 47.7 0z" Margin="0,-3" Visibility="Hidden" Height="16" Width="16" Fill="Orange" Stretch="Fill"/>
                </TextBlock>
            </TreeViewItem.Header>
        </TreeViewItem>
        <TreeViewItem FontSize="14">
            <TreeViewItem.Header>
                <TextBlock>
                    <Run Text="高二"/>
                    <Run Text="&#9;&#9;"/>
                    <Path Data="M535.9 165.2l96.5 195.4c3.9 7.9 11.4 13.3 20.1 14.6l215.7 31.3c21.9 3.2 30.6 30 14.8 45.5L726.8 604.1c-6.3 6.1-9.1 14.9-7.7 23.6L756 842.5c3.7 21.8-19.1 38.4-38.7 28.1L524.4 769.2c-7.8-4.1-17-4.1-24.8 0L306.7 870.6c-19.6 10.3-42.4-6.3-38.7-28.1l36.8-214.8c1.5-8.6-1.4-17.5-7.7-23.6L141.1 452c-15.8-15.4-7.1-42.3 14.8-45.5l215.7-31.3c8.7-1.3 16.2-6.7 20.1-14.6l96.5-195.4c9.7-19.9 37.9-19.9 47.7 0z" Margin="0,-3" Visibility="Hidden" Height="16" Width="16" Fill="Orange" Stretch="Fill"/>
                </TextBlock>
            </TreeViewItem.Header>
        </TreeViewItem>
        <TreeViewItem FontSize="14">
            <TreeViewItem.Header>
                <TextBlock>
                    <Run Text="高三"/>
                    <Run Text="&#9;&#9;"/>
                    <Path Data="M535.9 165.2l96.5 195.4c3.9 7.9 11.4 13.3 20.1 14.6l215.7 31.3c21.9 3.2 30.6 30 14.8 45.5L726.8 604.1c-6.3 6.1-9.1 14.9-7.7 23.6L756 842.5c3.7 21.8-19.1 38.4-38.7 28.1L524.4 769.2c-7.8-4.1-17-4.1-24.8 0L306.7 870.6c-19.6 10.3-42.4-6.3-38.7-28.1l36.8-214.8c1.5-8.6-1.4-17.5-7.7-23.6L141.1 452c-15.8-15.4-7.1-42.3 14.8-45.5l215.7-31.3c8.7-1.3 16.2-6.7 20.1-14.6l96.5-195.4c9.7-19.9 37.9-19.9 47.7 0z" Margin="0,-3" Visibility="Hidden" Height="16" Width="16" Fill="Orange" Stretch="Fill"/>
                </TextBlock>
            </TreeViewItem.Header>
        </TreeViewItem>
    </TreeViewItem>
</TreeView>

后台查找目标代码: 

foreach (TreeViewItem tvi in tree.Items) {    // 获取一级控件,即:小学、初中、高中
    foreach (TreeViewItem child in tvi.Items) {    // 获取二级控件,例:高一、高二、高三
        header = ((Run)((TextBlock)child.Header).Inlines.ElementAt(0)).Text;
        // 只有标题为“高二”的组件可显示,其它隐藏
        ((Path)((InlineUIContainer)((TextBlock)child.Header).Inlines.ElementAt(4)).Child).Visibility = header.Equals("高二") ? Visibility.Visible : Visibility.Hidden;
    }
}

解析:首先通过 (TextBlock)child.Header 得到自定义的Header控件。然后通过属性 Inlines 得到TextBlock的所有内容。(这里开始有点绕)Inlines得到的是InlineCollection类对象(相当于数组),它装载的都是Inline类对象。平时在TextBlock中使用的LineBreak、Run、Span等等都是派生自Inline类。所以属性Inlines包含了在xaml中定义的文本和自定义控件。

注意:这里有个坑,例子中TextBlock里面定义了3个元素,但实际有5个元素;程序会在元素与元素间填充一个内容为空格的Run对象(解决方法:多个元素写在同一行)。

还有一点,按照TextBlock对内容的要求,正常情况下是不能直接添加控件的,必须是Inline类对象。这里就需要用到InlineUIContainer 类了,它的作用相当于桥梁,使普通控件能够添加到TextBlock里,使其表现力更加丰富。例子中当添加Path到TextBlock中时,实际上是帮我们做了如下的简化(两者效果一样):

// 标准版
<TextBlock>
    <Run Text="高一"/>
    <Run Text="&#9;&#9;"/>
    <InlineUIContainer>
        <Path Data="M535.9 165.2l96.5 195.4c3.9 7.9 11.4 13.3 20.1 14.6l215.7 31.3c21.9 3.2 30.6 30 14.8 45.5L726.8 604.1c-6.3 6.1-9.1 14.9-7.7 23.6L756 842.5c3.7 21.8-19.1 38.4-38.7 28.1L524.4 769.2c-7.8-4.1-17-4.1-24.8 0L306.7 870.6c-19.6 10.3-42.4-6.3-38.7-28.1l36.8-214.8c1.5-8.6-1.4-17.5-7.7-23.6L141.1 452c-15.8-15.4-7.1-42.3 14.8-45.5l215.7-31.3c8.7-1.3 16.2-6.7 20.1-14.6l96.5-195.4c9.7-19.9 37.9-19.9 47.7 0z" Margin="0,-3" Visibility="Hidden" Height="16" Width="16" Fill="Orange" Stretch="Fill"/>
    </InlineUIContainer>
</TextBlock>

// 简化版
<TextBlock>
    <Run Text="高一"/>
    <Run Text="&#9;&#9;"/>
    <!--UI控件自动封装到InlineUIContainer中-->
    <Path Data="M535.9 165.2l96.5 195.4c3.9 7.9 11.4 13.3 20.1 14.6l215.7 31.3c21.9 3.2 30.6 30 14.8 45.5L726.8 604.1c-6.3 6.1-9.1 14.9-7.7 23.6L756 842.5c3.7 21.8-19.1 38.4-38.7 28.1L524.4 769.2c-7.8-4.1-17-4.1-24.8 0L306.7 870.6c-19.6 10.3-42.4-6.3-38.7-28.1l36.8-214.8c1.5-8.6-1.4-17.5-7.7-23.6L141.1 452c-15.8-15.4-7.1-42.3 14.8-45.5l215.7-31.3c8.7-1.3 16.2-6.7 20.1-14.6l96.5-195.4c9.7-19.9 37.9-19.9 47.7 0z" Margin="0,-3" Visibility="Hidden" Height="16" Width="16" Fill="Orange" Stretch="Fill"/>
</TextBlock>

嵌入一个面板也是可以的(官方例子)。

正是由于TextBlock对外部控件的处理方式,所以cs代码中索引4得到是InlineUIContainer对象而不是Path对象。然后通过属性 Child 得到Path对象。

吐槽一句,个人认为WPF还是很优秀的(至少在我用过的UI中)。虽有官方中文文档支持,可惜细节的描述不够,网上资讯少之又少。今天写的这些还是自己看文档 + 不断调试摸索出来的。不然谁知道它可以简写,取组件还得通过InlineUIContainer对象啊。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值