C# Showing Data in DatagridView? - c#

This is my Form 1
When I click on the last row it shows data on the second grid control, Now I Want to Show this Data on the following Form (form 2 (Form with Purchase Written on Orange Color)) datagridview How can I do this.
table.Columns.Add("Item Name", Type.GetType("System.String"));
table.Columns.Add("Main Qty", Type.GetType("System.Decimal"));
table.Columns.Add("Price", Type.GetType("System.Decimal"));
table.Columns.Add("Per", Type.GetType("System.String"));
table.Columns.Add("Basic Amount", Type.GetType("System.Decimal"));
table.Columns.Add("Dis Amount", Type.GetType("System.Decimal"));
table.Columns.Add("Dis Percentage", Type.GetType("System.Decimal"));
table.Columns.Add("Tax Amount", Type.GetType("System.Decimal"));
table.Columns.Add("Net Value", Type.GetType("System.Decimal"));
dataGridView1.DataSource = table;
Above is the Form Load of (form 2)
And Below is the RowClick of Form 1
private void gridView1_RowClick(object sender, DevExpress.XtraGrid.Views.Grid.RowClickEventArgs e)
{
try
{
FRM_Purchase frm = new FRM_Purchase();
var ctx = new BizPlusEntities();
int GettingIdForShowing = (int)gridView1.GetRowCellValue(e.RowHandle, "PurchaseID");
var GettinginToDatabase = ctx.Purchases.Where(x => x.PurchaseID == GettingIdForShowing).ToList();
foreach (var item in GettinginToDatabase)
{
frm.txtPartyName.Text = item.PartyName;
frm.txtDate.Text = item.Date.ToString();
frm.txtTerms.Text = item.Terms;
frm.txtSeries.Text = item.Series;
frm.txtDueDate.Text = item.DueDate.ToString();
frm.txtPinvoice.Text = item.Pinvoice.ToString();
UniqueIdentifier = item.UniquePurchaseNumber;
string SelectingUniqueIdentfier = ctx.Purchases.SingleOrDefault(x => x.PurchaseID == item.PurchaseID)?.UniquePurchaseNumber ?? "Nulled";
var GettingInItems = ctx.ItemPurchaseDatas.Where(x => x.UniquePurchaseNumber == SelectingUniqueIdentfier).ToList();
foreach (var Sam in GettingInItems)
{
TItemName = Sam.ItemName;
TMainQty = Sam.MainQty ?? 0;
TPrice = Sam.Price ?? 0;
TPer = Sam.Per;
TBasicAmount = Sam.BasicAmount ?? 0;
TDisAmt = Sam.DisAmount ?? 0;
TDisP = Sam.DecimalPercentage ?? 0;
TTaxAmount = Sam.Gst ?? 0;
TTotalAmount = Sam.TotalAmount ?? 0;
}
frm.Show();
}
}
catch (Exception)
{
throw;
}
}
The problem is When I do frm.Table.Rows.Add(TItem,TMainQty...) on Form1
it shows input array is longer than the number of columns in this table
and when I create a new column it says the column already exists.

My advice would be to separate your data from the way that the data is displayed. Apart from that this makes it easier to unit test your data handling, it gives the displayer of the data the freedom to change how this data is being displayed.
In WPF this separation of model and view is almost forced, in Winforms you really have to pay attention otherwise you mix your data handling with the way that it is displayed, making it hard to change this.
In your case: should Form1 care about how the data is displayed in Form2, should it know that Form2 uses a DataGridView? Or should Form1 only care about what data is displayed in Form2, not in what format?
A proper interface with Form2 would be, that other Forms tell what data should be displayed, and if the data can be changed, that the other Form can ask afterwards the value of the data. Something like this:
private void ShowForm2()
{
var dataToShow = this.FetchDataToShow();
using (var dlg = new Form2())
{
dlg.Data = dataToShow;
var dlgResult = dlg.ShowDialog(this);
if (dlgResult == DialogResult.OK)
{
var dataToProcess = dlg.Data;
this.ProcessData(dataToProcess);
}
}
}
This way, you only tell Form2 what data to show, other forms don't really care about how Form2 shows its data. This gives Form2 the freedom to change how the data is displayed. Every user of this Form will have the same human interface.
By the way: did you notice that I also separated where Form1 gets the data for Form2 from and where it stores the results? This procedure also does not care about how the data is displayed in Form1, and gives you the freedom to change Form1, without having to change this procedure.
Use Databinding
It is usually way easier to use DataBinding to handle the rows in a DataGridView than to access the rows and the cells of the DataGridView directly.
To use databinding, your columns need to know which property of your Class should be displayed in this column. This is usually done in visual studio designer.
In your case, it seems that the DataGridView of Form2 needs to show ItemPurchaseDatas: every Row in the DataGridView will show several properties of one ItemPurchaseData. Using visual studio designer you will have added columns, and in every column you select the name of the property that needs to be displayed in that column:
DataGridView dataGridView1 = new DataGridView();
DataGridViewColumn columnName = new DataGridViewColumn();
columName.HeaderText = "Item Name";
columName.DataPropertyName = nameof(ItemPurchaseData.Name);
...
DataGridViewColumn columnPrice = new DataGridViewColumn();
columnPrice.HeaderText = "Price";
columnPrice.DataPropertyName = nameof(ItemPurchaseData.Price);
...
We earlier saw that the dialog had a property Data, that contains the data to be shown.
The form needs a method to extract the ItemPurchaseDatas that must be shown in the DataGridView:
public IEnumerable<ItemPurchaseData> GetInitialItemPurchaseDatas()
{
// TODO: use property Data to extract the ItemPurchaseDatas that must be shown
// in the DataGridView
}
Now all you have to do is on the event handler of FormLoad, get the data and put it in the DataSource of dataGridView1:
private void OnFormLoading(object sender, ...)
{
List<ItemPurchaseData> itemPurchaseDatas = GetInitialItemPurchaseDatas().ToList();
this.dataGridView1.DataSource = itemPurchaseDatas;
}
This is enough to show the data. However, it will be readonly: any changes that the operator makes: edits, addition of rows, removal of rows etc, are not reflected in itemPurchaseDatas. If you want that, you need an object that implements IBindingList like BindingList<T>.
If you want to know the changes that the operator made, it is usually wise to add the following methods:
private BindingList<ItemPurchaseData> DisplayedData
{
get => (BindingList<ItemPurchaseData>)this.dataGridView1.DataSource;
set => this.dataGridView1.DataSource = value;
}
Now every change that the operator makes to the displayed data: add / remove rows, change cells, etc are reflected in property DisplayedData. Again, the display of the data is separated from the data itself: If the operator changes the looks of how the data is displayed, sorting the rows, rearranging the columns has no influence on the DisplayedData.
If you regularly have to handle SelectedRows, consider to add the following properties:
private ItemPurchaseData CurrentItemPurchaseData =>
(ItemPurchaseData)this.dataGridView1.CurrentRow?.DataBoundItem;
private IEnumerable<ItemPurchaseData> SelectedItemPurchaseData =>
this.dataGridView1.DataSource.SelectedRows.Cast<DataGridViewRow>()
.Select(row => row.DataBoundItem)
.Cast<ItemPurchaseData>();
Usage: on form loading displaying the data in the DataGridView and after a button press process the edited data:
private void OnFormLoading(object sender, ...)
{
IEnumerable<ItemPurchaseData> itemPurchaseDatas = GetInitialItemPurchaseDatas();
this.DisplayedData = new BindingList<ItemPurchaseData>(itemPurchaseDatas.ToList());
}
private void OnButtonOk_Clicked(object sender, ...)
{
ICollection<ItemPurchaseData> editedData = this.DisplayedData;
// if needed: check which items are changed
this.ProcessChangedData(editedData);
}
Again: due to the separation of view and model, the code in the view are one-liners
If you only want to Display the data, it is

Related

How to update edits in DataGridView to table

I have bound a DataGridView to an SQL Server table in a .Net 5.0 WinForms project. Displaying the data works well.
I would like to update editions to the database as soon as I move to another row in the DataGridView. But I have not found a way to do this.
The solution presented here seems not to work with an OleDbDataAdapter. The Update method does not accept a DataRow. Examples in DOCS require a DataSet which I try to avoid. Other examples use a button to save changes.
The data gets loaded like this:
var dataAdapter = new OleDbDataAdapter(sqlQueryString, connString);
var dataTable = new DataTable();
dataAdapter.Fill(dataTable); // fill data table from SQL server
var bindingSource = new BindingSource();
bindingSource.PositionChanged += new System.EventHandler(bindingSource_PositionChanged);
bindingSource.DataSource = dataTable; // connect binding source to data table
dataGridView.DataSource = bindingSource; // connect DataGridView to binding source
For the update I finally have tried this:
private void bindingSource_PositionChanged(object sender, EventArgs e)
{
DataRow dataRow = ((DataRowView)((BindingSource)sender).Current).Row;
if (dataRow.RowState == DataRowState.Modified) // this is successful
{
dataAdapter.Update(dataRow); // compile error
}
}
I get the compile error
Cannot convert from 'System.Data.DataRow' to 'System.Data.DataRow[]'.
Any hint is appreciated.
MVVM
In modern programming, there is the tendency to separate the model from the view. This separation makes it easier to change the way that your data is displayed without having to change your model. You can also change parts of the model without having to change the display. It is easier to reuse the model and to unit test it without having to start a forms program.
In WPF this separation between model and view is almost enforced. When using winforms you have to take care that you do not mix them more than needed.
To keep these two separated, adapter code is needed to glue your model to your view. This adapter code is quite often called the viewmodel. the abbreviation of these three is quite often called MVVM. Consider to familiarize yourself with the ideas of MVVM.
Use a BindingList in your DataSource
If you want to separate your model from your view, you need methods to fetch the data that must be displayed from the database, and data to update items.
I don't know what you will be displaying in your DataGridView, but let's assume it is a sequence of Products, something like this:
class Product
{
public int Id {get; set;}
public string ProductCode {get; set;}
public string Name {get; set;}
public string Description {get; set;}
public decimal Price {get; set;}
...
}
You will have methods to fetch the Products that must be displayed, and to Update one Product, or maybe several Products at a time:
IEnumerable<Product> FetchProductsToDisplay(...)
{
// TODO: fetch the products from the database.
}
void UpdateProduct(Product product) {...}
void UpdateProducts(IEnumerable<Product> products) {...}
Implementation is out of scope of this question. By the way, did you notice, that because I put fetching and updating data in separate procedures, I hid where the Products are saved? It can be in an SQL server, but if you want it could also be a CSV or XML file, or even a dictionary, which could be handy for unit tests.
Besides: you can unit tests these methods without using your forms.
Using the visual studio designer you have added the columns and defined which column should show which Product property. You could also have done this in the constructor using property DataGridViewColumn.DataPropertyName
public MyForm()
{
InitializeComponents();
this.columnProductCode.DataPropertyName = nameof(Product.ProductCode);
this.columnName.DataPropertyName = nameof(Product.Name);
...
}
You don't need to set the DataPropertyName for properties that you won't show anyway.
Now to display the products, it is enough to assign the Products to the DataSource of the DataGridView:
var productsToDisplay = this.FetchProductsToDisplay(...);
this.dataGridView1.DataSource = productsToDisplay.ToList();
This will display the products. However, changes that the operator makes: Add / Remove / Edit rows are not updated. If you need this functionality, then the Products need to put in an object that implements IBindingList, like (surprise!) BindingList<Product>:
private BindingList<Product> DisplayedProducts
{
get => (BindingList<Product>)this.dataGridView1.DataSource;
set => this.dataGridView1.DataSource = value;
}
To Initialize the DataGridView:
private void DisplayProducts()
{
this.DisplayedProducts = new BindingList<Product>(this.FetchProductsToDisplay().ToList());
}
Now whenever the operator makes any change to the DataGridView: Add / Remove rows, or change the Displayed values in a row, these changes are reflected in DisplayedProducts.
If for instance the operator clicks Apply Now to indicate he has finished editing the products:
private void OnButtonApplyNow_Clicked(object sender, ...)
{
var products = this.DisplayedProducts;
// find out which Products are Added / Removed / Changed
this.ProcessEditedProducts(products);
}
Of course you can Add / Remove / Change displayed products programmatically:
void AddProductsToDisplay()
{
Product product = this.DisplayedProducts.AddNew();
this.FillNewProduct(product);
}
Back to your question
Ask yourself: Is it wise to update the database as soon as the position is changed?
If the operator starts typing, then remembers he can copy-paste items, he will stop typing, go to other controls to copy, and then continue editing the cell by pasting. Maybe he goes to other rows to look at information to decide what to put in the cell.
Another scenario: the Descriptions of Product A and Product B need to be exchanged. Think of the operator actions needed for this. When would it be wise to update the database? When are you certain that the operator is content with the new data?
Hence it is not wise to update the database as soon as a row is edited. The operator should explicitly indicate he has finished editing.
private void OnButtonOk_Clicked(object sender, ...)
{
var products = this.DisplayedProducts;
// find out which Products are Added / Removed / Changed
this.ProcessEditedProducts(products);
}
Further improvements
Once you've separated your data (model) from the way this data is displayed (view), using the DataSource, it is quite easy to access the Product that is displayed in the current row or in the selected rows:
Product CurrentProduct => (Product) this.dataGridView1.CurrentRow?.DataBoundItem;
IEnumerable<Product> SelectedProducts = this.dataGridView1.SelectedRows
.Cast<DataGridViewRow>()
.Select(row => row.DataBoundItem)
.Cast<Product>();
you can use foreach loop.
private void AddInfo()
{
// flag so we know if there was one dupe
bool updated = false;
// go through every row
foreach (DataGridViewRow row in dgv_Purchase.Rows)
{
// check if there already is a row with the same id
if (row.Cells["Pro_ID"].ToString() == txt_ProID.Text)
{
// update your row
row.Cells["Purchase_Qty"] = txt_Qty.Text;
updated = true;
break; // no need to go any further
}
}
// if not found, so it's a new one
if (!updated)
{
int index = dgv_Purchase.Rows.Add();
dgv_Purchase.Rows[index].Cells["Purchase_Qty"].Value = txt_Qty.Text;
}
}
Finally I've found the 2 missing lines:
private SqlCommandBuilder commandBuilder; // on UserControl level
commandBuilder = new SqlCommandBuilder(dataAdapter); // when loading data
A book has helped me: Michael Schmalz, C# Database Basics, O'Reilly
It is strange that the DOCS reference of SqlDataAdapter doesn't mention the SqlCommandBuilder.
Thanks to everybody who has spent precious time for a New contributor.

Add ContextMenuStrips to multiple DataGridViews on multiple TabPages

I am trying to load multiple DataTables into DataGridViews in separate tabpages of a TabControl, the DataTables are stored in a Dictionary which is in turn stored in a DataObject class.
I can get everything displaying correctly but when I try to add loop through the Datagridview Header to add a contextmenustrip, for some reason I can get it to work for the first DataGridView but subsequent DataGridViews do not have the context menu applied? I have tried adding a print statentment just before the foreach loop and the DataGridView gets a column count of zero…so im guessing that’s why the foreach loop isn’t doing anything…but all the data is still displayed correctly in the dataGridViews in their respective tabs…any help in pointing out what I’m missing would be greatly appreciated.
Regards
Amarino
Code given below
List<ImportObject> lImportObjects = new List<ImportObject>();
private void loadImportFilesToScreen(List<ImportObject> lImportObjects)
{
foreach (ImportObject lImportObject in lImportObjects) {
DisplayImportFiles(lImportObject);
}
}
public void DisplayImportFiles(ImportObject pImportObject)
{
string lTabName="";
//load DataGridView with DataTable
/*
foreach (KeyValuePair<string, DataTable> lDT in pImportObject.DataTableDictionary)
{
lTabName = DisplayTabsInApp(pImportObject.FileName + "_" + lDT.Key, lDT.Key);
LoadDatatableIntoGrid(lDT.Value, lTabName);
}
*/
for (int i = 0; i < pImportObject.DataTableDictionary.Count; i++)
{
KeyValuePair<string, DataTable> lItem = pImportObject.DataTableDictionary.ElementAt(i);
string lKey = lItem.Key;
DataTable lDT = lItem.Value;
lTabName = DisplayTabs(pImportObject.FileName + "_" + lKey, lKey);
LoadDatatableIntoGrid(lDT, lTabName);
lDT = null;
}
}
public string DisplayTabs(string pTabName, string pSheetName)
{
// Create a new Tab Page for this file. Set heading, set name.
TabPage lTabPage_NewFile = new TabPage();
lTabPage_NewFile.Text = pTabName;
lTabPage_NewFile.Name = "TAB_PAGE_" + pTabName;
tabControl_ImportFiles.TabPages.Add(lTabPage_NewFile);
return lTabPage_NewFile.Name;
}
public void LoadDatatableIntoGrid(DataTable pDataTable, string pTabName) {
DataGridView lDGV = new DataGridView();
lDGV.RowHeadersWidthSizeMode = DataGridViewRowHeadersWidthSizeMode.EnableResizing;
lDGV.RowHeadersVisible = false;
BindingSource BindingSource1 = new BindingSource(); //create new data binding source
BindingSource1.DataSource = pDataTable; //SetData source
lDGV.DataSource = BindingSource1;
lDGV.RowHeadersVisible = true;
tabControl_ImportFiles.TabPages[pTabName].Controls.Add(lDGV);
//DataGridView lDGV = tabControl_ImportFiles.TabPages[pTabName].Controls[0] as DataGridView;
PrintToConsoleInARD("DataGridView Column Count: " + lDGV.Columns.Count.ToString());
bool runOnce = true;
foreach (DataGridViewColumn lDGVColumn in lDGV.Columns) {
lDGVColumn.HeaderCell.ContextMenuStrip = lCMS_ColumnHeaders;
}
lDGV.Dock = DockStyle.Fill;
lDGV.VirtualMode = true;
BindingSource1 = null;
lDGV = null;
}
I am confident the reason for this is because the “tab page” is not displayed. As the trace is described inside the LoadDatatableIntoGrid method… if you put a breakpoint at the line…
tabControl_ImportFiles.TabPages[pTabName].Controls.Add(lDGV);
Add a watch to the variable lDGV.Columns.Count… will show that IDGV has zero (0) columns. Execute the line above and magically, the grid IDGV has columns. This appears correct since this is the first “tab page” and it is the active (displayed) tab page. Point being… if you do not add the grid to a “Active/Shown/Displayed” tab page most UI code will be ignored. This is why the next time around; the grid will be empty because the grid is added to a non “active/shown/displayed” tab page.
A simple solution is to simply “Show” the tab page before you add the grid to it. This appears to fix the issue you describe. Add the line below before the line above…
tabControl_ImportFiles.TabPages[pTabName].Show();
Hope this helps.

how to avoid rebinding grid each time the control is initialized?

I've created a custom user control with a grid. I'd like to bind this grid once, and use it over and over again in my app. If I put the binding within the control, the data is retrieved as many times as I use the control. How do I bind it only once??
public ClientLookUp()
{
InitializeComponent();
vw_clientsTableAdapter.Fill(dsclientlkup.vw_clients); //This occurs as many times as I have the user control, instead of just once.
}
Well anything you put in the constructor will be executed every time you construct the object!
What about providing an Initialize method that you can call whenever you need to reload the data??
If you want to load the data only once, then load it either into a static variable or a separate class that is referenced by the control.
If you really want to use the same single grid in your control over and over, you could create a single, static grid, and have your ClientLookUp constructor add it to the right place—Panel, or whatever—whenever a new one is created.
Before you go do this road however, ask yourself if this is really what you want to do. Having the same identical grid existing in many places may cause you problems down the road. If you want to support in-grid editing, you'll find that changing one value changes the identical value in all your other grids..
EDIT
I tried getting the below code to work, but I'm not sure this approach will be possible. It seems as though the minute you try to attach the same UI element into more than one place, it gets moved out of the last place you put it; it doesn't look like you can have the same grid being in more than one place at once. This makes sense when you think about it.
Here's the code I tried. Maybe it will be of some use to you.
public UserControl1()
{
InitializeComponent();
this.Controls.Add(myStaticGridView);
myStaticGridView.Dock = DockStyle.Fill;
}
static DataGridView _staticGrid;
public DataGridView myStaticGridView
{
get
{
if (_staticGrid != null)
return _staticGrid;
_staticGrid = new DataGridView();
_staticGrid.Columns.Add("A", "A");
_staticGrid.Columns.Add("B", "B");
_staticGrid.Columns.Add("C", "C");
_staticGrid.Columns[0].DataPropertyName = "A";
_staticGrid.Columns[1].DataPropertyName = "B";
_staticGrid.Columns[2].DataPropertyName = "C";
_staticGrid.DataSource = new[] {
new { A = "someA", B = "someB", C = "someC"},
new { A = "someA", B = "someB", C = "someC"},
new { A = "someA", B = "someB", C = "someC"},
new { A = "someA", B = "someB", C = "someC"},
};
return _staticGrid;
}
}
And then loading the control like this:
private void button1_Click(object sender, EventArgs e)
{
flowLayoutPanel1.Controls.Add(new UserControl1());
}

Adding User input to List then populating Gridview

I have an 3 tier application (DAL, BBL, UI)
BBL at the moment do Nothing just a pass-thru
I have a grid view and for simplicity's sake one text box(TB) and one drop down list(DDL).
and Two submit buttons.
(I changed my Custom Class to Object. just for this example)
First Submit button adds the TB.text & DDL.SelectedValue to a Object X in the UI.
the BBL takes that object X to adds it to a List(X) in the BBL.
Then the BBL should populate the Gridview with the List(X). (with ajax partial page load)
the second Submit should send the full List(X) to the database.
The problem im having is when I click the first Submit(the local) I dont get new Rows just keep over writing the same row. what am I Missing?
in the UI class
private businesslogic blogic = new businesslogic();
protected void B1_local_Click(object sender, EventArgs e)
{
object x = new object();
x.id = Convert.ToInt32(TB_1.Text);
x.var1 = Convert.ToInt32(DDL_1.SelectedValue);
blogic.addrowtolist(x);
Gridview1.DataSource = blogic.grablist();
Gridview1.Databind();
}
in the BBL class
public List<object> locallist = new List<object>();
public void addrowtolist(object x)
{
locallist.Add(x);
}
public List<object> grablist()
{
return locallist;
}
With every postback, you're loading a new BL with a new (empty) List. To see your List grow, you're going to need to save it somewhere that persists (doesn't disappear) between one request and the next.
I would recommend putting your List in a Session key
Session["items"] = blogic.locallist;
and then pulling it out and sending it to a second BL constructor on postback. This is probably simplest, but not always the correct approach.
http://msdn.microsoft.com/en-us/library/z1hkazw7.aspx

BindingSource sees changes, DataTable doesn't

I have recently switched over from Java/RMI to C# / .net, and am working on my first project using databinding to update records in Oracle. On this first form I'm building, I have a detail view for vehicle records (VIN, year/make/model, license plate number, that sort of thing). The first thing I did in terms of writing to the DB was saving updates:
private void btn_saveDesc_Click(object sender, EventArgs e)
{
bool isSaved = false;
hJA_VEHICLEBindingSource.EndEdit();
DataSet1.HJA_VEHICLEDataTable ch =
(DataSet1.HJA_VEHICLEDataTable)dataSet1.HJA_VEHICLE.GetChanges();
if (ch == null)
{
MessageBox.Show("There are no changes to save.");
}
else
{
Service<TVDDataService.IService1>.Use(svcProxy =>
{
isSaved = svcProxy.SaveVehicles(ch);
});
if (isSaved)
{
// update the vehicle in the local dataset
var modifiedRows = from row in dataSet1.HJA_VEHICLE
where row.RowState == DataRowState.Modified
select row;
foreach (DataRow row in modifiedRows)
{
row.Delete();
}
dataSet1.HJA_VEHICLE.Merge(ch);
dataSet1.HJA_VEHICLE.AcceptChanges();
}
if(isSaved)
{
MessageBox.Show("The record has been saved.");
}
else
{
MessageBox.Show("The record could not be saved.");
}
}
}
That all seemed ok, so I moved on to adding new records. I made a button (I saw online where various people had said it was as good or better to make your own than use a binding navigator) and put this in its handler:
DataRowView drv = (DataRowView)hJA_VEHICLEBindingSource.AddNew();
currVeh_id = nextID(); // generate arbitrary ID for the record
drv["VEH_ID"] = currVeh_id;
drv["GRNTE_ID"] = lastSelectedGranteeID; // just to have an initial value
hJA_VEHICLEBindingSource.Filter = "VEH_ID = " + currVeh_id;
So there (above) I'm putting initial values into some required columns (VEH_ID is the PK). Then I ran the app, entered a value in the textbox for VIN, and went to save (same code as above) and this time GetChanges() returned null.
So I tried this in the "add new" button handler, in place of the first thing:
DataSet1.HJA_VEHICLERow newRow =
(DataSet1.HJA_VEHICLERow)dataSet1.HJA_VEHICLE.NewRow();
currVeh_id = nextID();
newRow.VEH_ID = currVeh_id;
newRow.GRNTE_ID = lastSelectedGranteeID;
dataSet1.HJA_VEHICLE.AddHJA_VEHICLERow(newRow);
hJA_VEHICLEBindingSource.Filter = "VEH_ID = " + currVeh_id;
Now I have something really interesting happening.
- I can successfully enter and save data on any number of new records, until I select an existing record. If I move to an existing record and then add a new record, then when I go to save the new record, the values that were explicitly set in code get written to the DB but data entered into the GUI do not "take" for the new record.
- I can successfully change any number of existing records, until I enter a new record. If I add one or more new records, save, and then try to save changes to an existing record, the call to GetChanges() returns null (so again, apparently it is not "seeing" what's been entered through the GUI).
So in both of these cases, the change from old to new, or new to old, appears to introduce some condition that makes the datatable unaware of what was entered into the GUI. But in changing from old to new it only takes selecting an existing record, whereas with changing from new to old, it only breaks after saving (if I do a new record but then abandon it without saving, I can modify existing records without problems).
I added this into the save handler, just prior to the actual save (in a loop because ch is a datatable, but really the code is set up to where you have to either save or abandon the new record before moving on - thus the loop only executes once):
foreach (DataSet1.HJA_VEHICLERow r in ch)
{
DataRowView drv = (DataRowView)hJA_VEHICLEBindingSource.Current;
MessageBox.Show(drv["VIN_ID"].ToString());
MessageBox.Show(r.VEH_ID + "\n" + r.GRNTE_ID + "\n'"
+ r.VIN_ID + "'");
}
Where VIN_ID is the column to which this particular textbox is bound (I tried this with other fields on the form too, to verify it wasn't just something flaky about that one textbox). The first message box (DataRowView from the binding source) shows the vin that I entered into the textbox. The second message box (row from the table returned by GetChanges()) shows the empty string for VIN_ID, although it has the correct (set through code) values for VEH_ID and GRNTE_ID. The same thing happens if I select a different value for GRNTE_ID using the combo box bound to that column; the row from the datatable still has the value that was set through code, "unaware" of the value selected through the GUI.
Why would the datatable and binding source have different values for the same field? And why would the datatable be able to "see" values entered through the GUI only until the user switches from existing to new, or from new to existing?
Thanks.
Angel:
I'm doing that in my Service:
public bool SaveVehicles(DataSet1.HJA_VEHICLEDataTable vehicles)
{
bool saved = false;
if (vehicles != null && !vehicles.HasErrors)
{
HJA_VEHICLETableAdapter ta = new HJA_VEHICLETableAdapter();
int result = ta.Update(vehicles);
saved = (result > 0);
}
return saved;
}
This is called from this block from my original post:
Service<TVDDataService.IService1>.Use(svcProxy =>
{
isSaved = svcProxy.SaveVehicles(ch);
});
Johannes:
The call to EndEdit() is the second line in the save handler (near the top of my post). Should I be calling it somewhere else as well?
Thanks.
Just to clarify: SaveVehicles cannot be the source of the problem, since the problem is appearing before SaveVehicles is ever called. What condition could cause a discrepancy between the BindingSource and the DataTable after EndEdit() has been called and before anything actually writes to the DB?
You have to update your table adapter after use EndEdit(); also you can update your complete DataSet with the follow snippet :
this.BindingSource1.EndEdit();
this.TableAdapter1.Update(this.DataSet1);
Hopes Helps...
*IF you are using a BindingSource as well:
Just do a simple BindingSource.EndEdit() and your TextBox data will be sent over to the DataTable.
Example:-
_bsHeader.EndEdit();
if (_dsHeader.HasChanges())
{
DataTable dsInsert = _dsHeader.GetChanges(DataRowState.Added).Copy();
_objDal.Insert(dsInsert);
}
Hope this helps anyone who stumbles here.

Categories