Связывание TextBlock отображает имя класса вместо пустой строки

У меня есть следующее приложение Windows RT. Я связываю List of Strings с ItemsControl TextBlocks. Это отображает пустые строки как "System.Collections.Generic.List'1 [System.String]" вместо пустой строки. Я хотел бы, чтобы он отображал пустую строку вместо типа DataContext.

код позади:

public sealed partial class MainPage : Page
{
    public MainPage()
    {
        this.InitializeComponent();
        DataContext = new List<string>() { "", "not empty string" };
    }
}

XAML:

<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
    <ItemsControl ItemsSource="{Binding}">
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <StackPanel Orientation="Vertical"/>
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding}" FontSize="25"/>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
</Grid>

выход:

System.Collections.Generic.List'1[System.String]
non empty string

Я сделал тот же пример с традиционным wpf, и он корректно отображает пустые строки.

Edit Это приводит к тому же.

код позади:

public class Model
{
    private readonly List<string> items = new List<string>() { "", "non empty string" };

    public List<string> Items
    {
        get { return items; }
    } 
}

public sealed partial class MainPage : Page
{
    public MainPage()
    {
        this.InitializeComponent();
        DataContext = new Model();
    }
}

XAML:

<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
    <ItemsControl ItemsSource="{Binding Path=Items}">
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <StackPanel Orientation="Vertical"/>
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding}" FontSize="25"/>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
</Grid>

Ответ 1

Фактически вы можете увидеть, что это ошибка (или нечетная намеренная функция), добавив конвертер в TextBlock Binding.

Добавить статический ресурс:

<Page.Resources>
    <local:NoNullsConverter x:Key="fixNulls"></local:NoNullsConverter>
</Page.Resources>

Затем измените привязку для ссылки на конвертер:

<TextBlock Text="{Binding Converter={StaticResource fixNulls}}" FontSize="25"/>

Добавьте этот класс:

public class NoNullsConverter : IValueConverter
{
    // This converts the value object to the string to display.
    public object Convert(object value, Type targetType,
        object parameter, string language)
    {
        return value is string ? value : "";         
    }

    public object ConvertBack(object value, Type targetType, 
        object parameter, string language)
    {
        throw new NotImplementedException();
    }
}

Если вы поместите точку прерывания в оператор return, вы увидите, что первое значение, которое фактически передано, - это весь список. Да, неожиданно. Однако, если вы используете этот Конвертер в письменном виде, он обрабатывает эту странность и просто возвращает более логичную пустую строку.

Или вы можете сделать что-то более интересное и создать простой класс-оболочку:

public class StringContext
{
    public string Value { get; set; }
    public static implicit operator StringContext(string value)
    {
        return new StringContext() { Value = value };
    }

    public override string ToString()
    {
        return Value;
    }
}

С помощью этого класса вы можете просто использовать привязку, как ожидалось:

<TextBlock Text="{Binding}" FontSize="25"/>

Однако вам нужно использовать другой тип класса в объявлении списка:

DataContext = new List<StringContext>() { "", "not empty string" };

Используя неявный листинг, он "просто работает", поскольку он преобразует String в StringContext. Да, это добавит накладные расходы на создание ненужного класса, но это действительно сработает.:) Я бы предпочел вариант конвертера.

Ответ 2

Я действительно не могу объяснить, почему, но он работает так, как ожидалось, когда вы прямо устанавливаете свойство ItemsSource:

<ItemsControl x:Name="itemsControl">
    ...
</ItemsControl>

public MainPage()
{
    this.InitializeComponent();
    itemsControl.ItemsSource = new List<string>() { "", "not empty string" };
}

Я также пробовал это:

<ItemsControl ItemsSource="{Binding Items}">
    ...
</ItemsControl>

public MainPage()
{
    this.InitializeComponent();
    Items = new List<string>() { "", "not empty string" };
    DataContext = this;
}

public IEnumerable Items { get; set; }

но это приводит к отображению

TextBlockBindingTest.MainPage

не пустая строка

По-видимому, когда привязка элемента оценивается как null или empty, он возвращается к унаследованному DataContext. Я предполагаю, что это ошибка в WinRT.


В качестве альтернативы вы также можете установить свойство Name класса MainPage и написать привязку следующим образом:

<Page ... x:Name="mainPage">
...
<ItemsControl ItemsSource="{Binding Items, ElementName=mainPage}">
    ...
</ItemsControl>

и не задайте DataContext:

public MainPage()
{
    this.InitializeComponent();
    Items = new List<string>() { "", "not empty string" };
}

Ответ 3

или попробуйте установить код привязки позади:

//list is ItemsControl
var bin = new Binding();
var mylist = new List<string>(){"","not empty string"};
bin.Source = mylist;
list.SetBinding(ItemsControl.ItemsSourceProperty,bin);

Это сработало для меня.

Ответ 4

Собственно, я наткнулся на более простое решение. Это, без сомнения, ошибка. Тот же код в WPF дает ожидаемые результаты. Источником ошибки является свойство TargetNullValue, которое неправильно интерпретирует пустые строки. Чтобы обойти ошибку, вы можете либо использовать предложенный выше IValueConverter (решения для конвертеров имеют проблемы с производительностью из моего опыта), либо использовать:

        <TextBlock Text="{Binding TargetNullValue=' '}" FontSize="25"/>