Как отобразить ObservableCollection‹string› в UserControl

Я новичок в WPF, и я нашел несколько похожих вопросов, но не могу понять последнюю часть. У меня есть ViewModel с ObservableCollection, которая содержит сообщения об ошибках. Я хочу отобразить их в форме и разрешить пользователю выбирать и копировать все или часть сообщений. (Раньше в приложениях WinForm я использовал для этого RichTextBox, но не могу понять, как привязать его к коллекции в WPF.)

Я получил то, что хотел, со следующим xaml, но нет встроенного способа выбора и копирования, как я мог бы с RichTextBox. Кто-нибудь знает, какой элемент управления я должен использовать, или есть ли способ включить выбор/копирование содержимого всех TextBlocks или способ привязать это к RichTextBox?

    <Grid Margin="6">
    <ScrollViewer VerticalScrollBarVisibility="Auto"  Height="40" Grid.Column="0" Margin="6">
        <ItemsControl ItemsSource="{Binding ErrorMessages}" >            
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                     <TextBlock Text="{Binding Mode=OneWay}" />
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </ScrollViewer>
</Grid>

[Изменить] @Andrey Shvydky - Это не поместится в комментарий. Мне потребовалось некоторое время, чтобы понять правильный синтаксис (особенно с /,), но в итоге я остановился на синтаксисе Flow Document, показанном ниже. Он выглядит правильно в форме и на первый взгляд кажется, что он поддерживает выбор всего / копирование. Но когда я вставляю после выбора всего/копии, ничего не появляется. Кто-нибудь знает, почему?

 <Grid Margin="6">
    <FlowDocumentScrollViewer>
        <FlowDocument >
            <Paragraph>
                <ItemsControl ItemsSource="{Binding ErrorMessages, Mode=OneWay}" />
                <Run Text="{Binding /, Mode=OneWay}" />
            </Paragraph>
        </FlowDocument>
    </FlowDocumentScrollViewer>
</Grid>

person Tod    schedule 14.06.2011    source источник


Ответы (4)


Может быть полезно создать FlowDocument и показать этот документ в FlowDocumentReader. . Попробуйте начать с этой статьи: Обзор документа Flow.

Пример генерации:

    void ShowErrors(FlowDocumentReader reader, Exception[] errors) {
        FlowDocument doc = new FlowDocument();
        foreach (var e in errors) {
            doc.Blocks.Add(new Paragraph(new Run(e.GetType().Name)) {
                Style = (Style)this.FindResource("header")
            });
            doc.Blocks.Add(new Paragraph(new Run(e.Message)) {
                Style = (Style)this.FindResource("text")
            });
        }
        reader.Document = doc;
    }

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

<Window x:Class="WpfApplication1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="350" Width="525">
<Window.Resources>
    <Style x:Key="header" TargetType="{x:Type Paragraph}">
        <Setter Property="FontWeight" Value="Bold"/>
    </Style>
    <Style x:Key="text" TargetType="{x:Type Paragraph}">
        <Setter Property="Margin" Value="30, 0, 0, 0"/>
    </Style>
</Window.Resources>
<FlowDocumentReader Name="reader">
</FlowDocumentReader>

Result:

введите здесь описание изображения

person Andriy Shvydky    schedule 14.06.2011
comment
Мне потребовалось некоторое время, чтобы понять правильный синтаксис (особенно /, но в конце концов я придумал ‹FlowDocumentScrollViewer› ‹FlowDocument › ‹Paragraph› ‹ItemsControl ItemsSource={Binding ErrorMessages, Mode=OneWay} /› ‹Run Text={Binding /, Mode=OneWay} /› ‹/Абзац› ‹/FlowDocument› ‹/FlowDocumentScrollViewer› - person Tod; 14.06.2011
comment
FlowDocument не поддерживает привязку. В этом случае вы должны сгенерировать документ в коде и присвоить его свойству Document FlowDocumentReader. Это очень похоже на подход с RichTextBox в WinForms. Конечно, вы также можете использовать ItemControl, но FlowDocumentReader — это простой способ добиться функциональности выбора/копирования. - person Andriy Shvydky; 14.06.2011
comment
Я хотел удалить свой комментарий здесь. Это было слишком сложно читать, и не все поместилось, поэтому я добавил правку в свой исходный пост. Я не знал, что FlowDocument напрямую не поддерживает привязку напрямую, что мне кажется несколько странным, потому что элемент ‹Run› действительно поддерживает привязку. Основываясь на вашем предложении, у меня все выглядит великолепно, я просто не могу вставить результаты, и я понятия не имею, почему. Я могу выбрать все и копировать, и это работает, но когда я вставляю, ничего не появляется. - person Tod; 14.06.2011

Самый простой способ:

Предполагая, что ваша модель представления реализует INotifyPropertyChange, создайте обработчик событий для события ObservableCollection PropertyChanged. Создайте свойство, которое объединяет все элементы наблюдаемой коллекции в одну строку. Всякий раз, когда наблюдаемая коллекция изменяется, запускайте событие уведомления для вашего нового свойства. Привязать к этому свойству

public class ViewModel : INotifyPropertyChange
{
    public ViewModel()
    {
        MyStrings.CollectionChanged += ChangedCollection;
    }
    public ObservableCollection<string> MyStrings{get;set;}

    public void ChangedCollection(args,args)
    {
        base.PropertyChanged("MyAllerts");
    }

    public string MyAllerts
    {
        get
        {
            string collated = "";
            foreach(var allert in MyStrings)
            {
                collated += allert;
                    collated += "\n";
            }
        }
    }

}

Я знаю, что этот код чреват ошибками (я написал его на SO вместо VS), но он должен дать вам некоторое представление.

person TerrorAustralis    schedule 14.06.2011
comment
Спасибо, я думаю, я думал, что упускаю что-то очевидное, но это кажется разумным обходным путем. Поскольку я все еще учусь, я, вероятно, воспользуюсь аналогичным предложением ниже и попытаюсь узнать больше о создании конвертера. - person Tod; 14.06.2011
comment
Конкатенация необработанных foreach строк — это не то, что вам следует делать, используйте StringBuilder иначе при каждой итерации будет создаваться новая строка. (Или просто используйте String.Join, который делает все это для ты внутри) - person H.B.; 14.06.2011
comment
Спасибо ХБ. Не знал о string.join, обычно используют построитель строк. Просто использовал быстрый и грязный пример. Действительно ли string.join эффективен или это просто замаскированный foreach? - person TerrorAustralis; 15.06.2011

Если у вас нет большого количества сообщений, простой конвертер может быть жизнеспособным:

<TextBox IsReadOnly="True">
    <TextBox.Text>
        <Binding Path="Messages" Mode="OneWay">
            <Binding.Converter>
                <vc:JoinStringsConverter />
            </Binding.Converter>
        </Binding>
    </TextBox.Text>
</TextBox>
public class JoinStringsConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        var strings = value as IEnumerable<string>;
        return string.Join(Environment.NewLine, strings);
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotSupportedException();
    }
}
person H.B.    schedule 14.06.2011
comment
Поскольку я учусь, это звучит как хороший способ узнать и написать свой первый конвертер. Спасибо за предложение и код. - person Tod; 14.06.2011

person    schedule
comment
Я не уверен, как это поможет. Все мои сообщения об ошибках правильно отображаются в моем коде (я просто не могу их скопировать и пропустить). У меня нет члена, представляющего отдельное сообщение об ошибке, только набор. Возможно, я недостаточно хорошо понимаю WPF, чтобы понять вашу точку зрения. - person Tod; 14.06.2011
comment
@Tod: Объяснений нет, и я тоже не вижу в этом смысла. - person H.B.; 14.06.2011