WPF Xceed PropertyGrid ComboBox binding - how to populate with a list of items from another class?

598 Views Asked by At

I am using an Xceed PropertyGrid to show properties for various objects. In most cases this is straightforward. I just set the SelectedObject property the appropriate object.

However, I have a case where one object property is a reference to another object from a collection of a different class. I have created a simplified example here for demonstration and testing purposes. In this example, the Person object has a City property, which is an instance of the CityModel class. So for any given Person, the user should be able to select their city of residence from the cities collection. Collections of both people and cities are - or at least would be in the real thing - dynamic and user-changeable.

I can populate a ComboBox using an EditorTemplate but am having problems referencing the cities collection.

If trying to define the Source as a StaticResource, I am not getting the expected behavior. This How-to from Microsoft shows an ObservableCollection (People) being defined as a Resource and subsequently referenced as a StaticResource in a data binding. When I try to do the same, Visual Studio is clearly expecting the Resource definition to be a class rather than a class instance. It gives me the option of CityModel or PersonModel, but not the cities collection which is what I thought I should be specifying here based on the How-to.

Intellisense showing available options for data source definition

If Source does not point to the collection, I'm not sure how and where to specify it in the binding since (again based on Intellisense) it cannot go in the Path.

Without using a StaticResource I can do either of the following, both of which result in a blank list:

ItemsSource="{Binding Source=DataContext.cities, Path=Name}"

ItemsSource="{Binding Source=cities, Path=Name}"

Or this, which results in the text string "cities" being used as the source:

ItemsSource="{Binding Source=cities}"

In the code below I can get the desired result by duplicating the cities collection within the Person class, which exposes it to the ComboBox without a having to specify a Source, but this is very much a workaround.

<Grid>
    <StackPanel Orientation="Vertical">
        <TextBlock Padding="0,10,0,5">People:</TextBlock>
        <ListBox Name="listPeople" ItemsSource="{Binding people}" DisplayMemberPath="FirstName" SelectionChanged="listBox_SelectionChanged" />
        <TextBlock Padding="0,10,0,5">Cities:</TextBlock>
        <ListBox Name="listCities" ItemsSource="{Binding cities}" DisplayMemberPath="Name" SelectionChanged="listBox_SelectionChanged" />
        <TextBlock Padding="0,10,0,5">Properties:</TextBlock>
        <xctk:PropertyGrid x:Name="MyPropertyGrid">
            <xctk:PropertyGrid.EditorDefinitions>
                <xctk:EditorTemplateDefinition TargetProperties="City">
                    <xctk:EditorTemplateDefinition.EditingTemplate>
                        <DataTemplate>
                            <ComboBox ItemsSource="{Binding Instance.citiesList}" SelectedValue="{Binding Instance.City}" />
                        </DataTemplate>
                    </xctk:EditorTemplateDefinition.EditingTemplate>
                </xctk:EditorTemplateDefinition>
            </xctk:PropertyGrid.EditorDefinitions>
        </xctk:PropertyGrid>
    </StackPanel>
</Grid>

public partial class MainWindow : Window
{
    
    public ObservableCollection<CityModel> cities { get; set; } = new ObservableCollection<CityModel>();
    public ObservableCollection<PersonModel> people { get; set; } = new ObservableCollection<PersonModel>();
    public MainWindow()
    {
        InitializeComponent();

        DataContext = this;

        // Create some cities...
        CityModel tokyo = new CityModel() { Id = 0, Name = "Tokyo" };
        CityModel london = new CityModel() { Id = 1, Name = "London" };
        CityModel buenosAires = new CityModel() { Id = 2, Name = "Buenos Aries" };
        cities = new ObservableCollection<CityModel>() { tokyo, london, buenosAires };

        // Create some people...
        PersonModel juan = new PersonModel() { FirstName = "Juan", LastName = "Garcia", City = buenosAires };
        PersonModel bridget = new PersonModel() { FirstName = "Bridget", LastName = "Jones", City = london };
        PersonModel aoife = new PersonModel() { FirstName = "Aoife", LastName = "O'Connor", City = tokyo };
        people = new ObservableCollection<PersonModel>() { juan, bridget, aoife };

        // Workaround in order to expose list of cities along with Person properties...
        foreach (PersonModel person in people) { person.citiesList = cities; }

    }

    private void listBox_SelectionChanged(object sender, RoutedEventArgs e)
    {
        ListBox listBox = sender as ListBox;
        MyPropertyGrid.SelectedObject = listBox.SelectedItem;
    }
}

public class PersonModel
{
    public string FirstName { get; set; } = "";

    public string LastName { get; set; } = "";

    public CityModel City { get; set; } = new CityModel();

    // Workaround in order to expose list of cities along with Person properties...
    public ObservableCollection<CityModel> citiesList { get; set; } = new ObservableCollection<CityModel>();
}

public class CityModel
{
    public int Id { get; set; } = 0;
    public string Name { get; set; } = "";
    public override string ToString() { return Name; }
}
0

There are 0 best solutions below