ComboBox.SelectedValue not working as expected - c#

So I have a DataGridView which I am using as a “row selector” on a form, and a bunch of controls are bound to the bindingSource.
One of the bound controls is a ComboBox serving as a lookup which enables status choices for the rows, this is populated from a DataTable with data pulled from the DB.
The population of this box is without any issue.
When a given row is selected from the DGV the form controls display data from the given row as they should, however the “statusComboBox” is not quite playing the game.
If in the DGV, I choose a row that has a different status to one previously selected, it works as it should, however, if I choose a row with the same value to a previously selected row, instead of the box showing the DisplayMember it shows the ValueMember.
IT only seems to occur in the above scenario, where the rows selection only instigates a display response from the bound ComboBox providng a previous selection had a different “Status ID”. What have I dont wrong that would cause this behaviour?
So the form load looks like this
private void ProjectsForm_Load(object sender, EventArgs e)
{
InitBindingSource();
//// bind Selector
//ASMod$ this needs to be 'true' unless you explicitly declare columns
ProjectsDataGridView.AutoGenerateColumns = false;
ProjectsDataGridView.DataSource = ProjectsBindingSource;
GetData();
//Set GeneralStatusBox
Helpers.GeneralStatusInitLookup(statusComboBox, ProjectsBindingSource);
}
The ProjectBindingSource is initialised thus:
private void InitBindingSource()
{
ProjectsBindingSource = new BindingSource();
projectsBindingNavigator.BindingSource = ProjectsBindingSource;
ProjectsBindingSource.PositionChanged += new EventHandler(ProjectsBindingSource_PositionChanged);
}
A ProjectsAddDataBindings procedure, and the contained DataBindings.Add for the ComboBox (executed at the end of a GetData routine that additionally populated ProjectsBindingSource):
ProjectsAddDataBindings();
{
…
this.statusComboBox.DataBindings.Add("Text", ProjectsBindingSource, "GSID");
…
}
After the GetData block the GeneralStatusInitLookup populates the Lookup elements, in a helper class simply because it provides functionality to a number of different forms
public static void GeneralStatusInitLookup(System.Windows.Forms.ComboBox comboBox, BindingSource primaryBindingSource)
{
string statusFilter = "";
statusFilter = Helpers.GetStatusGroupFilter(EndeavourForm.FilterId);
if (statusFilter != "")
{
statusFilter = " WHERE " + statusFilter;
}
//// string statusFilter = ""; //// temp
string sql = "";
sql = "SELECT GSID, ShortName FROM GeneralStatus" + statusFilter + " ORDER BY Pos";
GeneralStatusDataTable = Helpers.Db.GetDataTable(sql);
comboBox.DataSource = GeneralStatusDataTable;
comboBox.DisplayMember = "ShortName";
comboBox.ValueMember = "GSID";
comboBox.DataBindings.Add(new Binding("SelectedValue", primaryBindingSource.DataSource, "GSID"));
}
And the DGV initiated row change is handled like this
private void ProjectsBindingSource_PositionChanged(object sender, EventArgs e)
{
try
{
// Update the database with the user's changes.
UpdateProjects();
statusComboBox.SelectedValue = (int)CurrentDataRowView.Row["GSID"];
}
catch (Exception)
{
}
}
private void UpdateProjects()
{
try
{
ProjectsDataAdapter.Update((DataTable)ProjectsBindingSource.DataSource);
DataHelper.CommitProposedChanges(projectsDataSet);
if (this.projectsDataSet.HasChanges() == true)
{
ProjectsBindingSource.EndEdit();
ProjectsDataAdapter.Update();
}
CurrentDataRowView = (DataRowView)ProjectsBindingSource.Current;
}
catch (InvalidOperationException)
{
throw;
}
catch (Exception)
{
throw;
}
}
Anyway I hope I haven't swamped readers with to much code, but frankly I cant see where this is going wrong. So any help would be greatly appreciated.

This was a simple solution in the end. Both the GeneralStatusInitLookup() and the ProjectsAddDataBindings() blocks made use of DataBindings.Add ... For the lookup table this was fine, but with the binding to the main table; the later, I had used "Text" as the propertyName parameter.

Related

c# Populate datagridview based on ComboBox Item Selected

I have this datagrid where Combobox is populated from Db.
What I'm trying to achieve is that when I select something in the column "Esercizio", the cell of "Video" column auto populate with respective value from the "link_video" column of Db.
So if I select "kickback", I need to see the link video of kickback from db in the textbox cell.
Here's the code that i use to populate the combobox on form load:
private void Myform_Load(object sender, EventArgs e)
{
con = new SqlConnection("Data Source=(LocalDB)\\etc");
cmd = new SqlCommand();
con.Open();
cmd.Connection = con;
cmd.CommandText = "SELECT * FROM Esercizi";
dr = cmd.ExecuteReader();
while (dr.Read())
{
//populate Column1 combobox with "nome" column from Esercizi db table
Column1.Items.Add(dr["nome"]);
}
con.Close();
}
datagridview
EDIT
I've figured out with 2 new problems.
I'm trying to load a saved workout from db but when I do this, no video link populate the dgv as the grid event doesn't fire.
What I've tried is to add a foreach loop to a new selectionindexchanged function and to fire it at the end of the Load Button code like this:
private void curCombo_LoadedValues(object sender, EventArgs e)
{
foreach (DataGridViewRow row in dataGridView1.Rows)
{
foreach (DataGridViewCell cell in row.Cells)
{
if (curCombo != null && curCombo.SelectedValue != null)
{
ExerciseAndVideo selectedExercise = (ExerciseAndVideo)curCombo.SelectedItem;
dataGridView1.CurrentRow.Cells["Video"].Value = selectedExercise.Video;
}
}
}
}
private void button9_Click(object sender, EventArgs e){
string connectionString = "Data Source=(LocalDB)\\etc";
string sql = "SELECT * FROM Schede WHERE Id = 6 AND dgv = 'dataGridView1'";
SqlConnection connection = new SqlConnection(connectionString);
SqlDataAdapter dataadapter = new SqlDataAdapter(sql, connection);using (DataTable dt = new DataTable())
{
dataadapter.Fill(dt);
//Set AutoGenerateColumns False
dataGridView1.AutoGenerateColumns = false;
//Set Columns Count
dataGridView1.ColumnCount = 6;
//Add Columns
dataGridView1.Columns[0].Name = "Esercizio";
dataGridView1.Columns[0].HeaderText = "Esercizio";
dataGridView1.Columns[0].DataPropertyName = "Esercizio";
dataGridView1.Columns[1].Name = "Serie";
dataGridView1.Columns[1].HeaderText = "Serie";
dataGridView1.Columns[1].DataPropertyName = "Serie";
dataGridView1.Columns[2].HeaderText = "Ripetizioni";
dataGridView1.Columns[2].Name = "Ripetizioni";
dataGridView1.Columns[2].DataPropertyName = "Ripetizioni";
dataGridView1.Columns[3].Name = "Recupero";
dataGridView1.Columns[3].HeaderText = "Recupero";
dataGridView1.Columns[3].DataPropertyName = "Recupero";
dataGridView1.Columns[4].Name = "Time Under Tension";
dataGridView1.Columns[4].HeaderText = "Time Under Tension";
dataGridView1.Columns[4].DataPropertyName = "Time_Under_Tension";
dataGridView1.DataSource = dt;
connection.Close();
}
curCombo_LoadedValues();
}
But I get this error "the are no arguments for the obligatory parameter sender...
How I can call it correctly?
The second Issue is that when I populate some dgv columns like this, combos stops working correctly and I get an error exception on the combobox :
dataGridView1.Rows.Add(7);
Random rnd = new Random();
dataGridView1.Rows[0].Cells[1].Value = 3;
dataGridView1.Rows[0].Cells[2].Value = rnd.Next(1, 13);
dataGridView1.Rows[0].Cells[3].Value = 1;
dataGridView1.Rows[0].Cells[4].Value = 201;
dataGridView1.Rows[1].Cells[1].Value = 2;
dataGridView1.Rows[1].Cells[2].Value = rnd.Next(1, 13);
dataGridView1.Rows[1].Cells[3].Value = 1;
dataGridView1.Rows[1].Cells[4].Value = 201;
dataGridView1.Rows[2].Cells[1].Value = 3;
dataGridView1.Rows[2].Cells[2].Value = rnd.Next(1, 13);
dataGridView1.Rows[2].Cells[3].Value = 1;
dataGridView1.Rows[2].Cells[4].Value = 201;
dataGridView1.Rows[3].Cells[1].Value = 4;
dataGridView1.Rows[3].Cells[2].Value = rnd.Next(1, 13);
dataGridView1.Rows[3].Cells[3].Value = 1;
dataGridView1.Rows[3].Cells[4].Value = 201;
dataGridView1.Rows[4].Cells[1].Value = 5;
dataGridView1.Rows[4].Cells[2].Value = rnd.Next(1, 13);
dataGridView1.Rows[4].Cells[3].Value = 1;
dataGridView1.Rows[4].Cells[4].Value = 201;
dataGridView1.Rows[5].Cells[1].Value = 6;
dataGridView1.Rows[5].Cells[2].Value = rnd.Next(1, 13);
dataGridView1.Rows[5].Cells[3].Value = 1;
dataGridView1.Rows[5].Cells[4].Value = 201;
dataGridView1.Rows[6].Cells[1].Value = 7;
dataGridView1.Rows[6].Cells[2].Value = rnd.Next(1, 13);
dataGridView1.Rows[6].Cells[3].Value = 1;
dataGridView1.Rows[6].Cells[4].Value = 201;
This is the look of the dgv now:
dgv
And this is the error the i get after combos stop working correctly (I click and no dropdown appear or if I click 2-3 times, a random item get selected but no video link appear in the other column):
error
In the posted code, it shows a query to a DB to get the exercise names and adds those names as the items to display in “each” combo box cell in that column. This is fine, however, there is zero (0) information about “which video” belongs to each of the items in the combo boxes list of items. I will assume the “relation” to which video is related to which exercise would involve another query to the DB.
If this is the case, then it is easy to see that when a combo box in the grid is changed, that you could simply query the DB for which video to use for the selected exercise. This would certainly work; however, it creates a redundancy issue. Example, let’s say the user selected “kicks” in the combo box in row 1 in the gird. The code queries the DB and gets the location of the “kicks” video and sets the “video” cell to this videos path. Then later, the user selects “kicks” again for some other row. This will re-query the DB for the SAME data we previously got. You want to avoid querying the DB unnecessarily.
So given this, it appears a better approach to avoid re-querying the DB unnecessarily, is that we somehow “combine” the exercise with the particular video that the exercise uses. You could do this ONCE when the form loads. Once you have the exercises, then loop though each exercise and query the DB for that exercises video and combine this with the exercise. Once we have the video link, we “save” this info. With this approach, you will not have to re-query the DB for any given exercise since we have saved that info for all exercises.
There are a myriad number of ways to “combine” the exercise with the video. One possible solution is to create a Class that has these two properties. Let’s call this Class ExerciseAndVideo… it has two properties, a string Exercise that is the exercise text and a string Video that defines the path to the video for that Exercise. This simple class may look something like…
public class ExerciseAndVideo {
public string Exercise { get; set; }
public string Video { get; set; }
}
This is one approach to “combine” an Exercise with a particular Video. We could then make a list of ExerciseAndVideo objects like…
List<ExerciseAndVideo> ExerciseList;
Then we cannot only use this list as a DataSource to the combo box column, but we could also use this list as a way to easily tell “which” video belongs to “which” exercise. The example below uses this strategy.
How to implement this in the DataGridView…
One thing to keep in mind is that a DataGridViewComboBoxCell is “different monster” than a “regular” ComboBox. Example; for a regular combo box, there is an event called… SelectedIndexChanged event. This event fires when the user “changes” the combo boxes currently selected index. On the other hand, a DataGridViewComboBoxCell does NOT have a SelectedIndexChanged event, which I assume you may be aware of. The grid’s absence of this event makes sense, because the grid’s combo box “column” may have MANY combo boxes in it.
Fortunately, even though the DataGridViewComboBoxCell does not have a SelectedIndexChanged event, we CAN cast an “individual” combo box cell to a ComboBox (in this case the combo box cell being edited), THEN we CAN subscribe to that ComboBoxes SelectedIndexChanged event. Then we could simply wait until the SelectedIndexChanged event fires then update the video cell data with the appropriate video link.
The DataGridView provides a special event for this called… EditingControlShowing. This event will fire when the user starts to “edit” a cell in the grid. In this particular case, this event would fire when the user clicks into a combo box cell and “starts” to change the cells value. Is what we want to do in this event is simply cast that combo box cell to a regular ComboBox…, THEN, subscribe to that ComboBoxes SelectedIndexChanged event which we will implement below.
The strategy goes like this… we will make a “global” ComboBox variable we will call curCombo. When the grids EditingControlShowing event fires and we see that the edited cell is an “Exercise” cell… Then we will cast THAT combo box cell in the grid to the global curCombo variable. Then we will subscribe to that combo boxes SelectedIndexChanged event. When the user “leaves” the cell we will unsubscribe from the global variables curCombo SelectedIndexChanged event to keep things clean.
Therefore, given this, the grids EditingControlShowing event may look something like below...
private void dataGridView1_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e) {
if (dataGridView1.Columns[dataGridView1.CurrentCell.ColumnIndex].Name == "Exercise") {
curCombo = e.Control as ComboBox;
if (curCombo != null) {
curCombo.SelectedIndexChanged -= new EventHandler(curCombo_SelectedIndexChanged);
curCombo.SelectedIndexChanged += new EventHandler(curCombo_SelectedIndexChanged);
}
}
}
Obviously we need to implement the event handler curCombo_SelectedIndexChanged. In this event, we would know that previously, the user selected a combo box cell and has changed the value in that cell to some other value. Since the Exercise changed, we know we need to change the “Video” cell.
Again there are numerous ways you could do this, however, if we set the grid’s “Exercise” combo box column’s DataSource as a List of ExerciseAndVideo objects, then we should be able to get that particular ExerciseAndVideo object directly from the global ComboBox curCombo. This will tell us “which” video to place in the “Video” cell. This may look something like…
private void curCombo_SelectedIndexChanged(object sender, EventArgs e) {
if (curCombo != null && curCombo.SelectedValue != null) {
ExerciseAndVideo selectedExercise = (ExerciseAndVideo)curCombo.SelectedItem;
dataGridView1.CurrentRow.Cells["Video"].Value = selectedExercise.Video;
}
}
private void dataGridView1_CellLeave(object sender, DataGridViewCellEventArgs e) {
if (dataGridView1.Columns[e.ColumnIndex].Name == "Exercise") {
if (curCombo != null) {
curCombo.SelectedIndexChanged -= new EventHandler(curCombo_SelectedIndexChanged);
}
}
}
To complete the example and putting all this together may produce something like below…
List<ExerciseAndVideo> ExerciseList;
ComboBox curCombo;
public Form2() {
InitializeComponent();
}
private void Form2_Load(object sender, EventArgs e) {
ExerciseList = GetExerciseVideoComboBoxListFromDB();
dataGridView1.Columns.Add(GetExcerciseComboBoxColumn(ExerciseList));
dataGridView1.Columns.Add(GetLinkColumn());
dataGridView1.CellLeave += new DataGridViewCellEventHandler(dataGridView1_CellLeave);
dataGridView1.EditingControlShowing += new DataGridViewEditingControlShowingEventHandler(dataGridView1_EditingControlShowing);
}
private DataGridViewComboBoxColumn GetExcerciseComboBoxColumn(List<ExerciseAndVideo> exerciseData) {
DataGridViewComboBoxColumn cbCol = new DataGridViewComboBoxColumn();
cbCol.HeaderText = "Exercise";
cbCol.Name = "Exercise";
cbCol.DisplayMember = "Exercise";
cbCol.DataSource = exerciseData;
return cbCol;
}
private DataGridViewLinkColumn GetLinkColumn() {
DataGridViewLinkColumn col = new DataGridViewLinkColumn();
col.HeaderText = "Video";
col.Name = "Video";
col.AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
return col;
}
private List<ExerciseAndVideo> GetExerciseVideoComboBoxListFromDB() {
List<ExerciseAndVideo> exerciseList = new List<ExerciseAndVideo>();
exerciseList.Add(new ExerciseAndVideo { Exercise = "Crosses", Video = #"C:/somepath/Crosses.htm/" });
exerciseList.Add(new ExerciseAndVideo { Exercise = "Kickback", Video = #"C:/somepath/Kickback.htm" });
exerciseList.Add(new ExerciseAndVideo { Exercise = "Leg Extensions", Video = #"C:/somepath/LegExtensions.htm" });
exerciseList.Add(new ExerciseAndVideo { Exercise = "Crunches", Video = #"C:/somepath/Crunches.htm" });
exerciseList.Add(new ExerciseAndVideo { Exercise = "Pushups", Video = #"C:/somepath/Pushups.htm" });
return exerciseList;
}
What if the grid has a data source?
This works as expected when the grid has no data source. However, if the grid has a data source and one of the columns/properties in the data source is bound to our “Exercise” combo box column, then, is what will happen… is that after the data is loaded, the combo boxes should display the proper exercise, however, all the video cells will remain empty. This is obviously because the grid events are not fired when the data is loaded.
So, in that case, you will want a method that loops through all the rows in the grid, checks what the exercise value is for that row, then set the video cell value to the correct video link. Since you do not say if the grid does or does not have a data source, I will assume this is all you need. If there is a data source, I recommend you check each “Exercise” to make sure the “Exercises” in the data are in the combo boxes list of items, if one or more “Exercises” are in the data that are not in the combo box columns list of items, then you will get the grids DataError when you attempt to set the grids data source.
I hope this makes sense.
Edit... An example of setting the video cell after the data has been loaded into the grid.
private void SetVideoCellsAfterDataLoad() {
foreach (DataGridViewRow row in dataGridView1.Rows) {
if (!row.IsNewRow && row.Cells["Exercise"].Value != null) {
foreach (ExerciseAndVideo eav in ExerciseList) {
if (row.Cells["Exercise"].Value.ToString() == eav.Exercise) {
row.Cells["Video"].Value = eav.Video;
break;
}
}
}
}
}
I mixed ComboBox with DatagridViewComboboxColumn.
It's partly your fault :).
Here you have a form with events. Since CellValueChanged fires on cell exit, I added a Dirty StateEvent to update the Video column.
From the designer, just put the datagrid in the form and make sure the name is the same.
IMHO, these 3 events are crucial
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
Init();
}
private void Init()
{
var list = new List<Exercise>() {
new Exercise (){Name="Name1", Link= "Link1" },
new Exercise (){Name="Name3", Link= "Link3" },
new Exercise (){Name="Name4", Link= "Link4" },
};
var comboColumn = new DataGridViewComboBoxColumn() { Name = "ExerciseName", CellTemplate = new DataGridViewComboBoxCell() };
comboColumn.DisplayMember = nameof(Exercise.Name);
comboColumn.ValueMember = nameof(Exercise.Link);
comboColumn.DataSource = list;
dataGridView1.Columns.Add(comboColumn);
dataGridView1.Columns.Add(new DataGridViewTextBoxColumn() { Name = "Video" });
dataGridView1.CellContentClick += DataGridView1_CellContentClick;
dataGridView1.CellValueChanged += DataGridView1_CellValueChanged;
dataGridView1.CurrentCellDirtyStateChanged += DataGridView1_CurrentCellDirtyStateChanged;
}
private void DataGridView1_CurrentCellDirtyStateChanged(object sender, EventArgs e)
{
var currentCell = (sender as DataGridView).CurrentCell;
if(currentCell.ColumnIndex == 0)
dataGridView1.CommitEdit(DataGridViewDataErrorContexts.Commit);
}
private void DataGridView1_CellValueChanged(object sender, DataGridViewCellEventArgs e)
{
if (e.ColumnIndex != 0)
return;
var comboCell = (dataGridView1.Rows[e.RowIndex].Cells[0] as DataGridViewComboBoxCell);
var value = comboCell.Value;
dataGridView1.Rows[e.RowIndex].Cells["Video"].Value = value;
}
private void DataGridView1_CellContentClick(object sender, DataGridViewCellEventArgs e)
{
}
}
public class Exercise
{
public string Name { get; set; }
public string Link { get; set; }
}

Get active datagrid's name upon cell editing event?

I have been trying to get the active datagrid's name upon a cell editing event.
Firstly, I don't know if this is a good practice, but I have a event that runs when a datagrid's cell is edited. I then am trying to test if the user added a row to the table. I want a way to see which Table is being edited so as I can put in an if clause to direct it to the correct code so it won't throw an error.
private void DataGrid_CellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
{
Staff_Time_TBL selectedRow = e.Row.Item as Staff_Time_TBL;
long id = selectedRow.ID;
if (id == -1)
{
// give a GUID and then insert it into the database when saved
selectedRow.ID = DateTime.UtcNow.Ticks;
sql.Staff_Time_TBLs.InsertOnSubmit(selectedRow);
}
try
{
sql.SubmitChanges();
LastSavedTextBlock.Text = "Last saved: " + DateTime.Now.ToLongTimeString();
}
catch(Exception ex)
{
Alerts.Error("Couldn't save changed to the database", "Database Error", ex);
}
}
At present, obviously if this table in the code below is not accessed it throws an error,
Staff_Time_TBL selectedRow = e.Row.Item as Staff_Time_TBL;
long id = selectedRow.ID;
My attempts at getting the datagrid's name, this just returns DataGrid
var tblName = sender.GetType().Name;
And this returns null for the variable tblName2 and throws an exception on the last line due to that.
string dataGridName = "";
DataObject tblName2 = sender as DataObject;
dataGridName = tblName2.ToString();
There is this thread that gets all tables names and This thread that checks to see if one exists, but I can't find anything of how to get the sender datagrid's name.
Obviously if this is not good practise I would like to know. Thanks.
Use VisualTreeHelper class :
private void Dgrid_CellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
{
FrameworkElement source = e.EditingElement;
while (!(source is DataGrid))
source = VisualTreeHelper.GetParent(source) as FrameworkElement;
MessageBox.Show(((DataGrid)source).Name);
}
If it is the name of the data grid you want, this should work.
DataGrid dg = (DataGrid)sender; // Will throw an exception if not a DataGrid
string name = dg.Name;
Not sure why you use DataObject.

ListBox DataSource assignment

I have here a desktop application with ListBox that will accept records (files inside a directory and its sub-directories) of more than 10,000. When I assign its DataSource with that DataTable of more than maybe 50,000 it makes the UI hang even though it is inside the DoWork of a BackgroundWorker, thus, hangs also my ProgressBar that indicates the progress of the assignment of data in the ListBox.
I have also used the method here to avoid cross threading while assigning its DisplayMember and ValueMember but still it gets hang.
Here's the code:
private void bgWorkerForLstBox1_DoWork(object sender, DoWorkEventArgs e)
{
string viewPath = string.Empty;
if (radFullPath.Checked)
viewPath = "fullPath";
else if (radShortPath.Checked)
viewPath = "truncatedPath";
else
viewPath = "fileName";
if (dt1 != null)
if (dt1.Rows.Count > 0)
SetListBox1Props(viewPath, "fullPath");
}
delegate void SetListBox1PropsCallback(string DisplayMember, string ValueMember);
private void SetListBox1Props(string DisplayMember, string ValueMember)
{
if (this.lstBox1.InvokeRequired)
{
SetListBox1PropsCallback d = new SetListBox1PropsCallback(SetListBox1Props);
this.Invoke(d, new object[] { DisplayMember, ValueMember });
}
else
{
this.lstBox1.DataSource = dt1;
this.lstBox1.DisplayMember = DisplayMember;
this.lstBox1.ValueMember = ValueMember;
}
}
The number of items you want to show is too large for windows. If you need this and don't want to implement some sort of pagination, i would suggest to use a ListView control in VirtualMode. See this link for more information: http://msdn.microsoft.com/en-us/library/system.windows.forms.listview.virtualmode.aspx

DataGrid.Items filled with Null data

I'm trying to fill a asp.DataGrid with data from a DB2 database. The problem I'm getting is that the data is coming back null. The weird thing is, the records themselves (seem to) load from the database, as when I step through the code while debugging, the DataGrid.Items.Count = the number of records I have in the database itself.
Additionally, while troubleshooting, I've added an asp.Label that's initially hidden to display the number of records found in the DataGrid itself, and each time it displays the correct number of records.
Here's my code:
protected void Page_Load(object sender, EventArgs e)
{
dta_grd = new DataGrid();
dta_grd = Ex_DLL.GetData("select * from tstint/m02");
Lbl_Dsply.Visible = true;
if (Supp_Data.Items.Count == 0)
{
Lbl_Dsply.Text = "No Records Found!";
}
else
{
Lbl_Dsply.Text += dta_grd.Items.Count.ToString();
}
}
Ex_DLL is simply the name of a library that does all the connections to the Database itself.
The Code for Ex_DLL.GetData():
public static DataGrid GetData(string str_sql)
{
//EX_Global.DB2_Conn("DEV") is just an internal connection function that houses
//the connection settings.
iDB2Connection db2_conn = EX_Global.DB2_Conn("DEV");
iDB2Command db2_cmd = null;
iDB2DataAdapter db2_adpt = null;
DataSet dta_ds = new DataSet();
DataGrid ret_val = new DataGrid();
try
{
if (db2_conn.State != System.Data.ConnectionState.Open) { db2_conn.Open(); }
db2_cmd = new iDB2Command(str_sql, db2_conn);
db2_adpt = new iDB2DataAdapter(db2_cmd);
db2_adpt.Fill(dta_ds);
ret_val.DataSource = dta_ds;
ret_val.DataBind();
db2_conn.Close();
}
catch (Exception) { }
return ret_val;
}
Now, when I read them individually using idb2DataReader, it's actually reading from the database, but there's just something lost in translation from reading the database to filling the DataGrid itself.
Any ideas?
There are two problems (may be more) I can see:
You instantiate the DataGrid control during page execution but forget to add it to page's Controls collection.
You missed Datasource property and DataBind() method.
In design environment, add a "PlaceHolder" control on a page (.aspx) (Say PlaceHolder1)
protected void Page_Load(object sender, EventArgs e)
{
dta_grd = new DataGrid();
dta_grd.DataSource = Ex_DLL.GetData("select * from tstint/m02");
dta_grd.DataBind(); // this method populates the DataGrid from assigned datasource
PlaceHolder1.Controls.Add(dta_grd);
Lbl_Dsply.Visible = true;
if (Supp_Data.Items.Count == 0)
{
Lbl_Dsply.Text = "No Records Found!";
}
else
{
Lbl_Dsply.Text += dta_grd.Items.Count.ToString();
}
}

Adding columns to a DataTable bound to a DataGridView does not update the view

I have a DataTable which is populated from a CSV file then, using a DataGridView the data is edited in memory. As far as I understand the programmatic editing of the data should be done on the DataTable where the user editing is done via. the DataGridView.
However when I add columns programmatically to the DataTable, it is not reflected automatically in the DataGridView and I suspect the converse is also true.
How do you keep the two concurrent? I thought the idea of data binding was that this was automatic...
Also adding rows works fine.
SOLVED:
The AutoGeneratedColumns was set to false in the designer code despite being true in the properties dialog (and set explicitly in the code). The initial columns were generated programmatically and so should not have appeared however this was not picked up on since the designer code also continued to generate 'designed in' columns that were originally used for debugging.
Moral: Check the autogenerated code!
In addition to this, see this post and this post
This doesn't sound right. To test it out, I wrote a simple app, that creates a DataTable and adds some data to it.
On the button1.Click it binds the table to the DataGridView.
Then, I added a second button, which when clicked, adds another column to the underlying DataTable.
When I tested it, and I clicked the second button, the grid immedialtey reflected the update.
To test the reverse, I added a third button which pops up a dialog with a DataGridView that gets bound to the same DataTable. At runtime, I then added some values to the first DataGridView, and when I clicked the button to bring up the dialog, the changes were reflected.
My point is, they are supposed to stay concurrent. Mark may be right when he suggested you check if AutoGenerateColumns is set to true. You don't need to call DataBind though, that's only for a DataGridView on the web. Maybe you can post of what you're doing, because this SHOULD work.
How I tested it:
DataTable table = new DataTable();
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
table.Columns.Add("Name");
table.Columns.Add("Age", typeof(int));
table.Rows.Add("Alex", 26);
table.Rows.Add("Jim", 36);
table.Rows.Add("Bob", 34);
table.Rows.Add("Mike", 47);
table.Rows.Add("Joe", 61);
this.dataGridView1.DataSource = table;
}
private void button2_Click(object sender, EventArgs e)
{
table.Columns.Add("Height", typeof(int));
foreach (DataRow row in table.Rows)
{
row["Height"] = 100;
}
}
private void button3_Click(object sender, EventArgs e)
{
GridViewer g = new GridViewer { DataSource = table };
g.ShowDialog();
}
public partial class GridViewer : Form //just has a DataGridView on it
{
public GridViewer()
{
InitializeComponent();
}
public object DataSource
{
get { return this.dataGridView1.DataSource; }
set { this.dataGridView1.DataSource = value; }
}
}
Is AutoGenerateColumns set to true? If you are making changes after the initial Binding you'll also have to call DataBind() to rebind the changed datasource. I know this is true for an AJAX callback, I think it is true for a WinForms control PostBack.
Here is another solution which you dont need to assign a "name" to the data grid, so that the data grid can be a template element.
(In my case, data grid is a template element inside TabControl.ContentTemplate)
The key to show the new column (added programmatically after the initial binding) is forcing the datagrid to refresh.
From the answer in Force WPF DataGrid to regenerate itself, Andres suggested setting AutoGenerateColumns from false to true will force datagrid to refresh.
Which means I simply need to:
Bind the AutoGenerateColumns to a property of my object
Set the propery to false before adding column
Set the propery to true after added column.
Here is the code:
XAML:
<DataGrid AutoGenerateColumns="{Binding toggleToRefresh}"
ItemsSource="{Binding dataTable}"
/>
C#:
public class MyTabItem : ObservableObject
{
private DataTable _dataTable = new DataTable();
public DataTable dataTable
{
get { return _dataTable; }
}
private bool _toggleToRefresh = true;
public bool toggleToRefresh
{
get { return _toggleToRefresh; }
set
{
if (_toggleToRefresh != value)
{
_toggleToRefresh = value;
RaisePropertyChanged("toggleToRefresh");
}
}
}
public void addDTColumn()
{
toggleToRefresh = false;
string newColumnName = "x" + dataTable.Columns.Count.ToString();
dataTable.Columns.Add(newColumnName, typeof(double));
foreach (DataRow row in dataTable.Rows)
{
row[newColumnName] = 0.0;
}
toggleToRefresh = true;
}
public void addDTRow()
{
var row = dataTable.NewRow();
foreach (DataColumn col in dataTable.Columns)
{
row[col.ColumnName] = 0.0;
}
dataTable.Rows.Add(row);
}
}
Hope this help :)
I had the same issue and I issued a DataBind(). It's not the silver bullet for everything, but it's what helped me in a few cases. I had to put it in before capturing information through a DataView, after the EditCommand and UpdateCommand events immediately after the EditItemIndex statement,
protected void datalistUWSolutions_EditCommand(object source, DataListCommandEventArgs e)
{
datalistUWSolutions.EditItemIndex = e.Item.ItemIndex;
datalistUWSolutions.DataBind(); // refresh the grid.
}
and
protected void datalistUWSolutions_UpdateCommand(object source, DataListCommandEventArgs e)
{
objDSSolutions.UpdateParameters["Name"].DefaultValue = ((Label)e.Item.FindControl("lblSolutionName")).Text;
objDSSolutions.UpdateParameters["PriorityOrder"].DefaultValue = ((Label)e.Item.FindControl("lblOrder")).Text;
objDSSolutions.UpdateParameters["Value"].DefaultValue = ((TextBox)e.Item.FindControl("txtSolutionValue")).Text;
objDSSolutions.Update();
datalistUWSolutions.EditItemIndex = -1; // Release the edited record
datalistUWSolutions.DataBind(); // Redind the records for refesh the control
}

Categories

Resources