数据绑定 TextBlock 在 Silverlight/WP7 中运行 [英] Databinding TextBlock Runs in Silverlight / WP7
问题描述
我在 Windows Phone 7 上使用 Silverlight.
I'm using Silverlight on Windows Phone 7.
我想以粗体显示 TextBlock 中某些文本的第一部分,其余部分以普通字体显示.完整的文本必须换行.我希望粗体部分包含来自我的 ViewModel 中一个属性的文本,而纯文本包含来自不同属性的文本.
I want to display the first part of some text in a TextBlock in bold, and the rest in normal font. The complete text must wrap. I want the bolded part to contain text from one property in my ViewModel, and the plain text to contain text from a different property.
TextBlock 在与 LongListSelector 关联的 DataTemplate 中定义.
The TextBlock is defined in a DataTemplate associated with a LongListSelector.
我最初的尝试是:
<TextBlock TextWrapping="Wrap">
<TextBlock.Inlines>
<Run Text="{Binding Property1}" FontWeight="Bold"/>
<Run Text="{Binding Property2}"/>
</TextBlock.Inlines>
</TextBlock>
这在运行时失败,并显示非常无用的AG_E_RUNTIME_MANAGED_UNKNOWN_ERROR".这是一个已知问题,因为 Run 元素不是 FrameworkElement 并且无法绑定.
This fails at runtime with the spectacularly unhelpful "AG_E_RUNTIME_MANAGED_UNKNOWN_ERROR". This is a known issue because the Run element is not a FrameworkElement and cannot be bound.
我的下一个尝试是放置占位符,然后在代码中更新它们:
My next attempt was to put placeholders in place, and then update them in code:
<TextBlock Loaded="TextBlockLoaded" TextWrapping="Wrap">
<TextBlock.Inlines>
<Run FontWeight="Bold">Placeholder1</Run>
<Run>Placeholder2</Run>
</TextBlock.Inlines>
</TextBlock>
在代码隐藏中(是的,我很绝望!):
In the code-behind (yes I am desparate!):
private void TextBlockLoaded(object sender, RoutedEventArgs e)
{
var textBlock = (TextBlock)sender;
var viewModel = (ViewModel)textBlock.DataContext;
var prop1Run = (Run)textBlock.Inlines[0];
var prop2Run = (Run)textBlock.Inlines[1];
prop1Run.Text = viewModel.Property1;
prop2Run.Text = viewModel.Property2;
}
这似乎有效,但因为我使用的是 LongListSelector,虽然项目被回收,但 Loaded 代码隐藏事件处理程序不会重新初始化运行,所以很快就会显示错误的文本...
This seemed to work, but because I am using the LongListSelector, although items get recycled, the Loaded codebehind event handler doesn't re-initialize the Runs, so very quickly the wrong text is displayed...
我已经研究过使用 LongListSelector 的 Linked 事件(我已经用它来释放我在列表中显示的图像),但我看不到如何使用它来重新初始化 Runs 的文本属性.
I've looked at using the LongListSelector's Linked event (which I already use to free up images that I display in the list), but I can't see how I can use that to re-initialize the Runs' text properties.
感谢任何帮助!
推荐答案
我终于找到了适合我的解决方案.
I finally found a solution that works for me.
正如我在评论中提到的,Paul Stovell 的方法不工作.
As I mention in the comment, Paul Stovell's approach would not work.
相反,我使用类似的方法将附加属性添加到 TextBlock,绑定到 TextBlock 的 DataContext,并在运行时附加属性,指示它们应该绑定到哪些 ViewModel 属性:
Instead I used a similar approach to add an attached property to the TextBlock, bound to the TextBlock's DataContext, and attached properties on the runs, indicating which ViewModel properties they should be bound to:
<TextBlock TextWrapping="Wrap"
Views:BindableRuns.Target="{Binding}">
<TextBlock.Inlines>
<Run FontWeight="Bold" Views:BindableRuns.Target="Property1"/>
<Run Views:BindableRuns.Target="Property2"/>
</TextBlock.Inlines>
</TextBlock>
然后在我附加的 TextBox Target (datacontext) 属性的更改事件中,我更新了 Runs,并订阅了 TextBox Target 属性更改的通知.当 TextBox Target 属性发生变化时,我会相应地更新任何关联的 Run 文本.
Then in my attached TextBox Target (datacontext) property's changed event, I update the Runs, and subscribe to be notified of changes to the TextBox Target properties. When a TextBox Target property changes, I updated any associated Run's text accordingly.
public static class BindableRuns
{
private static readonly Dictionary<INotifyPropertyChanged, PropertyChangedHandler>
Handlers = new Dictionary<INotifyPropertyChanged, PropertyChangedHandler>();
private static void TargetPropertyPropertyChanged(
DependencyObject dependencyObject,
DependencyPropertyChangedEventArgs e)
{
if(!(dependencyObject is TextBlock)) return;
var textBlock = (TextBlock)dependencyObject;
AddHandler(e.NewValue as INotifyPropertyChanged, textBlock);
RemoveHandler(e.OldValue as INotifyPropertyChanged);
InitializeRuns(textBlock, e.NewValue);
}
private static void AddHandler(INotifyPropertyChanged dataContext,
TextBlock textBlock)
{
if (dataContext == null) return;
var propertyChangedHandler = new PropertyChangedHandler(textBlock);
dataContext.PropertyChanged += propertyChangedHandler.PropertyChanged;
Handlers[dataContext] = propertyChangedHandler;
}
private static void RemoveHandler(INotifyPropertyChanged dataContext)
{
if (dataContext == null || !Handlers.ContainsKey(dataContext)) return;
dataContext.PropertyChanged -= Handlers[dataContext].PropertyChanged;
Handlers.Remove(dataContext);
}
private static void InitializeRuns(TextBlock textBlock, object dataContext)
{
if (dataContext == null) return;
var runs = from run in textBlock.Inlines.OfType<Run>()
let propertyName = (string)run.GetValue(TargetProperty)
where propertyName != null
select new { Run = run, PropertyName = propertyName };
foreach (var run in runs)
{
var property = dataContext.GetType().GetProperty(run.PropertyName);
run.Run.Text = (string)property.GetValue(dataContext, null);
}
}
private class PropertyChangedHandler
{
private readonly TextBlock _textBlock;
public PropertyChangedHandler(TextBlock textBlock)
{
_textBlock = textBlock;
}
public void PropertyChanged(object sender,
PropertyChangedEventArgs propertyChangedArgs)
{
var propertyName = propertyChangedArgs.PropertyName;
var run = _textBlock.Inlines.OfType<Run>()
.Where(r => (string) r.GetValue(TargetProperty) == propertyName)
.SingleOrDefault();
if(run == null) return;
var property = sender.GetType().GetProperty(propertyName);
run.Text = (string)property.GetValue(sender, null);
}
}
public static object GetTarget(DependencyObject obj)
{
return obj.GetValue(TargetProperty);
}
public static void SetTarget(DependencyObject obj,
object value)
{
obj.SetValue(TargetProperty, value);
}
public static readonly DependencyProperty TargetProperty =
DependencyProperty.RegisterAttached("Target",
typeof(object),
typeof(BindableRuns),
new PropertyMetadata(null,
TargetPropertyPropertyChanged));
}
这篇关于数据绑定 TextBlock 在 Silverlight/WP7 中运行的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!