DataGridViewComboBoxColumn setting selected index - winforms

I just cant get this to work.
I have a datagridview in winforms and in this one of my columns is a DataGridViewComboBoxColumn.
In my constructor I set it up like so
DataGridViewComboBoxColumn column = (DataGridViewComboBoxColumn)RectangleGrid.Columns["Material"];
DataTable data = new DataTable();
data.Columns.Add(new DataColumn("Value", typeof(int)));
data.Columns.Add(new DataColumn("Description", typeof(string)));
foreach (Materials M in DataStructure.Active.Active_Materials)
{
data.Rows.Add(M.MaterialNr, (M.MaterialNr + 1).ToString() + " " + M.Material.Name);
}
column.DataSource = data;
column.ValueMember = "Value";
column.DisplayMember = "Description";
And it actually works well except that nothing is selected in the drop down box which I want. I have googled this and for instance tried this approach: http://goo.gl/kBy8W but with no go because EditingControlShowing only happens when i click the box and not when it first comes up (so I can set selected index once it's clicked but thats no good).
The CellFormatting version at least changes the value but it just puts a string there rather than my first index from my data source.
I also tried this
column.DefaultCellStyle.NullValue = data.Rows[0]["Description"];
column.DefaultCellStyle.DataSourceNullValue = data.Rows[0]["Value"];
and that seemed to work but then when i selected the first index in the dropdown (so drop the dropdown down and then select the first index and then deselct the cell) I got an error from ParseFormattedValue where it says it cannot convert "value" to system.String.
There was this which seemed to be on the right track but i could not get it to work: http://goo.gl/VevA3

I ended up "solving" it in a very dirty solution that I don't like but it sort of works.
I had my datagridview bound to a datatable (not database) so what i did was i connected an event handler to the TableNewRow event in the datatable.
then i did this in that event handler
private void NewRectangleInserted(Object sender, DataTableNewRowEventArgs e)
{
if (e.Row[0].ToString() == "")
{
e.Row[0] = 0;
}
}
So basically when a new row is created I set the value of the combobox to 0 unless the user created the row by adding a value in that particular cell (why i check if it equals "")
The result is that as soon as you highlight a new line or any cell in a line the combobox is filled in. however the new line at the botton still has a blank combobox until you highlight it.
not perfect but a lot better. to bad it had to be done with a hack!

Related

getting error when trying to read from DataGridView with event handler winform

I have a winform that has a gridview that I am applying data to via a Dataset. When the data binds, it calls the SelectionChanged event handler. I researched that and found a way around it by adding an if clause to see if the DGV has focus (all other resolutions did not work that I found). That part is working as planned. When I step through the program, the event handler tries to go through the code 3 times when it binds the data. The if clause stops it from reaching the code. My issue is after the data binds and I then choose a row in the DGV, the event handler then throws "An unhandled exception of type 'System.ArgumentOutOfRangeException' occurred in mscorlib.dll". When stepping through the code, the DGV is returning the proper row index to my 'int row' variable, but the code I use to get the row/cell info throws the error before it applies it to the 'loadtableID' variable. I need help. You can ignore the second DGV in the top. It takes the selected row's info and gets another DB table info. Also, if it helps, I did not apply a datasource to the program or create datasets for each individual dataset that is returned, I am using the system's generic Dataset when returning data.
private void gvMainSelectResults_SelectionChanged(object sender, EventArgs e)
{
if (gvMainSelectResults.Focused)
{
gvMainArchiveResults.DataSource = null; //second DGV that is populated later and everytime is cleared with a new selection
loadTableID = 0;
orgID = 0;
dbFileName = "";
sourceType = "";
int row = gvMainSelectResults.CurrentCell.RowIndex;
loadTableID = Convert.ToInt32(gvMainSelectResults.SelectedRows[row].Cells["LoadTableID"].Value); //this is where I get the error, even if the "int row" has the correct index number
orgID = Convert.ToInt32(gvMainSelectResults.SelectedRows[row].Cells["OrganizationID"].Value);
dbFileName = Convert.ToString(gvMainSelectResults.SelectedRows[row].Cells["FileName"].Value);
sourceType = Convert.ToString(gvMainSelectResults.SelectedRows[row].Cells["SourceType"].Value);
more code here...
You are using the RowIndex value to get your text from the SelectedRows collection.
But this collection contains only
Gets the collection of rows selected by the user.
This means that the collection contains only a subset of the rows present in your grid. When RowIndex is 2 and you have just one row in the SelectedRows collection you get the OutOfRange exception.
With the RowIndex value you should refer to the Rows collection instead
loadTableID = Convert.ToInt32(gvMainSelectResults.Rows[row].Cells["LoadTableID"].Value);
orgID = Convert.ToInt32(gvMainSelectResults.Rows[row].Cells["OrganizationID"].Value);
dbFileName = Convert.ToString(gvMainSelectResults.Rows[row].Cells["FileName"].Value);
sourceType = Convert.ToString(gvMainSelectResults.Rows[row].Cells["SourceType"].Value);

Adding a checkbox column to the infragistics ultrawingrid that is bind to a datasource

I am trying to add a new checkbox column to the ultrawingrid that is binding to a dataset, when ever I add a new column it says key not found, any ideas on how to fix it, Thank you...
Below is the code
private void grdPayVis_InitializeLayout(object sender, InitializeLayoutEventArgs e)
{
var gridBand = grdPayVis.DisplayLayout.Bands[0];
gridBand.Columns["Select"].Header.Caption = "Select";
gridBand.Columns["Select"].Header.Appearance.TextHAlign = HAlign.Center;
gridBand.Columns["Select"].Header.VisiblePosition = 0;
gridBand.Columns["Select"].Hidden = false;
gridBand.Columns["Select"].Style = Infragistics.Win.UltraWinGrid.ColumnStyle.CheckBox;
gridBand.Columns["Select"].AutoSizeMode = ColumnAutoSizeMode.AllRowsInBand;
gridBand.Columns["Select"].CellActivation = Activation.AllowEdit;
gridBand.Columns["Select"].CellAppearance.TextHAlign = HAlign.Center;
gridBand.Columns["Select"].CellClickAction = CellClickAction.Edit;
}
Swetha
Steve's answer above is good, but it can be simplified and made better in a couple of ways.
First, a lot of that code isn't really necessary. For example, the default Hidden state of a column is false, so there's no need to set that. Same for AutoSizeMode and CellClickAction.
Also, if you add an unbound column, it's default DataType is string, which doesn't make a lot of sense for a CheckBox column. By setting the DataType to bool, you can avoid the need to set the Style and also clear up the problem of every cell being indeterminate by default.
private void ultraGrid1_InitializeLayout(object sender, Infragistics.Win.UltraWinGrid.InitializeLayoutEventArgs e)
{
UltraGridLayout layout = e.Layout;
UltraGridBand rootBand = layout.Bands[0];
if (false == rootBand.Columns.Exists("Select"))
{
UltraGridColumn checkBoxColumn = rootBand.Columns.Add("Select");
checkBoxColumn.Header.VisiblePosition = 0;
checkBoxColumn.DataType = typeof(bool);
}
}
As for determining the value of the cell and keeping track of the 'selected' rows, you have to understand a little bit about how the grid cell editors work. To really understand it, imagine a cell that contains DateTime info. The user enters the cell and intends to type in a date, like '1/19/2015'. When the user begins typing, he starts by typing a '1'. If you check the cell's value at this point, the grid cannot possibly convert the current text in the cell ("1") into a date. So because of this, the grid doesn't attempt to update the underlying data source with the value until something else happens, like if the user leaves the cell or loses focus on the grid.
Of course, if the cell has a checkbox, then this problem doesn't exist, since the user is incapable of entering an invalid value, but the grid still works the same way and doesn't update the value until the user leaves the cell.
So... when you are dealing with any cell that is NOT in edit mode (not currently active) then you can use the Value property of the cell reliably. When the cell is in edit mode (is active) then you can't rely on the Value, which reads from the data source, you have to use the cell's Text.
Therefore, given any cell in a boolean (checkbox) column, to get an accurate reflection of the current state of the checkbox on the screen, you would do something like this:
private bool GetCheckBoxCellCurrentValue(UltraGridCell cell)
{
if (cell.IsInEditMode)
return bool.Parse(cell.Text);
else
return (bool)cell.Value;
}
Finally, the grid doesn't keep any kind of list of the 'checked' cells. But you could do this yourself without too much difficulty. What you would have to do is build the initial list right after you bind the grid. Then trap events such as CellChange, AfterRowAdded, BeforeRowDeleted, and perhaps some others to continuously keep your list up to date.
When the grid has its datasource set to a datatable or other binding source, it automatically creates the columns present in the datatable or in the properties of the datasource. If you want to have another column you need to ADD it before trying to reference it from the Band columns
private void grdPayVis_InitializeLayout(object sender, InitializeLayoutEventArgs e)
{
var gridBand = grdPayVis.DisplayLayout.Bands[0];
// Check if the column exists, if not, add it
if(!gridBand.Columns.Exists("Select"))
gridBand.Columns.Add("Select", "Select");
// Not needed, the ADD adds the Key and the Caption
// gridBand.Columns["Select"].Header.Caption = "Select";
// Now you can reference the column with the Key = "Select"
gridBand.Columns["Select"].Header.VisiblePosition = 0;
gridBand.Columns["Select"].Hidden = false;
gridBand.Columns["Select"].Style = Infragistics.Win.UltraWinGrid.ColumnStyle.CheckBox;
gridBand.Columns["Select"].AutoSizeMode = ColumnAutoSizeMode.AllRowsInBand;
gridBand.Columns["Select"].CellClickAction = CellClickAction.Edit;
}

Is there a way to force a DataGridView to fire its CellFormatting event for all cells?

We use the CellFormatting event to colour code cells in various grids all over our application.
We've got some generic code which handles export to Excel (and print) but it does it in Black & White. Now we want to change this and pick up the colour from the grids.
This question & answer has helped (and it works) ... except there's a problem with larger grids that extend beyond a single screen. The portions of the grid which haven't yet been displayed on screen are (logically) never getting their CellFormatting code fired, and so their underlying colour never gets set. As a result, in Excel, the colour coding fizzles out half way down the page.
Seems there are three solutions:
1) Tell the user he has to scroll to all parts of the grid before doing an Export to Excel. Ha! Not a serious solution
2) Programmatically scroll to all parts of the grid before doing an Export to Excel. Only slighly less horrible than (1)
3) In our Export to Excel code, fire something at the top which tells the DataGridView to paint/format its entire area e.g.
MyDataGridView.FormatAllCells()
Is there something that does something like this???
Oh, and there is a fourth option but this will involve touching a massive amount of existing code:
4) Stop using CellFormatting event, format the cells at load time. Problem with this is we'd have to retool every grid in our application since CellFormatting is the way we've done it since year dot.
As noted in the other answers, accessing the DataGridViewCell.FormattedValue is indeed an easy way to force the CellFormatting event to be (re-)called for a specific cell. In my case, however, this property was also leading to undesirable side-effects involving the auto-resizing of the columns. While searching a long time for a workable solution, I finally encountered the following magic methods that work perfectly: DataGridView.Invalidate(), DataGridView.InvalidateColumn(), DataGridView.InvalidateRow(), and DataGridView.InvalidateCell().
These 4 methods force the CellFormatting event to be re-called only for the specified scope (cell, column, row, or whole table), and also without causing any nasty auto-resizing artifacts.
I have a possible solution - In your export function access the Cell.FormattedValue property of each cell. According to Microsoft, this forces the CellFormatting event to fire.
Assuming, as #DavidHall suggests, there is no magic .FormatAllCells our only option is to stop using CellFormatting.
However, new problem here is that applying cell style formatting during load doesn’t seem to have any effect. Lots of posts out there if you Google it. Also they point out that if you put the same code under a button on the form and click it after loading (instead of in the load, the code will work ... so the grid has to be visible before styling can apply). Most advice on the topic suggests you use ... drumroll ... CellFormatting. Aargh!
Eventually found a post which suggests using the DataBindingComplete event of the grid. And this works.
Admittedly, this solution is a variant of my unwanted option "4".
I had the same problem and I've ended up with something quite similar to your solution #4.
like you, I've used the DataBindingComplete event. but, Since I've used Extension method, the changes in the existing code are bearable:
internal static class DataGridViewExtention
{
public static void SetGridBackColorMyStyle(this DataGridView p_dgvToManipulate)
{
p_dgvToManipulate.RowPrePaint += p_dgvToManipulate_RowPrePaint;
p_dgvToManipulate.DataBindingComplete += p_dgvToManipulate_DataBindingComplete;
}
// for the first part - Coloring the whole grid I used the `DataGridView.DataBindingComplete` event:
private static void p_dgvToManipulate_DataBindingComplete(object sender, DataGridViewBindingCompleteEventArgs e)
{
foreach (DataGridViewRow objCurrRow in ((DataGridView)sender).Rows)
{
// Get the domain object from row
DomainObject objSelectedItem = (DomainObject)objCurrRow.DataBoundItem;
// if item is valid ....
if objSelectedItem != null)
{
// Change backcolor of row using my method
objCurrRow.DefaultCellStyle.BackColor = GetColorForMyRow(objSelectedItem);
}
}
}
// for the second part (disabling the Selected row from effecting the BackColor i've setted myself, i've used `DataGridView.RowPrePaint` event:
private static void p_dgvToManipulate_RowPrePaint(object sender, DataGridViewRowPrePaintEventArgs e)
{
// If current row about to be painted is selected by user
if (((DataGridView)sender).Rows[e.RowIndex].Selected)
{
// Get current grid row
var objGridRow = ((DataGridView)sender).Rows[e.RowIndex];
// Get selectedItem
DomainObject objSelectedItem = (DomainObject)objGridRow.DataBoundItem;
// if item is valid ....
if (objSelectedItem != null && objSelectedItem.ObjectId != 0)
{
// Set color for row instead of "DefaultCellStyle" (same color as we used at DataBindingComplete event)
objGridRow.DefaultCellStyle.SelectionBackColor = GetColorForMyRow(objSelectedItem);
}
// Since the selected row is no longer unique, we need to let the used to identify it by making the font Bold
objGridRow.DefaultCellStyle.Font = new Font(((DataGridView)sender).Font.FontFamily, ((DataGridView)sender).Font.Size, FontStyle.Bold);
}
// If current row is not selected by user
else
{
// Make sure the Font is not Bold. (for not misleading the user about selected row...)
((DataGridView)sender).Rows[e.RowIndex].DefaultCellStyle.Font = new Font(((DataGridView)sender).Font.FontFamily,
((DataGridView)sender).Font.Size, FontStyle.Regular);
}
}
}
A possible solution if you do want to reuse the formatting provided during the Cellformatting-event (e.g. the cellstyle-elements like fontbold and backgroundcolor). These cellstyles seem to be only available between the 'cellformatting' and 'cellpainting' events but not in the datagridview-cell's style itself..
Capture the cellstyles during the cellformatting-event with a second handler like this:
in the exportmodule add a shared list, array or dictionary to store the cellstyles:
Dim oDataGridFormattingDictionary as Dictionary(Of String, DataGridViewCellStyle) = nothing
initialize the dictionary and add a second handler to the datagridview in your printing or export-code. In vb.net something like this:
oDataGridFormattingDictionary = New Dictionary(Of String, DataGridViewCellStyle)
AddHandler MyDatagridviewControl.CellFormatting, AddressOf OnPrintDataGridView_CellFormatting
Add the code for the handler
Private Sub OnPrintDataGridView_CellFormatting(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewCellFormattingEventArgs)
If e.RowIndex > -1 AndAlso e.ColumnIndex > -1 AndAlso Not e.CellStyle Is Nothing Then
If Not oDataGridFormattingDictionary Is Nothing andalso oDataGridFormattingDictionary.ContainsKey(e.RowIndex & "_" & e.ColumnIndex) = False Then
oDataGridFormattingDictionary.Add(e.RowIndex & "_" & e.ColumnIndex, e.CellStyle)
End If
End If
End Sub
Very important: to make sure the original cellformating-event (AND the second cellformatting-handler after that) are actually called you have to request the formattedvalue for each cell that you want to print (e.g.
oValue = Datagridview.rows(printRowIndex).Cells(printColumnIndex).FormattedValue)
!
When printing you can now check if the cell has formatting. E.g.:
if not oDataGridFormattingDictionary is nothing andalso oDataGridFormattingDictionary.ContainsKey(printRowIndex & "_" & printColumnIndex) Then
... the cellstyle is accesible via:
oDataGridFormattingDictionary(printRowIndex & "_" & printColumnIndex)
end if
at the end of the export or printcode remove the handler and set the dictionary to nothing
RemoveHandler DirectCast(itemToPrint.TheControl, DataGridView).CellFormatting, AddressOf OnPrintDataGridView_CellFormatting
oDataGridFormattingDictionary = nothing

TDD with DataGridView

I'm relatively new to TDD, and still trying to learn to apply some of the concepts. Here's my situation.
I've got a WinForm with a DataGridView. I'm trying to write a test for the routine to be called by a button click that will perform some operations on the selected rows of the grid.
So I will be passing in the DataGridViewSelectedRowCollection object (i.e, the dgv.SelectedRows property at the time the button is clicked).
The DataGridViewSelectedRowCollection object has no constructor, so the only way I can figure to create it is to put together a DataGridView in my test project, then select some rows and pass in the SelectedRows property. But clearly, I don't want to re-create the whole form there.
So I do a DataGridView dgv = new DataGridView(), and gin up a BindingList (actually a SortableBindingList) just like the grid is bound to in the real application. The test list has 3 rows in it. And I do a dgv.DataSource = myList.
Now, at that point in the real application, the grid view is bound. If I look at dgv.Rows.Count, it's equal to the number of rows in the list. However, in my test, setting the DataSource property to the list still results in zero rows in the grid.
I'm thinking there's something missing in the creation of the gridview that normally gets done when it's added to the control list of the form. It probably initializes the handler for the OnDataSourceChanged event or something, and that isn't being done in my test code, but I'm really at a loss as to how to fix it, again, without re-creating a whole form object in my test fixture.
Here's the relavant code form my test method:
DataGridView residueGrid = new DataGridView();
List<Employee> baseListToGrid = new List<Employee>();
SortableBindingList<Employee> listToGrid = new SortableBindingList<Employee>(baseListToGrid);
residueGrid.DataSource = listToGrid;
for (int ix = 1; ix < 4; ix++)
{
listToGrid.Add(ObjectMother.GetEmployee(ix));
}
Assert.AreEqual(3, listToGrid.Count, "SortableBindingList does not have correct count");
Assert.AreEqual(3, residueGrid.Rows.Count, "DataGrid is not bound to list");
Thanks for any help you can give me.
DataGridView residueGrid = new DataGridView();
List<Employee> baseListToGrid = new List<Employee>();
SortableBindingList<Employee> listToGrid = new SortableBindingList<Employee>(baseListToGrid);
// residueGrid.DataSource = listToGrid; <-- move this line...
for (int ix = 1; ix < 4; ix++)
{
listToGrid.Add(ObjectMother.GetEmployee(ix));
}
// residueGrid.DataSource = listToGrid; <-- ...to here!
Assert.AreEqual(3, listToGrid.Count, "SortableBindingList does not have correct count");
Assert.AreEqual(3, residueGrid.Rows.Count, "DataGrid is not bound to list");
A useful structure for writing test is the following:
public void MyTest()
{
// Arrange
// Act
// Assert
}
In this case, Arrange would be instantiating all the objects, and filling the list. Act is where you set the data source of the gridview, and Assert is where you check that everything went OK. I usually write out those three comment lines each time I start writing a test.
Well, I solved the problem, and pretty much confirmed that it is something being done in the initialization of the control when added to the form that makes the DataSource binding work.
It suddenly dawned on me that that the "target" created by the MS testing framework is a private accessor to the Form itself. So I changed the line
DataGridView residueGrid = new DataGridView();
in the above code to, instead of creating a new DGV object, just reference the one on the target form:
DataGridView residueGrid = target.residueGrid;
That change made everything work as expected.

How to set the ColumnIndex of a newly added DataGridViewButton column

I have a really annoying issue with a button cell in a DataGridView control. I'm binding the grid to a dataset at runtime. Some of the rows in the grid will be linked to pdf documents. I create a button column and add it to the grid, then I loop through the rows and based on the value of a certain column I set the text of the cell in the button column. When I step through the code I can see the ColumnIndex of the button column is 10. However when the form appears, the button text values for the rows I want are blank.
When I click the button I check in the CellContentClick event to see if the ColumnIndex is 10 (which is the button column) it tells me the ColumnIndex is 0, even though it's the last column. Then when I reload the grid I call the BindHistoryGrid method again which drops the column if it exists and re-adds it. This time it sets the button text correctly. Is there some strange behavior going on that I can't see? How do I set the button ColumnIndex to 10 the first time I add it (even though it tells me that it's 10)?
private DataGridViewButtonColumn PDFButtonColumn;
private void BindHistoryGrid()
{
dataGridViewStmt.DataSource = ah.getAccountHistory(0, dateTimePicker1.Value, dateTimePicker2.Value);
if (dataGridViewStmt.Columns["GetPDFFile"] != null)
dataGridViewStmt.Columns.Remove("GetPDFFile");
dataGridViewStmt.Columns[0].DisplayIndex = 0;
dataGridViewStmt.Columns[0].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
dataGridViewStmt.AutoResizeColumns(DataGridViewAutoSizeColumnsMode.AllCells);
dataGridViewStmt.Columns[0].Visible = false;
dataGridViewStmt.Columns[1].Visible = false;
dataGridViewStmt.Columns.Add(PDFButtonColumn);
dataGridViewStmt.RowHeadersVisible = false;
dataGridViewStmt.ReadOnly = true;
dataGridViewStmt.AllowUserToAddRows = false;
foreach (DataGridViewRow row in dataGridViewStmt.Rows)
{
//if (((string)row.Cells[5].Value).Contains("Invoice"))
if (((int)row.Cells[9].Value) > 0)
{
((DataGridViewButtonCell)(row.Cells[10])).Value = "Get Invoice";
}
else
{
((DataGridViewButtonCell)(row.Cells[10])).Value = "";
}
}
}
private void dataGridViewStmt_CellContentClick(object sender, DataGridViewCellEventArgs e)
{
if (e.ColumnIndex == 10 && dataGridViewStmt.CurrentRow.Cells[6].Value != System.DBNull.Value)
{
string pdfFile = "";
int docID = 0;
pdfFile = (string)dataGridViewStmt.CurrentRow.Cells[5].Value + ".pdf";
docID = (int)dataGridViewStmt.CurrentRow.Cells[9].Value;
if (docID > 0)
{
getPDFFile(docID, pdfFile, "pdf");
}
else
{
MessageBox.Show("No invoice available for this item"; }
}
}
I called my bindGrid() method from the two place one after the InitializeComponent() in form's constructor as well as from form1_load(). it works for me.
hope this will also helps you.
I didn't get any replies here so I posted on another forum. I eventually got an answer of sorts, but the whole thing is still pretty vague. The answer I got stated that in order to preserve resources, the grid doesn't always refresh itself. An example is if you have a form with a tab control that has 2 tabs, place a grid on the 1st tab and set column properties after binding in Form Load. This will work. However, when you place the grid on the 2nd tab, using the same binding won't work:
http://social.msdn.microsoft.com/Forums/en-US/csharpgeneral/thread/99ab9fbf-9eaa-4eef-86b8-8f4e49fa81c5
I still haven't found out how or when it decides to preserve resources, if there's a way to bypass this behaviour, if this behaviour is documented anywhere etc. If anyone can throw more light on it I'm all ears.
I had the very same issue. I originally had a DataGridView on a separate form and it worked perfectly with the button column - which I add in code after setting the datasource. However, when I decided to move the grid onto another form with a Tabbed Control (on to the Tab(2) page as it happens), the button column index kept reverting to zero. It looked perfectly OK on the grid of course, i.e. in the correct physical location, and if I stepped through the code in debug mode the Index didn't change, but when I ran the program it did change! Very frustrating.
I solved it by setting the tab page to the page that my grid was located BEFORE setting the datasource.
My simple process was like this (I use VB10):
TabControl1.SelectedIndex = 2 ' this is where the datagridview is
MyGrid.DataSource = Nothing
MyGrid.Columns.Clear
' I execute an Sql command into a DataReader, then fill a DataTable and then assign it to the grid
MyGrid.DataSource = MyDataTable
' Now add button column
Dim btnCol as New DatGridViewButtonColumn
MyGrid.Columns.Add(btnCol)

Resources