title: Dynamically setting DataGridViewComboBoxCell's DataSource to filtered DataView based off of other cell selection
body: <p>I have been looking high and low for a way to do the following, but to no avail. I came up with a solution that works, but am wondering if there is a better way of handling it.</p>
  <p><strong>The Problem:</strong></p>
  <p>I am using a DataGridView that has two DataGridViewComboBoxColumn, col1 and col2.</p>
  <p>col1 has had its DataSource set to a DataTable. Based off of the selection from a given cell in col1, I would like to have the respective col2 cell in the same row have its DataSource set to be a filtered DataView of col2's DataSource.</p>
  <p>The abbreviated code from my current implementation might better help describe what I am trying to do:</p>
  <pre><code>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 = &quot;ID1&quot;;
  col1.DisplayMember = &quot;Display1&quot;;
  col1.ValueMember = &quot;ID1&quot;;
  col1.DataSource = dataTable1;
  col1.ValueType = typeof(Int32);
  
  DataGridViewComboBoxColumn col2 = new DataGridViewComboBoxColumn();
  col2.DataPropertyName = &quot;ID2&quot;;
  col2.DisplayMember = &quot;Display2&quot;;
  col2.ValueMember = &quot;ID2&quot;;
  col2.DataSource = dataTable2;
  col2.ValueType = typeof(Int32);
  
  dg.Columns.Add(col1);
  dg.Columns.Add(col2);
  </code></pre>
  <p>Then I define the event handler as:</p>
  <pre><code>private void dg_CellValueChanged(object sender, DataGridViewCellEventArgs e)
  {
      if (e.ColumnIndex == 0 &amp;&amp; e.RowIndex &gt; -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, &quot;ID1 = &quot; + selectedID, &quot;DisplayOrder&quot;, 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 = &quot;Display2&quot;; // Have to redefine the Display/Value members
          cell.ValueMember = &quot;ID2&quot;;
          cell.Value = dv[0][&quot;ID2&quot;]; // 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;
      }
  }
  </code></pre>
  <p>The last part is my concern.
  When dynamically switching the DataSource of cell2, if cell2's current selection doesn't appear in the new DataView, then an exception will be thrown:</p>
  <p><strong>System.ArgumentException: DataGridViewComboBoxCell value is not valid.</strong></p>
  <p>To avoid that, I am setting the datasource to the macro-DataTable (containing all of the values that could be shown based off of cell1's selection), changing the selected value of cell2 to be the first result in the DataView and then setting cell2's DataSource to be said DataView. This all ensures that the cell is never going to have an invalid selection and it works as expected.</p>
  <p>My question is, is there a better/simpler way of doing this? For my use, this code is only activated when creating new rows, so it isn't being done more than a few times in the given form. However, if there is a better way of doing it or some suggestions on making it better, I would appreciate it!</p>
  <p>(first question here, so I am also open to suggestions for posting ... apologies for any missteps)</p>
  <p><strong>EDIT (providing table structure - also changed &quot;BoundID&quot; to be &quot;ID1&quot; to avoid confusion):</strong></p>
  <p>DataGrid's table structure would be:</p>
  <blockquote>
  <p>MainTableID int</p>
  <p>ID1 int --This is a foreign key to col1</p>
  <p>ID2 int --This is a foreign key to col2</p>
  </blockquote>
  <p>Column 1's table structure:</p>
  <blockquote>
  <p>ID1 int</p>
  <p>Display1 varchar(50)</p>
  </blockquote>
  <p>Column 2's table structure:</p>
  <blockquote>
  <p>ID2 int</p>
  <p>Display2 varchar(50)</p>
  <p>ID1 int --This is a foreign key to col1</p>
  </blockquote>
  <p><strong>Updated:</strong>
  I have redefined the CellValueChanged event handler per Mohsen's suggestions:</p>
  <pre><code>private void dg_CellValueChanged(object sender, DataGridViewCellEventArgs e)
  {
      if (e.ColumnIndex == 0 &amp;&amp; e.RowIndex &gt; -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), &quot;ID1 = &quot; + dgv.CurrentCell.Value, &quot;DisplayOrder&quot;, DataViewRowState.CurrentRows);
          if(dv.Count == 0)
              return;
  
          cell.DisplayMember = &quot;Display2&quot;; // Have to redefine the Display/Value members
          cell.ValueMember = &quot;ID2&quot;;
          cell.DataSource = dv;
      }
  }
  </code></pre>
  <p>And I have added in an event handler for the DataError like he suggested:</p>
  <pre><code>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(&quot;The following exception was encountered: &quot; + e.Exception);
      }
  }
  </code></pre>
  <p>This seems to work perfectly.</p>