La définition dynamique de DataGridViewComboBoxCell de la source de données à DataView filtrée à partir d'autres cellules de la sélection

J'ai été à la recherche haute et basse pour un moyen de le faire, mais en vain. Je suis venu avec une solution qui fonctionne, mais je me demande si il ya une meilleure façon de les gérer.

Le Problème:

Je suis à l'aide d'un DataGridView qui a deux DataGridViewComboBoxColumn, col1 et col2.

col1 a eu sa source de données définie à une DataTable. En fonction de la sélection à partir d'une cellule donnée dans col1, je voudrais avoir respectifs col2 cellule de la même ligne avoir sa source de données défini pour être un DataView filtrée de col2 de la source de données.

La version abrégée du code à partir de mon actuel de la mise en œuvre pourrait mieux aider à décrire ce que je suis en train de faire:

DataGridView dg = new DataGridView();
dg.DataSource = new BindingSource() { DataSource = _myDataTable }; //DataTable with Foreign Keys for ID1 and ID2
dg.CellValueChanged += new DataGridViewCellEventHandler(dg_CellValueChanged);
DataGridViewComboBoxColumn col1 = new DataGridViewComboBoxColumn();
col1.DataPropertyName = "ID1";
col1.DisplayMember = "Display1";
col1.ValueMember = "ID1";
col1.DataSource = dataTable1;
col1.ValueType = typeof(Int32);

DataGridViewComboBoxColumn col2 = new DataGridViewComboBoxColumn();
col2.DataPropertyName = "ID2";
col2.DisplayMember = "Display2";
col2.ValueMember = "ID2";
col2.DataSource = dataTable2;
col2.ValueType = typeof(Int32);

dg.Columns.Add(col1);
dg.Columns.Add(col2);

Puis-je définir le gestionnaire d'événement:

private void dg_CellValueChanged(object sender, DataGridViewCellEventArgs e)
{
if (e.ColumnIndex == 0 && e.RowIndex > -1)
{
var dgv = sender as DataGridView;
if (dgv == null)
return;
int selectedID;
if (!int.TryParse(dgv[e.ColumnIndex, e.RowIndex].Value.ToString(), out selectedID))
return;
var cell = dgv[e.ColumnIndex + 1, e.RowIndex] as DataGridViewComboBoxCell;
if (cell == null)
return;
var col = dgv.Columns[e.ColumnIndex + 1] as DataGridViewComboBoxColumn;
if(col == null)
return;
var dt = col.DataSource as DataTable; //The macro-DataTable containing all possible values
if(dt == null)
return;
DataView dv = new DataView(dt, "ID1 = " + selectedID, "DisplayOrder", DataViewRowState.CurrentRows);
if(dv.Count == 0)
return;
//This is the part that I am wondering if there is a better way of handling
cell.DataSource = dt; //Set the data source to the macro-DataTable so that when we set the Value to something in the new DataView it will not throw an exception
cell.DisplayMember = "Display2"; //Have to redefine the Display/Value members
cell.ValueMember = "ID2";
cell.Value = dv[0]["ID2"]; //Set the value to the first option in the new DataView to avoid an exception being thrown when setting the dv as the DataSource
cell.DataSource = dv;
}
}

La dernière partie qui est ma préoccupation.
Lors de la commutation dynamique de la source de données de cellule2, si cellule2 actuel de sélection n'apparaît pas dans le nouveau DataView, une exception sera levée:

Système.ArgumentException: DataGridViewComboBoxCell valeur n'est pas valide.

Pour éviter cela, je suis en train de la source de données à la macro-DataTable (contenant toutes les valeurs que peut être illustré à partir d'cell1 de sélection), la modification de la valeur sélectionnée de cellule2 pour être le premier résultat dans la fenêtre données et de définir ensuite cellule2 de la source de données pour être dit DataView. Tout cela garantit que la cellule est jamais une sélection non valide et qu'il fonctionne comme prévu.

Ma question est, est-il mieux/plus simple façon de le faire? Pour mon utilisation, ce code est activé uniquement lors de la création de nouvelles lignes, de sorte qu'il n'est pas fait plus de quelques fois dans la forme donnée. Cependant, si il ya une meilleure façon de le faire, ou quelques suggestions sur ce qu'elle soit mieux, je vous en serais reconnaissant!

(première question ici, donc je suis également ouvert à toutes les suggestions pour l'affichage ... excuses pour les erreurs commises)

MODIFIER (en fournissant à la structure de la table - aussi changé "BoundID" être "ID1" pour éviter toute confusion):

DataGrid de la structure de la table serait:

MainTableID int

ID1 int --C'est une clé étrangère vers col1

ID2 int --C'est une clé étrangère vers col2

La colonne 1 du tableau de la structure:

ID1 int

Display1 varchar(50)

La colonne 2 du tableau de la structure:

ID2 int

Display2 varchar(50)

ID1 int --C'est une clé étrangère vers col1

Mise à jour:
J'ai redéfini la CellValueChanged gestionnaire d'événement par Mohsen suggestions:

private void dg_CellValueChanged(object sender, DataGridViewCellEventArgs e)
{
if (e.ColumnIndex == 0 && e.RowIndex > -1)
{
var dgv = sender as DataGridView;
var cell = dgv[e.ColumnIndex + 1, e.RowIndex] as DataGridViewComboBoxCell;
if (cell == null)
return;
DataView dv = new DataView( ((DataTable)((DataGridViewComboBoxColumn)dgv.Columns[e.ColumnIndex + 1]).DataSource), "ID1 = " + dgv.CurrentCell.Value, "DisplayOrder", DataViewRowState.CurrentRows);
if(dv.Count == 0)
return;
cell.DisplayMember = "Display2"; //Have to redefine the Display/Value members
cell.ValueMember = "ID2";
cell.DataSource = dv;
}
}

Et j'ai ajouté dans un gestionnaire d'événement pour la DataError comme il le suggère:

void dg_DataError(object sender, DataGridViewDataErrorEventArgs e)
{
if(e.ColumnIndex != 1)
{
//Alert the user for any other DataError's outside of the column I care about
MessageBox.Show("The following exception was encountered: " + e.Exception);
}
}

Cela semble fonctionner parfaitement.

OriginalL'auteur Smitty | 2012-10-26