First of all, in order to work with WPF or other XAML-based technologies, you must understand that
UI is not Data. Data is Data. UI is UI.
This means that you should not manipulate any ComboBox
or any other UI elements in code, in order to populate them with data, but instead create a ViewModel
and bind these objects to that.
In this example, the Window
itself is used as ViewModel
because it's a simple example, but you should consider moving all application logic to a separate class:
<Window x:Class="MiscSamples.UIisNotData"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="UIisNotData" Height="300" Width="300">
<UniformGrid Rows="1" Columns="2">
<DockPanel>
<TextBlock Text="Owners:" DockPanel.Dock="Top" FontWeight="Bold" TextAlignment="Center" Margin="2"/>
<Button Content="Add" Width="80" DockPanel.Dock="Bottom" Margin="2" Click="AddOwner"/>
<ListBox ItemsSource="{Binding Owners}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding Name}" x:Name="block"/>
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" Visibility="Collapsed" x:Name="box"/>
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource FindAncestor, AncestorType=ListBoxItem}}" Value="True">
<Setter TargetName="block" Property="Visibility" Value="Collapsed"/>
<Setter TargetName="box" Property="Visibility" Value="Visible"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DockPanel>
<DockPanel>
<TextBlock Text="Dogs:" DockPanel.Dock="Top" FontWeight="Bold" TextAlignment="Center" Margin="2"/>
<ListBox ItemsSource="{Binding Dogs}" HorizontalContentAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate>
<DockPanel>
<ComboBox ItemsSource="{Binding DataContext.Owners, RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}"
SelectedItem="{Binding Owner}" DisplayMemberPath="Name"
DockPanel.Dock="Right" Width="100"/>
<TextBlock>
<Run Text="{Binding Name}"/>
<Run Text=", "/>
<Run Text="{Binding Kind}"/>
</TextBlock>
</DockPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DockPanel>
</UniformGrid>
</Window>
Code Behind (This code should be placed in a ViewModel):
public partial class UIisNotData : Window
{
public ObservableCollection<Owner> Owners { get; set; }
public ObservableCollection<string> Kinds { get; set; }
public ObservableCollection<Dog> Dogs { get; set; }
public UIisNotData()
{
InitializeComponent();
Owners = new ObservableCollection<Owner>
{
new Owner() {Name = "Jack"},
new Owner() {Name = "Mike"},
new Owner() {Name = "Kirk"},
new Owner() {Name = "John"},
};
Kinds = new ObservableCollection<string>
{
"Affenpinscher",
"Afghan Hound",
"Airedale Terrier",
"Akita"
//.. All the rest of dog Breeds taken from http://www.petmd.com/dog/breeds?breed_list=az#.UVsQKpPcmQo
};
Dogs = new ObservableCollection<Dog>
{
new Dog() {Name = "Bobby", Kind = Kinds[0], Owner = Owners[0]},
new Dog() {Name = "Fido", Kind = Kinds[1], Owner = Owners[1]},
new Dog() {Name = "Toby", Kind = Kinds[2], Owner = Owners[2]}
};
DataContext = this;
}
private void AddOwner(object sender, RoutedEventArgs e)
{
Owners.Add(new Owner(){Name = "New Owner"});
}
}
Data Model:
public class Owner : PropertyChangedBase
{
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged("Name");
}
}
}
public class Dog: PropertyChangedBase
{
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged("Name");
}
}
private Owner _owner;
public Owner Owner
{
get { return _owner; }
set
{
_owner = value;
OnPropertyChanged("Owner");
}
}
private string _kind;
public string Kind
{
get { return _kind; }
set
{
_kind = value;
OnPropertyChanged("Kind");
}
}
}
PropertyChangedBase Class:
public class PropertyChangedBase:INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Result:

There are 3 important aspects you need to consider about this example:
- I am in no way manipulating UI elements in code. That's completely unnecessary most of the time in WPF.
- The classes from the Data Model implement
INotifyPropertyChanged
in order to support 2-way binding in WPF.
- The Collections are of type
ObservableCollection<T>
in order to support automatic notification when elements are added/removed from the collection (in order to automatically update the ListBoxes
, etc).
Another thing you may notice is that the XAML elements in my example have no specific size or Margin
values. Things like Margin="338,10,0,0"
is usually what you get from the Visual Studio designer and indicates a poorly structured layout. I recommend you look at the Layout elements in WPF (DockPanel
, StackPanel
, Grid
, UniformGrid
, WrapPanel
, etc), and start coding the XAML yourself instead of using the designer. This will allow a much higher level of scalability and will also save you from the nuances of Fixed-position elements.