views:

70

answers:

2

I am trying to bind a 2D array of buttons arranged in stackpanels to a 2D ObservableCollection...

Yet, I'm afraid I don't understand something very elementary about binding.

My XAML:

    <Window.Resources>
    <DataTemplate x:Key="ItemsAsButtons">
        <Button Content="{Binding}" Height="100" Width="100"/>
    </DataTemplate>
    <DataTemplate x:Key="PanelOfPanels">
        <ItemsControl ItemsSource="{Binding Path=DayNumbers}" ItemTemplate="   {DynamicResource ItemsAsButtons}">
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <StackPanel Orientation="Horizontal"/>
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
        </ItemsControl>
    </DataTemplate>
    </Window.Resources>

...
        <ItemsControl x:Name="DaysPanel" Grid.ColumnSpan="7" Grid.Row="2"
                      ItemTemplate="{DynamicResource PanelOfPanels}"/>

My C# code: The backend:

/// <summary>
/// Window BE for Calendar.xaml
/// </summary>
public partial class Calendar : Window
{

    private CalendarViewModel _vm;
    public Calendar()
    {
        InitializeComponent();
        _vm = new CalendarViewModel();
        this.DataContext = _vm; 
    }
}

The ViewModel:

class CalendarViewModel
{
    CalendarMonth _displayedMonth;
    EventCalendar _calendar;

    public CalendarViewModel()
    {
        _displayedMonth = new CalendarMonth();
    }

    public ObservableCollection<ObservableCollection<int>> DayNumbers
    {
        get
        {
            return _displayedMonth.DayNumbers;
        }
    }
}

I'm trying to populate the buttons with values from CalendarViewModel.DayNumbers - yet the buttons do not appear. I'm clearly doing something wrong with my binding.

+3  A: 

Change all your DynamicResource to StaticResource. This shouldn't stop it working, but might be inefficient at runtime. Have a look this page for WPF resources overview.

Also your ItemsControl is not bound to DayNumbers. Add a binding like so:

<ItemsControl x:Name="DaysPanel" Grid.ColumnSpan="7" Grid.Row="2"
                  ItemTemplate="{StaticResource PanelOfPanels}"
                  ItemsSource={Binding DayNumbers}/>

When you set the DataContext on Calendar window you set which object will be the default binding source for the whole window. You didn't specify which property of your ViewModel is bound to the ItemsControl. This is what the code above does.

EDIT Because you are overriding the item template for the ItemsControl and provide a collection container there, you need to provide the ItemsSource for it as well. The syntax {Binding} simply means bind to each member or enumeration, in this case ObservableCollection<int>.

Just to reiterate, the template is exactly that - a template for displaying data. It should be reusable, you should be able to bind it to whatever model you want. A rule of thumb - the data binding to actual data should happen on the control, not the template.

Igor Zevaka
Doesn't the code in the template provide the binding?<ItemsControl ItemsSource="{Binding Path=DayNumbers}" ItemTemplate="{DynamicResource ItemsAsButtons}">I've tried adding the line of code you reccomended to the actual invocation of the ItemsControl, but that didn't help.
Vladislav
I missed that bit. Never tried it that way, seems very awkward. The template is supposed to define the look and feel of the control, not bind to data.
Igor Zevaka
The line in the template turns out to be mandatory, but had to be changed to ItemsSource="{Binding}".You are saying that the template should not provide binding - is there a way around this (Other then manually binding in the C# code)
Vladislav
+2  A: 
  1. Like Igor said, you need specify ItemsSource={Binding DayNumbers} in outer-most ItemsControl, otherwise, it binds to the DataContext, which is CalendarViewModel and it is not IEnumerable.

  2. Once you do that, it will apply <DataTemplate x:Key="PanelOfPanels"> for each item inside DayNumbers. Note that the DataContext of the DataTemplate in each element in DayNumbers, which is of type ObservableCollection<int>. Here you cannot specify ItemsSource="{Binding Path=DayNumbers}" as DayNumbers is not a valid property in ObservableCollection<int>. Instead, since ObservableCollection<int> is already a IEnumerable, it should be fine not specifying ItemsSource since it will by default bind to DataContext.

  3. Finally, it goes to your inner-most <DataTemplate x:Key="ItemsAsButtons">, and you can put button there as what you did.

Hope it clarifies a little bit. Sorry I don't have the environment to test it out and give you the solution.

Debugging WPF bindings is not straightforward. One tip is you can use dummy converter and set breakpoint in the Convert method to see what it binds.

public class DebugConverter1 : IValueConverter
  {
    #region IValueConverter Members

    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
       return value;
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
      return value;
    }

    #endregion
  }

{Binding Converter={StaticResource debugConverter1}}
Jerry Liu
Now I understand - I needed to specify a plain binding in the upper tier list, I can see what Igor was saying now.
Vladislav