Home > front end >  DataGridViewComboBoxColumn's ValueMember from different source
DataGridViewComboBoxColumn's ValueMember from different source

Time:07-31

I have a binding source creditUserBindingSource in my below class which recieves a list of CreditUser class as datasource. In my DataGridView I have a DataGridViewComboBoxColumn called ResponsibleList which receives a list of string as DataSource ["Production", "Distribution", "Customer Service", "Sales"]. I want the ResponsibleList to display the responsible from the responsible variable from the list of CrteditUsers for each user.

public partial class CreditUserLimitsForm : Form
    {
        private List<CreditUser> creditUser;
        private bool SetupCheckStatus = false;
        //private Dictionary<string, string> fbu;

        public CreditUserLimitsForm()
        {
            InitializeComponent();
        }

        private void CreditUserLimitsForm_Load(object sender, EventArgs e)
        {
            //fbu = MainForm.srv.GetFBU();
            //foreach (KeyValuePair<string, string> k in fbu)
            //{
            //    lvwFBU.Items.Add(new ListViewItem(new string[] { k.Key, k.Value }));
            //}
            try
            {
                creditUser = MainForm.srv.GetCreditUser("","").ToList();
                creditUserBindingSource.DataSource = creditUser;
                SetAlternateChoicesUsingDataSource(ResponsibleList);
            }
            catch (Exception ex)
            {
                Cursor = Cursors.Default;
                NutraMsg.DisplayError(this, ex, MainForm.GetMessageDisplayType());
            }
        }
        private void SetAlternateChoicesUsingDataSource(DataGridViewComboBoxColumn comboboxColumn)
        {
            {
                comboboxColumn.DataSource = MainForm.srv.GetResponsibleList();
                comboboxColumn.ValueMember = "Responsible";
                comboboxColumn.DisplayMember = comboboxColumn.ValueMember;
            }
        }
}

Here's the code for CreditUser class

   public class CreditUser : INotifyPropertyChanged
    {
        public string Responsible { get; set; }
        public int UserId { get; set; }
        public int RoutingId { get; set; }
        public string UserName { get; set; }
        public List<string> AllowedCustomerTypes { get; set; }
        public decimal CreditLimit { get; set; }
        public bool Restricted
        {
            get
            {
                foreach (UserCatalog uc in Catalogs)
                {
                    if (uc.Restricted)
                    {
                        return true;
                    }
                }
                return false;
            }
        }
}

CodePudding user response:

If you're binding a list of string values then don't set the DisplayMember or ValueMember. The point of those is to specify members of the items you want to use but you don't want to use members of the items. You want to use the items themselves. Here is a simple example that demonstrates this:

private class Record
{
    public int Id { get; set; }
    public string Name { get; set; }
}

private void Form1_Load(object sender, EventArgs e)
{
    var idColumn = new DataGridViewTextBoxColumn { HeaderText = "Id", DataPropertyName = "Id" };
    var nameColumn = new DataGridViewComboBoxColumn
                     {
                         HeaderText = "Name",
                         DataPropertyName = "Name",
                         DataSource = new[] {"First", "Second", "Third"}
                     };

    dataGridView1.Columns.AddRange(idColumn, nameColumn);
    dataGridView1.DataSource = new BindingList<Record>
    {
        new() {Id = 1, Name = "First"},
        new() {Id = 2, Name = "Second"},
        new() {Id = 3, Name = "Third"}
    };
}

CodePudding user response:

I see that you have made a custom DataGridComboBoxColumn and have implemented a version of SetAlternateChoicesUsingDataSource that seems to be modeled after the method of the same name in the Microsoft code example for screenshot

I'd like to offer a guideline example showing one way this outcome could be realized.


DataSources for DataGridViewComboBoxColumn and DataGridView

The data source of DataGridViewComboBoxColumn will be a BindingList<string> that initially contains all possible values but this will change. When when a new cell is selected its contents will be modified based on the individual CreditUser that has been selected.

private readonly BindingList<string> ResponsibleList = new BindingList<string>
{
    String.Empty,
    "Production", 
    "Distribution", 
    "Customer Service", 
    "Sales"
};

The DataSource for dataGridViewCreditUser is a binding list named CreditUsers.

readonly BindingList<CreditUser> CreditUsers = new BindingList<CreditUser>();

Initialize

Assigning these data sources is done in the override of OnLoad (there's no need to have the form subscribe to its own Load event). Allow me to explain what I've done and you can modify this flow to your specific requirements.

protected override void onl oad(EventArgs e)
{
    dataGridViewCreditUser.DataSource = CreditUsers;

Adding one or more items will autogenerate the columns.

    // Calls a mock method that returns a simulated response of three CreditUsers.
    foreach (var creditUser in mockMainForm_srv_GetCreditUser("", ""))
    {
        CreditUsers.Add(creditUser);
    }

Create a ComboBox column that will be swapped out for the autogenerated one. This is where ResponsibleList becomes the DataSource for the ComboBox and that won't change even though the items it contains will.

    var colCB = new DataGridViewComboBoxColumn
    {
        Name = nameof(CreditUser.CustomerType),
        DataPropertyName = nameof(CreditUser.CustomerType),
        HeaderText = "Customer Type",
        AutoSizeMode = DataGridViewAutoSizeColumnMode.AllCells,
        DataSource = ResponsibleList,
    };

Perform the swap. Remove the autogenerated column and replace it with the custom version.

    var index = dataGridViewCreditUser.Columns[nameof(CreditUser.CustomerType)].Index;
    dataGridViewCreditUser.Columns.RemoveAt(index);
    dataGridViewCreditUser.Columns.Insert(index, colCB);

Cancel the known exception that will occur when the data source is cleared during the update process.

    dataGridViewCreditUser.DataError  = (sender, e) =>
    {
            // This transitory state is caused by AllowedCustomerTypeForUser.Clear();
            e.Cancel = e.Exception.Message == "DataGridViewComboBoxCell value is not valid.";
    };

Subscribe to event such that allowed customer types will update when cell selection changes

    dataGridViewCreditUser.CurrentCellChanged  = (sender, e) =>
    {
        if (dataGridViewCreditUser.CurrentCell != null)
        {
            var creditUser = CreditUsers[dataGridViewCreditUser.CurrentCell.RowIndex];
            Text = creditUser.ToString(); // Update title bar.
            var colCB = ((DataGridViewComboBoxColumn)dataGridViewCreditUser.Columns[nameof(CreditUser.CustomerType)]);
            AllowedCustomerTypeForUser.Clear();
            foreach (var allowedType in creditUser.AllowedCustomerTypes)
            {
                AllowedCustomerTypeForUser.Add(allowedType);
            }
        }
    };

Make sure the cell is NOT left in an editing state after change of ComboBox or CheckBox.

    dataGridViewCreditUser.CurrentCellDirtyStateChanged  = (sender, e) =>
    {
        switch (dataGridViewCreditUser.Columns[dataGridViewCreditUser.CurrentCell.ColumnIndex].Name)
        {
            case nameof(CreditUser.CustomerType):
            case nameof(CreditUser.Restricted):
                dataGridViewCreditUser.CommitEdit(DataGridViewDataErrorContexts.Commit);
                break;
        }
    };

To monitor ongoing changes, update the Title bar whenever the source list is modified.

    CreditUsers.ListChanged  = (sender, e) =>
    {
        if (dataGridViewCreditUser.CurrentCell != null)
        {
            var creditUser = CreditUsers[dataGridViewCreditUser.CurrentCell.RowIndex];
            Text = creditUser.ToString();
        }
    };

Now that the DataGridView is all set up the columns can be formatted.

    foreach (DataGridViewColumn col in dataGridViewCreditUser.Columns)
    {
        if (col.Name == nameof(CreditUser.UserName))
        {
            col.AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
        }
        else
        {
            col.AutoSizeMode = DataGridViewAutoSizeColumnMode.AllCells;
        }
    }
}

MOCK QUERY FOR TESTING

// MOCK for minimal example
private List<CreditUser> mockMainForm_srv_GetCreditUser(string v1, string v2)
{
    return new List<CreditUser>
    {
        new CreditUser
        {
            UserName = "Tom",
            CreditLimit=10000m,
            AllowedCustomerTypes = new List<string>
            { 
                "Production", 
                "Distribution", 
                String.Empty 
            },
        },
        new CreditUser
        {
            UserName = "Richard",
            CreditLimit=1250m,
            AllowedCustomerTypes = new List<string>
            { 
                "Distribution", 
                "Customer Service", 
                String.Empty 
            },
            Restricted = true
        },
        new CreditUser
        {
            UserName = "Harry",
            CreditLimit=10000m,
            AllowedCustomerTypes = new List<string>
            { 
                "Production", 
                "Customer Service", 
                "Sales", 
                String.Empty 
            },
        },
    };
}
  • Related