Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TextBlock binding displays class name instead of empty string

I have the following Windows RT app. I bind a List of Strings to an ItemsControl of TextBlocks. This will display the empty strings as "System.Collections.Generic.List'1[System.String]" instead of just an empty string. I would like it to display an empty string instead of the type of the DataContext.

code behind:

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>

output:

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

I made the same example with traditional wpf and it displays the empty strings correctly.

Edit This outputs the same thing.

code behind:

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>
like image 380
David Klimek Avatar asked Apr 10 '13 16:04

David Klimek


2 Answers

You can actually see that it's a bug (or odd intentional feature) by adding a Converter to the TextBlock Binding.

Add a static resource:

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

Then change the Binding to reference the Converter:

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

Add this class:

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();
    }
}

If you put a break point on the return statement, you'll see that the first value that is passed actually is the entire list. Yes, unexpected. However, if you use this Converter as written, it handles that oddity and just returns a more logical empty string.

Or, you can do something more interesting and create a simple wrapper class:

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;
    }
}

With this class, you can just use the Binding as expected:

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

However, you would need to use a different class type in the declaration of the List:

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

Using the implicit cast, it "just works" as it converts the String to a StringContext. Yes, it would add the overhead of creating an unnecessary class, but it does work. :) I'd prefer the Converter option.

like image 117
WiredPrairie Avatar answered Oct 15 '22 12:10

WiredPrairie


I really can't explain why, but it works as expected when you directly set the ItemsSource property:

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

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

I also tried this:

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

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

public IEnumerable Items { get; set; }

but it results in displaying

TextBlockBindingTest.MainPage

not empty string

Apparently, when the item binding evaluates to null or empty, it falls back to the inherited DataContext. I guess this is a bug in WinRT.


Alternatively, you may also set the Name property of the MainPage class and write a binding like this:

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

and not set the DataContext:

public MainPage()
{
    this.InitializeComponent();
    Items = new List<string>() { "", "not empty string" };
}
like image 34
Clemens Avatar answered Oct 15 '22 11:10

Clemens