Привязка данных TextBlock выполняется в Silverlight / WP7 - PullRequest
11 голосов
/ 10 марта 2011

Я использую Silverlight на Windows Phone 7.

Я хочу отобразить первую часть некоторого текста в TextBlock жирным шрифтом, а остальную часть - обычным шрифтом. Полный текст должен обернуться. Я хочу, чтобы выделенная часть содержала текст из одного свойства в моей ViewModel, а обычный текст содержал текст из другого свойства.

TextBlock определен в шаблоне данных, связанном с 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 и не может быть связан.

Моя следующая попытка состояла в том, чтобы поместить заполнители на место, а затем обновить их в коде:

<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 codebehind не инициализирует повторные выполнения, поэтому очень быстро отображается неправильный текст ...

Я рассмотрел использование связанного события LongListSelector (которое я уже использую для освобождения изображений, отображаемых в списке), но я не вижу, как я могу использовать это для повторной инициализации текстовых свойств Runs .

Любая помощь приветствуется!

Ответы [ 2 ]

5 голосов
/ 10 марта 2011

Я наконец нашел решение, которое работает для меня.

Как я упоминаю в комментарии, подход Пола Стоувелла не будет работать.

Вместо этого я использовал аналогичный подход для добавления присоединенного свойства к TextBlock, привязанного к DataContext TextBlock, и прикрепленных свойств в прогонах, указывающих, к каким свойствам 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) я обновляю прогоны и подписываюсь, чтобы получать уведомления об изменениях свойств цели TextBox. Когда свойство 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 голосов
/ 10 марта 2011

Я предлагаю вам попробовать BindableRun .Я использовал его только в WPF, но не понимаю, почему он не работает в Silverlight.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...