かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

UWPで色付きTextBlockを作ろう

Textプロパティに所定の書式に従った文字列突っ込んだらいい感じに色つけて表示してくれるTextBlockみたいなのが欲しくなることってないでしょうか。

そんな時は、TextBlockを1枚ラップしたカスタムコントロールかユーザーコントロールを作ると捗ります。今回は、実装が簡単なユーザーコントロールを例に作ってみたいと思います。

ColoredTextBlockという名前でユーザーコントロールを作って以下のようにTextBlockを置きます。

<UserControl x:Class="ColoredTextBlock.ColoredTextBlock"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="using:ColoredTextBlock"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             mc:Ignorable="d"
             d:DesignHeight="300"
             d:DesignWidth="400">

    <Grid>
        <TextBlock x:Name="TextBlock" />
    </Grid>
</UserControl>

そして、このユーザーコントロールにText依存関係プロパティを作ります。Textが変更されたときのコールバックでTextをパースしてTextBlockのInlinesを組み立てます。

using System.Reflection;
using System.Xml.Linq;
using Windows.UI;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Documents;
using Windows.UI.Xaml.Media;

namespace ColoredTextBlock
{
    public sealed partial class ColoredTextBlock : UserControl
    {
        public static readonly DependencyProperty TextProperty =
            DependencyProperty.Register(
                nameof(Text),
                typeof(string),
                typeof(ColoredTextBlock),
                new PropertyMetadata(null, (s, e) =>
                {
                    ((ColoredTextBlock)s).Parse();
                }));

        public string Text
        {
            get { return (string)GetValue(TextProperty); }
            set { SetValue(TextProperty, value); }
        }

        private void Parse()
        {
            // ここで文字列をパースしてInlinesを組み立てる
        }

        public ColoredTextBlock()
        {
            this.InitializeComponent();
        }
    }
}

今回はXML形式でFontタグにColor属性で色を指定できるみたいなのをイメージして以下のようなパース処理を実装しました。

this.TextBlock.Inlines.Clear();
try
{
    var doc = XDocument.Parse(this.Text);
    foreach (var node in doc.Root.Nodes())
    {
        if (node.NodeType == System.Xml.XmlNodeType.Text)
        {
            var textNode = (XText)node;
            this.TextBlock.Inlines.Add(new Run
            {
                Text = textNode.Value
            });
        }
        else if (node.NodeType == System.Xml.XmlNodeType.Element)
        {
            var elm = (XElement)node;
            if (elm.Name != "Font")
            {
                continue;
            }

            var color = elm.Attribute("Color")?.Value ?? "Black";
            var text = elm.Value;

            var property = (Color)(typeof(Colors).GetTypeInfo().GetDeclaredProperty(color)?.GetValue(null) ?? Colors.Black);
            this.TextBlock.Inlines.Add(new Run
            {
                Text = text,
                Foreground = new SolidColorBrush(property)
            });
        }
    }
}
catch { }

以下のようにTextBoxの入力とバインドして使ってみます。

<Page x:Class="ColoredTextBlock.MainPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:ColoredTextBlock"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      mc:Ignorable="d">

    <StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <TextBox x:Name="TextBox"
                 Text="&lt;Doc&gt;&lt;Font Color='Blue'&gt;Hello&lt;/Font&gt; &lt;Font Color='Red'&gt;World&lt;/Font&gt;&lt;/Doc&gt;" />
        <local:ColoredTextBlock Text="{x:Bind TextBox.Text, Mode=OneWay}" />
    </StackPanel>
</Page>

実行すると以下のようになります。

f:id:okazuki:20160227221833p:plain

ソースコード

ソースコードの全体はGitHubに上げています。

github.com