WPFウィンドウにTextBlockがあります。
<TextBlock>
Some <Bold>formatted</Bold> text.
</TextBlock>
レンダリングするとこんな感じ、
一部のフォーマット済みテキスト。
私の質問は、このインライン「コンテンツ」をアプリケーションのリソースにバインドできるかどうかです。
私は限りました:
アプリケーションリソース文字列を作成し、
myText="Some <Bold>formatted</Bold> text."
および次のxaml(簡潔にするために一部のコードを省略)
<Window xmlns:props="clr-namespace:MyApp.Properties">
<Window.Resources>
<props:Resources x:Key="Resources"/>
</Window.Resources>
<TextBlock x:Name="Try1"
Text="{Binding Source={StaticResource Resources} Path=myText}"/>
<TextBlock x:Name="Try2">
<Binding Source="{StaticResource Resources}" Path="myText" />
</TextBlock>
</Window>
Try1は、タグを配置してレンダリングし、フォーマットには影響しません。
<Bold>フォーマットされた<Bold>テキスト。
リソース「myText」はタイプInlineではなく文字列であるため、Try2はコンパイルもレンダリングもしません。
これは一見シンプルな作業は可能ですか?
これは、再帰的にテキストをフォーマットするための私の変更されたコードです。太字、斜体、下線、およびLineBreakを処理しますが、より多くをサポートするように簡単に拡張できます(switchステートメントを変更)。
public static class MyBehavior
{
public static string GetFormattedText(DependencyObject obj)
{
return (string)obj.GetValue(FormattedTextProperty);
}
public static void SetFormattedText(DependencyObject obj, string value)
{
obj.SetValue(FormattedTextProperty, value);
}
public static readonly DependencyProperty FormattedTextProperty =
DependencyProperty.RegisterAttached("FormattedText",
typeof(string),
typeof(MyBehavior),
new UIPropertyMetadata("", FormattedTextChanged));
static Inline Traverse(string value)
{
// Get the sections/inlines
string[] sections = SplitIntoSections(value);
// Check for grouping
if (sections.Length.Equals(1))
{
string section = sections[0];
string token; // E.g <Bold>
int tokenStart, tokenEnd; // Where the token/section starts and ends.
// Check for token
if (GetTokenInfo(section, out token, out tokenStart, out tokenEnd))
{
// Get the content to further examination
string content = token.Length.Equals(tokenEnd - tokenStart) ?
null :
section.Substring(token.Length, section.Length - 1 - token.Length * 2);
switch (token)
{
case "<Bold>":
return new Bold(Traverse(content));
case "<Italic>":
return new Italic(Traverse(content));
case "<Underline>":
return new Underline(Traverse(content));
case "<LineBreak/>":
return new LineBreak();
default:
return new Run(section);
}
}
else return new Run(section);
}
else // Group together
{
Span span = new Span();
foreach (string section in sections)
span.Inlines.Add(Traverse(section));
return span;
}
}
/// <summary>
/// Examines the passed string and find the first token, where it begins and where it ends.
/// </summary>
/// <param name="value">The string to examine.</param>
/// <param name="token">The found token.</param>
/// <param name="startIndex">Where the token begins.</param>
/// <param name="endIndex">Where the end-token ends.</param>
/// <returns>True if a token was found.</returns>
static bool GetTokenInfo(string value, out string token, out int startIndex, out int endIndex)
{
token = null;
endIndex = -1;
startIndex = value.IndexOf("<");
int startTokenEndIndex = value.IndexOf(">");
// No token here
if (startIndex < 0)
return false;
// No token here
if (startTokenEndIndex < 0)
return false;
token = value.Substring(startIndex, startTokenEndIndex - startIndex + 1);
// Check for closed token. E.g. <LineBreak/>
if (token.EndsWith("/>"))
{
endIndex = startIndex + token.Length;
return true;
}
string endToken = token.Insert(1, "/");
// Detect nesting;
int nesting = 0;
int temp_startTokenIndex = -1;
int temp_endTokenIndex = -1;
int pos = 0;
do
{
temp_startTokenIndex = value.IndexOf(token, pos);
temp_endTokenIndex = value.IndexOf(endToken, pos);
if (temp_startTokenIndex >= 0 && temp_startTokenIndex < temp_endTokenIndex)
{
nesting++;
pos = temp_startTokenIndex + token.Length;
}
else if (temp_endTokenIndex >= 0 && nesting > 0)
{
nesting--;
pos = temp_endTokenIndex + endToken.Length;
}
else // Invalid tokenized string
return false;
} while (nesting > 0);
endIndex = pos;
return true;
}
/// <summary>
/// Splits the string into sections of tokens and regular text.
/// </summary>
/// <param name="value">The string to split.</param>
/// <returns>An array with the sections.</returns>
static string[] SplitIntoSections(string value)
{
List<string> sections = new List<string>();
while (!string.IsNullOrEmpty(value))
{
string token;
int tokenStartIndex, tokenEndIndex;
// Check if this is a token section
if (GetTokenInfo(value, out token, out tokenStartIndex, out tokenEndIndex))
{
// Add pretext if the token isn't from the start
if (tokenStartIndex > 0)
sections.Add(value.Substring(0, tokenStartIndex));
sections.Add(value.Substring(tokenStartIndex, tokenEndIndex - tokenStartIndex));
value = value.Substring(tokenEndIndex); // Trim away
}
else
{ // No tokens, just add the text
sections.Add(value);
value = null;
}
}
return sections.ToArray();
}
private static void FormattedTextChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
string value = e.NewValue as string;
TextBlock textBlock = sender as TextBlock;
if (textBlock != null)
textBlock.Inlines.Add(Traverse(value));
}
}
編集: (スプークが提案)
短いバージョンですが、テキストがXML有効である必要があります。
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Xml;
// (...)
public static class TextBlockHelper
{
#region FormattedText Attached dependency property
public static string GetFormattedText(DependencyObject obj)
{
return (string)obj.GetValue(FormattedTextProperty);
}
public static void SetFormattedText(DependencyObject obj, string value)
{
obj.SetValue(FormattedTextProperty, value);
}
public static readonly DependencyProperty FormattedTextProperty =
DependencyProperty.RegisterAttached("FormattedText",
typeof(string),
typeof(TextBlockHelper),
new UIPropertyMetadata("", FormattedTextChanged));
private static void FormattedTextChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
string value = e.NewValue as string;
TextBlock textBlock = sender as TextBlock;
if (textBlock != null)
{
textBlock.Inlines.Clear();
textBlock.Inlines.Add(Process(value));
}
}
#endregion
static Inline Process(string value)
{
XmlDocument doc = new XmlDocument();
doc.LoadXml(value);
Span span = new Span();
InternalProcess(span, doc.ChildNodes[0]);
return span;
}
private static void InternalProcess(Span span, XmlNode xmlNode)
{
foreach (XmlNode child in xmlNode)
{
if (child is XmlText)
{
span.Inlines.Add(new Run(child.InnerText));
}
else if (child is XmlElement)
{
Span spanItem = new Span();
InternalProcess(spanItem, child);
switch (child.Name.ToUpper())
{
case "B":
case "BOLD":
Bold bold = new Bold(spanItem);
span.Inlines.Add(bold);
break;
case "I":
case "ITALIC":
Italic italic = new Italic(spanItem);
span.Inlines.Add(italic);
break;
case "U":
case "UNDERLINE":
Underline underline = new Underline(spanItem);
span.Inlines.Add(underline);
break;
}
}
}
}
}
そして使用例:
<RootItem xmlns:u="clr-namespace:MyApp.Helpers">
<TextBlock u:TextBlockHelper.FormattedText="{Binding SomeProperty}" />
</RootItem>
アタッチされたビヘイビアーを使用するのはどうですか?以下のコードは太字のタグのみを処理します。太字にする必要のある各単語は、太字のタグで囲む必要があります。おそらく、クラスに他の形式も受け入れさせたいでしょう。また、スペースはより適切に処理する必要があります。クラスは連続するスペースを取り除き、最後に1つ追加します。したがって、以下のクラスをデモコードとしてのみ検討してください。これを使用するには、さらに作業が必要ですが、開始する必要があります。
XAML:
<Window x:Class="FormatTest.Window1"
xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.Microsoft.com/winfx/2006/xaml"
xmlns:FormatTest="clr-namespace:FormatTest"
Title="Window1" Height="300" Width="300">
<TextBlock FormatTest:FormattedTextBehavior.FormattedText="{Binding Path=Text}" />
</Window>
コードビハインド:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
namespace FormatTest
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
DataContext = this;
}
public string Text { get { return "Some <Bold>formatted</Bold> text."; } }
}
public static class FormattedTextBehavior
{
public static string GetFormattedText(DependencyObject obj)
{
return (string)obj.GetValue(FormattedTextProperty);
}
public static void SetFormattedText(DependencyObject obj, string value)
{
obj.SetValue(FormattedTextProperty, value);
}
public static readonly DependencyProperty FormattedTextProperty =
DependencyProperty.RegisterAttached("FormattedText",
typeof(string),
typeof(FormattedTextBehavior),
new UIPropertyMetadata("", FormattedTextChanged));
private static void FormattedTextChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
TextBlock textBlock = sender as TextBlock;
string value = e.NewValue as string;
string[] tokens = value.Split(' ');
foreach (string token in tokens)
{
if (token.StartsWith("<Bold>") && token.EndsWith("</Bold>"))
{
textBlock.Inlines.Add(new Bold(new Run(token.Replace("<Bold>", "").Replace("</Bold>", "") + " ")));
}
else
{
textBlock.Inlines.Add(new Run(token + " "));
}
}
}
}
}
編集:
この行、
<props:Resources x:Key="Resources"/>
project.Properties.Resources名前空間にアクセスするための悪いアプローチです。再コンパイルすると、厄介な不具合が発生します。
x:Static
を使用して、このような処理を行う方がはるかに優れています。
Text="{x:Static props:Resources.SomeText}"
あなたのバインディングで。 Thxから Ben
わかりました、これは私がやった方法です。完璧ではありませんが、動作します。
FormattedTextというプロジェクトリソースがあることに注意してください。
cs:
// TextBlock with a bindable InlineCollection property.
// Type is List(Inline) not InlineCollection becuase
// InlineCollection makes the IDE xaml parser complain
// presumably this is caused by an inherited attribute.
public class BindableTextBlock : TextBlock
{
public static readonly DependencyProperty InlineCollectionProperty =
DependencyProperty.Register(
"InlineCollection",
typeof(List<Inline>),
typeof(BindableTextBlock),
new UIPropertyMetadata(OnInlineCollectionChanged));
private static void OnInlineCollectionChanged(DependencyObject sender,
DependencyPropertyChangedEventArgs e)
{
BinableTextBlock instance = sender as BindableTextBlock;
if (instance != null)
{
List<Inline> newText = e.NewValue as List<Inline>;
if (newText != null)
{
// Clear the underlying Inlines property
instance.Inlines.Clear();
// Add the passed List<Inline> to the real Inlines
instance.Inlines.AddRange(newText.ToList());
}
}
}
public List<Inline> InlineCollection
{
get
{
return (List<Inline>)GetValue(InlineCollectionProperty);
}
set
{
SetValue(InlineCollectionProperty, value);
}
}
}
// Convertor between a string of xaml with implied run elements
// and a generic list of inlines
[ValueConversion(typeof(string), typeof(List<Inline>))]
public class StringInlineCollectionConvertor : IValueConverter
{
public object Convert(object value,
Type targetType,
object parameter,
System.Globalization.CultureInfo culture)
{
string text = value as String;
// a surrogate TextBlock to Host an InlineCollection
TextBlock results = new TextBlock();
if (!String.IsNullOrEmpty(text))
{
//Arbritary literal acting as a replace token,
//must not exist in the empty xaml definition.
const string Replace = "xxx";
// add a dummy run element and replace it with the text
results.Inlines.Add(new Run(Replace));
string resultsXaml = XamlWriter.Save(results);
string resultsXamlWithText = resultsXaml.Replace(Replace, text);
// deserialise the xaml back into our TextBlock
results = XamlReader.Parse(resultsXamlWithText) as TextBlock;
}
return results.Inlines.ToList<Inline>();
}
// Not clear when this will be called but included for completeness
public object ConvertBack(
object value,
Type targetType,
object parameter,
System.Globalization.CultureInfo culture)
{
String results = String.Empty;
InlineCollection inlines = value as InlineCollection;
if (inlines != null)
{
//read the xaml as xml and return the "content"
var reader =
XElement.Parse(XamlWriter.Save(inlines)).CreateReader();
reader.MoveToContent();
results = reader.ReadInnerXml();
}
return results;
}
}
xaml:
<Window
xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.Microsoft.com/winfx/2006/xaml"
xmlns:props="clr-namespace:Project.Properties"
xmlns:local="clr-namespace:Project">
<Window.Resources>
<props:Resources x:Key="Resources"/>
<local:StringInlineCollectionConvertor x:Key="InlineConvert"/>
</Window.Resources>
<local:BindableTextBlock InlineCollection="
{Binding Source={StaticResource Resources},
Path=FormattedText,
Converter={StaticResource InlineConvert}}"/>
</Window>
2つのクラスを作りました。コレクションをStringに変換したり、Stringに変換したりするための「バインド可能な」InlineCollectionおよびIValueConverterを持つサブクラス化されたTextBlock。
プロパティのタイプとして直接InlineCollectionを使用すると、VS2010は不満を示しましたが、コードはまだ正常に実行されました。インラインの一般的なリストに変更しました。 InlineCollectionにはコンストラクタがないことをVSに伝える継承された属性があると思います。
InlineCollectionプロパティをBindableTextBlockのContentPropertyにしようとしましたが、問題が発生し、時間切れになりました。次のステップに進んで、それについて教えてください。
正誤表をお詫び申し上げますが、このコードは書き起こして消毒する必要がありました。
これを行うより良い方法がある場合、確かにあるはずです。それも教えてください。この機能が組み込まれていればいいのではないでしょうか、それとも見逃していませんか?
私はアプリケーションでこれを行う必要があり、TextBlockインラインで通常可能な多くのマークアップをサポートする必要があったため、上記のWallstreet Programmerの回答を採用しました(これは美しく機能し、このトピックで見つけた他のほとんどの回答よりも複雑ではありません)。そしてそれを拡張しました。他の誰かがこれが便利だと思うかもしれません。
私はこれをすべてのタグで完全にテストしていませんが、テストしたものはすべて魅力のように機能しました。また、これは世界で最速のコードではないのではないかと思いますが、ListViewで数千のフォーマットされたメッセージを使用した私の独自のテストは、意外と不快なように見えました。 YMMV。コードは以下の通りです:
XAML:
<Window x:Class="FormatTest.Window1"
xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.Microsoft.com/winfx/2006/xaml"
xmlns:FormatTest="clr-namespace:FormatTest"
Title="Window1" Height="300" Width="300">
<TextBlock FormatTest:FormattedTextBehavior.FormattedText="{Binding Path=Text}" />
</Window>
C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;
namespace FormatTest
{
public static class FormattedTextBehavior
{
public class TextPart
{
public String mType = String.Empty;
public Inline mInline = null;
public InlineCollection mChildren = null;
public TextPart() {}
public TextPart(String t, Inline inline, InlineCollection col)
{
mType = t;
mInline = inline;
mChildren = col;
}
}
private static Regex mRegex = new Regex(@"<(?<Span>/?[^>]*)>", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static Regex mSpanRegex = new Regex("(?<Key>[^\\s=]+)=\"(?<Val>[^\\s\"]*)\"", RegexOptions.Compiled | RegexOptions.IgnoreCase);
public static string GetFormattedText(DependencyObject obj)
{
return (string)obj.GetValue(FormattedTextProperty);
}
public static void SetFormattedText(DependencyObject obj, string value)
{
obj.SetValue(FormattedTextProperty, value);
}
public static readonly DependencyProperty FormattedTextProperty =
DependencyProperty.RegisterAttached("FormattedText",
typeof(string),
typeof(FormattedTextBehavior),
new UIPropertyMetadata("", FormattedTextChanged));
private static void FormattedTextChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
TextBlock textBlock = sender as TextBlock;
FormatText(e.NewValue as string, new TextPart("TextBlock", null, textBlock.Inlines));
}
public static void FormatText(String s, TextPart root)
{
int len = s.Length;
int lastIdx = 0;
List<TextPart> parts = new List<TextPart>();
parts.Add(root);
Match m = mRegex.Match(s);
while (m.Success)
{
String tag = m.Result("${Span}");
if (tag.StartsWith("/"))
{
String prevStr = s.Substring(lastIdx, m.Index - lastIdx);
TextPart part = parts.Last();
if (!String.IsNullOrEmpty(prevStr))
{
if (part.mChildren != null)
{
part.mChildren.Add(new Run(prevStr));
}
else if (part.mInline is Run)
{
(part.mInline as Run).Text = prevStr;
}
}
if (!tag.Substring(1).Equals(part.mType, StringComparison.InvariantCultureIgnoreCase))
{
Logger.LogD("Mismatched End Tag '" + tag.Substring(1) + "' (expected </" + part.mType + ">) at position " + m.Index.ToString() + " in String '" + s + "'");
}
if (parts.Count > 1)
{
parts.RemoveAt(parts.Count - 1);
TextPart parentPart = parts.Last();
if (parentPart.mChildren != null)
{
parentPart.mChildren.Add(part.mInline);
}
}
}
else
{
TextPart prevPart = parts.Last();
String prevStr = s.Substring(lastIdx, m.Index - lastIdx);
if (!String.IsNullOrEmpty(prevStr))
{
if (prevPart.mChildren != null)
{
prevPart.mChildren.Add(new Run(prevStr));
}
else if (prevPart.mInline is Run)
{
(prevPart.mInline as Run).Text = prevStr;
}
}
bool hasAttributes = false;
TextPart part = new TextPart();
if (tag.StartsWith("bold", StringComparison.InvariantCultureIgnoreCase))
{
part.mType = "BOLD";
part.mInline = new Bold();
part.mChildren = (part.mInline as Bold).Inlines;
}
else if (tag.StartsWith("underline", StringComparison.InvariantCultureIgnoreCase))
{
part.mType = "UNDERLINE";
part.mInline = new Underline();
part.mChildren = (part.mInline as Underline).Inlines;
}
else if (tag.StartsWith("italic", StringComparison.InvariantCultureIgnoreCase))
{
part.mType = "ITALIC";
part.mInline = new Italic();
part.mChildren = (part.mInline as Italic).Inlines;
}
else if (tag.StartsWith("linebreak", StringComparison.InvariantCultureIgnoreCase))
{
part.mType = "LINEBREAK";
part.mInline = new LineBreak();
}
else if (tag.StartsWith("span", StringComparison.InvariantCultureIgnoreCase))
{
hasAttributes = true;
part.mType = "SPAN";
part.mInline = new Span();
part.mChildren = (part.mInline as Span).Inlines;
}
else if (tag.StartsWith("run", StringComparison.InvariantCultureIgnoreCase))
{
hasAttributes = true;
part.mType = "RUN";
part.mInline = new Run();
}
else if (tag.StartsWith("hyperlink", StringComparison.InvariantCultureIgnoreCase))
{
hasAttributes = true;
part.mType = "HYPERLINK";
part.mInline = new Hyperlink();
part.mChildren = (part.mInline as Hyperlink).Inlines;
}
if (hasAttributes && part.mInline != null)
{
Match m2 = mSpanRegex.Match(tag);
while (m2.Success)
{
String key = m2.Result("${Key}");
String val = m2.Result("${Val}");
if (key.Equals("FontWeight", StringComparison.InvariantCultureIgnoreCase))
{
FontWeight fw = FontWeights.Normal;
try
{
fw = (FontWeight)new FontWeightConverter().ConvertFromString(val);
}
catch (Exception)
{
fw = FontWeights.Normal;
}
part.mInline.FontWeight = fw;
}
else if (key.Equals("FontSize", StringComparison.InvariantCultureIgnoreCase))
{
double fs = part.mInline.FontSize;
if (Double.TryParse(val, out fs))
{
part.mInline.FontSize = fs;
}
}
else if (key.Equals("FontStretch", StringComparison.InvariantCultureIgnoreCase))
{
FontStretch fs = FontStretches.Normal;
try
{
fs = (FontStretch)new FontStretchConverter().ConvertFromString(val);
}
catch (Exception)
{
fs = FontStretches.Normal;
}
part.mInline.FontStretch = fs;
}
else if (key.Equals("FontStyle", StringComparison.InvariantCultureIgnoreCase))
{
FontStyle fs = FontStyles.Normal;
try
{
fs = (FontStyle)new FontStyleConverter().ConvertFromString(val);
}
catch (Exception)
{
fs = FontStyles.Normal;
}
part.mInline.FontStyle = fs;
}
else if (key.Equals("FontFamily", StringComparison.InvariantCultureIgnoreCase))
{
if (!String.IsNullOrEmpty(val))
{
FontFamily ff = new FontFamily(val);
if (Fonts.SystemFontFamilies.Contains(ff))
{
part.mInline.FontFamily = ff;
}
}
}
else if (key.Equals("Background", StringComparison.InvariantCultureIgnoreCase))
{
Brush b = part.mInline.Background;
try
{
b = (Brush)new BrushConverter().ConvertFromString(val);
}
catch (Exception)
{
b = part.mInline.Background;
}
part.mInline.Background = b;
}
else if (key.Equals("Foreground", StringComparison.InvariantCultureIgnoreCase))
{
Brush b = part.mInline.Foreground;
try
{
b = (Brush)new BrushConverter().ConvertFromString(val);
}
catch (Exception)
{
b = part.mInline.Foreground;
}
part.mInline.Foreground = b;
}
else if (key.Equals("ToolTip", StringComparison.InvariantCultureIgnoreCase))
{
part.mInline.ToolTip = val;
}
else if (key.Equals("Text", StringComparison.InvariantCultureIgnoreCase) && part.mInline is Run)
{
(part.mInline as Run).Text = val;
}
else if (key.Equals("NavigateUri", StringComparison.InvariantCultureIgnoreCase) && part.mInline is Hyperlink)
{
(part.mInline as Hyperlink).NavigateUri = new Uri(val);
}
m2 = m2.NextMatch();
}
}
if (part.mInline != null)
{
if (tag.TrimEnd().EndsWith("/"))
{
if (prevPart.mChildren != null)
{
prevPart.mChildren.Add(part.mInline);
}
}
else
{
parts.Add(part);
}
}
}
lastIdx = m.Index + m.Length;
m = m.NextMatch();
}
if (lastIdx < (len - 1))
{
root.mChildren.Add(new Run(s.Substring(lastIdx)));
}
}
}
}
Behaviorを使用して実装したものと同じです。以下のコード:
public class FormatTextBlock : Behavior<System.Windows.Controls.TextBlock>
{
public static readonly DependencyProperty FormattedTextProperty =
DependencyProperty.Register(
"FormattedText",
typeof(string),
typeof(FormatTextBlock),
new PropertyMetadata(string.Empty, OnFormattedTextChanged));
public string FormattedText
{
get { return (string)AssociatedObject.GetValue(FormattedTextProperty); }
set { AssociatedObject.SetValue(FormattedTextProperty, value); }
}
private static void OnFormattedTextChanged(DependencyObject textBlock, DependencyPropertyChangedEventArgs eventArgs)
{
System.Windows.Controls.TextBlock currentTxtBlock = (textBlock as FormatTextBlock).AssociatedObject;
string text = eventArgs.NewValue as string;
if (currentTxtBlock != null)
{
currentTxtBlock.Inlines.Clear();
string[] strs = text.Split(new string[] { "<Bold>", "</Bold>" }, StringSplitOptions.None);
for (int i = 0; i < strs.Length; i++)
{
currentTxtBlock.Inlines.Add(new Run { Text = strs[i], FontWeight = i % 2 == 1 ? FontWeights.Bold : FontWeights.Normal });
}
}
}
}
XAML-名前空間のインポート
<UserControl x:Class="MyClass"
xmlns:i="http://schemas.Microsoft.com/expression/2010/interactivity"
xmlns:behav="clr-namespace:myAssembly.myNameSapce;Assembly=myAssembly"
>
次に、動作を次のように使用します。
<TextBlock TextWrapping="Wrap">
<i:Interaction.Behaviors>
<behav:FormatTextBlock FormattedText="{Binding Path=UIMessage}" />
</i:Interaction.Behaviors>
</TextBlock>
Vincents ソリューションにハイパーリンクと画像のサポートを追加しました:
public static class FormattedTextBlock
{
public static string GetFormattedText(DependencyObject obj)
{
return (string)obj.GetValue(FormattedTextProperty);
}
public static void SetFormattedText(DependencyObject obj, string value)
{
obj.SetValue(FormattedTextProperty, value);
}
public static readonly DependencyProperty FormattedTextProperty =
DependencyProperty.RegisterAttached("FormattedText",
typeof(string),
typeof(FormattedTextBlock),
new UIPropertyMetadata("", FormattedTextChanged));
static Inline Traverse(string value)
{
// Get the sections/inlines
string[] sections = SplitIntoSections(value);
// Check for grouping
if(sections.Length.Equals(1))
{
string section = sections[0];
string token; // E.g <Bold>
int tokenStart, tokenEnd; // Where the token/section starts and ends.
// Check for token
if(GetTokenInfo(section, out token, out tokenStart, out tokenEnd))
{
// Get the content to further examination
string content = token.Length.Equals(tokenEnd - tokenStart) ?
null :
section.Substring(token.Length, section.Length - 1 - token.Length * 2);
switch(token.ToUpper())
{
case "<B>":
case "<BOLD>":
/* <b>Bold text</b> */
return new Bold(Traverse(content));
case "<I>":
case "<ITALIC>":
/* <i>Italic text</i> */
return new Italic(Traverse(content));
case "<U>":
case "<UNDERLINE>":
/* <u>Underlined text</u> */
return new Underline(Traverse(content));
case "<BR>":
case "<BR/>":
case "<LINEBREAK/>":
/* Line 1<br/>line 2 */
return new LineBreak();
case "<A>":
case "<LINK>":
/* <a>{http://www.google.de}Google</a> */
var start = content.IndexOf("{");
var end = content.IndexOf("}");
var url = content.Substring(start + 1, end - 1);
var text = content.Substring(end + 1);
var link = new Hyperlink();
link.NavigateUri = new System.Uri(url);
link.RequestNavigate += Hyperlink_RequestNavigate;
link.Inlines.Add(text);
return link;
case "<IMG>":
case "<IMAGE>":
/* <image>pack://application:,,,/ProjectName;component/directory1/directory2/image.png</image> */
var image = new Image();
var bitmap = new BitmapImage(new Uri(content));
image.Source = bitmap;
image.Width = bitmap.Width;
image.Height = bitmap.Height;
var container = new InlineUIContainer();
container.Child = image;
return container;
default:
return new Run(section);
}
}
else return new Run(section);
}
else // Group together
{
Span span = new Span();
foreach(string section in sections)
span.Inlines.Add(Traverse(section));
return span;
}
}
static void Hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs e)
{
Process.Start(e.Uri.ToString());
}
/// <summary>
/// Examines the passed string and find the first token, where it begins and where it ends.
/// </summary>
/// <param name="value">The string to examine.</param>
/// <param name="token">The found token.</param>
/// <param name="startIndex">Where the token begins.</param>
/// <param name="endIndex">Where the end-token ends.</param>
/// <returns>True if a token was found.</returns>
static bool GetTokenInfo(string value, out string token, out int startIndex, out int endIndex)
{
token = null;
endIndex = -1;
startIndex = value.IndexOf("<");
int startTokenEndIndex = value.IndexOf(">");
// No token here
if(startIndex < 0)
return false;
// No token here
if(startTokenEndIndex < 0)
return false;
token = value.Substring(startIndex, startTokenEndIndex - startIndex + 1);
// Check for closed token. E.g. <LineBreak/>
if(token.EndsWith("/>"))
{
endIndex = startIndex + token.Length;
return true;
}
string endToken = token.Insert(1, "/");
// Detect nesting;
int nesting = 0;
int temp_startTokenIndex = -1;
int temp_endTokenIndex = -1;
int pos = 0;
do
{
temp_startTokenIndex = value.IndexOf(token, pos);
temp_endTokenIndex = value.IndexOf(endToken, pos);
if(temp_startTokenIndex >= 0 && temp_startTokenIndex < temp_endTokenIndex)
{
nesting++;
pos = temp_startTokenIndex + token.Length;
}
else if(temp_endTokenIndex >= 0 && nesting > 0)
{
nesting--;
pos = temp_endTokenIndex + endToken.Length;
}
else // Invalid tokenized string
return false;
} while(nesting > 0);
endIndex = pos;
return true;
}
/// <summary>
/// Splits the string into sections of tokens and regular text.
/// </summary>
/// <param name="value">The string to split.</param>
/// <returns>An array with the sections.</returns>
static string[] SplitIntoSections(string value)
{
List<string> sections = new List<string>();
while(!string.IsNullOrEmpty(value))
{
string token;
int tokenStartIndex, tokenEndIndex;
// Check if this is a token section
if(GetTokenInfo(value, out token, out tokenStartIndex, out tokenEndIndex))
{
// Add pretext if the token isn't from the start
if(tokenStartIndex > 0)
sections.Add(value.Substring(0, tokenStartIndex));
sections.Add(value.Substring(tokenStartIndex, tokenEndIndex - tokenStartIndex));
value = value.Substring(tokenEndIndex); // Trim away
}
else
{ // No tokens, just add the text
sections.Add(value);
value = null;
}
}
return sections.ToArray();
}
private static void FormattedTextChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
string value = e.NewValue as string;
TextBlock textBlock = sender as TextBlock;
if(textBlock != null)
textBlock.Inlines.Add(Traverse(value));
}
}
ありがとう Vincent 素晴らしいテンプレートをありがとう、それは魅力のように機能します!
私にとってこの作品:
XAML:
<phone:PhoneApplicationPage x:Class="MyAPP.Views.Class"
xmlns:utils="clr-namespace:MyAPP.Utils">
そしてあなたのTextBlock XAML:
<TextBlock utils:TextBlockHelper.FormattedText="{Binding Text}" />
コード:
public static class TextBlockHelper
{
public static string GetFormattedText(DependencyObject textBlock)
{
return (string)textBlock.GetValue(FormattedTextProperty);
}
public static void SetFormattedText(DependencyObject textBlock, string value)
{
textBlock.SetValue(FormattedTextProperty, value);
}
public static readonly DependencyProperty FormattedTextProperty =
DependencyProperty.RegisterAttached("FormattedText", typeof(string), typeof(TextBlock),
new PropertyMetadata(string.Empty, (sender, e) =>
{
string text = e.NewValue as string;
var textB1 = sender as TextBlock;
if (textB1 != null)
{
textB1.Inlines.Clear();
var str = text.Split(new string[] { "<b>", "</b>" }, StringSplitOptions.None);
for (int i = 0; i < str.Length; i++)
textB1.Inlines.Add(new Run { Text = str[i], FontWeight = i % 2 == 1 ? FontWeights.Bold : FontWeights.Normal });
}
}));
}
文字列バインディングで使用:
String Text = Text <b>Bold</b>;