Visual Studio とかでは当たり前のように表示されている出力ウィンドウのような部分で使われているのは
おそらく RichTextBox に類するものではないかと思っています.
テキストの種類によって色を変えたり太さを変えたりしてますよね.
自分のソフトでも同じようなものを実現しようとすると,
WPF では RichTextBox を使いたくなります.
RichTextBox は XAML 上では次のように使うようです.
1 2 3 4 5 6 7 8 9 10 |
<RichTextBox> <RichTextBox.Document> <FlowDocument> <Paragraph>111</Paragraph> <Paragraph Foreground="Blue">222</Paragraph> <Paragraph FontWeight="Bold">333</Paragraph> <Paragraph Background="Yellow">444</Paragraph> </FlowDocument> </RichTextBox.Document> </RichTextBox> |
Document プロパティは FlowDocument クラスのようで,
その下に Paragraph を並べることで文章を表示するようです.
じゃあ MVVM で RichTextBox の中身をデータバインディングしてほげほげしましょ.
と調子に乗って Document にバインドしようとすると次のように怒られてしまいました.
Document プロパティは DependencyProperty ではないようで,
このままではデータバインディングできないようです.
そこで CodePlex かどこかで紹介されていたネタがこちら.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
public class BindableRichTextBox : RichTextBox { #region 依存関係プロパティ public static readonly DependencyProperty DocumentProperty = DependencyProperty.Register("Document", typeof(FlowDocument), typeof(BindableRichTextBox), new UIPropertyMetadata(null, OnRichTextItemsChanged)); #endregion // 依存関係プロパティ #region 公開プロパティ public new FlowDocument Document { get { return (FlowDocument)GetValue(DocumentProperty); } set { SetValue(DocumentProperty, value); } } #endregion // 公開プロパティ #region イベントハンドラ private static void OnRichTextItemsChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { var control = sender as RichTextBox; if (control != null) { control.Document = e.NewValue as FlowDocument; } } #endregion // イベントハンドラ } |
DependencyProperty ではないのなら DependencyProperty にすればいいじゃない!
というわけで RichTextBox から派生させた BindableRichTextBox クラスを 定義します.
Document プロパティを改めて定義し直し,
Document プロパティが変更されたら RichTextBox クラスとしての Document プロパティに横流ししています.
さらに,このまま FlowDocument としてデータバインドするのもいいんですが,
出力ウィンドウのようなものを考えた場合,
もう少し情報量を制限したものとバインドしたほうがやりやすいと思うので,
次のようなクラスを作ります.
1 2 3 4 5 6 7 |
public class RichTextItem { public string Text { get; set; } public Brush Foreground { get; set; } public FontWeight FontWeight { get; set; } public Thickness Margin { get; set; } } |
文字列を格納する Text プロパティと,
それを修飾するための Foreground プロパティと FontWeight プロパティ.
それから行間を決める Margin プロパティです.
このクラスのコレクションをバインドして,
あとはコンバータで FlowDocument に変換させます.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
public class RichTextItemsToDocumentConverter : IValueConverter { public object Convert(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture) { var doc = new FlowDocument(); foreach (var item in value as ICollection<RichTextItem>) { var paragraph = new Paragraph(new Run(item.Text)) { Foreground = item.Foreground, FontWeight = item.FontWeight, Margin = item.Margin, }; doc.Blocks.Add(paragraph); } return doc; } public object ConvertBack(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture) { throw new System.NotImplementedException(); } } |
こうすることで例えば ObservableCollection<RichTextItem> 型の OutMessage プロパティを用いて
1 |
<local:BindableRichTextBox Document="{Binding OutMessage}" /> |
というような感じで Document プロパティにバインドできる RichTextBox ができあがります.
上の画像は Add ボタンでその上の TextBox の内容をランダムな色で追加するというもので,
10 行目を追加したところ.
後は自動的に末尾にスクロールさせたりとかすれば,
出力ウィンドウの再現もそう遠くはないはず.