I have a Datagridview, and a field from the database. I want save this field to row tag in gridview, but after saving to the tag with a Foreach loop, in another function I see it returns null value.
How do I save this field?
Thank you
private void UC_DKHoc_Load(object sender, EventArgs e)
{
var dsLop = LopDangKyServices.LayDanhSachLopDangKy();
var DatasourceGV = from lop in dsLop
select new
{
lop.MaLopDangKy,
lop.TenLopDangKy,
lop.CLB1.TenCLB,
lop.NamHoc,
lop.HocPhi,
lop.LichHoc
};
advancedDataGridView.DataSource = DatasourceGV.ToList(); // Display to gridview
int i = 0;
foreach (DataGridViewRow row in advancedDataGridView.Rows)
{
row.Tag = dsLop[i++].CLB; //save complete
}
advancedDataGridView.DisableFilter(STT);
}
In The function I see null values.
private void simpleButton1_Click(object sender, EventArgs e)
{
MessageBox.Show(advancedDataGridView.Rows[1].Tag.ToString()); //Null value
}
You can use either of the following options:
Include all the properties which you need in the list (including CLB). Then after assigning the list as DataSource of the DataGridView, hide the corresponding column:
advancedDataGridView.DataSource = list;
advancedDataGridView.Columns["CLB"].Visible = false;
Create a model class containing all the properties which you need, including CLB. Then just decorate CLB with [Browsable(false)] attribute. (You need to select the result by creating instance of the model class, rather than anonymous type.)
[Browsable(false)]
public int CLB { get; set;}
Set AutoGenerateColumns of DataGridView to false and add desired columns to DataGridView. Make sure you set Name, DataPropertyName and HeaderText. Then set DataSource of DataGridView to the list which contains all properties, including CLB:
dataGridView1.Columns.Add(new DataGridViewTextBoxColumn() {
Name = "MaLopDangKy",
DataPropertyName = "MaLopDangKy",
HeaderText = "MaLopDangKy" });
//Add rest of columns ...
dataGridView1.AutoGenerateColumns = false;
dataGridView1.DataSource = list;
You can decide to add CLB as an invisible column of DataGridView. Even if you didn't add it as a column, you can access to the DataBoundItem property of the DataGridViewRow which points to the list member instance which is displaying the the row. Cast it to the list member type and use its CLB property.
Related
I have a User Control with an Ultragrid. In a particular form where I add a ValueList. The value list will not show for the particular column I'm interested in. If I then code in another column instead by changing the index value for columns I get the value list in the column.
The code looks like:
private void AddCombo(object sender, UcUltraGen.RowClickArgs e)
{
ValueList vl;
if (!ucUltraGridMain.Grid.DisplayLayout.ValueLists.Exists("Texas"))
{
vl = ucUltraGridMain.Grid.DisplayLayout.ValueLists.Add("Texas");
}
else
{
vl = ucUltraGridMain.Grid.DisplayLayout.ValueLists["Texas"];
}
var row = e.VariantRow;
List<PcBase> list = PcBase.PcBaseList.Where(x => x.VariantId == row.Cells["Id"].Text).ToList();
AddValueList(list, vl);
ucUltraGridMain.Grid.DisplayLayout.Bands[0].Columns[1].ValueList =
ucUltraGridMain.Grid.DisplayLayout.ValueLists["Texas"];
And if I change to
...
ucUltraGridMain.Grid.DisplayLayout.Bands[0].Columns[2]
It works. How can I have changed the behavior of columns[1]?
The property in column[1] was read only that is it only had a "get" implemented. By adding a set it worked. Hope this helps someone.
I'm trying to display several properties from a related entity on a DataGridView in a winforms app. It seems pretty ordinary to me but I'm having trouble finding examples. It's an order entry operation. OrderSheet data, the ID and the pickup date for the order, then the line items (OrderSheetItems in the model below) in the grid. The order lineitems have a navigation property, Product, based on the ProductId. I can use a DataGridViewComboBoxColumn with ProductId as ValueMember and another field as DisplayMember. But I want to include more data in other columns, size, color, material, etc.
Here's the code for loading the data
try
{
_context.OrderSheets.Include(o => o.OrderSheetItems.Select(i => i.Product)).Load();
orderSheetBindingSource.DataSource = _context.OrderSheets.Local.ToBindingList();
}
catch (Exception ex)...
The ProductId is in a separate column just for experimenting, that will be the combobox later.
So is there a way to bind the other columns to the data in Product navigation property of the OrderSheetItem or do I have to handle CellValueChanged on the product id to physically set the data in the other columns? If there's a way to bind the columns then would that be via code in OnLoad or somewhere in the grid view columns designer?
TIA, Mike
You can use either of these options:
Use DataGridViewComboBoxColumn
Add corresponding properties to child entity partial class
Shape the query to include properties of navigation property using Linq
Use CellFormatting event to get value for sub property bounded columns
Show string representation of object by overriding ToString()
Use a custom TypeDescriptor to enable data binding to sub properties.
Option 1 - Use DataGridViewComboBoxColumn
Usage: This approach would be useful specially in cases which you want to keep the control editable.
In this approach you can use DataGridViewComboBoxColumn to show any field of navigationn property. To show multiple field sub properties of navigation property in grid, use multiple DataGridViewComboBoxColumn bound to same navigation property with different DisplayMember
In this approach, additional to your ProductId column, add more DataGridViewComboBoxColumn to the grid and then perform these settings for all additional combo columns:
Set DataPropertyName of them to ProductId
Set the DataSource property of them, to exactly the same data source you used for main ProductId column, for example productBindingSource
Set ValueMember of them to the same value member you set for product id column, it's the key column of your product table.(ProductId)
Set DisplayMember for each of them to a column that you want to show, for example, set one of them to Name. one to Price, one to Size, ... . This way you can show related entity fields.
Set ReadOnly property of them to true. It makes the cell read only.
If you want to make columns readonly Set DisplayStyle property of them to Nothing. It removes dropdown style.
If you want to keep ProductId editable, keep the DisplayStyle of it to DropDownButton. This way when you change the value of ProductId column using combobox, when you leave the row and moved to next row, you will see other cells of row, shows other properties of the selected product. Also since the other combobox columns are read only and have no combobox style, the user can not change the value of them and they act only like a read only text box column that show other properties from related entity.
Option 2 - Add corresponding properties to child entity partial class
Usage: This approach would be useful when you don't need to edit values.
In this approach, You can define properties in child entity partial class return value of corresponding property of parent entity. For example for product name, define this property in order item partial class:
public string ProductName
{
get
{
if (this.Product != null)
return this.Product.Name;
else
return string.Empty;
}
}
Then you can simply include products when selecting order items and bind the grid column to corresponding properties of order item.
Option 3 - Shape the query to include properties of navigation property
Usage: This approach would be useful when you don't need to edit values.
You can shape the query to include properties of navigation property. You can use an anonymous object or a View Mode simply, for example:
var list = db.OrderDetails.Include("Products").Where(x=>x.OrderId==1)
.Select(x=> new OrderDetailVM() {
Id = x.Id,
ProductId = x.ProductId,
ProductName = x.Product.Name,
Price = x.Product.Price
}).ToList();
Option 4 - Use CellFormatting event to get value for sub property bounded columns
Usage: This approach would be useful when you don't need to edit values.
In this approach you can use CellFormatting event of DataGridView. You can simply set e.Value based on column index. For example:
void dataGridView1_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
{
//I Suppose you want to show product name in column at index 3
if(e.RowIndex>=0 && e.ColumnIndex==3)
{
var orderLineItem= (OrderLineItem)(this.dataGridView1.Rows[e.RowIndex]
.DataBoundItem);
if (order!= null && orderLineItem.Product != null)
e.Value = orderLineItem.Product.Name);
}
}
You can use different criteria to handle different columns and show different sub properties.
Also you can make it more dynamic and reusable using reflection. You can extract the value of sub property of navigation property using reflection. To do so you should create column and set DataPropertyName to sub properties like Product.Name then in CellFormatting event, using reflection, get the value for column. Here is a good article by Antonio Bello about this approach:
DataGridView: How to Bind Nested Objects
Option 5 - Show string representation of object by overriding ToString()
Usage: This approach would be useful when you don't need to edit values.
If you want to show only a single column of navigation property, you can simply override ToString() method of navigation property class and return suitable value. This way, when showing a property of that type in grid, you will see a friendly text. For example in partial class of Product, you can write:
public override string ToString()
{
return this.Name;
}
Option 6 - Use a custom TypeDescriptor to enable data binding to sub properties
Usage: This approach would be useful when you don't need to edit values.
In this approach you can create a custom TypeDescriptor that enables you to perform data binding to second-level properties. Here is a good article by Linda Liu about this approach:
How to bind a DataGridView column to a second-level property of a data source
Using CellFormatting and CellParsing to Show and Edit nested properties in DataGridView
Features:
Any level of nesting is supported.
Both editable and readonly are supported
How it works:
Each column which has "." in the DataPropertyName will be considered as nested property.
CellFormatting event will be handled to get the value of the nested property using a recursive function.
CellParsing event will be handled to set the value of the nested property using a recursive function.
Here are the methods:
public object GetPropertyValue(object source, string name)
{
if (name.Contains("."))
{
var nameParts = name.Split(new[] { '.' }, 2);
return GetPropertyValue(GetPropertyValue(source, nameParts[0]), nameParts[1]);
}
else
{
var property = TypeDescriptor.GetProperties(source)[name];
return property.GetValue(source);
}
}
public void SetPropertyValue(object source, string name, object value)
{
if (name.Contains("."))
{
var nameParts = name.Split(new[] { '.' }, 2);
SetPropertyValue(GetPropertyValue(source, nameParts[0]), nameParts[1], value);
}
else
{
var property = TypeDescriptor.GetProperties(source)[name];
property.SetValue(source, value);
}
}
And here are the event handlers:
private void CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
{
if (e.ColumnIndex < 0 || e.RowIndex < 0) return;
var dg = (DataGridView)sender;
var propertyName = dg.Columns[e.ColumnIndex].DataPropertyName;
if (propertyName.Contains("."))
{
var dataObject = dg.Rows[e.RowIndex].DataBoundItem;
e.Value = GetPropertyValue(dataObject, propertyName);
}
}
private void CellParsing(object sender, DataGridViewCellParsingEventArgs e)
{
var dg = (DataGridView)sender;
var propertyName = dg.Columns[e.ColumnIndex].DataPropertyName;
if (propertyName.Contains("."))
{
var dataObject = dg.Rows[e.RowIndex].DataBoundItem;
SetPropertyValue(dataObject, propertyName, e.Value);
}
}
And here is the example:
var categories = new List<Category>() {
new Category{ Id= 1, Name = "C1"},
new Category{ Id= 2, Name = "C2"}
};
var products = new List<Product>() {
new Product(){ Id = 1, Name ="P1", Category = categories[0]},
new Product(){ Id = 2, Name ="P2", Category = categories[0]},
new Product(){ Id = 3, Name ="P3", Category = categories[1]},
};
var dg = new DataGridView();
dg.AutoGenerateColumns = false;
dg.Columns.Add(new DataGridViewTextBoxColumn()
{
HeaderText = "Id",
DataPropertyName = "Id"
});
dg.Columns.Add(new DataGridViewTextBoxColumn()
{
HeaderText = "Name",
DataPropertyName = "Name"
});
dg.Columns.Add(new DataGridViewTextBoxColumn()
{
HeaderText = "CategoryId",
DataPropertyName = "Category.Id"
});
dg.Columns.Add(new DataGridViewTextBoxColumn()
{
HeaderText = "CategoryName",
DataPropertyName = "Category.Name"
});
dg.Dock = DockStyle.Fill;
dg.DataSource = products;
this.Controls.Add(dg);
dg.CellFormatting += CellFormatting;
dg.CellParsing += CellParsing;
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
Is it possible, to bind (or similar) a standard textbox to display the contents (dynamically) of the selected cell within a datagridview textboxcolumn?
My goal is that when a cell within this column has it's value altered, the textbox.text is also changed, and when the user selects a cell then types something in this separate textbox the value is updating the datagridview textboxcolumns value on the fly.
Yes you may bind common dataSource to both TextBox and DataGridView.
public class Foo
{
public string Item { get; set; }
}
private void Form2_Load(object sender, EventArgs e)
{
List<Foo> list = new List<Foo>()
{
new Foo() { Item="1" },
new Foo() { Item="2" }
};
dataGridView1.DataSource = list;
textBox1.DataBindings.Add("Text", list, "Item");
}
Applying the idea from "adatapost" to a DataGridView in C#:
TextBox txt = this.txtBox1;
DataGridView dgv = this.datagridview1;
txt.DataBindings.Add("Text", dgv.DataSource, "FieldName");
dgv = null;
txt = null;
It might not be necessary to declare separate variables to hold your TextBox and DataGridView while you create the Binding. I just show these for clarity, in fact, this would be sufficient:
this.txtBox1.DataBindings.Add("Text", this.datagridview1.DataSource, "FieldName"
To clarify, "Text" means output the value back to the "Text" property of the TextBox, and "FieldName" should be replaced with whatever the column is in your DataGridView that you want to bind to.
Note - make sure that you have already set the DataSource / populated the DataGridView before you put the Binding code, if the DataSource is not set and therefore containing the field that you wish to bind to you will get an error.
I have a DataGridView that has a ComboBox column. I populate this column's list with items of a type Field:
DataGridViewComboBoxColumn fieldsColumn = argumentsDataGridView.Columns["field"] as DataGridViewComboBoxColumn;
foreach (Field field in SessionData.Fields)
fieldsColumn.Items.Add(field);
fieldsColumn.DisplayMember = "Name";
Then after some user's action I put a value in the cell of this column like this:
private void AddArgument(string argumentName, Field field)
{
int index = argumentsDataGridView.Rows.Count;
argumentsDataGridView.Rows.Add(new DataGridViewRow());
DataGridViewRow newRow = argumentsDataGridView.Rows[index];
newRow.Cells["nameArg"].Value = argumentName;
-> newRow.Cells["field"].Value = field;
}
If I now access the cell's Value, it is of a type Field. If I select different item from the combo, the cell's Value becomes a string. How can I handle it? I need items of type Field.
the solution was to create a property Self in class Field:
public Field Self
{
get { return this; }
}
and set is as a ValueMember of the combo box column.
I thought that without specifing ValueMember this is a default return value and I was wrong.