【问题标题】:Databinding TextBlock Runs in Silverlight / WP7数据绑定 TextBlock 在 Silverlight / WP7 中运行
【发布时间】:2011-03-09 23:29:15
【问题描述】:

我在 Windows Phone 7 上使用 Silverlight。

我想以粗体显示 TextBlock 中某些文本的第一部分,其余部分以普通字体显示。完整的文本必须换行。我希望粗体部分包含来自 ViewModel 中一个属性的文本,而纯文本包含来自不同属性的文本。

TextBlock 在与 LongListSelector 关联的 DataTemplate 中定义。

我最初的尝试是:

<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 并且无法绑定。

我的下一个尝试是放置占位符,然后在代码中更新它们:

<TextBlock Loaded="TextBlockLoaded" TextWrapping="Wrap">
    <TextBlock.Inlines>
        <Run FontWeight="Bold">Placeholder1</Run>
        <Run>Placeholder2</Run>
    </TextBlock.Inlines>
</TextBlock>

在代码隐藏中(是的,我很绝望!):

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 代码隐藏事件处理程序不会重新初始化 Runs,所以很快就会显示错误的文本......

我已经研究过使用 LongListSelector 的 Linked 事件(我已经使用它来释放我在列表中显示的图像),但我看不出如何使用它来重新初始化 Runs 的文本属性.

任何帮助表示赞赏!

【问题讨论】:

  • +1。好问题,我也想知道这个问题。

标签: silverlight windows-phone-7


【解决方案1】:

我终于找到了适合我的解决方案。

正如我在评论中提到的,Paul Stovell 的 approach 不起作用。

相反,我使用类似的方法将附加属性添加到 TextBlock,绑定到 TextBlock 的 DataContext,并在运行时附加属性,指示它们应该绑定到哪些 ViewModel 属性:

<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) 属性的 changed 事件中,我更新 Runs,并订阅接收 TextBox Target 属性更改的通知。当 TextBox Target 属性发生变化时,我相应地更新了任何关联的 Run 文本。

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));

}

【讨论】:

    【解决方案2】:

    我建议你试试BindableRun。我只在 WPF 中使用过它,但我不明白为什么它在 Silverlight 中不起作用。

    【讨论】:

    • 不幸的是 Run 在 Silverlight 中被密封,所以这不起作用。我正在寻找另一种方法:paulstovell.wordpress.com/2007/03/21/attached-bindablerun,但这利用了 Silverlight 中不可用的 UIPropertyMetadata ...当我将其更改为 PropertyMetadata 时,当 LongListSelector 在 ContentPresenter 上调用 Measure 时,我收到运行时错误 AG_E_PARSER_PROPERTY_NOT_FOUND。还在努力!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多