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.

sll
  • 61,540
  • 22
  • 104
  • 156

3 Answers3

56

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>
maxp
  • 24,209
  • 39
  • 123
  • 201
Rhys Bevilaqua
  • 2,102
  • 17
  • 20
  • Very nice. Is there a similar solution for silverlight? – Shaun Rowan Sep 13 '12 at 19:24
  • 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 libraries – Rhys Bevilaqua Sep 14 '12 at 01:12
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 :)

sll
  • 61,540
  • 22
  • 104
  • 156
Rachel
  • 130,264
  • 66
  • 304
  • 490
  • Does `DataContext.SomeCollection.Count` return `0` in case when `DataContext.SomeCollection` is null? – sll Nov 16 '11 at 16:54
  • @sll Nope, but you can always write a 2nd trigger to see if `SomeCollection` is equal to `{x:Null}` – Rachel Nov 16 '11 at 16:58
  • Right, could you please add this to the answer as well? – sll Nov 16 '11 at 16:59
  • are you sure 'DataContext.SomeCollection.Count' is a valid path? Also –  Nov 16 '11 at 17:03
  • @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. – Rachel Nov 16 '11 at 17:18
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>

  • 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? – sll Nov 16 '11 at 15:50