How-to define Empty DataTemplate for the ItemsControl based controls like ListView or DataGrid
Asked Answered
R

3

17

ASP.NET controls like ListView allows providing a custom template by setting the ListView.EmptyDataTemplate property, this template will be rendered in case of empty data source.

How to do the same in WPF (XAML only preferrable) for ItemsControl based controls like ListView and DataGrid? So I want to show my custom DataTemplate in case when ItemsSource is empty.

Reichenberg answered 16/11, 2011 at 15:32 Comment(0)
K
9

You can use set the Template property based on a DataTrigger

For example,

In Resources:

<ControlTemplate x:Key="EmptyListBoxTemplate">
     <TextBlock Text="Items count == 0" />
</ControlTemplate>

Control itself:

<ListBox ItemsSource="{Binding SomeCollection}">
    <ListBox.Style>
        <Style TargetType="{x:Type ListBox}">
            <Style.Triggers>
                <DataTrigger Value="{x:Null}" Binding="{Binding DataContext.SomeCollection, RelativeSource={RelativeSource Self}}">
                    <Setter Property="Template" Value="{StaticResource EmptyListBoxTemplate}" />
                </DataTrigger>
                <DataTrigger Value="0" Binding="{Binding DataContext.SomeCollection.Count, RelativeSource={RelativeSource Self}}">
                    <Setter Property="Template" Value="{StaticResource EmptyListBoxTemplate}" />
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </ListBox.Style>
</ListBox>

There might be an simplier way of doing the binding, but I don't have a compiler on me right now to figure out what it would be :)

Koziarz answered 16/11, 2011 at 16:44 Comment(5)
Does DataContext.SomeCollection.Count return 0 in case when DataContext.SomeCollection is null?Reichenberg
@Reichenberg Nope, but you can always write a 2nd trigger to see if SomeCollection is equal to {x:Null}Koziarz
Right, could you please add this to the answer as well?Reichenberg
are you sure 'DataContext.SomeCollection.Count' is a valid path? AlsoStirrup
@Dmitry Yes, it binds to ListBox.DataContext.SomeCollection.Count, which should be a valid path since ItemsSource="{Binding SomeCollection}" is a valid path. I can't remember the value that gets returned if the binding is not valid, but if SomeCollection is null then WPF will ignore the binding error caused by .Count. It won't throw an exception.Koziarz
S
57

There is a 100% xaml solution that makes use of the "HasItems" dependancy property.

<ItemsControl>
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding Description}"/>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
    <ItemsControl.Style>
        <Style TargetType="ItemsControl">
            <Style.Triggers>
                <Trigger Property="HasItems" Value="false">
                        <Setter Property="Template">
                            <Setter.Value>
                                <ControlTemplate>
                                    <TextBlock Text="This Control is empty"/>
                                </ControlTemplate>
                            </Setter.Value>
                        </Setter>
                </Trigger>
            </Style.Triggers>
        </Style>
    </ItemsControl.Style>
</ItemsControl>
Schramm answered 12/9, 2012 at 7:2 Comment(2)
Very nice. Is there a similar solution for silverlight?Haste
It's been a while since i've worked with silveright, but as long as the trigger stuff works then you should be able to use a DataTrigger with a valueconverter to check the value of Items.Count on the control. Failing that you should be able to achieve similar results using the blend interaction librariesSchramm
K
9

You can use set the Template property based on a DataTrigger

For example,

In Resources:

<ControlTemplate x:Key="EmptyListBoxTemplate">
     <TextBlock Text="Items count == 0" />
</ControlTemplate>

Control itself:

<ListBox ItemsSource="{Binding SomeCollection}">
    <ListBox.Style>
        <Style TargetType="{x:Type ListBox}">
            <Style.Triggers>
                <DataTrigger Value="{x:Null}" Binding="{Binding DataContext.SomeCollection, RelativeSource={RelativeSource Self}}">
                    <Setter Property="Template" Value="{StaticResource EmptyListBoxTemplate}" />
                </DataTrigger>
                <DataTrigger Value="0" Binding="{Binding DataContext.SomeCollection.Count, RelativeSource={RelativeSource Self}}">
                    <Setter Property="Template" Value="{StaticResource EmptyListBoxTemplate}" />
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </ListBox.Style>
</ListBox>

There might be an simplier way of doing the binding, but I don't have a compiler on me right now to figure out what it would be :)

Koziarz answered 16/11, 2011 at 16:44 Comment(5)
Does DataContext.SomeCollection.Count return 0 in case when DataContext.SomeCollection is null?Reichenberg
@Reichenberg Nope, but you can always write a 2nd trigger to see if SomeCollection is equal to {x:Null}Koziarz
Right, could you please add this to the answer as well?Reichenberg
are you sure 'DataContext.SomeCollection.Count' is a valid path? AlsoStirrup
@Dmitry Yes, it binds to ListBox.DataContext.SomeCollection.Count, which should be a valid path since ItemsSource="{Binding SomeCollection}" is a valid path. I can't remember the value that gets returned if the binding is not valid, but if SomeCollection is null then WPF will ignore the binding error caused by .Count. It won't throw an exception.Koziarz
S
2

you can use DataTemplate selector to do that.

http://msdn.microsoft.com/en-us/library/system.windows.controls.datatemplateselector.aspx

UPDATE 1

Code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace EmptyRowsTemplate
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            this.Loaded += (o, e) => 
            {
                this.l.ItemsSource = new List<string>(3)
                {
                    "A",
                    null,
                    "B"
                };
            };
        }
    }

    public class TemplateManager : DependencyObject
    {
        public static readonly DependencyProperty BlankDataTemplateProperty =
            DependencyProperty.RegisterAttached("BlankDataTemplate",
            typeof(DataTemplate),
            typeof(TemplateManager),
            new PropertyMetadata(new PropertyChangedCallback((o, e) => 
            {
                ((ItemsControl)o).ItemTemplateSelector = new BlankDataTemplateSelector();
            })));

        public static void SetBlankDataTemplate(DependencyObject o, DataTemplate e)
        {
            o.SetValue(TemplateManager.BlankDataTemplateProperty, e);
        }

        public static DataTemplate GetBlankDataTemplate(DependencyObject o)
        {
            return (DataTemplate)o.GetValue(TemplateManager.BlankDataTemplateProperty);
        }

        private class BlankDataTemplateSelector : DataTemplateSelector
        {
            public BlankDataTemplateSelector()
                : base()
            {
            }

            public override DataTemplate SelectTemplate(object item, DependencyObject container)
            {
                ItemsControl itemControl =
                    (ItemsControl)ItemsControl.ItemsControlFromItemContainer(ItemsControl.ContainerFromElement(null, container));

                if (item == null)
                {
                    return TemplateManager.GetBlankDataTemplate(itemControl);
                }
                else
                {
                    return base.SelectTemplate(item, container);
                }

            }
        }
    }
}

Markup

<Window x:Class="EmptyRowsTemplate.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:EmptyRowsTemplate"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <ListBox x:Name="l">
            <local:TemplateManager.BlankDataTemplate>
                <DataTemplate>
                    <Button Background="Red">No Data!</Button>
                </DataTemplate>
            </local:TemplateManager.BlankDataTemplate>
        </ListBox>
    </Grid>
</Window>

Stirrup answered 16/11, 2011 at 15:46 Comment(1)
Even I'm looking for XAML only solution could you provide an example how you would handle Empty data set with template selector considering that selector handling single item in SelectTemplate() method and there are no items at all?Reichenberg

© 2022 - 2024 — McMap. All rights reserved.