Процедурное генерирование текста со встроенным <span>XAML из привязки - PullRequest
0 голосов
/ 25 февраля 2020

Я создаю C# WPF-приложение, использующее MVVM для отделения представления от бизнес-логи c.

Один из моих источников данных - это устаревшее приложение, которое выводит цветовые коды ANSI, и я хотел бы чтобы точно воспроизвести их в пользовательском интерфейсе, используя текстовые блоки, или блоки, или что-либо более подходящее.

Мне удалось написать очень простой преобразователь, чтобы превратить коды ANSI в элементы со стилями для установки цвета:

"\x1b[30mblack\x1b[37mwhite"

становится

@"<span style=""color:#000000"">black<span style=""color:#BBBBBB"">white</span></span>"

Однако я не могу найти способ привязать этот текст к моим представлениям, в то же время воспроизводя цвета.

Большинство примеров в Интернете сосредоточены на ситуациях, когда текст всегда будет одинаковым, поэтому цвета могут быть жестко закодированы в XAML, а текст привязан к разным интервалам / сериям. Это не сработает для меня.

Я кратко рассмотрел использование элемента управления WebBrowser, но это кажется очень большим молотком для такой маленькой проблемы. Я собираюсь иметь сотни этих меток в списке, поэтому производительность является проблемой.

Наконец-то я нашел решение, посредством которого XAML может быть записан в представление во время выполнения, но мне не повезло вообще моя преобразованная строка для загрузки в качестве действительного XAML: Richtextbox wpf привязка

Это один из тех случаев, когда WPF делает простую проблему чрезвычайно сложной. Есть ли хорошее решение, которое я пропускаю?

1 Ответ

1 голос
/ 25 февраля 2020

Что именно вы здесь пытаетесь разобрать? В вашем тексте говорится, что вы пытаетесь проанализировать XAML, но код, который вы указали, - HTML?

Если вы можете придерживаться XAML, то это относительно просто. Прежде всего вам понадобятся некоторые данные XAML в вашей модели представления:

public string[] Spans { get; } = new string[]
{
    "<Span Foreground=\"Blue\">Hello World!</Span>",
    "<Span Foreground=\"Green\">Goodbye World!</Span>"
};

Всякий раз, когда у вас есть список вещей для рисования в WPF, вы обычно используете ItemsControl. Однако вместо списка вам, вероятно, понадобится WrapPanel. Конвертер можно использовать для преобразования каждого элемента списка в диапазон, который вы можете заключить в родительский ContentControl:

<ItemsControl ItemsSource="{Binding Spans}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <ContentControl Content="{Binding Path=., Converter={StaticResource SpanConverter}}" />
        </DataTemplate>
    </ItemsControl.ItemTemplate>
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <WrapPanel />
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
</ItemsControl>

Тогда все, что вам нужно, - это сам конвертер, чтобы получить необработанный XAML, разобрать его и вернуть полученный объект. Вам также нужно добавить пространство имен по умолчанию (пустое) вместе с "x", чтобы анализатор знал, где найти объекты, которые он десериализует:

public class SpanConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var context = new ParserContext();
        context.XmlnsDictionary.Add("", "http://schemas.microsoft.com/winfx/2006/xaml/presentation");
        context.XmlnsDictionary.Add("x", "http://schemas.microsoft.com/winfx/2006/xaml");
        return XamlReader.Parse(value?.ToString(), context) as ContentElement;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return Binding.DoNothing;
    }
}

Результат:

enter image description here

Теперь возьмите все это и выбросьте, потому что синтаксический анализ необработанного XAML не , как вы делаете это в MVVM. Чтобы сделать это правильно, вы должны создать модель представления для ваших отрезков следующим образом:

public class SpanViewModel
{       
    public string Text { set; get; }
    public Color Foreground { set; get; }
    // .. plus any other fields you want
}

И вместо этого вы создадите их список:

public SpanViewModel[] Spans { get; } = new SpanViewModel[]
{
    new SpanViewModel{Text="Hello World!", Foreground=Colors.Blue},
    new SpanViewModel{Text="Goodbye World!", Foreground=Colors.Green}
};

Мы собираемся использовать DataTemplate, поэтому избавьтесь от ItemTemplate из ItemsControl:

<ItemsControl ItemsSource="{Binding Spans}">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <WrapPanel />
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
</ItemsControl>

и создайте DataTemplate для SpanViewModel, привязав к соответствующим свойствам (вам нужно использовать TextBlock, потому что Span не поддерживает привязку ):

<Window.Resources>
    <DataTemplate DataType="{x:Type local:SpanViewModel}">
        <TextBlock Text="{Binding Text}">
            <TextBlock.Style>
                <Style TargetType="{x:Type TextBlock}">
                    <Setter Property="Foreground">
                        <Setter.Value>
                            <SolidColorBrush Color="{Binding Foreground}" />
                        </Setter.Value>
                    </Setter>
                </Style>
            </TextBlock.Style>
        </TextBlock>
    </DataTemplate>
</Window.Resources>

Существует множество вариантов, но этого должно быть достаточно, чтобы начать работу.

...