2013年5月18日土曜日

wpf : ToolTipの幅

前の投稿でToolTipの改行について書きました。 簡単なToolTipだけならこれでもいいかもしれませんが、ファイルから読み込んだ情報を表示する時のなど、表示内容が固定ではないときには上手いやり方ではありません。 よりよいやり方の、最大幅を指定して自動で改行させる方法が分かったので書いておきます。

ToolTipはLabelやButtonと同じようにContentを設定できます。 つまり、Image、TextBlock、Gridなどを組み合わせて複雑なレイアウトにすることができます。 それについて説明するのはちょっと面倒なので、ここではstringのみを表示する場合について説明します。 (この投稿の内容を応用すれば複雑なレイアウトにも使えます。) 一言で言えば、ToolTipにTextBlockを載せます。

まずは一番簡単な、Contentに直接TextBlockを載せる方法です。

<Label>
    <Label.ToolTip>
        <TextBlock TextWrapping="Wrap" MaxWidth="200">
            吾輩は猫である。名前はまだ無い。どこで生れたかとんと見当がつかぬ。何でも薄暗いじめじめした所でニャーニャー泣いていた事だけは記憶している。吾輩はここで始めて人間というものを見た。しかもあとで聞くとそれは書生という人間中で一番獰悪な種族であったそうだ。
        </TextBlock>
    </Label.ToolTip>
    MainWindow
</Label>

TextBlockはTextWrappingプロパティを設定すれば自動で改行してくれます。 それをToolTipのContentに設定します。 MaxWidthやWidthを設定しないと改行されないので注意。

これだと全部のToolTipに対して同じ事を書かなければならず、面倒です。 Styleに書いて自動で全部のToolTipに適用されるようにしましょう。

<Window x:Class="ToolTipTest.TestWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="TestWindow" Height="200" Width="200">
    <Window.Resources>
        <Style TargetType="ToolTip">
            <Setter Property="ContentTemplate">
                <Setter.Value>
                    <DataTemplate>
                        <TextBlock TextWrapping="Wrap" MaxWidth="300" Text="{Binding}"/>
                    </DataTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </Window.Resources>
    <Label ToolTip="吾輩は猫である。名前はまだ無い。どこで生れたかとんと見当がつかぬ。何でも薄暗いじめじめした所でニャーニャー泣いていた事だけは記憶している。吾輩はここで始めて人間というものを見た。しかもあとで聞くとそれは書生という人間中で一番獰悪な種族であったそうだ。">
        TestWindow
    </Label>
</Window>

見ての通り、TextプロパティとContentをバインドするのでContentの中身は文字列じゃないとダメです。 このやり方だと、Viewport3Dの中のModelUIElement3Dに書かれたToolTipにも適用されるようです。 MaxWidthもBindingすれば、中身によってToolTipの幅を変更することもできます。 そのときはテキストの長さとToolTipの幅との変換をするIValueConverterとかが必要になるかもしれません(未確認)。

テキストの長さによって表示時間を変える場合はToolTipの親の添付プロパティToolTipService.ShowDurationを書き換えなければなりません。 DataTemplateの中からバインドするには、添付ビヘイビアとかを作らなきゃダメかな? これも未確認です。 単純にアプリケーション全体のToolTipの表示時間を一括で設定するなら次のコードでできます。

ToolTipService.ShowDurationProperty.OverrideMetadata(
        typeof(DependencyObject),
        new FrameworkPropertyMetadata(/*適切な値*/Int32.MaxValue)
);

ToolTipService.ShowDurationのデフォルト値を上書きします。 AppクラスやMainWindowなどのコンストラクタにでも書いておけばok。

なんかの理由でxamlで書きたくない場合、C#のコードでTemplateを書くこともできます。

public static void AddToolTipStyleToResources(ResourceDictionary rd, double widthMax)
{
    var styleToolTip = new Style(typeof(ToolTip));
    var template = new DataTemplate();
    var templateElement = new FrameworkElementFactory(typeof(TextBlock));
    templateElement.SetValue(TextBlock.MaxWidthProperty, widthMax);
    templateElement.SetValue(TextBlock.TextWrappingProperty, TextWrapping.Wrap);
    templateElement.SetBinding(TextBlock.TextProperty, new Binding());
    template.VisualTree = templateElement;
    styleToolTip.Setters.Add(new Setter(System.Windows.Controls.ToolTip.ContentTemplateProperty, template));
    rd[typeof(ToolTip)] = styleToolTip;
}

Windowクラスのコンストラクタで次のように書いたりして使います。

public MainWindow()
{
    InitializeComponent();
    AddToolTipStyleToResources(this.Resources, 150);
}