How to programmatically add new lines to a winforms datagridview in response to a trigger code entered into the current line?
I have a datagridview in a c# .net WinForms application that captures order detail lines. When the operator enters an item code that is a deal/composite item i want to be able to automatically insert that item's parts.
I thought this would be easy as I have an import function whereby a spreadsheet can be parsed and the lines entered directly into the datagridview' s datasource (a strongly typed table). When this happens the grid updates itself nicely.
However Im finding when the operator is "in" a line and i try to trigger this action the new data fills the newline row "visually" but a new newline is not created and if i then click on the trigger line the new line data disappears. So basically the operator can no longer enter anything after the they entered a trigger code and the part lines basically lock up.
I've tried using BindingSources as the datasource as well but that has no effect.
Im thinking that this i because the code is still in some "event" and that is preventing the addition of new rows. I've tried handl开发者_开发问答ing the RowEnter event to then check if the last entered code was a deal code but then Im back in the middle of an event and the whole thing "locks" up again. So how to get out of the event bubble so i can add new lines?
Some of my code below:
In subclassed datagridview: (this.Controller.BuildOrderLine(this.CurrentOrderLine);
protected override void OnCellValueChanged(System.Windows.Forms.DataGridViewCellEventArgs e)
{
base.OnCellValueChanged(e);
if ((this.Order == null) || (this.IsBinding) || (this.SelectedRows.Count == 0) || (e.RowIndex < 0)) { return; }
this.LockCells(e.RowIndex, !this.IsCurrentOrderLineStubValid);
this.ComputeOrderLine();
}
protected void ComputeOrderLine()
{
if (this.IsReadOnlyGrid) { return; }
if (!this.SetCurrentOrderLine(this.CurrentCell.RowIndex)) { return; }
if (this.CurrentCell.ColumnIndex == COLINDEX_LINEPRICE) { this.CurrentOrderLine.OverrideUnitPriceOn = true; }
if (this.CurrentCell.ColumnIndex == COLINDEX_DISC)
{
this.CurrentOrderLine.OverrideDiscountOn = true;
this.CurrentOrderLine.OverrideDiscountCommitted = false;
if (this.IsCurrentCellValueNullOrEmpty) { this.CurrentOrderLine.LineDiscountPct = 0; }
}
if (!this.CurrentOrderLine.IsLKeyNull())
{
this.Controller.BuildOrderLine(this.CurrentOrderLine);
this.LockCells(this.CurrentRow.Index, this.CurrentOrderLine.IsParentItem);
this.HighlightRow(this.CurrentCell.RowIndex);
}
}
In a controller:
public void BuildOrderLine(DBilling.BillingDocumentDetailRow orderLine)
{
SalesItem item = this.FindMaterialItem(orderLine.LKey);
orderLine.LineSellingUnit = item.TriggerItem.SellingUnit;
if (item.IsDealCode)
{
this.CurrentOrderLineItem = item;
this.CurrentOrderLineRow = orderLine;
}
this.LineBuilder.ProjectItem(orderLine, item); }
In a business helper object:
public virtual void ProjectItemDetails(DBilling.BillingDocumentDetailRow headerline, SalesItem item)
{
if (!item.IsDealCode) { return; }
DBilling.BillingDocumentDetailRow line = null;
for (int i = 0; i < item.Parts.Count; i++)
{
line = this.DocumentDetail.NewBillingDocumentDetailRow();
line.MaterialItemId = item.Parts[i].PartItem.PartItemId;
line.LKey = item.Parts[i].BaseItem.LKey;
line.MName = item.Parts[i].BaseItem.MName;
line.ParentItemLineItemId = headerline.LineItemId;
line.IsParentItem = false;
line.LineCaptureMethod = "I";
this.ProjectItemDetailLine(headerline.QuantitySold, ref line, item.Parts[i]);
this.DocumentDetail.AddBillingDocumentDetailRow(line);
this.ComputeLineTotals(line);
}
}
O.k. so the short answer to my problem was to swap out the data table implementations for a BindingSource. I've encountered a couple of other problems in the past which were solved by simply using a BindingSource component. The documentation does say that DataGridView is designed to work very closing with BindingSources and my experiences confirm this.
The reason why this works appears to be because the DataGridView uses a CurrencyManager internally to track row position. Although I believed that this currency manager would wrap the datasource I bound the grid too, which it does to some extent, it doesn't appear to hang up/ end current row editing when using a datatable as the datasource.
So I assume that's because the table doesn't fire off the expected events / perhaps doesn't implement the appropriate interfaces.
Detail:
Using Redgate Reflector I was able to drill into the DataGridView control. DataGridView makes uses of a couple of internal classes that you simply cant access from your own code, the most important of which (for the problem at hand) is:
internal class DataGridViewDataConnection
In that class is a long method called:
private void ProcessListChanged(ListChangedEventArgs e)
An important part of that method is as follows:
if ((this.owner.NewRowIndex != -1) && (e.NewIndex == this.owner.Rows.Count))
{
throw new InvalidOperationException();
}
this.owner.Rows.InsertInternal(e.NewIndex, this.owner.RowTemplateClone, true);
Label_05B4:
if (((this.owner.Rows.Count > 0) && !this.dataConnectionState[8]) && !this.owner.InSortOperation)
{
this.MatchCurrencyManagerPosition(false, e.ListChangedType == ListChangedType.Reset);
}
}
finally
{
this.dataConnectionState[0x10] = false;
}
You can see that the this.MatchCurrencyManagerPosition method moves the current position of the newline row to the end of the grid as below:
public void MatchCurrencyManagerPosition(bool scrollIntoView, bool clearSelection)
{
if (this.owner.Columns.Count != 0)
{
int columnIndex = (this.owner.CurrentCellAddress.X == -1) ? this.owner.FirstDisplayedColumnIndex : this.owner.CurrentCellAddress.X;
if (columnIndex == -1)
{
DataGridViewColumn firstColumn = this.owner.Columns.GetFirstColumn(DataGridViewElementStates.None);
firstColumn.Visible = true;
columnIndex = firstColumn.Index;
}
int position = this.currencyManager.Position;
if (position == -1)
{
if (!this.owner.SetCurrentCellAddressCore(-1, -1, false, false, false))
{
throw new InvalidOperationException(SR.GetString("DataGridView_CellChangeCannotBeCommittedOrAborted"));
}
}
else if (position < this.owner.Rows.Count)
{
if ((this.owner.Rows.GetRowState(position) & DataGridViewElementStates.Visible) == DataGridViewElementStates.None)
{
this.owner.Rows[position].Visible = true;
}
if (((position != this.owner.CurrentCellAddress.Y) || (columnIndex != this.owner.CurrentCellAddress.X)) && ((scrollIntoView && !this.owner.ScrollIntoView(columnIndex, position, true)) || (((columnIndex < this.owner.Columns.Count) && (position < this.owner.Rows.Count)) && !this.owner.SetAndSelectCurrentCellAddress(columnIndex, position, true, false, false, clearSelection, false))))
{
throw new InvalidOperationException(SR.GetString("DataGridView_CellChangeCannotBeCommittedOrAborted"));
}
}
}
}
This was the problem I needed to solve as the line(s) being input via the trigger were getting hung in the newline row and I simply couldn't find the appropriate event to end the edit lock when using data tables and as above these methods were inaccessible to my code.
Once i could see that internally the DataGridView was using a currency manager I figured I'd try a Binding Source replacement for the data tables which has solved the problem.
精彩评论