How can I have an empty item in the bound ComboBox which uses NULL as the value for an Insert or Update?
With the code below, I can manually add the additional row. The column inspector_id is the primary key of an FK relationship. I have to set inspector_id = -1, since C# does not allow an int to be null. However, the insert (or update) fails since there is no inspector_id: -1 in the database.
private void ItemInfo_Load(object sender, EventArgs e)
{
// TODO: This line of code loads data into the 'someDBDataSet.inspector' table. You can move, or remove it, as needed.
this.inspectorTableAdapter.ClearBeforeFill = false;
someDBDataSet.inspectorRow newRow = this.someDBDataSet.inspector.NewinspectorRow();
newRow.inspector_id = -1; // Since an int in C# cannot be null
newRow.fullName = "(none)";
newRow.employeeCode = "";
this.someDBDataSet.inspector.AddinspectorRow(newRow);
this.inspectorTableAdapter.Fill(this.someDBDataSet.inspector);
//this.inspectorTableAdapter.ClearBeforeFill = false;
// TODO: This line of code loads data into the 'someDBDataSet.item' table. You can move, or remove it, as needed.
this.itemTableAdapter.Fill(this.someDBDataSet.item);
}
Eureka! Bind to a view, not table.
Bind inspector_idComboBox to a new SQL Server view of the inspector table.
SELECT NULL as inspector_id, '(none)' as fullName, '' as employeeCode
UNION
SELECT inspector_id, fullName, employeeCode
FROM dbo.inspector
Pros:
The (none) item is in the ComboBox
The SelectedItem and text persists when selecting the item.
The SQL view allows a NULL value for inspector_id
No workarounds are needed in the application code. Just fill the DataSet from the view.
Allows more flexibility as the relationship is not bound.
... brilliant!
Another approach is to clear the ComboBox when selecting (none):
private void inspector_idComboBox_SelectedIndexChanged(object sender, EventArgs e)
{
if (inspector_idComboBox.SelectedValue != null)
if ((int)inspector_idComboBox.SelectedValue == -1)
inspector_idComboBox.SelectedItem = null;
}
Pros:
The correct NULL value is saved to the DataSet and sent on to the database.
No external clear button is needed.
Cons:
Selecting (none) also clears the text. I'd prefer for (none) to stay selected.
After trying various approaches, I initially decided on the following:
private void ItemInfo_Load(object sender, EventArgs e)
{
this.inspectorTableAdapter.Fill(this.someDBDataSet.inspector);
this.itemTableAdapter.Fill(this.someDBDataSet.item);
}
private void noInspector_btn_Click(object sender, EventArgs e)
{
inspector_idComboBox.SelectedItem = null;
}
Rather than adding a dummy item into the ComboBox, I added a (link) button to clear the ComboBox.
Pros:
The ComboBox clears.
The tableAdapter sets item.inspector_id = NULL
Cons:
Other form controls bound to inspector fields remain unchanged (as there is no "empty" inspector row to use).
No text is displayed in inspector_idComboBox when SelectedItem is null. I'd prefer having something like (none) shown in the box.
Related
I have a View Model in which my data is stored to be used on the presenter and window. However, I now need to get (when pressing a button) the current row's results on the grid into my view model. I first used the mouse double click event, but the requirements changed from that to a button on the form:
private void dgvResults_CellMouseDoubleClick(object sender, DataGridViewCellMouseEventArgs e)
{
if (this.colOrderNo.Index != e.ColumnIndex || e.RowIndex < 0) return;
var auditOrder = (PurchaseOrdersTrackingViewModel) this.dgvResults.Rows[e.RowIndex].DataBoundItem;
this.AuditOrderKey = $"{auditOrder.OrderNo}|{auditOrder.ItemNo}|{auditOrder.WarehouseId}|{auditOrder.ItemSequence}";
}
That's my old code. Now I need that here:
private void btnAuditLog_Click(object sender, EventArgs e)
Not sure how to refer to the RowIndex without the DataGridViewCellMouseEventArges event. I tried the CurrentRow property, but it doesn't do the job.
Just another question on the fly, if someone could assist with that as well, how do I get a value from one form to another, spesifically a properties value. It's not on the grid, in a control. It's basically just public string order that's all. I set the value on one form, then need it for query filtering on another. Let me know if you need anything else.
You want to use the SelectedRows property of the DataGridView. It tells which row is selected. In this case, you seem to want the first selected row (since you have a single property called this.AuditOrderKey).
The column order no to longer matters in this context.
if (dgvResults.SelectedRows.Count == 0) return;
var auditOrder = (PurchaseOrdersTrackingViewModel) dgvResults.SelectedRows[0].DataBoundItem;
this.AuditOrderKey = $"{auditOrder.OrderNo}|{auditOrder.ItemNo}|{auditOrder.WarehouseId}|{auditOrder.ItemSequence}";
If you don't have selected rows, CurrentRow is an option:
if (dgvResults.CurrentRow == null) return;
var auditOrder = (PurchaseOrdersTrackingViewModel) dgvResults.CurrentRow.DataBoundItem;
this.AuditOrderKey = $"{auditOrder.OrderNo}|{auditOrder.ItemNo}|{auditOrder.WarehouseId}|{auditOrder.ItemSequence}";
Use the SelectedRows property of the DataGridView. Assuming that you set your DataGridView as MultiSelect = false; and SelectionMode = FullRowSelect;
if (dgvResults.SelectedRows.Count > 0)
{
dgvResults.SelectedRows[0].Cells["yourColumnName"].Value.ToString();
}
In your button click event, it will look like this. (Assuming that your button name is btnEdit)
private void btnEdit_Click(object sender, EventArgs e)
{
if (dgvResults.SelectedRows.Count > 0)
{
string selectedValue = dgvResults.SelectedRows[0].Cells["yourColumnName"].Value.ToString();
}
}
I have a ComboBox, which holds a 3rd level selection (Parent-category > Sub-category > Brand). When any of the two higher level categories are changed, the DataSource for the Brand selection should be refreshed, and the selection reset.
Now this works quite well for the Sub-category selection, but not for the Parent-category selection Despite using almost identical syntax.
I could of course just make a quick fix, using cboBrand.Text = string.Empty, but the thing is, that the brand combo box still holds the same number of slots as before, just empty. I.e. if there was 3 brand options before changing the parent category, there will be 3 empty slots in the drop down list. I have checked, and the Count() of viewModel.BrandOptions is zero, so this is not the issue.
Does anyone have any idea, what I am doing wrong?
Parent-category Changed (does not work)
private void cboCategory_SelectionChangeCommitted(object sender, EventArgs e)
{
viewModel.Category = (CategoryModel)cboCategory.SelectedItem;
// Update sub-category data source
cboSubCategory.DataSource = viewModel.SubCategoryOptions;
// Update Brand data source and reset selection
cboBrand.DataSource = viewModel.BrandOptions;
cboBrand.SelectedIndex = -1;
}
Sub-category Changed (this one works fine)
private void cboSubCategory_SelectionChangeCommitted(object sender, EventArgs e)
{
if (cboCategory.SelectedItem != null)
viewModel.SubCategory = (CategoryModel)cboSubCategory.SelectedItem;
// Update Brand data source and reset selection
cboBrand.DataSource = viewModel.BrandOptions;
cboBrand.SelectedIndex = -1;
}
Update
Setting cboBrand.DataSource = null will make it clear the combo box. But it does not fix the multiple blank rows, in the drop down list. I have also checked (before changing the data source to null) if there was any objects in the data source. This was not the case, as you can see in the image.
Have you tried putting:
private void cboCategory_SelectionChangeCommitted(object sender, EventArgs e)
{
viewModel.Category = (CategoryModel)cboCategory.SelectedItem;
// Update sub-category data source
cboSubCategory.DataSource = viewModel.SubCategoryOptions;
cboSubCategory.SelectedIndex = -1; // << new line to blank out SubCat
// Update Brand data source and reset selection
cboBrand.DataSource = viewModel.BrandOptions;
cboBrand.SelectedIndex = -1;
}
... I have a feeling that what's going on is that you're refreshing two data sources in two sequential lines, and that it's a timing issue - cboBrand is trying to refresh its data before cboSubCategory has the correct value in place.
I have a form with a DataGridView on it, that gets populated in the form's Constructor from a DataSource using a data adapter (AdsDataAdapter). It shows people's first name, surname and reference number (which is the primary key of the underlying table in the database). The rows in the DataGridView however are ordered by the surname field (by using the ORDER BY clause in the SELECT statement that gets passed to the data adapter).
After the DataGridView is populated, I want to be able to type a surname in a TextBox, and have the DataGridView navigate to the first row where the first letters of the surname matches what the user types.
How do I go about doing this navigation?
The "navigate" solution
private void textBox1_TextChanged(object sender, EventArgs e)
{
for (int i=0;i<theDataGridView.RowCount;i++)
{
if (theDataGridView.Rows[i].Cells["surname"].Value.ToString().StartsWith(textBox1.Text))
{
theDataGridView.CurrentCell = theDataGridView.Rows[i].Cells["surname"];
// optionally
theDataGridView.FirstDisplayedScrollingRowIndex = theDataGridView.CurrentCell.RowIndex;
break ;
}
}
}
The "Filter" solution
First, bind your DataTable to the DataGridView via a BindingSource.
BindingSource theBindingSource = new BindingSource() ;
theBindingSource.DataSource = theDataTable ;
theDataGridView.DataSource = theBindingSource ;
Then set the Filter property of the BindingSource in the TextChanged event:
private void textBox1_TextChanged(object sender, EventArgs e)
{
theBindingSource.Filter = "surname LIKE '"+textBox1.Text+"%'";
// if no match : Cancel filter
if (theDataGridView.RowCount==0) theBindingSource.Filter = "" ;
}
I am working with a datagrid in WPF which is bound to a collectionviewsource. The viewsource is bound to an observable collection named Rows.
The datagrid has add and delete functions which function properly except on small problem.
Here are images:
The datagrid has more data than this. Each test starts off with two sequences (the two rows you see belong to a single test) and they are grouped and sorted by a unique ID.
I have clicked the red "X" to delete the row. I will now click the "Add" button located at the top-left of the image.
The data is still there.
These are my add and delete functions:
private void Add(object sender, ExecutedRoutedEventArgs e)
{
var testRun = e.Parameter as TestRun;
if (testRun != null)
{
var numberOfRows = testRun.Property.GetValue("numberOfRows").ToNullable<int>().GetValueOrDefault(2);
numberOfRows++;
testRun.Property.SetValue("numberOfRows", numberOfRows.ToString());
this.Rows.Add(new ESCHandle(testRun, numberOfRows));
}
}
private void Delete(object sender, ExecutedRoutedEventArgs e)
{
var esc = e.Parameter as ESCHandle;
if (esc != null)
{
this.Rows.Remove(esc);
var numberOfRows = esc.TestRun.Property.GetValue("numberOfRows").ToNullable<int>().GetValueOrDefault(2);
numberOfRows--;
esc.TestRun.Property.SetValue("numberOfRows", numberOfRows.ToString());
}
}
The ESC object is properly removed from the observablecollection on Delete. But on when I add another ESC object/row to the colleciton and datagrid, the data is somehow copied to the new object.
You may forget to call a refresh method on datagrid to update its visual elements, such as the rows.
Datagrid.Items.Refresh(), as described here:
http://programmer.wrighton.org/2009/01/wpf-datagrid-items-refresh.html
This problem may be caused because PropertyChange is not raised properly.
I have a master-detail layout with a section of popup menus (the Details) and a section with a DataGridView which holds the rows.
The popup-menu state is updated when the selected row in the DataGridView changes and the state in the DGV's selected row should update when the popup-menu changes.
All of this works except the row in the DataGridView doesn't immediately update when I change the value of the popup-menu. I have to select a different row in order to see my edits.
I'm assuming this is because the edit hasn't been committed until the selection changes.
My question is: How do I make the change to the popup become immediately reflected in the DataGridView?
I have experimented with calling EndEdit() in the SelectionChangeCommitted handler for the popup-menu, but this has no effect. I'm interested in a technique that would allow me to create a DataGridView that would behave as if there were no Undo mechanism to begin with. Ideally the solution would be generic and transplantable to other projects.
It looks like existing answers work well with BindingSource. In my case, where DataTable was directly used as a DataSource, they didn't work for some reason.
// Other answers didn't work in my setup...
private DataGridView dgv;
private Form1()
{
var table = new DataTable();
// ... fill the table ...
dgv.DataSource = table;
}
After some hair-pulling, I got it work without adding BindingSource indirection:
// Add this event handler to the DataGridView
private void dgv_CellEndEdit(object sender, DataGridViewCellEventArgs e)
{
dgv.BindingContext[dgv.DataSource].EndCurrentEdit();
}
private Form1()
{
dgv.CellEndEdit += dgv_CellEndEdit;
// ...
}
Here's what was going on. The answer was in the properties of the ComboBox instances. I needed to change their DataSourceUpdateMode from OnValidation to OnPropertyChanged. This makes sense. The DataGridView was very likely showing the current state of the data. It was just that the data hadn't been edited yet because focus had not left the ComboBox, validating the input.
Thanks to everyone for your responses.
this works well for me:
private void CurrentCellDirtyStateChanged(object sender, EventArgs e)
{
var dgw = (DataGridView) sender;
dgw.CommitEdit(DataGridViewDataErrorContexts.Commit);
}
Use this extension method. It works for all columns types, not just ComboBoxes:
public static void ChangeEditModeToOnPropertyChanged(this DataGridView gv)
{
gv.CurrentCellDirtyStateChanged += (sender, args) =>
{
gv.CommitEdit(DataGridViewDataErrorContexts.Commit);
if (gv.CurrentCell == null)
return;
if (gv.CurrentCell.EditType != typeof(DataGridViewTextBoxEditingControl))
return;
gv.BeginEdit(false);
var textBox = (TextBox)gv.EditingControl;
textBox.SelectionStart = textBox.Text.Length;
};
}
This method commits every change just after the change is made.
When we have a text column, after typing a character, its value will commit to the DataSource and the editmode of the cell will end.
Therefore current cell should return to edit mode, and position of the cursor set to end of text in order to enable user to continue typing reminder of the text.
Call the DataGridView.EndEdit method.
following will work
_dataGrid.EndEdit()
s fine once you set the value.