My goal is to reuse a ListView
and a couple of other controls that are inside of a UserControl
I designed.
For the sake of brevity, imagine that I have a Person
class like this, and a list of its instances.
public class Person
{
public string Name { get; set; }
public string City { get; set; }
}
My MainWindow
:
<Window x:Class="ReusableListView.MainWindow"
...
WindowStartupLocation="CenterScreen"
Title="MainWindow" Height="600" Width="600">
<Grid>
<local:UCListView Margin="8"
ItemsSource="{Binding PersonList, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</Window>
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private ObservableCollection<Person> _personList = null;
public ObservableCollection<Person> PersonList
{
get { return _personList; }
set { _personList = value; OnPropertyChanged("PersonList"); }
}
private Person _selectedPerson = null;
public Person SelectedPerson
{
get { return _selectedPerson; }
set { _selectedPerson = value; OnPropertyChanged("SelectedPerson"); }
}
public MainWindow()
{
InitializeComponent();
PersonList = GetPeople();
}
private ObservableCollection<Person> GetPeople()
{
var list = new ObservableCollection<Person>
{
new Person() { Name = "Jane", City = "NYC" },
new Person() { Name = "John", City = "LA" }
};
return list;
}
}
I want to display the Name
property of Person
as individual items in my ListView
inside the UserControl
, and then to the right of it, I want to display the selected person's City
property. So my UserControl
look like this:
<UserControl x:Class="ReusableListView.UCListView"
...
x:Name="MyListViewUC"
d:DesignHeight="500" d:DesignWidth="580">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ListView Grid.Column="0" MinWidth="256" Margin="8"
DataContext="{Binding ElementName=MyListViewUC}"
ItemsSource="{Binding ItemsSource}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock HorizontalAlignment="Left" VerticalAlignment="Center"
Width="Auto" Margin="8" Background="Pink"
Text="{Binding Name}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<TextBox Grid.Column="1" Margin="8" Background="PaleGreen"/>
</Grid>
</UserControl>
And the UserControl
code behind:
public partial class UCListView : UserControl
{
public UCListView()
{
InitializeComponent();
}
public object ItemsSource
{
get { return GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register("ItemsSource", typeof(object), typeof(UCListView), new PropertyMetadata(null));
}
The above code is stitched together from most examples I saw online including SO. Here are my problems and questions.
UserControl
list. What seems to be the problem?SelectedPerson
property to the UserContro.
so it knows how to display the correct City
based on selection?Besides that you missed to set the DataContext of the Window like
DataContext = this;
you should consider to derive your control directly from ListView or the simpler ListBox, because then you would get direct access to all its useful properties.
The difference to a UserControl is that the XAML is in a default Style in the ResourceDictionary Themes/Generic.xaml
, which is automatically generated when you add a custom control to a WPF project.
The control's code, where you change the base class from Control to ListBox:
public class MyListBox : ListBox
{
static MyListBox()
{
DefaultStyleKeyProperty.OverrideMetadata(
typeof(MyListBox),
new FrameworkPropertyMetadata(typeof(MyListBox)));
}
}
Generic.xaml:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:...">
<Style TargetType="local:MyListBox">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:MyListBox">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ScrollViewer Grid.Column="0">
<ItemsPresenter/>
</ScrollViewer>
<TextBlock Grid.Column="1"
Text="{TemplateBinding SelectedValue}"/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
You would use your MyListBox like any other ListBox:
<local:MyListBox ItemsSource="{Binding PersonList}"
SelectedItem="{Binding SelectedPerson}"
DisplayMemberPath="Name"
SelectedValuePath="City">
If you're not intending to have additional properties in your derived ListBox, you could as well not derive a control at all, and just assign the ControlTemplate to a ListBox when you declare it:
<Window.Resources>
<ControlTemplate x:Key="MyListBoxTemplate">
...
</ControlTemplate>
</Window.Resources>
...
<ListBox Template="{StaticResource MyListBoxTemplate}"
ItemsSource="{Binding PersonList}"
SelectedItem="{Binding SelectedPerson}"
DisplayMemberPath="Name"
SelectedValuePath="City">