WPF - TextBlock Text + Hyperlink
How would one generate this xaml from C# code instead:
Currently:
<TextBlock>
Click <Hyperlink Command="{Binding MyCommand}">here</Hyperlink> to continue.
</TextBlock>
What I want:
<TextBlock Text="{Binding MyTextWithHyperlink, Mode=OneWay}" />
public string MyTextWithHyperlink
{
get
{
return ""; //???
}
}
And yes, I have a valid reason to do it this way instead of in xaml. :)
UPDATE: This is why I want to return the String, because IDataError returns a string...
String IDataError.this[String columnName]
{
get
{
if (columnName == "MyProperty")
{
if (something1) return ""; //????
if (something2) return "some other string";
}
ret开发者_JAVA百科urn null;
}
}
Unfortunately there's no easy way to do that... As far as I know, the best you can do is return a XAML string and use a converter to parse it.
Warning: ugly code ahead...
XAML
<Window.Resources>
<local:XamlToTextBlockConverter x:Key="xamlConverter" />
</Window.Resources>
<Grid>
<ContentControl Content="{Binding MyTextWithHyperlink, Converter={StaticResource xamlConverter}}" />
</Grid>
Converter
public class XamlToTextBlockConverter : IValueConverter
{
#region Implementation of IValueConverter
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
string xaml = value as string;
if (xaml == null)
return Binding.DoNothing;
const string textBlockFormat =
@"<TextBlock xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation"">{0}</TextBlock>";
string fullXaml = string.Format(textBlockFormat, xaml);
return (TextBlock)XamlReader.Parse(fullXaml);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
ViewModel
public string MyTextWithHyperlink
{
get { return "Click <Hyperlink Command=\"{Binding MyCommand}\">here</Hyperlink> to continue"; }
}
Note the use of a ContentControl
rather than a TextBlock
: that's because the TextBlock.Text
property can only contain plain text, not a formatted document, and the Inlines
property cannot be bound because it's not a dependency property (and anyway it's readonly). Instead we manually create a TextBlock
in the converter and assign it to the content of the ContentControl
.
It's definitely not a very elegant solution, but it works...
So you're trying to dictate the visual tree from your view model? Bad idea.
Instead, why don't you simply set a state property based on the validation and trigger the visual tree based on that? You could do so using a trigger, or using the visual state manager.
This control works are a replacement for TextBlock. It has bindable MarkupText property that understands same syntax you use when specifying TextBlock content.
Usage:
<local:MarkupTextBlock MarkupText="{Binding MyText}"/>
in C#
public string MyText
{
get
{
return "My <Bold>link</Bold> is <Hyperlink NavigateUri='http://search.msn.com'>MSN</Hyperlink>.";
}
}
Control source code:
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Markup;
/// <summary>
/// The markup text block is a replacement for <see cref="TextBlock"/>
/// that allows to specify markup content dynamically.
/// </summary>
[ContentProperty("MarkupText")]
[Localizability(LocalizationCategory.Text)]
public class MarkupTextBlock : TextBlock
{
/// <summary>
/// The markup text property.
/// </summary>
public static readonly DependencyProperty MarkupTextProperty = DependencyProperty.Register(
"MarkupText",
typeof( string ),
typeof( MarkupTextBlock ),
new FrameworkPropertyMetadata(
string.Empty,
FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender,
OnTextMarkupChanged));
private const string FlowDocumentPrefix =
"<FlowDocument xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'><Paragraph><Span>";
private const string FlowDocumentSuffix = "</Span></Paragraph></FlowDocument>";
/// <summary>
/// Initializes a new instance of the <see cref="MarkupTextBlock"/> class.
/// </summary>
/// <param name="markupText">
/// The markup text.
/// </param>
public MarkupTextBlock(string markupText)
{
MarkupText = markupText;
}
/// <summary>
/// Initializes a new instance of the <see cref="MarkupTextBlock"/> class.
/// </summary>
public MarkupTextBlock()
{
}
/// <summary>
/// Gets or sets content of the <see cref="MarkupTextBlock"/>.
/// </summary>
[Localizability(LocalizationCategory.Text)]
public string MarkupText
{
get { return Inlines.ToString(); }
set { SetValue(MarkupTextProperty, value); }
}
private static void OnTextMarkupChanged(
DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
var markupTextBlock = dependencyObject as MarkupTextBlock;
if( markupTextBlock != null )
{
var flowDocument = new StringBuilder();
flowDocument.Append(FlowDocumentPrefix);
flowDocument.Append(dependencyPropertyChangedEventArgs.NewValue);
flowDocument.Append(FlowDocumentSuffix);
var document = (FlowDocument) XamlReader.Parse(flowDocument.ToString());
var paragraph = document.Blocks.FirstBlock as Paragraph;
if( paragraph != null )
{
var inline = paragraph.Inlines.FirstInline;
if( inline != null )
{
paragraph.Inlines.Remove(inline);
markupTextBlock.Inlines.Clear();
markupTextBlock.Inlines.Add(inline);
}
}
}
}
}
精彩评论