Dennis Eldridge
Dennis Eldridge

Reputation: 51

Binding a List to a ComboBox in WPF

I'm trying to create a UI with WPF using MVVM, but I'm having a bit of trouble. I want to have a DataGrid with two columns. The first column will be a label, and the second column will have a ComboBox in it. The label column always shows up, but nothing ever shows up in the ComboBox. I'm don't think I'm binding it properly, but I'm not sure how to fix it.

I created a class with the properties that I want to appear in the DataGrid. The label will be the Parameter property and I have a list property called Revisions for the ComboBox.

class RevisionMapModel
{
    private string _parameter;
    public string Parameter
    {
        get { return _parameter; }
    }

    private List<string> _revisions;
    public List<string> Revisions
    {
        get { return _revisions; }

    }

    private string _selection;
    public string Selection
    {
        get { return _selection; }
        set { _selection = value; }
    }


    public RevisionMapModel(string parameter, List<string> revisions)
    {

        // set the parameter name and the list of revisions
        _parameter = parameter;
        _revisions = revisions;
        _selection = "";


        // add a default revision
        _revisions.Add("None");

        // attempt to find which revision matches with this parameter
        FullRevisionMatch(_parameter, _revisions);

        // if a full match isn't found, then try again
        if (_selection == "None")
        {
            PartialRevisionMatch(_parameter, _revisions);
        }
    }
}

Here is the ViewModel class that serves as the DataContext. The DataGrid is bound to the RevisionMapModels property, which is a list of the previous class.

class RevisionMapViewModel
{

    private string _label;
    public string Label
    {
        get { return _label; }

    }

    private List<RevisionMapModel> _revisionMapModels;
    public List<RevisionMapModel> RevisionMapModels
    {
        get { return _revisionMapModels; }
    }


    public RevisionMapViewModel(List<Definition> parameters, List<Revision> revisions, string label)
    {

        // instantiate the list
        _revisionMapModels = new List<RevisionMapModel>();
        _label = label;



        // convert the parameters and revisions to strings
        List<string> revisionDescriptions = revisions.Select(r => r.Description).ToList();
        List<string> parameterNames = parameters.Select(p => p.Name).ToList();


        // create classes for each parameter
        List<string> reservedRevisions = new List<string>();
        for (int i=0; i< parameterNames.Count; i++)
        {
            RevisionMapModel model = new RevisionMapModel(parameterNames[i], revisionDescriptions);


            // check to ensure this parameter is not mapped to a revision that is already selected
            if (reservedRevisions.Contains(model.Selection))
            {
                // change the selection to none if it's already used
                model.Selection = "None";
            }
            else
            {
                reservedRevisions.Add(model.Selection);
            }


            // add it to the list
            _revisionMapModels.Add(model);

        }


    }

}

This is my XAML below. I'm guessing it's having trouble binding to the Revisions property because it's a list. I've tried a number of things, but nothing seems to get this to show up.

<Window x:Class="SheetRevisions.RevisionMapView"
             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:SheetRevisions"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800" MinWidth="500" MinHeight="500" Width="500">
    <Grid>
        <DataGrid Margin="20,40,20,45" MinWidth="50" MinHeight="47" ItemsSource="{Binding RevisionMapModels}" AutoGenerateColumns="False">
            <DataGrid.Columns>
                <DataGridTextColumn Header="Parameter Name" Binding="{Binding Parameter}" Width="Auto"/>
                <DataGridComboBoxColumn Header="Revision Description" ItemsSource="{Binding Revisions}" Width="*" />        
            </DataGrid.Columns>
        </DataGrid>
        <Label Content="{Binding Label}" HorizontalAlignment="Left" Height="25" Margin="20,10,0,0" VerticalAlignment="Top" Width="412"/>
        <Button Content="Cancel" HorizontalAlignment="Right" Margin="0,0,20,10" VerticalAlignment="Bottom" Width="75" IsCancel="True"/>
        <Button Content="OK" HorizontalAlignment="Right" Margin="0,0,110,10" VerticalAlignment="Bottom" Width="75" IsDefault="True"/>

    </Grid>
</Window>

I'm really new to WPF so any help is appreciated.

Upvotes: 1

Views: 68

Answers (2)

Dennis Eldridge
Dennis Eldridge

Reputation: 51

In addition to the comments above about implimenting INotifyPropertyChanged I also needed to change my XAML to use a DataGridTemplateColumn rather than a DataGridComboBoxColumn. It has something to do with a list not being a valid source as I found out from this post:

https://social.msdn.microsoft.com/Forums/en-US/e14be49f-1c03-420e-8a15-ca98e7eedaa2/how-to-bind-net-4-datagridcomboboxcolumn-to-a-collection-within-a-collection?forum=wpf

Working XAML:

<Window x:Class="SheetRevisions.RevisionMapView"
             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:SheetRevisions"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800" MinWidth="500" MinHeight="500" Width="500">

    <Grid>
        <DataGrid Margin="20,40,20,45" MinWidth="50" MinHeight="47" ItemsSource="{Binding RevisionMapModels}" AutoGenerateColumns="False">
            <DataGrid.Columns>
                <DataGridTextColumn Header="Parameter Name" Binding="{Binding Parameter}" Width="*" IsReadOnly="True"/>


                <DataGridTemplateColumn>
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <ComboBox ItemsSource="{Binding Revisions}" SelectedItem="{Binding Selection}" Width="Auto"/>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>


            </DataGrid.Columns>
        </DataGrid>
        <Label Content="{Binding Label}" HorizontalAlignment="Left" Height="25" Margin="20,10,0,0" VerticalAlignment="Top" Width="412"/>
        <Button Content="Cancel" HorizontalAlignment="Right" Margin="0,0,20,10" VerticalAlignment="Bottom" Width="75" IsCancel="True"/>
        <Button Content="OK" HorizontalAlignment="Right" Margin="0,0,110,10" VerticalAlignment="Bottom" Width="75" IsDefault="True"/>

    </Grid>
</Window>

Revised model:

public class RevisionMapModel : INotifyPropertyChanged
{

    #region fields

    private Logger _logger = LogUtils.LogFactory.GetCurrentClassLogger();

    private string _parameter;
    public string Parameter
    {
        get { return _parameter; }
        set { }
    }

    private List<string> _revisions;
    public List<string> Revisions
    {
        get { return _revisions; }


    }

    private string _selection;
    public string Selection
    {
        get { return _selection; }
        set
        {
            _selection = value;
            OnPropertyRaised("Selection");
        }

    }

    public event PropertyChangedEventHandler PropertyChanged;


    public RevisionMapModel(string parameter, List<string> revisions)
    {

        // set the parameter name and the list of revisions
        _parameter = parameter;
        _revisions = revisions;
        _selection = "";



        // add a default revision
        _revisions.Add("None");

        // attempt to find which revision matches with this parameter
        FullRevisionMatch(_parameter, _revisions);

        // if a full match isn't found, then try again
        if (_selection == "None")
        {
            PartialRevisionMatch(_parameter, _revisions);
        }
    }

    protected void OnPropertyRaised(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

}

Upvotes: 0

marizombie
marizombie

Reputation: 443

As already mentioned here, you need to implement INotifyPropertyChanged in your ViewModel. Something like

RevisionMapViewModel : INotifyPropertyChanged

//...your code here

public event PropertyChangedEventHandler PropertyChanged;

protected void NotifyPropertyChanged(String info) {
    if (PropertyChanged != null) {
        PropertyChanged(this, new PropertyChangedEventArgs(info));
    }
}

Then, RevisionMapModel should be a public class, as I think. Also, in case you work in Visual Studio, some tips about your incorrect bindings can be visible in Output window.

Upvotes: 1

Related Questions