Access cell data in a DataGrid - c#

Seems like a really simple thing, but all of the research and everything I have tried just doesn't seem to work. I am using VS2013 and .Net 4.5
I have a 2 column WPF DataGrid that isn't using databinding. The grid is already populated with data from a SQLCE table and I want to select a row and return as a string the data in the cell of column 0 of the selected row.
I can see that the data is there in the debug watch window
I just don't know how to access that value so that I can use it.
private void dgPickList_MouseDoubleClick( object sender, MouseButtonEventArgs e )
{
selectItem();
this.Close();
}
private void selectItem()
{
_pickedItem = dgPickList.SelectedItem.ToString();
}
This code obviously doesn't work so well but it does let me see the cell data in a Non-Public members _values field.
I really appreciate any help.
Jeff

Cast the SelectedItem back to the original type you populated the grid with.
For example, if you use a DataTable, cast to a DataRow and grab the first column (here I'm assuming it's an integer type that represents a unique ID):
var id = ((DataRow)dgPickList.SelectedItem).Field<int>(0);
If you're using a collection of a custom class, something like this should work:
var id = ((MyClass)dgPickList.SelectedItem).Id;

use DataGrid.SelectedCells instead. This will return a collection of selected cells in which you can find the column you need to read.
eg:
var celldata=dataGrid1.SelectedCells[0];
var val=celldata.Column.GetCellContent(celldata.Item);

Related

empty a specific column of numbers from the datagridview

Question:
How to empty specific column of numbers from datagridview? (i mean erase the values without header, not delete the column).
for example FROM THIS TO THAT, so i dont need to do it manually
How i created the datagridview:
i created a database table, with 5 ID's all of datatype "int", i connected the datagridview through DataSource, bindingsource. And when i put values in the table and press button:(button code below)
table1BindingSource2.EndEdit();
table1TableAdapter2.Update(database1DataSet8.Table1;
It will save the values into DataTable.
For example the first ID in the table is : Konduktivita1[µS_cm-1]
What i think is neccesery is to actually delete the values from DataTable but i dont how to do it.
It would be easier if you provided some kind of code, but let's try something like this.
var sourceList = dataGridName.ItemsSource as List<sqlTable>; //or whatever your data is called
foreach (var item in sourceList)
{
item.Konduktiva1 = null; // or 0
}

How to handle calculated fields in otherwise bound forms?

This concerns an approval field on a form. In the database, it's a bool field, an int field containing a FK to approvers, and a date-time field that, together, indicate whether something was approved and if so, who approved it and when. On the form, this has to translate into something like "Approved by John Smith on 01/02/03 04:05."
I handle this with a navigation bar. When the binding source position changes, the event is trapped and the code formats the calculated fields, like this (what the code does it not that important).
private void ctlNavBar1_displayCurrent(object sender, EventArgs e)
{
var drv = talsBindingSource.Current as DataRowView;
if (drv != null)
{
ctlBoundCheckButton1.lblText = $"Submitted {drv.Row.Field<DateTime>("SubmitDate").ToString("MM/dd/yy hh:mm tt")}";
ctlBoundCheckButton1.setControls(true);
if (drv.Row.Field<bool>("Approved"))
{
var sup = talsSupervisorsBindingSource.Current as DataRowView;
ctlBoundCheckButton2.lblText = $"Approved by {sup.Row.Field<string>("FullName")} on {drv.Row.Field<DateTime>("ApproveDate").ToString("MM/dd/yy")}";
ctlBoundCheckButton2.setControls(true);
}
}
else
{
using (DialogCenteringService centeringService = new DialogCenteringService(this))
{
MessageBox.Show("No TALs to Approve", "Confirm", MessageBoxButtons.OK);
}
Close();
}
}
The problem is that
public TALsApprove()
{
InitializeComponent();
talsTableAdapter.FillForApproval(timeTrackDataSet.TALs, User.ID);
usersTableAdapter.FillBySupervisor(timeTrackDataSet.Users, User.ID);
timeSlipsTableAdapter.FillBySupervisor(timeTrackDataSet.TimeSlips, User.ID);
ctlNavBar1.displayCurrent += ctlNavBar1_displayCurrent;
ctlNavBar1.bindingSource = talsBindingSource;
// this assignment doesn't fire Position Changed (or anything else, as far as I can tell)
}
the binding source event PositionChanged does not fire when the binding source is first assigned. I've worked around that by using the form Shown event, like this.
private void TALsApprove_Shown(object sender, EventArgs e)
{
ctlNavBar1_displayCurrent(null, new EventArgs());
}
So my questions are:
1.- Does calling event handlers directly like this mess with any of .NET's internals? (e.g. memory leaks, stack problems, etc.)
2.- Is there a less kludgy way of handling the calculation of fields when the binding source is first initialized, as well as when the contents of the current record change? I experimented with the binding source events CurrentChanged and CurrentItemChanged, but they seem to over-fire, firing even when no actual field value had changed.
There are a couple of ways I might think to tidy this up:
1 ) Use a calculated column
Assumptions:
You have a strongly typed DataSet with two tables like Applications and Users
You have the following columns in Applications: Approved(bool), ApproveDate(datetime), ApprovedByUserId(int)
You have a single datarelation between Applications and Users that maps Applications.ApprovedByUserId (many) to User.UserId (one), and UserId is also an int
Process:
In your dataset click in your Applications table, and add a string column
Set its Expression property to something like: IIF([Approved] = False,'Not approved','Approved by' + Parent.Username + ' on ' + [ApproveDate])
You have some process alreadythat fills good data into the table:
When you run the app it becomes the datatable's problem to build the narrative:
Let's edit another detail in at runtime:
When you finish the edit and move off the row it will be committed to the table and the narrative updates automatically
You can read more about what syntax you can use in DataColumn.Expression
If you don't have a bool approved you could add one or use some other test like IIF(ApprovedByUserId IS NULL,'Not Appproved,'App...'). If yo uhave multiple datarelations coming off Applications you specify the name of the relation after Parent like Parent(App_User).UserName` assuming the datarelation is called App_User
Bind different things on the UI
Noone ever said you only had to bind Text. If you had a "Approved" bool column in your dataset, you could have several labels in a row:
--label1----- --label2------ --label3-- --label4--
"Approved by" BindParentName " on " BindDate
You can bind every one of their Visible properties to the Approved bool so the labels disappear if the user navs to an unapproved row.
The easiest way to get the parent user name into the Applications data table (because all these labels are bound to a indingsource that sits on the Applications table, right?) is to use an Expression on a new column like above, but simpler (just Parent.UserName or Parent(App_User).UserName`) to import the user name into the Applications datatable.
There are other ways, involving multiple binding sources that bind to datarelations.. We can even jiggle a combo box into doing it - the combo has a DataSource of the users table, but a DataMember of "ApprovedByUserId" from the applications table; it will perform 2 way lookup of the ApprovedByUserId <--> UserId equivalence

datagridview column index

I have a form with a DataGridView widget and I need to get the index of the column with the selected name.
For example, let's say that I have a table with 2 columns: Name, Surname. I need a way to get index of the column name. The problem is that it changes all the time depending on the DataSource but that column always has the same name "Name".
Does anyone know how to solve the problem?
To retrieve a DataGridView column by name you simply reference it through the columns collection indexer:
datagridview1.Columns["columnName"]
Then you can get the column index from that column:
datagridview1.Columns["columnName"].Index;
Do note that if you use an invalid column name then this reference will return null, so you may want to check that the column reference is not null before using it, or use the columns collection .Contains() method first.
If I am right, e.ColumnIndex will also work for this. you can check the MSDN Documentation here
You can get the index by using the Index property of the DataGridViewColumn widget, as such:
ColumnName.Index
This avoids the need for checking whether the column name is valid at runtime as it will generate a compilation error if the column does not exist. This also makes refactoring easier.
I recommend you give the columns a sensible name (for example DCOL_SomeName) so that you can easily distinguish them. Including the name of the DataGridView widget would help if you have multiple DataGridView widgets on the same form.
create a static class below the code
public static class MyTools
{
public static int IndexByName(this DataGridView dgv, string name)
{
foreach(DataGridViewColumn col in dgv.Columns)
{
if(col.HeaderText.ToUpper().Trim() == name.ToUpper().Trim())
{
return col.Index;
}
}
return -1;
}
}
and then call it with your dataGridView
int index = datagridview1.IndexByName("columnName");
I have found it safer to use the column object's Name property, instead of using the column name as a string, because this allows for more consistent code refactoring in the future.
datagridview1.Columns[column1.Name].Index;
Also, it is important to first make sure the column is not null and, as others have said, that it is contained within the datagridview.

How to access a cell in a DataRowView based on the columns DataPropertyName?

I have a Windows Forms application with a DataSet. I've simply used the Data | Add New DataSource to add the Products table of the Northwind database to my DataSources and created a DataGridView showing the contents of the Products table. I just dragged the Products table from the Data Sources window to the form, so all the columns are created automatically.
Now, I want the rows containing a product where the Discontinued column is true to be painted in a different color. I've created a CellPainting event handler for it, but I'm having trouble locating the value for the Discontinued column.
Because the DataGridView is created automatically, the Columns in it have names like dataGridViewTextBoxColumn1, which has a DataPropertyName of "ProductID".
My question is: how can I find the value for Discontinued based on the DataPropertyName? Or am I required to use the name of the column itself? (In which case I better give it a meaningful name)
My code is:
private void productsDataGridView_CellPainting(object sender,
DataGridViewCellPaintingEventArgs e)
{
if (e.RowIndex >= 0)
{
var row = productsDataGridView.Rows[e.RowIndex];
if ((bool) (row.Cells[ nameOrIndexOfColumn ].Value))
{
e.CellStyle.ForeColor = Color.DarkGray;
}
}
}
How can I access the Value of the Column with DataPropertyName "Discontinued"?
Solution
Based on Neil Barnwell's answer, this seems to be a way.
private void productsDataGridView_CellPainting(object sender, DataGridViewCellPaintingEventArgs e)
{
if (e.RowIndex >= 0)
{
var productView = (DataRowView) productsDataGridView.Rows[e.RowIndex].DataBoundItem;
var product = productView.Row as NorthwindDataSet.ProductsRow;
if (product != null && product.Discontinued)
{
e.CellStyle.ForeColor = Color.DarkGray;
}
}
}
The big advantage of this is that the Discontinued value doesn't have to be an actual column on the DataGridView.
Don't get the value from the column in the grid, get the value from the actual datarow that populates the gridrow. This way you can avoid all the magic strings etc.
There's a little bit of casting to be done because the [Type]DataRow is hidden inside a DataView that's attached to the grid, but it is a more elegant approach (and far less brittle in case of future changes) if you integrate it nicely into your code than relying on magic strings.
Here's an old blog post of mine that describes in detail how to do it:
http://koder.wordpress.com/2010/04/09/getting-data-from-a-winforms-datagridview/
UPDATE
You've mentioned that you're using Northwind and that you've "simply dragged the Products table to the form", so I'm guessing this isn't a mission-critical piece of software, but for the benefit of other people reading, I just wanted to suggest that this wouldn't be a typical approach any more in a real application.
Typically these days we'd consider having a Domain Model, perhaps using an ORM to obtain our Domain Objects from the data store (of course datasets aren't a real ORM), then possibly using things like MVVM to build data structures optimised for binding to UI elements from those Domain Objects.
Using this approach, because you have the actual data to hand in your ViewModel, you can calculate such rules as colours etc from the real data, and the UI simply displays the results of applying those business rules.
Why don't you try giving a name to that particular column yourself and then you know how to access it. Visual studio shouldn't have any problem with this even if its auto generated. Because it was generated to help you do all the binding and column creating stuff in the background. But it is still available for editing, autogenerated doesn't mean it generated at run-time . Its just helping you do the usual stuff, but you can still edit it.
row.Cells[ nameOrIndexOfColumn ]
You can try something on these lines
if (dataGridView1.Columns[e.ColumnIndex].DataPropertyName == "Discontinued")
{
if (dataGridView1[reqdColumnIndex, e.RowIndex].FormattedValue as bool)
{
e.CellStyle.ForeColor = Color.Gray;
}
}
reqdColumnIndex is the column which has your bool value

How to create LookUp fields in DataGridView?

In my DataGridView I'am displaying a buch of columns from one table. In this table I have a column which points to item in another table. As you may already guessed, I want to display in the grid in one column some text value from the second table instead of and ItemID.
I could not find a right example on the net how to do this.
Lets assume that I have two tables in databes:
Table Users:
UserID UserName UserWorkplaceID
1 Martin 1
2 John 1
3 Susannah 2
4 Jack 3
Table Workplaces:
WorkplaceID WorkplaceName
1 "Factory"
2 "Grocery"
3 "Airport"
I have one untyped dataset dsUsers, one binding source bsUsers, and two DataAdapters for filling dataset (daUsers, daWorkplaces).
Code which I am performing:
daUsers.Fill(dsUsers);
daWorkplaces.Fill(dsUsers);
bsUsers.DataSource = dsUsers.Tables[0];
dgvUsers.DataSource = bsUsers;
At this point I see in my dgvUsers three columns, UserID, UserName and UserWorkplaceID. However, instead of UserWorkplaceID and values 1,2,3 I would like to see "Factory", "Grocery" and so on...
So I've added another column to dgvUsers called "WorkplaceName" and in my code I am trying to bind it to the newly created relation:
dsUsers.Relations.Add("UsersWorkplaces", dsUsers.Tables[1].Columns["WorkplaceID"], dsUsers.Tables[0].Columns["UserWorkplaceID"]);
WorkplaceName.DataPropertyName = "UsersWorkplaces.WorkplaceName";
Unfortunately that doesn't work. Relation is created without errors but fields in this column are empty after running the program.
What I am doing wrong?
I would like to also ask about an example with LookUp combobox in DataGridView which allow me to change the UserWorkplaceID but instead of numeric value it will show a tex value which is under WorkplaceName.
Thanks for your time.
In my opinion, the best decision would be to use the DataGridViewComboBoxColumn column type. If you do it, you should create a data adapter with lookup data beforehand and then set DataSource, DataPropertyName, DisplayMember, and ValueMember properties of the DataGridViewComboBoxColumn. You could also set the DisplayStyle property to Nothing to make the column look like a common data column. That's it.
I don't know if you can do exactly what you want, which seems to be binding the DataGridView to two different DataTable instances simulataneously. I don't think the DataGridView class supports that -- or if it does it's a ninja-style move I haven't seen.
Per MSDN, your best bet is probably using the CellFormatting event on the DataGridView and check for when the cell being formatted is in the lookup column, then you could substitute your value from the other table. Use an unbound column for the WorkplaceName column, hide the UserWorkplaceID column and then implement the CellFormatting event handle to look up the value in the row, e.g.:
private void dgv_CellFormatting(object sender,
DataGridViewCellFormattingEventArgs e)
{
if (dgv.Columns[e.ColumnIndex].Name.Equals("WorkplaceName")
{
// Use helper method to get the string from lookup table
e.Value = GetWorkplaceNameLookupValue(
dataGridViewScanDetails.Rows[e.RowIndex].Cells["UserWorkplaceID"].Value);
}
}
If you've got a lot of rows visible, this might impact performance but is probably a decent way to get it working.
If this doesn't appeal to you, maybe use the DataTable.Merge() method to merge your lookup table into your main table. A quick glance at one of my ADO.NET books suggests this should work, although I have not tried it. But I'm not sure if this is too close to the idea suggested previously which you shot down.
As for your second question about the lookup combobox, you should really post it in a separate question so it gets proper attention.
You could make SQL do the job instead. Use a join to return a table with Workplace names instead of IDs, output that table into a dataset and use it instead.
eg.
SELECT A.UserID, A.UserName, B.WorkplaceID
FROM Users A
JOIN Workplaces B ON A.UserWorkplaceID = B.WorkplaceID
Then use its output to fill dsUsers.

Categories