Reject changes on a single DataColumn in a DataRow - c#

I want to reject changes made to a single DataColumn on a DataRow within a DataTable.
I made this test example:
DataTable table = new DataTable();
table.Columns.Add("testColumn1");
table.Columns.Add("testColumn2");
DataRow row = table.NewRow();
row["testColumn1"] = "This change should be preserved";
row["testColumn2"] = "This change should be rejected";
table.Rows.Add(row);
row.RejectChanges();
This rejects all changes made to the row. In my case the user might have some unsaved changes in one of the other columns, so using this will not work for this scenario.
I am looking for a similar functionality that reverts changes for "testcolumn2" only, for example:
row["testColumn2"].RejectChanges();
I looked up the documentation for the DataColumn class and could not find any similar method to DataRow.RejectChanges:
https://msdn.microsoft.com/en-us/library/system.data.datacolumn(v=vs.110).aspx
Is this possible to do with the C# framework or do I have to use an alternative solution?
Alternative solutions are also appreciated.

Seems to me the simplest solution it to set the value of the column to its original value using DataRowVersion:
row["testColumn2"] = row["testColumn2", DataRowVersion.Original];
There is one problem with this approach if you are using a transaction to batch together some updates. Let's say within the transaction you call Adapter.Update to write changes to the DB, but the DB rejects them due to a constraint violation. Even when I call transaction.RollBack() I am still getting the new version when I ask for the Original.
It's as though .Net calls AcceptChanges without making sure the transaction will not be rolled back. This might require a separate question to Stack Overflow to get a solution.

You can use the event DataTable.ColumnChanging. This triggers every time a change is made to a DataColumn in the table.
table.ColumnChanging += OnColumnChanging;
The method OnColumnChanging checks that the column name equals the column I want to backup, and that the call is not coming from the "revert value" part of the code (refer to the boolean isRevertingChanges).
If both of these are true, it creates a backup column (This is skipped if it already exists). Finally it writes the value that is about to be overwritten to the backup column on the row.
private bool isRevertingChanges;
void OnColumnChanging(object sender, DataColumnChangeEventArgs e)
{
if (!isRevertingChanges && e.Column.ColumnName.Equals(TestColumn2))
{
if (!e.Row.Table.Columns.Contains(TestColumn2Backup))
e.Row.Table.Columns.Add(TestColumn2Backup);
e.Row[TestColumn2Backup] = e.Row[TestColumn2];
}
}
Since the criteria for reverting changes is irrelevant in this case I have replaced it with if(true) so the revert-part of the code is triggered every time.
First it sets the isRevertingChanges variable to true (this is to prevent OnColumnChanging() from making a backup of the value that is being reverted), then it reverts the column back to its original value and finally it sets isRevertingChanges back to false so OnColumnChanging() will continue backing up values every time the column change.
if (true)
{
isRevertingChanges = true;
row[TestColumn2] = row[TestColumn2Backup];
isRevertingChanges = false;
}
Here is the full source code:
[TestClass]
public class RejectDataColumnChanges
{
private bool isRevertingChanges;
private const string TestColumn1 = "testColumn1";
private const string TestColumn2 = "testColumn2";
private const string TestColumn2Backup = "testColumn2Backup";
private const string PreservedMessage = "This change should be preserved";
private const string RejectedMessage = "This change should be rejected";
[TestMethod]
public void RejectDataColumnChangesTest()
{
DataTable table = new DataTable("testTable1");
table.Columns.Add(TestColumn1);
table.Columns.Add(TestColumn2);
DataRow row = table.NewRow();
row[TestColumn1] = PreservedMessage;
row[TestColumn2] = PreservedMessage;
table.Rows.Add(row);
table.ColumnChanging += OnColumnChanging;
row[TestColumn2] = RejectedMessage;
Assert.AreEqual(PreservedMessage, row[TestColumn1]);
Assert.AreEqual(RejectedMessage, row[TestColumn2]);
Assert.AreEqual(PreservedMessage, row[TestColumn2Backup]);
if (true)
{
isRevertingChanges = true;
row[TestColumn2] = row[TestColumn2Backup];
isRevertingChanges = false;
}
Assert.AreEqual(PreservedMessage, row[TestColumn1]);
Assert.AreEqual(PreservedMessage, row[TestColumn2]);
Assert.AreEqual(PreservedMessage, row[TestColumn2Backup]);
}
void OnColumnChanging(object sender, DataColumnChangeEventArgs e)
{
if (!isRevertingChanges && e.Column.ColumnName.Equals(TestColumn2))
{
if (!e.Row.Table.Columns.Contains(TestColumn2Backup))
e.Row.Table.Columns.Add(TestColumn2Backup);
e.Row[TestColumn2Backup] = e.Row[TestColumn2];
}
}
}

Related

Want to add certain items from a list to a DataSource

A brief intro
The program runs a test on the machines. Then a dialogBox appears asking the user if all the machines worked correctly. If they say no another window appears with the dataGridView asking which machines failed through the checkBox method. This then sets the status to the ERROR status so the program can continue running while ignoring the machines with errors.
I have this class with the two properties
public class ASM
{
public byte DeviceID
public ASMStatus Status
}
I put this in a list
list<ASM001.ASM> ASMs = new list();
Now I want to add this list to a bindingSource in my dataGridView but only those whose Status equals ASMStatus.IDLE
I thought about just creating those that have idle into another list and attaching that to the binding list, however, the dataGridView also has a checkBox column that determines if the status needs to be changed to ASMStatus.ERROR
public partial class FailedMessageBox : Form
{
public FailedMessageBox()
{
InitializeComponent();
DataGridViewCheckBoxColumn col1 = new DataGridViewCheckBoxColumn();
col1.HeaderText = "Device Failed";
dataGridView1.Columns.Add(col1);
}
private void FailedMessageBox_Load(object sender, EventArgs e)
{
dataGridView1.DataSource = Global.ASMs;
}
}
I want to make sure that when the user clicks OK the current ASMs in the list get set to ERROR which is why I thought a bindinglist would work the best
I am wondering if there was a quick way to do this or if I just have to do a bunch of loops.
What about something similar in your Page_load:
using(var context = new DbContext())
{
// You could put a Where Clause here to limit the returns
// i.e. context.ASMs.Where(s => s.Status == ASMStatus.IDLE).Load()
context.ASMs.Load();
var data = context.ASMs.Local.ToBindingList();
var bs = new BindingSource();
bs.DataSource = data;
dataGridView1.DataSource = bs;
}
You should add a BindingSource directly to the page but I am just showing adding it in code. Then you can access it like this:
foreach(DataRow row in dataGridView1.Rows)
{
if ((bool) row.Cells["Device Failed"].Value == true)
{
var line = row.DataBoundItem();
line.Status = ASMStatus.ERROR;
}
}
Make sure you save the changes.

How to populate a DataGridViewCombobBoxColumn with values

I am using a DataGridView in a C# WinForms application and want to let the user choose between a set of values that will be the same in each cell. I suppose there needs to be a way to populate all the cells with a list of values.
Using the GUI designer, I have added COL_BUSINESS_INDUSTRY, which is a DataGridViewComboBoxColumn to the DataGridView. In the constructor, I want to populate the ComboBoxes with a list of values, for which I am using the following code:
COL_BUSINESS_INDUSTRY.DataSource = LoadIndustries();
COL_BUSINESS_INDUSTRY.DataPropertyName = "industryName";
COL_BUSINESS_INDUSTRY.DisplayMember = COL_BUSINESS_INDUSTRY.DataPropertyName;
COL_BUSINESS_INDUSTRY.ValueMember = COL_BUSINESS_INDUSTRY.DataPropertyName;
LoadIndustries() will return a DataTable that has two fiels: id and industryName
The problem is that when I execute this, the ComboBoxes contain no values.
I have also tried having LoadIndustries() return a List<string>, without setting the DataPropertyName, but that didn't work either.
I also tried doing this, with LoadIndustries() returning a List<string>, but still no luck:
COL_BUSINESS_INDUSTRY.Items.AddRange();
Obviously I'm doing something wrong but it's been over 3 hours of looking online and I have no idea. Why are my ComboBoxes not displaying the values?
EDIT: Following TaW's suggestion, I've made the following changes, which still don't work:
Added the following class to contain the values:
class IndustryObj
{
public string theString { get; set; }
public string theId { get; set; }
public IndustryObj(string id, string s) { theId = id; theString = s; }
public override string ToString() { return theString; }
}
I am loading the industries in the following method. I can guarantee that loading the values in the objects is working fine:
private static List<IndustryObj> LoadIndustries()
{
var industries = new List<IndustryObj>();
const string sql = "SELECT id, name FROM Industry ORDER BY name";
using (var sqlQuery = new CustomSqlQuery(MyDatabases.Telemarketing, sql))
{
foreach (var record in sqlQuery)
{
industries.Add(new IndustryObj(record[0].ToString(), record[1].ToString()));
}
}
return industries;
}
This is the method where I am populating the DataGridView. All values are loading fine, except for the ones in COL_BUSINESS_INDUSTRY:
private void LoadBusinesses()
{
COL_BUSINESS_INDUSTRY.DataSource = LoadIndustries();
COL_BUSINESS_INDUSTRY.DataPropertyName = "theString";
COL_BUSINESS_INDUSTRY.DisplayMember = COL_BUSINESS_INDUSTRY.DataPropertyName;
COL_BUSINESS_INDUSTRY.ValueMember = "theId";
const string sql = "SELECT id, company, contact, address, phone, email, status, industry, callbackDate, callbackTime, createdBy, lastUpdatedBy, lockedUntil FROM Business ORDER BY company";
using (var sqlQuery = new CustomSqlQuery(MyDatabases.Telemarketing, sql))
{
var columns = new DataGridViewColumn[]
{
COL_BUSINESS_COMPANY,
COL_BUSINESS_CONTACT,
COL_BUSINESS_ADDRESS,
COL_BUSINESS_PHONE,
COL_BUSINESS_EMAIL,
COL_BUSINESS_STATUS,
COL_BUSINESS_INDUSTRY,
COL_BUSINESS_CALLBACKDATE,
COL_BUSINESS_CALLBACKTIME,
COL_BUSINESS_CREATEDBY,
COL_BUSINESS_LASTUPDATEDBY,
COL_BUSINESS_LOCKEDUNTIL
};
foreach (var record in sqlQuery)
{
dgv_Businesses.Rows.Add(CustomDGVRow.InitializeFromDataReader(true, record, columns));
}
}
}
EDIT 2: Following TaW's second suggestion, I've done the following. This did not help either:
private List<IndustryObj> industryObjects;
private void LoadBusinesses()
{
industryObjects = LoadIndustries();
COL_BUSINESS_INDUSTRY.DataSource = industryObjects;
COL_BUSINESS_INDUSTRY.DataPropertyName = "theString";
COL_BUSINESS_INDUSTRY.DisplayMember = COL_BUSINESS_INDUSTRY.DataPropertyName;
COL_BUSINESS_INDUSTRY.ValueMember = "theId";
...
EDIT 3: Luminous actually got it, but that introduces a number of other problems. The loading method now looks like this:
private void LoadBusinesses()
{
// Load the records.
const string sql =
"SELECT id, company, contact, address, phone, email, status, industry, callbackDate, callbackTime, createdBy, lastUpdatedBy, lockedUntil FROM Business ORDER BY company";
using (var sqlQuery = new CustomSqlQuery(MyDatabases.Telemarketing, sql))
{
var columns = new DataGridViewColumn[]
{
COL_BUSINESS_COMPANY,
COL_BUSINESS_CONTACT,
COL_BUSINESS_ADDRESS,
COL_BUSINESS_PHONE,
COL_BUSINESS_EMAIL,
COL_BUSINESS_STATUS,
COL_BUSINESS_INDUSTRY,
COL_BUSINESS_CALLBACKDATE,
COL_BUSINESS_CALLBACKTIME,
COL_BUSINESS_CREATEDBY,
COL_BUSINESS_LASTUPDATEDBY,
COL_BUSINESS_LOCKEDUNTIL
};
foreach (var record in sqlQuery)
{
dgv_Businesses.Rows.Add(CustomDGVRow.InitializeFromDataReader(true, record, columns));
}
}
// Load the industries and bind the industry DataSource to the industry ComboBoxColumn.
COL_BUSINESS_INDUSTRY.DataSource = LoadIndustries();
COL_BUSINESS_INDUSTRY.DataPropertyName = "theString";
COL_BUSINESS_INDUSTRY.DisplayMember = COL_BUSINESS_INDUSTRY.DataPropertyName;
COL_BUSINESS_INDUSTRY.ValueMember = "theId";
// Select the correct industries in the industry column.
foreach (var rawRow in dgv_Businesses.Rows)
{
var row = rawRow as CustomDGVRow;
if (row == null) continue;
foreach (var item in ((CustomDGVComboBoxCell) row.Cells[COL_BUSINESS_INDUSTRY.Index]).Items)
{
var industry = item as IndustryObj;
if (industry == null) continue;
if (row.Cells[COL_BUSINESS_INDUSTRY.Index].Value != null && industry.theId == row.Cells[COL_BUSINESS_INDUSTRY.Index].Value.ToString())
{
row.Cells[COL_BUSINESS_INDUSTRY.Index].Value = industry;
}
}
}
}
As you can see, I also need to select one value from the list of items, based on its id in the database. Because I only bind the datasource after loading the items (as per Luminous's suggestion), I need to do another pass through the rows and set the correct industry value for each row. However, since I have a huge number of rows, this is extremely expensive. So in order to award the bounty, I need an answer to two more questions:
How can I avoid doing another pass to set the correct values?
Why do I need to first load the rows and only then bind the datasource? This seems rather unintuitive and it causes my problem. What if the requirements ask for more data to be loaded at some point after the initial load? Would I then need to do the binding again?
The first thing you need to explain is why you have your column set to be a checkboxcolumn and not a comboboxcolumn? That may be your whole problem.
Now, by your code, you have a list already filled with your possible industries. How are you going to databind to any of your tuples(rows) if you don't have any rows to begin with? You should be databinding all of the cells in the column after you fill your dgv with your query result.
If that's not the issue I made a simple example of databinding a List<String> to a cell.
Here's an example of putting a List into a datagridviewcombobox:
private List<String> theList = new List<String>();
public Form1()
{
InitializeComponent();
theList.Add("is");
theList.Add("this");
theList.Add("working");
theList.Add("yet?");
DataGridViewComboBoxCell dropDown = (DataGridViewComboBoxCell) dgv.Rows[0].Cells[0];
dropDown.DataSource = theList;
}
private void aButton_Click(object sender, EventArgs e)
{
theList.Clear();
theList.Add("I");
theList.Add("Knew");
theList.Add("this");
theList.Add("would");
theList.Add("work!!!");
}
How can I avoid doing another pass to set the correct values?
You can't (maybe). The datagridview is going to load one result at a time. Unless you are returning multiple values for that one cell, you are going to have to do another pass. The dgv is expecting to fill in the row with the data its given for that particular row. What you can try is not recognizing that column exists in your query (just remove it from the query) and that may cause it to pass over that column and not assign it a value. If that succeeds what you have to do next is make that cell automatically be those values when the row is created. You will know this is working if you were to just add rows and those cells were immediately populated even though there's no other data in any of the cells.
Why do I need to first load the rows and only then bind the datasource? This seems rather unintuitive and it causes my problem. What if the requirements ask for more data to be
loaded at some point after the initial load? Would I then need to do the binding again?
You have to have an object to databind to. Databinding to the column won't make you databind to the cells in that column. It wouldn't even databind to the column header. You have to databind to the cells themselves. Once you databind, you can change that list as much as you want and the cells which are databinded to your list will change as well. Once you understand this concept databinding will be a lot easier to implement in the future.
Looking at your code again I suggest you create your columns before you fill your table with data. That way your dgv doesn't skip the creation of the column when you insert your results, but skips the data allotment for that column. In your foreach loop create a row, assign the data, databind the cell to your List<String> and add the row to your datagridview. Once that's all working go grab yourself a drink and pat yourself on the back for a job well done.
Edit
Instead of doing what I previously said, try this. It doesn't even involve databinding a list. If you know what your string values are going to be for your drop down box at design time you can go to Columns>your_column>Items and enter the strings there. If you don't, that's ok too. Whenever you have your list of strings you can add them to the column's internal list like so:
foo DataGridViewComboBoxColumn = myDGV.columns("YourColumn")
foo.Items.Add("blarggg")
If you need to change the list at any time, all rows will be updated upon making said change.
I can tell you for certain that a List<string> can't work as a DataSource. Instead you need to wrap the strings in a class, even as simple as this..:
class StringClass
{
public string theString { get; set; }
public StringClass(string s) { theString = s; }
public override string ToString() { return theString;}
}
.. because a DataSource needs to be fed from Properties. You may want to try this to create such a list from your DataTable:
List<StringClass> industries = DT.AsEnumerable().Select(r=>r[fieldIndex].ToString() )
.Distinct().OrderBy(r=>r).Select(r => new StringClass(r)).ToList();
Obvioulsy you'll leave out Distinct and OrderBy if you don't want them and change the field index..!
Update
Here is a block of code (without the connection) that reads from a MySql table. It reads a list of suppliers (lieferanten) into DataTable DTL and then reads distinct the locations (ort) from the same DB table into a 2nd DataTable DTO.. From there I pull the source for the ComboBox into a list, as shown above.
It creates the three columns (int, string, string) and the two necessary template cells dct and dccb.
It sets the members and first sets the DataSourceof the combobox cell, then of the datagridview.
When the DataGridView fills, all combobox cells are set to the right values as it should be..
MySqlDataAdapter DA = new MySqlDataAdapter();
string sqlSelectAll = "SELECT * from lieferanten";
DA.SelectCommand = new MySqlCommand(sqlSelectAll, CO);
DA.Fill(DTL);
string sqlSelectOrte = "SELECT DISTINCT ort from lieferanten";
DA.SelectCommand = new MySqlCommand(sqlSelectOrte, CO);
DA.Fill(DTO);
ortsListe = DTO.AsEnumerable().Select(r => r["ort"].ToString())
.OrderBy(r => r).Select(r => new StringClass(r)).ToList();
BindingSource bSource = new BindingSource();
bSource.DataSource = DTL;
dgv_Lief.AutoGenerateColumns = false;
DataGridViewCell dct = new DataGridViewTextBoxCell();
DataGridViewColumn dc0 = new DataGridViewColumn();
dc0.ValueType = typeof(int);
dc0.Name = "lieferanten_key";
dc0.DataPropertyName = "lieferanten_key";
dc0.CellTemplate = dct;
dgv_Lief.Columns.Add(dc0);
DataGridViewColumn dc1 = new DataGridViewColumn();
dc1.ValueType = typeof(string);
dc1.Name = "name";
dc1.CellTemplate = dct;
dc1.DataPropertyName = "firma";
dgv_Lief.Columns.Add(dc1);
DataGridViewComboBoxCell dccb = new DataGridViewComboBoxCell();
dccb.DataSource = ortsListe;
dccb.DisplayMember = "theString"; // using the same field..
dccb.ValueMember = "theString"; // .. as I have only one
DataGridViewColumn dc2 = new DataGridViewColumn();
dc2.ValueType = typeof(string);
dc2.DataPropertyName = "ort";
dc2.CellTemplate = dccb;
dgv_Lief.Columns.Add(dc2);
dgv_Lief.DataSource = bSource;
Note the order of things. First set up the template cell dccb including the DataSource, then add it to the dgv, then set the dgv's DataSource.
I make use of the StringClass from above.
Even if I leave out setting the DGV's DataSource I can add rows and use the values from the ComboBoxColumn, as expected..
You can do all this without actually writing a line of code using the windows forms designer.
From the Data menu, add a new data source.
Select Database and click next
Select Dataset and click next
Enter the connection string details
Select the tables you want to include in your dataset (both the business and the industry tables)
Finish the wizard
From the data sources pane, drag the businesses table onto your form. This will generate a data grid view with all the columns in, add a binding source and table adapter to populate it.
Click on the little arrow at the top right of the data grid view and select edit columns
Select the industry column, and change the column type to DataGridViewComboBox Column
Open the Data source drop down, and select the industries table from your newly created dataset
Set the display member to the name column, and the Value Member to the primary key column.
You can then view the forms designer generated code to see how it all fits together. Its not the prettiest code, but its code you didn't have to write.
The interesting parts are the data sources, binding sources and table adapters that put it all together.
With a little bit of extra work on the data set, you can also save back to the database.
Heres the result of my quick attempt at this:
using System;
using System.Windows.Forms;
namespace WindowsFormsBindingDemo
{
public partial class BusinessesForm : Form
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
private System.Windows.Forms.DataGridView businessesDataGridView;
private BusinessesDataSet businessesDataSet;
private System.Windows.Forms.BindingSource businessesBindingSource;
private BusinessesDataSetTableAdapters.BusinessesTableAdapter businessesTableAdapter;
private BusinessesDataSetTableAdapters.TableAdapterManager tableAdapterManager;
private BusinessesDataSetTableAdapters.IndustriesTableAdapter industriesTableAdapter;
private System.Windows.Forms.BindingSource industriesBindingSource;
private System.Windows.Forms.DataGridViewTextBoxColumn iDDataGridViewTextBoxColumn;
private System.Windows.Forms.DataGridViewComboBoxColumn industryIDDataGridViewTextBoxColumn;
private System.Windows.Forms.DataGridViewTextBoxColumn businessNameDataGridViewTextBoxColumn;
public BusinessesForm()
{
this.components = new System.ComponentModel.Container();
this.businessesDataGridView = new System.Windows.Forms.DataGridView();
this.businessesDataSet = new WindowsFormsBindingDemo.BusinessesDataSet();
this.businessesBindingSource = new System.Windows.Forms.BindingSource(this.components);
this.businessesTableAdapter = new WindowsFormsBindingDemo.BusinessesDataSetTableAdapters.BusinessesTableAdapter();
this.tableAdapterManager = new WindowsFormsBindingDemo.BusinessesDataSetTableAdapters.TableAdapterManager();
this.industriesBindingSource = new System.Windows.Forms.BindingSource(this.components);
this.industriesTableAdapter = new WindowsFormsBindingDemo.BusinessesDataSetTableAdapters.IndustriesTableAdapter();
this.iDDataGridViewTextBoxColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.industryIDDataGridViewTextBoxColumn = new System.Windows.Forms.DataGridViewComboBoxColumn();
this.businessNameDataGridViewTextBoxColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
((System.ComponentModel.ISupportInitialize)(this.businessesDataGridView)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.businessesDataSet)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.businessesBindingSource)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.industriesBindingSource)).BeginInit();
this.SuspendLayout();
//
// businessesDataGridView
//
this.businessesDataGridView.AutoGenerateColumns = false;
this.businessesDataGridView.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
this.businessesDataGridView.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
this.iDDataGridViewTextBoxColumn,
this.industryIDDataGridViewTextBoxColumn,
this.businessNameDataGridViewTextBoxColumn});
this.businessesDataGridView.DataSource = this.businessesBindingSource;
this.businessesDataGridView.Dock = System.Windows.Forms.DockStyle.Fill;
this.businessesDataGridView.Location = new System.Drawing.Point(0, 0);
this.businessesDataGridView.Name = "businessesDataGridView";
this.businessesDataGridView.RowTemplate.Height = 24;
this.businessesDataGridView.Size = new System.Drawing.Size(554, 374);
this.businessesDataGridView.TabIndex = 0;
//
// businessesDataSet
//
this.businessesDataSet.DataSetName = "BusinessesDataSet";
this.businessesDataSet.SchemaSerializationMode = System.Data.SchemaSerializationMode.IncludeSchema;
//
// businessesBindingSource
//
this.businessesBindingSource.DataMember = "Businesses";
this.businessesBindingSource.DataSource = this.businessesDataSet;
//
// businessesTableAdapter
//
this.businessesTableAdapter.ClearBeforeFill = true;
//
// tableAdapterManager
//
this.tableAdapterManager.BackupDataSetBeforeUpdate = false;
this.tableAdapterManager.BusinessesTableAdapter = this.businessesTableAdapter;
this.tableAdapterManager.IndustriesTableAdapter = this.industriesTableAdapter;
this.tableAdapterManager.UpdateOrder = WindowsFormsBindingDemo.BusinessesDataSetTableAdapters.TableAdapterManager.UpdateOrderOption.InsertUpdateDelete;
//
// industriesBindingSource
//
this.industriesBindingSource.DataMember = "Industries";
this.industriesBindingSource.DataSource = this.businessesDataSet;
//
// industriesTableAdapter
//
this.industriesTableAdapter.ClearBeforeFill = true;
//
// iDDataGridViewTextBoxColumn
//
this.iDDataGridViewTextBoxColumn.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill;
this.iDDataGridViewTextBoxColumn.DataPropertyName = "ID";
this.iDDataGridViewTextBoxColumn.HeaderText = "ID";
this.iDDataGridViewTextBoxColumn.Name = "iDDataGridViewTextBoxColumn";
this.iDDataGridViewTextBoxColumn.ReadOnly = true;
//
// industryIDDataGridViewTextBoxColumn
//
this.industryIDDataGridViewTextBoxColumn.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill;
this.industryIDDataGridViewTextBoxColumn.DataPropertyName = "IndustryID";
this.industryIDDataGridViewTextBoxColumn.DataSource = this.industriesBindingSource;
this.industryIDDataGridViewTextBoxColumn.DisplayMember = "IndustryName";
this.industryIDDataGridViewTextBoxColumn.FillWeight = 300F;
this.industryIDDataGridViewTextBoxColumn.HeaderText = "IndustryID";
this.industryIDDataGridViewTextBoxColumn.Name = "industryIDDataGridViewTextBoxColumn";
this.industryIDDataGridViewTextBoxColumn.Resizable = System.Windows.Forms.DataGridViewTriState.True;
this.industryIDDataGridViewTextBoxColumn.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.Automatic;
this.industryIDDataGridViewTextBoxColumn.ValueMember = "ID";
//
// businessNameDataGridViewTextBoxColumn
//
this.businessNameDataGridViewTextBoxColumn.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill;
this.businessNameDataGridViewTextBoxColumn.DataPropertyName = "BusinessName";
this.businessNameDataGridViewTextBoxColumn.FillWeight = 300F;
this.businessNameDataGridViewTextBoxColumn.HeaderText = "BusinessName";
this.businessNameDataGridViewTextBoxColumn.Name = "businessNameDataGridViewTextBoxColumn";
//
// BusinessesForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(554, 374);
this.Controls.Add(this.businessesDataGridView);
this.Name = "BusinessesForm";
this.Text = "Businesses";
this.Load += new System.EventHandler(this.BusinessesForm_Load);
((System.ComponentModel.ISupportInitialize)(this.businessesDataGridView)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.businessesDataSet)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.businessesBindingSource)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.industriesBindingSource)).EndInit();
this.ResumeLayout(false);
}
private void BusinessesForm_Load(object sender, EventArgs e)
{
// TODO: This line of code loads data into the 'businessesDataSet.Industries' table. You can move, or remove it, as needed.
this.industriesTableAdapter.Fill(this.businessesDataSet.Industries);
// TODO: This line of code loads data into the 'businessesDataSet.Businesses' table. You can move, or remove it, as needed.
this.businessesTableAdapter.Fill(this.businessesDataSet.Businesses);
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
}
}

C# Code-behind control form execution

I have a form that I would like to reuse for both adding a new record and editing an existing record. I am able to successfully load the page with the relevant data when I select a record from a GridView and I am able to update the db record appropriately. However, my issue is trying to use the form for both executions. Here is my logic in code behind: (I assign a session variable when I click on the row in GridView and this does work successfully)
protected void Page_Load(object sender, EventArgs e)
{
resultOutput.Visible = false;//Output results as to whether or not a record was added successfully is automatically hidden at page load
//Checking to see if session variable has been created
if (Session["editID"] != null)
{
//Create objects to get recipe data
dbCRUD db = new dbCRUD();
Recipe editRecipe = new Recipe();
//Grabbing session ID and assigning to a variable in order to remove the session variable
var id = Convert.ToInt32(Session["editID"]);
Session.Remove("editID");
//Call method to retrieve db data
editRecipe = db.SelectRecord(id);
//Populate results to text boxes
recordID.Text = id.ToString();
addName.Text = editRecipe.Name;
addTypeDDL.SelectedValue = editRecipe.Meal;
addDifficultyDDL.SelectedValue = editRecipe.Difficulty;
addCookTime.Text = editRecipe.Cook_Time.ToString();
addDirections.Text = editRecipe.Directions;
//Change Button Text
submitRecord.Visible = false;
changeRecord.Visible = true;
//Change Title Text
addEditTitle.Text = "Edit Recipe";
}
}
protected void submitRecord_Click(object sender, EventArgs e)
{
//Variables for execution results
var modified = "";
int returned = 0;
//Creating the recipe Object to pull the values from the form and
//send the recipe object as a parameter to the method containing insert stored procedure
//depending on Add or Edit
Recipe recipe = new Recipe();
recipe.Name = addName.Text;
recipe.Meal = addTypeDDL.Text;
recipe.Difficulty = addDifficultyDDL.Text;
recipe.Cook_Time = int.Parse(addCookTime.Text);
recipe.Directions = addDirections.Text;
//Creating object to access insert method
dbCRUD newRecord = new dbCRUD();
//Checking to see if the page is loaded for edit or new addition
if (Session["editID"] != null)
{
//If recordID exists, recipe will be passed as to UpdateRecord method
recipe.Recipe_ID = int.Parse(recordID.Text);
returned = newRecord.UpdateRecord(recipe);
modified = "has been edited.";
Session.Remove("editID");
}
else
{
//If recordID does not exist, record will be passed to InsertRecord method (new recipe)
returned = newRecord.InsertRecord(recipe);
modified = "added";
}
//Method returns 0 if successful, 1 if sql error, 2 if other error
if (returned == 1)
{
resultOutput.Text = "There was an sql exception";
resultOutput.Visible = true;
}
else if (returned == 2)
{
resultOutput.Text = "There was a non sql exception";
resultOutput.Visible = true;
}
else
{
resultOutput.Text = "\"" + addName.Text + "\" recipe " + modified;
resultOutput.Visible = true;
}
}
I have issues on the if(Session["editID"] != null) line - I am always moved to the else logic and the if logic never runs.
Here is my click method in the GridView:
protected void Grid_Recipe_SelectedIndexChanged(object sender, EventArgs e)
{
int index = Convert.ToInt32(Grid_Recipe.SelectedDataKey.Value);
Session["editID"] = index;
Response.Redirect("addRecord.aspx");
}
My question is how can I control execution during the submitRecord_Click event so that I call the appropriate method. Thanks!
Have you considered using
if(Page.IsPostBack)
{
code here
}
To detect whether you are posting back to the page? Then you could check your value of the item. I see no reason the code shouldn't be in the Session variable - have you tried putting a breakpoint in there to see if the code actually gets in there?
Also does your addRecord.aspx just add the record? If so, just add the record in this class, but use the PostBack check to see. Could you just make sure you are saving in the right context aswell:
// Outside of Web Forms page class, use HttpContext.Current.
HttpContext context = HttpContext.Current;
context.Session["editID"] = index;
...
int Id = (string)(context.Session["editID"]);
I was able to figure out my issue - which actually turned into two issues. First, I had to put my Page Load logic in a if(!IsPostBack) statement because I could not edit the page. Reason being is I was loading the originally posted data to the form on page load, which executed before my logic. Adding the if(!IsPostBack) control statement fixed this issue. From there, I'm still using a session variable to control code behind logic, only I made sure keep my session variable only between the form and the gridview. Basically, when the gridview loads and it is not a post back, the session variable is cleared. This let's me set a new session variable when I click on a row and then the session variable is cleared once I return to the grid to see the results. Thanks for the help!

DataSet validation in ColumnChanging event

In my partial class containing the DataSet event I have the following:
protected override void OnColumnChanging(System.Data.DataColumnChangeEventArgs e)
{
switch (e.Column.ColumnName)
{
case "ColumnA":
{
int value = GetValue(e.ProposedValue.ToString());
if (value == -1)
{
e.Row.SetColumnError("ColumnA", string.Format("ColumnA could not map [{0}] to a valid value", e.ProposedValue));
//e.ProposedValue = "";
}
break;
}
base.OnColumnChanging(e);
}
When I check for errors and get the column errors for my rows I see the appropriate message when GetValue(...) returns -1. I also see that the column in that has the bad data still contains that bad value. I was under the impression that calling SetColumnError(...) would reject the change made to that column (ColumnA) as per: How to: Validate Data During Column Changes
Reject the proposed value by setting the column error (SetColumnError)
from within the column-changing event handler.
So when I try to do something like the following:
TypedDataSet set = new TypedDataSet();
TypedDataTable.TypedDataRow row = set.TypedDataTable.NewRow();
row.ColumnA = "Bad Data";
set.TypedDataTable.AddTypedDataRow(row);
I'll see the validation code execute, but the value of ColumnA retains: "Bad Data". If I go as far as setting e.ProposedValue = null I can see the value change.
Update
Add event handlers for either RowsChanging or ColumnsChanging also produce similar results.
public override void BeginInit()
{
base.BeginInit();
TypedRowChanging += new TypedRowChangeEventHandler(TypedDataTable_TypedRowChanging);
ColumnChanging += new DataColumnChangeEventHandler(TypedDataTable_ColumnChanging);
}
Code in both of the event handlers is trivial and will call e.Row.SetColumnError("ColumnA", "some error"). So my original question remains:
What should be happening the in case where a column error is set on a column? Should it retain value, become null, 42?
According to the doc you linked, you don't want to override OnColumnChanged - you want to add an eventhandler for the ColumnChanged event. Try using the sample code provided there for a starting point.
After much testing and work I've come to the conclusion that the column retains the value when a column error is set on a specific column. My current solution is to check for errors on the entire DataSet and then proceed to aggregate those errors into a format for error handling
TypedDataSet set = new TypedDataSet();
TypedDataTable.TypedDataRow row = set.TypedDataTable.NewRow();
row.ColumnA = "Bad Data";
set.TypedDataTable.AddTypedDataRow(row);
// At this point the DataSet has column errors and can be checked for...
if (set.HasErrors)
{
// Build some error string container here
TypedDataSet.TypedDataRow row = tempSet.TypedDataTable.Single();
var errors = from column in row.GetColumnsInError()
select row.GetColumnError(column);
foreach (var error in errors)
{
// Add to error container here
}
}
After this I check if my error container has any data in it. I suppose it makes sense for the column to retain value so that the programmer has access to that value and can include that it in the call to SetColumnError(...).

Datagridview causing IndexOutOfRangeException when clicked upon

I have a datagridview which we will call dataGridViewExample.
My object (the uncommon datatypes is because my database is SQLite):
class MyObject
{
public Int64 Vnr { get; set; }
public string Name { get; set; }
public Single Price { get; set; }
public int Amount { get; set; }
}
Here is the relevant code:
//This form gets called with a .ShowDialog(); in my form1.
private List<MyObjecte> ExampleList = new List<MyObject>();
public MyForm()
{
dataGridViewExample.DataSource = OrdreInkøbsListe;
}
private void AddtoDataGridViewExample()
{
//Add a new MyObject to the list
ExampleList.Add(new myObject()
{
Vnr = newVnr,
Amount = newAmount,
Price = newPrice,
Name = newName
});
//refresh datasource
dataGridViewExample.DataSource = null;
dataGridViewExample.Refresh();
dataGridViewExample.DataSource = OrdreInkøbsListe;
ddataGridViewExample.Refresh();
}
When MyForm gets called with a .ShowDialog, it shows up fine and displays my DataGridView example just fine. As you can read from the code, the ExampleListis initially empty, so it just shows an empty datagridview with 4 columns: Vnr, Name, Price & Amount. If I click inside it etc. nothing happens - so everything is working as planned, so far.
Everytime I call AddtoDataGridViewExample() it adds the new object to the Datagridview, and the datagridview does update, listing all the objects added so far (they show themself as rows, again according to plan).
Now, remember that I just said that nothing happened if you clicked inside DataGridViewExample before I have called AddtoDataGridViewExample()?
Well, after having called AddtoDataGridViewExample() once or more, the program will crash if I click inside DataGridViewExample (for example: the users wants to select a row). It throws an IndexOutOfRangeException and talks about an -1 index.
It also throws the exception in the other form, on the line where I call MyForm with .ShowDialog();
I really am stuck on this, do you guys have any idea what is wrong??
My only clue is that I do believe the refresh of DataGridViewExample's datasource might be the cause of the problem.
Another important note: I have yet bound any events to my DataGridViewExample. So you can rule that idea out.
Here is all DataGridViewExample's properties:
this.dataGridViewExample.AllowUserToAddRows = false;
this.dataGridViewExample.AllowUserToDeleteRows = false;
this.dataGridViewExample.AllowUserToResizeColumns = false;
this.dataGridViewExample.AllowUserToResizeRows = false;
this.dataGridViewExample.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.Fill;
this.dataGridViewExample.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
this.dataGridViewExample.Location = new System.Drawing.Point(591, 53);
this.dataGridViewExample.MultiSelect = false;
this.dataGridViewExample.Name = "dataGridViewExample";
this.dataGridViewExample.ReadOnly = true;
this.dataGridViewExample.RowHeadersVisible = false;
this.dataGridViewExample.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect;
this.dataGridViewExample.ShowEditingIcon = false;
this.dataGridViewExample.Size = new System.Drawing.Size(240, 150);
this.dataGridViewExample.TabIndex = 31;
I guess the click event tries to get the currently selected row and do something with it, while dataGridViewExample.DataSource = null; clears the datasource, and the currently selected row becomes null.
If you set the DataGridView.DataSource to the list, you don't need to reset it to null, refresh, and reset it to the list again (and refresh again) to see the changes. It will be enough to just refresh the DataGridView.
You can also just try using an BindingList<T> object instead of a List<T>, which will automatically notify your grid of its internal changes (Adding and removing elements), and there's also an INotifyPropertyChanged interface you can implement on your MyObject class, that will make every property change in an object show on the grid (For any changes made to the object in the code, and not through the grid itself).
Have you tried running the debugger and break when InedxOutOfRangeException is thrown to see where the exception is thrown?
Select Debug > Exceptions then there's a Find button on the dialog so you don't have to browse through all of the possibilities.
I had similar situation. I assigned generic list of certain object to DataGridView. Then I was setting null to DataSource and after that refresh. After that I assign list of objects to DataSource. While clicked on grid while runtime error occured IndexOutOfRange. My solution was to assign new empty list of my object to that grid and refresh and after changes on my working list I do assign to DataSource and call Refresh. Now, it is working without any crashes. Please look on my code before:
grid.DataSource = null;
grid.Refresh();
if(cases.Count() > 0)
{
grid.DataSource = cases;
grid.Refresh();
}
And now on my code after:
grid.DataSource = new List<MyCase>();
grid.Refresh();
//do something with cases
if(cases.Count() > 0)
{
grid.DataSource = cases;
grid.Refresh();
}

Categories

Resources