-1

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;
            }
        }
}

2 Answers2

0

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"}
    };
}
user18387401
  • 2,514
  • 1
  • 3
  • 8
  • understood. but I want the DataGridViewComboBoxColumn's DisplayMember to display the name of responsible which is in the list of Credituser which I am getting from other datasource creditUserBindingSource.DataSource. – Aditya Bhouraskar Jul 29 '22 at 17:47
  • 1
    @AdityaBhouraskar, it's very frustrating when people ask for help because they don't know what they're doing but, when you provide it, they think they know better and don't even bother to test the help provided. If you had just read what I wrote and done what I told you then it would have worked. Now we both have to waste more time on this problem. I have added a code example to my answer that demonstrates that what I have told you to do will produce the result that you have asked for. – user18387401 Jul 30 '22 at 03:26
0

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 DataGridViewComboBoxColumn.

The purpose of SetAlternateChoicesUsingDataSource in that example is to provide ComboBox drop down options that are tailored and specified for each user using the AllowedCustomerTypes property that you show in your CreditUser class. Something like this:

screenshot

But based on your comment, the choices are the same for each row. More like this:

new screenshot

This means that your code can be simplified.


DataSources for DataGridViewComboBoxColumn and DataGridView

I believe what might be causing the confusion is that the data sources for DataGridView and for DataGridViewComboBoxColumn are completely unrelated in this case. Since there is no need to provide each user with individualized options, the source of drop down items only needs to be set one time for the entire column.

The DataSource for DataGridViewComboBoxColumn is an array of strings named ResponsibleList that will not change.

private readonly string[] ResponsibleList = new []
{
    "Production", 
    "Distribution", 
    "Customer Service", 
    "Sales",
    String.Empty
};

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 OnLoad(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.

    var colCB = new DataGridViewComboBoxColumn
    {
        Name = nameof(CreditUser.Responsible),
        // Connects the column value to the Responsible property of CreditUser
        DataPropertyName = nameof(CreditUser.Responsible),
        AutoSizeMode = DataGridViewAutoSizeColumnMode.AllCells,
        // Connects the four drop-down options in ResponsibleList to the ComboBox
        DataSource = ResponsibleList,
    };

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

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

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.Responsible):
            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) && (dataGridViewCreditUser.CurrentCell.RowIndex < CreditUsers.Count))
        {
            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,
        },
        new CreditUser
        {
            UserName = "Richard",
            CreditLimit=1250m,
            Restricted = true
        },
        new CreditUser
        {
            UserName = "Harry",
            CreditLimit=10000m,
        },
    };
}

CreditUser class

// REDUCED for minimal example
public class CreditUser : INotifyPropertyChanged
{
    string _UserName = string.Empty;
    public string UserName
    {
        get => _UserName;
        set
        {
            if (!Equals(_UserName, value))
            {
                _UserName = value;
                OnPropertyChanged();
            }
        }
    }
    string _Responsible = String.Empty;
    public string Responsible
    {
        get => _Responsible;
        set
        {
            if (!Equals(_Responsible, value))
            {
                _Responsible = value;
                OnPropertyChanged();
            }
        }
    }
    decimal _CreditLimit = 0;
    public decimal CreditLimit
    {
        get => _CreditLimit;
        set
        {
            if (!Equals(_CreditLimit, value))
            {
                _CreditLimit = value;
                OnPropertyChanged();
            }
        }
    }
    bool _Restricted = false;
    public bool Restricted
    {
        get => _Restricted;
        set
        {
            if (!Equals(_Restricted, value))
            {
                _Restricted = value;
                OnPropertyChanged();
            }
        }
    }
    public event PropertyChangedEventHandler PropertyChanged;
    private void OnPropertyChanged([CallerMemberName] string propertyName = null) =>
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

    public override string ToString()
    {
        var ctype = Responsible == string.Empty ? "Not Specified" : $"{Responsible}";
        return Restricted ?
        $"{UserName} ({ctype}) Restricted" :
        $"{UserName} ({ctype}) {CreditLimit}";
    }
}
IVSoftware
  • 5,732
  • 2
  • 12
  • 23
  • Thanks for a well written clear answer. As per your mock query for testing, my object is somewhat like this new CreditUser { UserName = "Harry", CreditLimit=10000m, Responsible = "Production" ResponsibleList = new List { "Production", "Customer Service", "Sales", "Distribution", }, } For every CreditUser ResponsibleList is the same of these 4 options, I want to display the value in Responsible in the combobox – Aditya Bhouraskar Aug 01 '22 at 16:15
  • Thx for clarifying! I will edit the answer so that everybody gets the same 4 options. In that case, the `CreditUser` class doesn't need the `ResponsibleList` property at all, and this might be what is causing some confusion. – IVSoftware Aug 01 '22 at 17:05
  • Code edited! If this is still not the outcome you're looking for, please let me know. You can [Clone](https://github.com/IVSoftware/display-multiple-values-in-dgv-column.git) this example but the new code is in a separate branch called `same-options-for-all-users` so just make sure to pull the correct version. – IVSoftware Aug 01 '22 at 17:49