Why is DataView readonly despite "AllowEdit=true"? - c#

I have code (below) that I think should allow me to update the underlying tables, but I continue to get a "Underlying data is read only" error.
In the code below, everything I've tried is included. Clearly, I'm missing something! A search in SO hasn't found clues...
APT.Columns["Selected"].ReadOnly = false;
TWR.Columns["Selected"].ReadOnly = false;
RWY.Columns["Selected"].ReadOnly = false;
APT.Columns["Selected"].Expression = "false";
TWR.Columns["Selected"].Expression = "false";
RWY.Columns["Selected"].Expression = "false";
// DataView with filtered "parent" table data
// Apply user's filter for the "Fix"
DataView dataView = new DataView(APT)
{
RowFilter = FixFilter(chkboxShowAll.Checked),
Sort = "FacilityID",
AllowEdit = true,
};
// RWY and TWR dont have ARTCC (filter item),
// so we can't filter by that,
// and "Join" makes the table readonly,
//so must grab root ID and change manually
DataView dvTWR = new DataView(TWR)
{
AllowEdit = true // Enable editing
};
DataView dvRWY = new DataView(RWY)
{
AllowEdit = true
};
// TWRs and RWYs have the same ID as the APT,
// but not may have a TWY or RWY (e.g., Seabase)
// This should update the "Selected" boolean
foreach (DataRowView dataRowView in dataView)
{
dataRowView.BeginEdit();
dataRowView["Selected"] = true; // *** <- EXCEPTION HERE
dataRowView.EndEdit();
int result = dvRWY.Find(dataRowView["ID"]);
if (result != -1)
{
dvRWY[result].BeginEdit();
dvRWY[result]["Selected"] = true;
dvRWY[result].EndEdit();
}
result = dvTWR.Find(dataRowView["ID"]);
if (result != -1)
{
dvTWR[result].BeginEdit();
dvTWR[result]["Selected"] = true;
dvTWR[result].EndEdit();
}
}
dvTWR.Dispose(); // Commit/close tables
dvRWY.Dispose();
At the annotated code line, the exception fires:
System.Data.ReadOnlyException: 'Column 'Selected' is read only.'
Can anyone tell me the error of my ways?

Dai is correct: A dataview cannot modify static public in-memory tables. However, a datagridview, bound directly to the datatable, can.
This is a better solution for me, as ultimately I need to display the "selected" rows: I created a datagridview for each table and placed each one in a tab control container. I checked the "Can edit" box for each datagridview in designer.
One can then drilldown to modify each column's readonly property within designer, but I found it easier to assign the readonly property after I read data into datatables from the text files (either one works, and one can change the designer-setting programmatically as they are actually the same property).
Looping the "Selected" column rows true or false is conveniently dependent on the filter applied to the datagridview, making a single calling routine efficient (just do or do not apply a filter to the datagridview(s) and pass the dgv and/or columns to the called routine).
As a plus, users can un/check "Selected" rows. The underlying table is changed! The tables can then be efficiently filtered on "Selected" for other events/output.
// schema built in VS designer...
static public DataTable APT = new SCTData.APTDataTable();
// Table populated from FAA fixed-column text files (cols vary by file)
ReadFixes.FillAPT();
// Assign the table to the datagridview, apply filter and sort
DgvArpt.DataSource = APT;
(DgvArpt.DataSource as DataTable).DefaultView.RowFilter = filter;
AdjustColumnOrderNSort(DgvArpt);
// Loop the filtered datagrid to make "Selected" col = true
UpdateSelected(DgvArpt,true);
private void UpdateSelected (DataGridView dgv, bool hasFilter = false)
{
// If the filter is applied, selected boxes are true
// otherwise, ALL the selected boxes are false
// But if the ShowAll box is checked, ignore the update
if (chkboxShowAll.Checked == false)
{
foreach (DataGridViewRow row in dgv.Rows)
{
row.Cells["Selected"].Value = hasFilter;
}
}
}

Related

Two different datagridview rows getting selected when one should be selected

I am working with winform project. i have two datagridview which has same number of columns and same structure. frist column is checkbox column.
here is code to bind two datagrid with same kind of data.
List<AllGroupNames> grpDtl = GetAllGroups(Nodes);
List<AllGroupNames> grpDtl1 = GetAllGroups(Nodes); //grpDtl.GetRange(0, grpDtl.Count);
//bind two grid with all groups name
if (_grpDtl != null && _grpDtl.Count > 0)
{
dgSingleGroups.AutoGenerateColumns = false;
dgSingleGroups.DataSource = grpDtl;
dgSingleGroups.Columns[0].DataPropertyName = "Select";
dgSingleGroups.Columns[1].DataPropertyName = "GroupName";
dgSingleGroups.Columns[1].ReadOnly = true;
dgSingleGroups.Columns[0].Width = 47;
dgSingleGroups.Columns[1].Width = 346;
dgAllGroups.AutoGenerateColumns = false;
dgAllGroups.DataSource = grpDtl1;
dgAllGroups.Columns[0].DataPropertyName = "Select";
dgAllGroups.Columns[1].DataPropertyName = "GroupName";
dgAllGroups.Columns[1].ReadOnly = true;
dgAllGroups.Columns[0].Width = 47;
dgAllGroups.Columns[1].Width = 346;
}
grpDtl1 = null;
grpDtl = null;
_grpDtl = null;
GetAllGroups() iterate in treeview node collection and accumulate node name.
private List<AllGroupNames> GetAllGroups(TreeNodeCollection tnCollection)
{
//accumulate group name in recursive fashion
foreach (TreeNode tn in tnCollection)
{
if (tn.Tag != null)
{
if (((object)tn.Tag).GetType().ToString().Contains("TunerDetails"))
{
_grpDtl.Add(new AllGroupNames { Select = false, GroupName = tn.Text });
}
GetAllGroups(tn.Nodes);
}
}
return _grpDtl;
}
Now problem is when i check second grid checkbox then my first grid checkbox is getting checked invisibly means when i am reading first grid's first column value in loop then i am getting checkbox value true. where as i have not checked my first grid's any checkbox.
when i select any row of second grid that same row is getting automatically selected in first grid.
I just select one row from right side grid and same row of left side grid automatically gets selected....which is problem for me. Why two grid syncing automatically. screen shot attached.
why it is happening i am not able to capture the reason. please help me what to change in code to get rid of this problem.
Apparently every object of class AllGroupName has a Boolean property Select.
You created Column[0] such, that the checkbox is checked whenever this boolean is true, and vice versa: whenever the operator checks this checkbox, the corresponding AllGroupName.Select will become true.
You decided to use the same DataSource as object in two DataGridViews.
Operator checks the checkbox of row[4] in Dgv1
This means that DataSource[4].Select = true;
This means that all objects that have this DataSource will be notified that [4] has changed
Row[4] in Dgv1 and Dgv2 will be updated: checkbox is checked.
If you don't want that changes in Dgv1 are refelected in Dgv2, then you should make a copy of the datasource.
List<...> data1 = ...; // shouldn't this be an BindingList<...> ?
Dgv1.DataSource = data1;
List<...> data2 = new List<...>(data1); // clone data1
Dgv2.DataSource = data2;

Bind and Highlight Specific DataGridView Row

I am binding data from excel file in a list on button click and this works perfectly. Finally the data is binded to a DataGridView. Now I want to iterate the list to check if there are any data that isn't included to the database after binding to a DataGridView. If any data mismatches, then it should highlight the specific row with red color in the DataGridView. Note: There could be multiple data that will not match. Something as the below image and the code tried:
grdUpload.Rows.Clear();
for (int i = 0; i < lstData.Count; i++) //lstData - The Data List
{
if (Facede.ExcelUpload.CheckIfExists(lstData)) //Checking if any data mismatches
{
grdUpload.DataSource = lstData;
grdUpload.Rows[i].DefaultCellStyle.BackColor = Color.Red; //Highlight the row data that mismatches
}
else
{
grdUpload.DataSource = lstData;
}
}
public bool CheckIfExists(List<Data> lst)
{
bool flag = false;
foreach (Data d in lst)
{
string Query = "SELECT M.EmpNo FROM Data m WHERE M.EmpNo = '" + d.EmpNo + "'";
DataTable dt = SelectData(Query);
if (dt != null && dt.Rows.Count > 0)
{
flag = true;
}
else
{
flag = false;
}
}
return flag;
}
Now the issue is it doesn't highlight the specific row if data like EmpNo mismatches. Anything that I am missing here?
Problem is in your for loop.
You are firstly binding data to your datagridview.
Then you are entering for loop
Inside it you ask if condition is met and if it is you AGAIN bind same data to datagridview but after it you color it.
For loop continues and it again enters part where it meets condition and AGAIN you BIND same data but now you overwrite colored data with new (but same) data and then color some new row.
So what you need to do is
Load data into datagridview
Loop through datagridviewrows and if meet condition color that row
So code should look like this:
//Here you bind your data to datagridview
//In code bellow if you want to get row's column's data use
//row.Cells["CELL_VALUE"].Value (convert to what datatype you need before comparing)
foreach (DataGridViewRow row in dataGridView1.Rows)
{
if (condition)))
{
dataGridView1.Rows[row.Index].DefaultCellStyle.BackColor = Color.Red;
}
}

Setting DataSource to DataGridViewComboBoxColumn

I'm trying to set DataSource to DataGridViewComboBoxColumn of my DataGridView. First I'm trying to bind a data source to my DataGridView, where bindingList is a List of my custom class Plugin with properties Name (string), Id (string) and Dependencies (List):
var bindingList = PluginsHandler.GetPlugins();
var source = new BindingSource(bindingList, null);
pluginsDataGridView.AutoGenerateColumns = false;
pluginsDataGridView.DataSource = source;
pluginsDataGridView.Columns["pluginName"].DataPropertyName = "Name";
pluginsDataGridView.Columns["pluginID"].DataPropertyName = "Id";
So I can set my first two columns, but now I want to bind data to a third column of type DataGridViewComboBoxColumn. I try to do it on DataBindingComplete event:
private void pluginsDataGridView_DataBindingComplete(object sender, DataGridViewBindingCompleteEventArgs e)
{
for (int i = 0; i < pluginsDataGridView.Rows.Count; i++)
{
var comboCell = (DataGridViewComboBoxCell) pluginsDataGridView.Rows[i].Cells["pluginDependencies"];
var entry = pluginsDataGridView.Rows[i].DataBoundItem as IPlugin;
comboCell.DataSource = entry.Dependencies;
}
}
Sadly comboBox is empty. Funny thing happens when I incorrectly put these lines after the first block of code I posted:
var dependenciesColumn = (DataGridViewComboBoxColumn) pluginsDataGridView.Columns["pluginDependencies"];
dependenciesColumn.DataPropertyName = "Dependencies";
Then binding seem to start to work, as I can see that there are some entries in comboboxes, but when I try to hover mouse on combobox, I am getting an error that says DataGridViewComboBoxCell value is not valid).
How can I make it work?
Instead of assigning each ComboCell a data Source, set the DataSource of the column. I assume the Dependencies property of PlugIn class is a List<string>.
pluginsDataGridView.Columns["pluginDependencies"].DataSource = //list of dependencies
You have to set the DataPropertyName of the Dependencies ComboBoxColumn to get the initial values. If you don't set it you won't see any value in the column when the application is loaded.
pluginsDataGridView.Columns["pluginDependencies"].DataPropertyName = "Dependencies"
Edit:
You have a list of dependencies for a plug-in. i.e, more than one value. Usually, to select one value from list of values, you associate list with ComboBoxColumn. Achieving your requirement of multiple values from a list using standard ComboBoxColumn is difficult. Write a custom CheckedComboBoxColumn where you can display and select multiple values.

How to get IDs of only checked rows of a datagridview

I have a datagridview that contains list of subjects populated from Subject table from database.Columns include
Select(checkbox),
SubjectId,
SubjectName,
SubjectGroup.
Now I want if a user Selects on any of the desired rows, the corresponding SubjectId's should be added to a List. I have made and inserted into the desired table in the database.
The problem is that the new column of checkboxes I have added to this datagridview is not being detected.
My code is:
foreach (DataGridViewRow row in gvSubjectsOpted.Rows)
{
if (Convert.ToBoolean(gvSubjectsOpted.SelectedRows[0].Cells["SelectId"].Value=true))
{
olist.Add(gvSubjectsOpted.SelectedRows[0].Cells["SubjectId"].Value.ToString());
}
}
Late to the party. I had the same issue with trying to get the checkbox column by name, use the index instead. Here is a linq example assuming the checkbox is column 0 and the stored values for TrueValue and FalseVale are true and false respectively.
var checkedRows = from DataGridViewRow r in gvSubjectsOpted.Rows
where Convert.ToBoolean(r.Cells[0].Value) == true
select r;
foreach (var row in checkedRows)
{
olist.Add(row.Cells["SubjectId"].Value.ToString());
}
I realise this is an old post but I came across it and didn't think it was really answered in an efficient way so I thought I would add my method.
I have a similar block in my windows app. I read the values from the grid when the user clicks a button, and I want to know which rows they checked. As the checkboxes are in Cell 0 and the data I want is in Cell 1, I use the following code. Note the cast: it is important as it allows us the use the Where clause and therefore just a single line of code to get the collection of data. I could use the name of the cells instead of magic index numbers but then it would not fit your app so I put numbers instead (you should use names)
var checkedRows = dataGridView
.Rows
.Cast<DataGridViewRow>()
.Where(x => x.Cells[0].Value.ToString() == "1")
.Select(x => x.Cells[1]);
Note that this will give you an IEnumerable of type DataGridViewCell. If you want you can either add something like .Value.ToString() to the select or do this when you use your collection.
You question is similar to another SO question.
Check the answer of this Datagridview checkboxcolumn value and functionality.
Try this
foreach(GridViewRow r in gvSubjectsOpted.Rows)
{
GridViewCheckBoxColumn c = r.cells[0].Controls[0] as GridViewCheckBoxColumn;
if(c.Checked)
{
//Do something.
}
}
private void button1_Click(object sender, EventArgs e)
{
string subjId;
List<string> lines = new List<string>();
for (int i = 0; i < gvSubjectsList.Rows.Count; i++)
{
bool Ischecked =Convert.ToBoolean(gvSubjectsList.Rows[i].Cells["Select"].Value);
if (Ischecked == true)
{
subjId = gvSubjectsList.Rows[i].Cells["SubjectId"].Value.ToString();
lines.Add(subjId);
}
}
comboBox1.DataSource = lines;
}
//the most important thing is to set 'true' and 'false' values against newly added checkboxcolumn instead of '0' and '1'...that is,
CBColumn.FalseValue = "false";
CBColumn.TrueValue = "true";

replace true/false in datagridview columns

I have a datagridview which I fill it as below :
var q= repository.GetStudents();//
dataGridView1.DataSource = null;
dataGridView1.Columns.Clear();
dataGridView1.DataSource = q;
dataGridView1.Columns.RemoveAt(1);
//Remove IsActive
//Cause I want to have my own implementation
dataGridView1.Columns[0].DataPropertyName = "StudentID";
dataGridView1.Columns[0].HeaderText = "Studunet ID";
dataGridView1.Columns[1].DataPropertyName = "IsActive";
dataGridView1.Columns[1].HeaderText = "Status";
The "IsActive" property is of boolean Type. When the "IsActive" cell is being displayed, it show true/false. I want to replace it with my own custom value.
I read this and this posts but I could not resolve my problem.
You can use the CellFormatting event of the DataGridView, e.g.:
void dataGridView_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
{
var grid = (DataGridView)sender;
if (grid.Columns[e.ColumnIndex].Name == "IsActive")
{
e.Value = (bool)e.Value ? "MY_TEXT_FOR_TRUE" : "MY_TEXT_FOR_FALSE";
e.FormattingApplied = true;
}
}
EDIT (as per comment):
It's very similar to what you're doing now, just remove the bound column and add a new column of the desired type and set the DataPropertyName properly e.g. :
this.dataGridView1.Columns.Remove("COL_TO_CUSTOMIZE");
var btnCol = new DataGridViewDisableButtonColumn();
btnCol.Name = "COL_TO_CUSTOMIZE";
btnCol.DataPropertyName = "COL_TO_CUSTOMIZE";
var col = this.dataGridView1.Columns.Add(btnCol);
Note that this append the column at the end, but you can decide the position of the column by using dataGridView.Columns.Insert method instead of Add.
One of the funky things about a DataGridViewComboBoxColumn is that you can give it one data source that has a column of values to lookup and a column of values to show, and you can bind it to another column of values and then it will perform the lookup for you
So, suppose your collection q of Students (or whatever they are) has an IsActive true/false and you want this to show as "All the time", or "Not a chance".. Let's hash together a combobox that does this:
var cb = new DataGridViewComboBoxColumn();
cb.DisplayMember = "DisplayMe"; //the related text to show in the combo
cb.ValueMember = "ValueToLookup"; //the name in the combo's lookup list
cb.DataPropertyName = "IsActive"; //the name of your property on Student, to look up
cb.DataSource = "All the time,Not a Chance"
.Split(',')
.Select(s => new { DisplayMe = s, ValueToLookup = (s[0] == 'A') } )
.ToList();
It doesn't really matter how we generat the combo's datasource; here I've made a string into a List<anonymous_string+bool> by splitting, then selecting a new anonymous type with the two property names I need; you can use anything that has some named properties - a List of KeyValuePair, Tuple, whatever..
The critical thing is that the combo can read the q.IsActive bool you cited in DataPropertyName, look that bool up in its list in the property named in the ValueMember, then display the property named in the DisplayMember. It works for editing too, so the user can choose a new item from the combo and the translation works back the other way - "what does the user choose? what is the value of its property named in ValueMember, put that value into the student IsActive property named in DataPropertyName".. And it doesnt stop at bools either; the value member can be anything - an int, date etc

Categories

Resources