C# — добавление элементов XAML в приложение UWP через x:Bind

Контекст: я пишу клиент UWP для Twitter.

Одним из полезных фрагментов данных, которые Twitter возвращает через свой API, являются объекты для контента, которые могут быть связаны непосредственно в твите — #hashtags, @usernames, $symbols и URL-адреса. Это позволяет легко извлекать эти объекты из строки, содержащей полный текст твита, чтобы превратить их в ссылки.

Я понимаю, как XAML должен искать это с помощью тегов <run> и <hyperlink>, и я понял, как динамически создавать этот XAML для каждого объекта твита.

Чего я не могу понять, так это того, как внедрить сгенерированный XAML в файл DataTemplate моего приложения. Поскольку содержимое твита должно отображаться на нескольких страницах в приложении, я использую ResourceDictionary для хранения всех стилей XAML, включая DataTemplate. Итак, я совершенно не уверен, как подключить сгенерированный XAML к пользовательскому интерфейсу моего приложения.

Например, если твит выглядит так:

«Эй, @twitter, ты попусту тратишь время! #FridayFeeling»

Мои сгенерированные объекты XAML выглядят так:

<Run>Hey </Run>
<Hyperlink link="http://twitter.com/twitter/">@twitter</Hyperlink>
<Run>, you're a time waster! </Run>
<Hyperlink link="http://twitter.com/search?hashtag=FridayFeeling">#FridayFeeling</Hyperlink>

Если в тексте твита не на что ссылаться, я могу просто использовать Tweet.Text как есть, поэтому я пытаюсь связать это с TextBox. Как я могу динамически вставить этот XAML?

Я застрял, полностью отказавшись от привязки данных и перебирая свою коллекцию, чтобы программно добавить все мои XAML?

Вот мой шаблон данных:

<DataTemplate x:Key="TweetTemplate" x:DataType="tweeter:Tweet2">
    <Grid Style="{StaticResource ListItemStyle}">
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="30"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="40"/>
        </Grid.RowDefinitions>
        <Grid Grid.Row="0" x:Name="RetweetedBy" x:Load="{x:Bind IsRetweet}" Height="28">
            <StackPanel Orientation="Horizontal" Padding="4 8 4 0">
                <StackPanel.Resources>
                    <Style TargetType="TextBlock">
                        <Setter Property="FontSize" Value="12"/>
                        <Setter Property="Foreground" Value="{ThemeResource SystemControlPageTextBaseMediumBrush}" />
                    </Style>
                </StackPanel.Resources>
                <Border Height="28">
                    <TextBlock Height="24" FontFamily="{StaticResource FontAwesome}" xml:space="preserve"><Run Text="&#xf079;&#160;"/></TextBlock>
                </Border>
                <TextBlock Text="{x:Bind Path=User.Name}" />
                <TextBlock Text=" retweeted"/>
            </StackPanel>
        </Grid>
        <Grid Grid.Row="1">
            <StackPanel Orientation="Horizontal" Padding="5">
                <TextBlock Text="{x:Bind Path=Tweet.User.Name}" Margin="0 0 8 0"  FontWeight="Bold" />
                <TextBlock Text="{x:Bind Path=Tweet.User.ScreenName, Converter={StaticResource GetHandle}}" Foreground="{ThemeResource SystemControlPageTextBaseMediumBrush}" />
                <TextBlock Text="&#x2981;" Margin="8 0" />
                <TextBlock Text="{x:Bind Path=Tweet.CreationDate, Converter={StaticResource FormatDate}}" />
            </StackPanel>
        </Grid>
        <Grid Grid.Row="2">
            <TextBlock Text="***this is where I'm having problems***" Padding="5" TextWrapping="WrapWholeWords"/>
        </Grid>
        <Grid Grid.Row="3">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="2.5*" MaxWidth="100"/>
                <ColumnDefinition Width="2.5*"/>
                <ColumnDefinition Width="2.5*"/>
                <ColumnDefinition Width="2.5*"/>
            </Grid.ColumnDefinitions>
            <Grid Grid.Column="0">
                <Button x:Name="cmdComment" Content="&#xf075;" Style="{StaticResource MetaButtons}" />
            </Grid>
            <Grid Grid.Column="1">
                <Button x:Name="cmdRetweet" Content="&#xf079;" Style="{StaticResource MetaButtons}" />
            </Grid>
            <Grid Grid.Column="2">
                <Button x:Name="cmdLike" Content="&#xf004;" Style="{StaticResource MetaButtons}" />
            </Grid>
            <Grid Grid.Column="3">
                <Button x:Name="cmdMessage" Content="&#xf0e0;" Style="{StaticResource MetaButtons}" />
            </Grid>
        </Grid>
    </Grid>
</DataTemplate>

person ClairelyClaire    schedule 05.08.2018    source источник
comment
Вы можете сгенерировать все содержимое (прогоны и гиперссылки) и добавить их как встроенную коллекцию текстового блока через свойство Attached для конвертеров; или создайте собственный элемент управления, чтобы вы могли использовать свой шаблон и отображать некоторые свойства зависимостей, к которым вам потребуется доступ   -  person Yves Israel    schedule 06.08.2018
comment
Должен ли я добавлять массив содержимого сразу или добавлять каждый элемент содержимого по мере его создания? Я не знаком с Attachedproperty. Не могли бы вы немного рассказать об этом?   -  person ClairelyClaire    schedule 06.08.2018
comment
Это зависит от вашей реализации. Если у вас есть все содержимое, вы добавите его сразу. Решение AttachmentProperty поможет вам получить доступ к коллекции InlineCollection текстового блока и управлять ею. Вы можете добавлять/удалять содержимое по своему усмотрению. Это также позволит вам использовать привязку данных   -  person Yves Israel    schedule 06.08.2018


Ответы (1)


Если вы хотите использовать прикрепленное свойство, вот пример для начала:

public static class Twitter
{
    public static readonly DependencyProperty InlinesProperty = DependencyProperty.RegisterAttached(
        "Inlines", typeof(ICollection<Inline>), typeof(Twitter), new PropertyMetadata(default(ICollection<Inline>), PropertyChangedCallback));

    private static void PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (!(d is TextBlock tb)) return;

        tb.Inlines.Clear();

        if (!(e.NewValue is ICollection<Inline> inlines)) return;

        foreach (var inline in inlines)
        {
            tb.Inlines.Add(inline);
        }
    }

    public static void SetInlines(DependencyObject element, ICollection<Inline> value)
    {
        element.SetValue(InlinesProperty, value);
    }

    public static ICollection<Inline> GetInlines(DependencyObject element)
    {
        return (ICollection<Inline>) element.GetValue(InlinesProperty);
    }
}

И в xaml:

        <TextBlock local:Twitter.Inlines ="{x:Bind TwitterData}"></TextBlock>

Где TwitterData — это List<Inline>

person Yves Israel    schedule 06.08.2018
comment
Хорошо, я сказал слишком рано - если я попытаюсь снова загрузить тот же объект твита в свой XAML (я использую элемент управления BladeView, поэтому щелчок по твиту открывает его в новой колонке), я получаю эту ошибку: WinRT information: Element is already the child of another element. - person ClairelyClaire; 06.08.2018
comment
Я понимаю, что вы повторно используете некоторое сгенерированное содержимое (встроенные строки) в нескольких местах. В этом случае используйте либо вычисляемые свойства, либо структуру данных, представляющую объект твита, и используйте преобразователь для динамического создания содержимого. Другой вариант - использовать пользовательский элемент управления. - person Yves Israel; 06.08.2018
comment
Я разобрался - я использовал MemberwiseClone(), чтобы сделать клон выбранного твита, и он без проблем загружается во второй блейд. Спасибо еще раз!! - person ClairelyClaire; 06.08.2018
comment
Хороший! Просто для информации: если вы используете {Binding} вместо x:Bind, с вычисляемым свойством это тоже будет работать. Вычисляемое свойство должно каждый раз оцениваться и создавать содержимое. Но это работает только с {Binding}, а не с {x:Bind}. - person Yves Israel; 06.08.2018