Reputation: 140
I've just began meddling with ItemsControls/Binding, and I've encountered an issue. I've looked at various tutorials regarding nested ItemsControls, so I'm not positive what it is that I'm doing wrong. I believe all is coded correctly, but the Expander doesn't display content as it ought to. The Header properly aligns itself to the top of its parent, but the ScrollViewer won't appear, and it only scrolls the parenting "TimeScrollViewer". Am I perhaps binding something incorrectly?
All suggestions are appreciated.
C#:
private string[][] hours = new string[][]
{
new string[] { "11:00", "11:30", "12:00", "12:30", "1:00", "1:30", "2:00", "2:30", "3:00", "3:30", "4:00", "4:30", "5:00", "5:30", "6:00", "6:30", "7:00", "7:30", "8:00", "8:30" },
new string[] { "5:00", "5:30", "6:00", "6:30", "7:00", "7:30", "8:00", "8:30", "9:00" },
new string[] { "5:00", "5:30", "6:00", "6:30", "7:00", "7:30", "8:00", "8:30", "9:00" },
new string[] { "5:00", "5:30", "6:00", "6:30", "7:00", "7:30", "8:00", "8:30", "9:00" },
new string[] { "5:00", "5:30", "6:00", "6:30", "7:00", "7:30", "8:00", "8:30", "9:00" },
new string[] { "5:00", "5:30", "6:00", "6:30", "7:00", "7:30", "8:00", "8:30", "9:00" },
new string[] { "5:00", "5:30", "6:00", "6:30", "7:00", "7:30", "8:00", "8:30", "9:00" }
};
public class GuestItem
{
public string GuestName { get; set; }
}
public class RegistryItem
{
public string Header { get; set; }
public List<GuestItem> GuestList = new List<GuestItem>();
}
Expander currentExpander = null;
public MainWindow()
{
int day = (int)DateTime.Now.DayOfWeek;
InitializeComponent();
List<RegistryItem> items = new List<RegistryItem>();
foreach(string hour in hours[day])
{
RegistryItem registryItem = new RegistryItem(){ Header = hour };
registryItem.GuestList.Add(new GuestItem() { GuestName = "Bob" });
registryItem.GuestList.Add(new GuestItem() { GuestName = "Frank" });
registryItem.GuestList.Add(new GuestItem() { GuestName = "Jim" });
items.Add(registryItem);
}
TimeItemsControl.ItemsSource = items;
}
private void ExpanderExpanded(object sender, RoutedEventArgs e)
{
if(currentExpander != null)
{
currentExpander.IsExpanded = false;
}
currentExpander = e.Source as Expander;
currentExpander.IsExpanded = true;
}
private void ExpanderCollapsed(object sender, EventArgs e)
{
currentExpander = null;
}
XAML:
<s:SurfaceScrollViewer Name="TimeScrollViewer" Grid.Row="1" Grid.Column="1" HorizontalScrollBarVisibility="Visible" VerticalScrollBarVisibility="Hidden" Background="#4CAAAAFF" Style="{DynamicResource SurfaceScrollViewerHorizontalTop}" Foreground="#4CAAAAFF">
<ItemsControl Name="TimeItemsControl">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Expander Expanded="ExpanderExpanded" Collapsed="ExpanderCollapsed" Header="{Binding Header}" Style="{DynamicResource SurfaceExpander}" HorizontalContentAlignment="Center" FontSize="21.333" Width="100">
<s:SurfaceScrollViewer Width="{Binding ElementName=TimeScrollViewer, Path=ActualWidth}" Height="{Binding ElementName=TimeScrollViewer, Path=ActualHeight}" HorizontalScrollBarVisibility="Visible" VerticalScrollBarVisibility="Hidden">
<ItemsControl ItemsSource="{Binding GuestList}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<s:SurfaceButton Content="{Binding GuestName}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</s:SurfaceScrollViewer>
</Expander>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</s:SurfaceScrollViewer>
Upvotes: 0
Views: 783
Reputation: 387607
When you run (debug) your application and check the output tab in Visual Studio, you can see the following output multiple times:
System.Windows.Data Error: 40 : BindingExpression path error: 'GuestList' property not found on 'object' ''RegistryItem' (HashCode=15478206)'. BindingExpression:Path=GuestList; DataItem='RegistryItem' (HashCode=15478206); target element is 'ItemsControl' (Name=''); target property is 'ItemsSource' (type 'IEnumerable')
So the data binding to the GuestList
property could not be resolved on the RegistryItem
object. If you look closely at the definition of the type, you can see why:
public class RegistryItem
{
public string Header { get; set; }
public List<GuestItem> GuestList = new List<GuestItem>();
}
GuestList
is not a property, but a field. The WPF binding engine requires properties, so the GuestList
field, despite being public, does not exist for the binding engine, resulting in above error. To fix this, simply make it a property. You can use an empty constructor to initialize the list:
public class RegistryItem
{
public string Header { get; set; }
public List<GuestItem> GuestList { get; set; }
public RegistryItem ()
{
GuestList = new List<GuestItem>();
}
}
Then everything will work correctly. So the bottom line is: Always check for error messages, especially binding errors, they usually tell you what might be wrong. As binding errors are usually rather hidden (since they don’t break stuff), you could use a technique described [in this other question]( How can I turn binding errors into runtime exceptions?) to turn them into full exceptions or to at least log them somewhere else.
Upvotes: 1