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
Related
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;
}
}
}
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 am new in Winform application, here i am using Entity framework, when i binding values to a combobox from sql table, i need to set first combobox item as "Please select", how can i set this..?
var qry = context.Tbl_EmployeeDetails.Where(x => x.IsDeleted == false).ToList();
if(qry!=null)
{
drpname.ValueMember = "RecordId";
drpname.DisplayMember = "Name";
drpname.DataSource = qry;
}
how can i set this first item as "please select" and value '0'
another suggestion in winform that..
how can i set value in a datagridview linkbutton column when i set 'Edit', 'Delete' columns are as linkbutton
You can insert that item in the beginning of the list.
Code:
var items = db.Tbl_EmployeeDetails.Where(x => x.IsDeleted == false).ToList();
items.Insert(0,new Tbl_EmployeeDetail() { RecordId= 0, Name = "[Please Select an Item]" });
drpname.DropDownStyle = ComboBoxStyle.DropDownList; //optional
drpname.ValueMember = "RecordId";
drpname.DisplayMember = "Name";
drpname.DataSource = items;
drpname.SelectedIndex = 0;
Screenshot:
Note
To have a hint in ComboBox without adding an item, take a look at following post:
Set hint or watermark or default text for ComboBox without adding it as item:
After you load your var qry which will be a List<string>
you want to do the following
var qry = context.Tbl_EmployeeDetails.Where(x => x.IsDeleted == false).ToList();
if(qry!=null)
{
drpname.ValueMember = "RecordId";
drpname.DisplayMember = "Name";
drpname.DataSource = qry;
drpname.Items.Insert(0, "--Please Select--");
drpname.SelectedIndex = 0;
}
Or you could have easily added it to qry since you are returning the data ToList()
for example
var qry = context.Tbl_EmployeeDetails.Where(x => x.IsDeleted == false).ToList();
qry.Insert(0, "--Please Select--");
if(qry!=null)
{
drpname.ValueMember = "RecordId";
drpname.DisplayMember = "Name";
drpname.DataSource = qry;
drpname.SelectedIndex = 0;
}
Your combobox cannot contain additional items if you are binding it to a data source. You have a few options here:
Don't bind it to a data source, i.e. populate it manually from your data set by looping through the items in your query and adding them individually to the combobox; this will allow you the opportunity to first add your "Please Select" item. This can be complicated because you will actually have to add the string names as the items in the combobox, and then you can set the item's Tag property to the actual query row to reference it later.
Include a fake item returned from your query that has a display member of "please select". Note, I don't recommend this approach because you're allowing your presentation logic to leak into your data/business layer.
Use a third-party control instead of the win forms combobox. I've used Infragistics and they have the ability to display a "suggestion" that's not actually in the list when nothing is selected.
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.
I have a custom object with several properties, one of which returns a list. This is the code for the object:
public class SearchResult
{
private int eventId;
private String eventTitle;
private int startDate;
private List<String> tags;
// Properties
public int EventId { get { return this.eventId; } }
public String EventTitle { get { return this.eventTitle; } }
public int StartDate { get { return this.startDate; } }
public List<String> Tags { get { return this.tags; } }
public SearchResult(int eventId, String eventTitle, int startDate, List<String> tags)
{
// Constructor code
}
public List<String> GetTags()
{
return this.tags;
}
}
I also have a DataGridViewComboBoxColumn that I want to bind to the Tags property. Basically, each SearchResult object will be displayed in its own row, and I want the List<String> in the Tags property of each object to be displayed in a ComboBox cell in that row. This is the code I have so far for my DataGridView:
BindingList<SearchResult> results = new BindingList<SearchResult>();
results.Add(new SearchResult(1, "This is a title", 2012, new List<String> { "Tag1", "Tag with a long name1" }));
results.Add(new SearchResult(2, "The quick brown fox", 2012, new List<String> { "Stack", "Overflow" }));
results.Add(new SearchResult(3, "In this tutorial, you create a class that is the type for each object in the object collection. ", 2012, new List<String> { "NYSE", "FTSE" }));
results.Add(new SearchResult(4, "another long piece of title text", -999, new List<String> { "Rabbits", "Chickens" }));
MyDataGrid.AutoGenerateColumns = false;
MyDataGrid.AllowUserToAddRows = false;
MyDataGrid.AllowUserToDeleteRows = false;
MyDataGrid.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.None;
MyDataGrid.BackgroundColor = System.Drawing.SystemColors.Control;
MyDataGrid.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
MyDataGrid.RowHeadersWidthSizeMode = System.Windows.Forms.DataGridViewRowHeadersWidthSizeMode.AutoSizeToDisplayedHeaders;
MyDataGrid.AutoSizeRowsMode = System.Windows.Forms.DataGridViewAutoSizeRowsMode.DisplayedCells;
MyDataGrid.DefaultCellStyle.WrapMode = DataGridViewTriState.True;
DataGridViewTextBoxColumn eventIdColumn = new DataGridViewTextBoxColumn();
eventIdColumn.DataPropertyName = "EventId";
eventIdColumn.HeaderText = "Event ID";
eventIdColumn.ReadOnly = true;
eventIdColumn.Width = 84;
DataGridViewTextBoxColumn eventTitleColumn = new DataGridViewTextBoxColumn();
eventTitleColumn.DataPropertyName = "EventTitle";
eventTitleColumn.HeaderText = "Event Title";
eventTitleColumn.ReadOnly = true;
eventTitleColumn.Width = 368;
DataGridViewTextBoxColumn startDateColumn = new DataGridViewTextBoxColumn();
startDateColumn.DataPropertyName = "StartDate";
startDateColumn.HeaderText = "Start Date";
startDateColumn.ReadOnly = true;
startDateColumn.Width = 130;
//I think I need to insert the code for the tags column here, but I'm not sure
MyDataGrid.Columns.Add(eventIdColumn);
MyDataGrid.Columns.Add(eventTitleColumn);
MyDataGrid.Columns.Add(startDateColumn);
//MyDataGrid.Columns.Add(tagsColumn);
MyDataGrid.DataSource = results;
I derived this code from a tutorial I found online, and it works perfectly.
I've been trying to bind the Tags property of SearchResult to a DataGridViewComboBoxColumn, but I'm not sure how. I've been looking at this question, which provides this code:
column.DataPropertyName = "Foo";
column.DisplayMember = "SomeNameField";
column.ValueMember = "Bar"; // must do this, empty string causes it to be
// of type string, basically the display value
// probably a bug in .NET
column.DataSource = from foo in Foo select foo;
grid.DataSource = data;
The reason I'm having trouble is because of a few nuances of the linked question that I don't understand.
According to the documentation and the linked question, DisplayMember should be linked to the property that "contains a description of the instance", but since SearchResult objects are added dynamically and don't have any description associated with them, should I just leave it blank?
ValueMember is giving me similar problems, since I'm unsure what to put even after reading its documentation.
In the linked question, the accepted answer binds the entire datagrid at once using LINQ. Is that how I should be doing this? I'm not sure how to modify that code for my situation, but I thought it would be something along these lines.
:
tagsColumn.DataPropertyName = "Tags";
tagsColumn.DisplayMember = ""; // I'm unsure of what to put here
tagsColumn.ValueMember = ""; // Once again, I don't know what to set this to
I also presume I should have a line that sets the DataSource for the column, e.g.
tagsColumn.DataSource = <some LINQ query, perhaps?>
but I don't know because the only mostly relevant C# source I've been able to find is that question.
UPDATE:
I did find a second question that suggests code similar to this for data binding:
// reference the combobox column
DataGridViewComboBoxColumn cboBoxColumn = (DataGridViewComboBoxColumn)dataGridView1.Columns[0];
cboBoxColumn.DataSource = Choice.GetChoices();
cboBoxColumn.DisplayMember = "Name"; // the Name property in Choice class
cboBoxColumn.ValueMember = "Value"; // ditto for the Value property
Based on that, I a) added the GetTags() method to SearchResult and added this code into my DataGridView initialisation code:
DataGridViewComboBoxColumn tagsColumn = new DataGridViewComboBoxColumn();
tagsColumn.DataSource = SearchResult.GetTags(); // ERROR
tagsColumn.DisplayMember = ""; // Still not sure
tagsColumn.ValueMember = ""; // ??
However, Visual Studio gives me an error on the second line when I try to run this:
An object reference is required for the non-static field, method, or property 'SearchResult.GetTags()'
UPDATE 2:
I'm still searching around for this without success. I don't understand how with other properties (e.g. EventId) I can simply declare the data property name as EventId, and it will display in the table, but I cannot do this with ComboBox columns.
Since the objects are instantiated in a separate class and put in a list, it doesn't seem to make sense to me that I should have to loop through the entire array of objects (of which there may be several hundred) to bind the Tags property to the ComboBox column for each instance, when I don't need to loop through the list of SearchResult objects to bind other properties, e.g. EventId.
Why does this binding-properties-by-name only work for some properties and not others?
I don't quite understand why you want to use DataGridViewComboBoxColumn to display a list of elements. This column kind is designed to allow user to select one of many possibilities. It seams it is not your case because you don't have public string SelectedTag{get;set;} property to store it. As I understand your model you have many tags already selected for your SearchResult and you want to display them in grid.
As documentation states:
http://msdn.microsoft.com/en-us/library/system.windows.forms.datagridviewcomboboxcolumn.datasource
Getting or setting this [DataSource] property gets or sets the DataSource property of the object returned by the CellTemplate property. Setting this property also sets the DataSource property of every cell in the column and refreshes the column display. To override the specified value for individual cells, set the cell values after you set the column value.
DataGridViewComboBoxColumn simply does not have capability to bind items property to data source because it assumes that there is only one list of elements that is used as data source for all rows of data grid.
I also assume that you would set ReadOnly = true property for this column as you have for all other. If so it would prevent user form seeing list of tags because drop down list would never be displayed.
If you wand to display list of strings in read only mode I would suggest to flatten this list of tags to single string:
public string Tags { get { return string.Join(", ", tags); } }
and display it in text column.
For the error , i can suggest you to make an instance of the class and then call the method as its not static or you can make your method static.
Moreover As you needs the comboboxcolumn ,
DataGridViewComboBoxColumn tagsColumn = new DataGridViewComboBoxColumn();
tagsColumn.DataSource = SearchResult.GetTags(); // ERROR
tagsColumn.DisplayMember = ""; // Still not sure
tagsColumn.ValueMember = ""; // ??
Mostly we have dropdowns for objects like Country(id,name) so DisplayMember = name will be shown as text in dropdown while ValueMember = id will be used in the referencing tables in database.But this is not your case.
Here you have a list of strings to show in dropdown , so you don't need to set them.
As written here
If the DataSource property is set to a string array, then ValueMember
and DisplayMember do not need to be set because each string in the
array will be used for both value and display.