I have a ListBox whose ListBoxItems are a control derived from UserControl. Each of these controls contain a UserControl derived from Button and some TextBoxes. I want to bind these to elements in my model by using an ItemTemplate in the ListBox, but I'm having trouble assigning values to the button from the ItemTemplate.
The ListViewer contains the ListBox and its DataTemplate:
<UserControl x:Class="TestListBoxBinding.ListViewer"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:model="clr-namespace:Model"
xmlns:vm="clr-namespace:ViewModel"
xmlns:local="clr-namespace:TestListBoxBinding"
mc:Ignorable="d">
<UserControl.DataContext>
<vm:ListViewerViewModel/>
</UserControl.DataContext>
<UserControl.Resources>
<DataTemplate DataType="{x:Type model:Person}">
<Grid Name="theListBoxItemGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<local:PersonButton Grid.Column="0" x:Name="thePersonButton"
FirstName="Ken"
LastName="Jones"/>
<TextBlock Grid.Column="1" Name="theFirstName" Text="{Binding FirstName}" Margin="5,0,5,0"/>
<TextBlock Grid.Column="2" Name="theLastName" Text="{Binding LastName}" Margin="0,0,5,0"/>
</Grid>
</DataTemplate>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Name="theHeader" Text="These are my people"/>
<ListBox Grid.Row="1" Name="theListBox"
ItemsSource="{Binding MyPeople}"/>
<StackPanel Grid.Row="2" Name="theFooterPanel" Orientation="Horizontal">
<TextBlock Name="theFooterLabel" Text="Count = "/>
<TextBlock Name="theFooterCount" Text="{Binding MyPeople.Count}"/>
</StackPanel>
</Grid>
</UserControl>
And its code behind:
using Model;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
namespace TestListBoxBinding
{
/// <summary>
/// Interaction logic for ListViewer.xaml
/// </summary>
public partial class ListViewer : UserControl
{
public ListViewer()
{
InitializeComponent();
}
}
}
namespace ViewModel
{
public class ListViewerViewModel
{
// Properties
public People MyPeople { get; private set; }
// Constructors
public ListViewerViewModel()
{
MyPeople = (People)Application.Current.Resources["theOneAndOnlyPeople"];
MyPeople.Add(new Person("Able", "Baker"));
MyPeople.Add(new Person("Carla", "Douglas"));
MyPeople.Add(new Person("Ed", "Fiducia"));
}
}
}
namespace Model
{
public class Person
{
// Properties
public string FirstName { get; set; }
public string LastName { get; set; }
// Constructors
public Person()
{
FirstName = "John";
LastName = "Doe";
}
public Person(string firstName, string lastName)
{
this.FirstName = firstName;
this.LastName = lastName;
}
}
public class People : ObservableCollection<Person>
{
}
}
The PersonButton has FirstName and LastName DependencyProperties defined in code behind that I will eventually use to set the button's contents for display. Here's its xaml:
<UserControl x:Class="TestListBoxBinding.PersonButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:TestListBoxBinding"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<Button Name="theButton" Padding="5,0,5,0"/>
</Grid>
</UserControl>
And its code behind:
using System.Windows;
using System.Windows.Controls;
namespace TestListBoxBinding
{
/// <summary>
/// Interaction logic for PersonButton.xaml
/// </summary>
public partial class PersonButton : UserControl
{
// Properties
public static readonly DependencyProperty FirstNameProperty = DependencyProperty.Register("FirstName", typeof(string), typeof(PersonButton));
public string FirstName { get { return (string)GetValue(FirstNameProperty); } set { SetValue(FirstNameProperty, value); } }
public static readonly DependencyProperty LastNameProperty = DependencyProperty.Register("LastName", typeof(string), typeof(PersonButton));
public string LastName { get { return (string)GetValue(LastNameProperty); } set { SetValue(LastNameProperty, value); } }
// Constructors
public PersonButton()
{
InitializeComponent();
FirstName = "Ira";
LastName = "Jacobs";
theButton.Content = FirstName + " " + LastName;
}
}
}
As you can see from the output:
The attempts to set the FirstName and LastName properties of the PersonButton from the DataTemplate for the ListBox fail. I set breakpoints on the property sets and they only hit when the controls were constructed, but never from the DataTemplate. I used the Live Visual Tree to confirm that the properties were, in fact, still set as they were initialized.
I should mention, too, that initially, I got an Intellisense warning that the properties were not accessible from the DataTemplate, but after I deleted the bin and obj folders, and cleaned and rebuilt the solution, that went away.
Update
I believe this is not the same as the suggested duplicate in the comments.
As to PersonButton not making any sense, this whole collection of code is a greatly simplified representation of a piece of a much more complex application that I created simply to show the problem I'm having in a more easily read form. I have tested this code and it duplicates the problem I'm having exactly. The actual control that PersonButton represents is much more complex and the use of properties defined in the code behind is entirely appropriate for that control. The only reason PersonButton is doing what it is doing is to make it easy to tell if those properties got set by the assignments.
Now, to address my question here, it is, "Why are PersonButton.FirstName and PersonButton.LastName not getting changed by the their assignment statements for thePersonButton in the DataTemplate for theListBox in my ListViewer control?"
As I mentioned before, I have run several tests, and they all verify that those values in the actual control are not getting set by the assignments. Since seeing Clemens' reply, I have even implemented the PropertyChangedCallback functions, and they, too, confirm that the PersonButton properties are not getting set by the ListBox assignments.
Of specific interest to me is that these assignments will become bindings themselves that allow me to capture changes to each individual item in the list in my Model through the ItemsSource mechanism. In my original application, those bindings work, but my problem is in actually assigning them to the PersonButton control.