I have a "populate combobox", and I'm so happy with it that I've even started using more comboboxes. It takes the combobox object by reference with the ID of the "value set" (or whatever you want to call it) from a table and adds the items and their respective values (which differ) and does the job.
I've recently had the brilliant idea of using comboboxes in a gridview, and I was happy to notice that it worked JUST LIKE a single combobox, but populating all the comboboxes in the given column at the same time.
ObjComboBox.Items.Add("yadayada");
//works just like
ObjComboBoxColumn.Items.Add("blablabla");
But When I started planning how to populate these comboboxes I've noticed: There's no "Values" property in ComboBoxDataColumn.
ObjComboBox.Values = whateverArray;
//works, but the following doesn't
ObjComboBoxColumn.Values = whateverArray;
Questions:
0 - How do I populate it's values ? (I suspect it's just as simple, but uses another name)
1 - If it works just like a combobox, what's the explanation for not having this attribute ?
-----[EDIT]------
So I've checked out Charles' quote, and I've figured I had to change my way of populating these bad boys. Instead of looping through the strings and inserting them one by one in the combobox, I should grab the fields I want to populate in a table, and set one column of the table as the "value", and other one as the "display". So I've done this:
ObjComboBoxColumn.DataSource = DTConfig; //Double checked, guaranteed to be populated
ObjComboBoxColumn.ValueMember = "Code";
ObjComboBoxColumn.DisplayMember = "Description";
But nothing happens, if I use the same object as so:
ObjComboBoxColumn.Items.Add("StackOverflow");
It is added.
There is no DataBind() function.
It finds the two columns, and that's guaranteed ("Code" and "Description") and if I change their names to nonexistant ones it gives me an exception, so that's a good sign.
-----[EDIT]------
I have a table in SQL Server that is something like
code | text
—————
1 | foo
2 | bar
It's simple, and with other comboboxes (outside of gridviews) i've successfully populated looping through the rows and adding the texts:
ObjComboBox.Items.Add(MyDataTable.Rows[I]["MyColumnName"].ToString());
And getting every value, adding it into an array, and setting it like:
ObjComboBox.Values = MyArray;
I'd like to populate my comboboxColumns just as simply as I do with comboboxes.
I don't mean to sound obnoxious, but do you know there's documentation for all this stuff?
From http://msdn.microsoft.com/en-us/library/system.windows.forms.datagridviewcomboboxcolumn.aspx:
You can populate the column drop-down
list manually by adding values to the
Items collection. Alternatively, you
can bind the drop-down list to its own
data source by setting the column
DataSource property. If the values are
objects in a collection or records in
a database table, you must also set
the DisplayMember and ValueMember
properties. The DisplayMember property
indicates which object property or
database column provides the values
that are displayed in the drop-down
list. The ValueMember property
indicates which object property or
database column is used to set the
cell Value property.
EDIT:
From your edit, it sounds like you might be trying to use non-public properties of the underlying type for DisplayMember and/or ValueMember. Or if your combobox datasource is a DataTable, make sure it has "Code" and "Description" columns.
Here's a simple demo. I create a list of Foo's and assign it as the DataSource of my combobox column. Just create a winforms application and paste this in.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
// this will be the datasource for the combo box column; you could also bind it to a dataset
List<Foo> foos = new List<Foo>() {
new Foo() { FooID = 0, FooName = "No Foo." },
new Foo() { FooID = 1, FooName = "Foo Me Once" },
new Foo() { FooID = 2, FooName = "Foo Me Twice" },
new Foo() { FooID = 3, FooName = "Pity The Foo!" }
};
DataGridView dataGridView1 = new DataGridView();
dataGridView1.AutoGenerateColumns = false;
// add normal text column
DataGridViewColumn column = new DataGridViewTextBoxColumn();
column.DataPropertyName = "MyText";
column.Name = "Text";
dataGridView1.Columns.Add(column);
// add the combo box column
DataGridViewComboBoxColumn comboCol = new DataGridViewComboBoxColumn();
comboCol.Name = "Foo";
// bind it to the list of foos to populate it
comboCol.DataSource = foos;
// specify which property of the grid's datasource to bind
comboCol.DataPropertyName = "MyFoo";
// specify the property of the combo's datasource to bind
comboCol.ValueMember = "FooID";
// specify the property of the combo's datasource to display
comboCol.DisplayMember = "FooName";
dataGridView1.Columns.Add(comboCol);
// add some data
BindingSource bindingSource1 = new BindingSource();
bindingSource1.Add(new BusinessObject(1, "You say"));
bindingSource1.Add(new BusinessObject(2, "George says"));
bindingSource1.Add(new BusinessObject(3, "Mr. T says"));
bindingSource1.Add(new BusinessObject());
dataGridView1.DataSource = bindingSource1;
Controls.Add(dataGridView1);
dataGridView1.Dock = DockStyle.Fill;
}
class Foo
{
public int FooID { get; set; }
public string FooName { get; set; }
}
class BusinessObject
{
public BusinessObject(int foo, string text)
{
MyFoo = foo;
MyText = text;
}
public BusinessObject()
{
MyFoo = 0;
MyText = "";
}
public string MyText { get; set; }
public int MyFoo { get; set; }
}
}
Related
I'm reading a text file line by line, and inserting it into an array.
I then have this list called custIndex, which contains certain indices, indices of the items array that I'm testing to see if they are valid codes. (for example, custIndex[0]=7, so I check the value in items[7-1] to see if its valid, in the two dictionaries I have here). Then, if there's an invalid code, I add the line (the items array) to dataGridView1.
The thing is, some of the columns in dataGridView1 are Combo Box Columns, so the user can select a correct value. When I try adding the items array, I get an exception: "The following exception occurred in the DataGridView: System.ArgumentException: DataGridViewComboBoxCell value is not valid."
I know the combo box was added correctly with the correct data source, since if I just add a few items in the items array to the dataGridView1, like just items[0], the combo box shows up fine and there's no exception thrown. I guess the problem is when I try adding the incorrect value in the items array to the dataGridView1 row.
I'm not sure how to deal with this. Is there a way I can add all of the items in items except for that value? Or can I add the value from items and have it show up in the combo box cell, along with the populated drop down items?
if(choosenFile.Contains("Cust"))
{
var lines = File.ReadAllLines(path+"\\"+ choosenFile);
foreach (string line in lines)
{
errorCounter = 0;
string[] items = line.Split('\t').ToArray();
for (int i = 0; i <custIndex.Count; i++)
{
int index = custIndex[i];
/*Get the state and country codes from the files using the correct indices*/
Globals.Code = items[index - 1].ToUpper();
if (!CountryList.ContainsKey(Globals.Code) && !StateList.ContainsKey(Globals.Code))
{
errorCounter++;
dataGridView1.Rows.Add(items);
}
}//inner for
if (errorCounter == 0)
dataGridView2.Rows.Add(items);
}//inner for each
}//if file is a customer file
Say your text file contains:
Australia PNG, India Africa
Austria Bali Indonisia
France England,Scotland,Ireland Greenland
Germany Bahama Hawaii
Greece Columbia,Mexico,Peru Argentina
New Zealand Russia USA
And lets say your DataGridView is setup with 3 columns, the 2nd being a combobox.
When you populate the grid and incorrectly populate the combobox column you will get the error.
The way to solve it is by "handling/declaring explicitly" the DataError event and more importantly populating the combobox column correctly.
private void dataGridView1_DataError(object sender, DataGridViewDataErrorEventArgs e)
{
//Cancelling doesn't make a difference, specifying the event avoids the prompt
e.Cancel = true;
}
private void dataGridView2_DataError(object sender, DataGridViewDataErrorEventArgs e)
{
e.Cancel = true;
}
So imagine the 2nd column contained a dropdownlist of countries and the 1st & 3rd column contained text fields.
For the 1st and 3rd columns they are just strings so I create a class to represent each row:
public class CountryData
{
public string FirstCountry { get; set; }
public string ThirdCountry { get; set; }
}
For the 2nd column "Countries" combobox cell's I have created a separate class because I will bind it to the 2nd columns datasource.
public class MultiCountryData
{
public string[] SeceondCountryOption { get; set; }
}
Populating the grid with combobox columns and the like as shown here: https://stackoverflow.com/a/1292847/495455 is not good practice. You want to separate your business logic from your presentation for a more encapsulated, polymorphic and abstract approach that will ease unit testing and maintenance. Hence the DataBinding.
Here is the code:
namespace BusLogic
{
public class ProcessFiles
{
internal List<CountryData> CountryDataList = new List<CountryData>();
internal List<MultiCountryData> MultiCountryDataList = new List<MultiCountryData>();
internal void foo(string path,string choosenFile)
{
var custIndex = new List<int>();
//if (choosenFile.Contains("Cust"))
//{
var lines = File.ReadAllLines(path + "\\" + choosenFile);
foreach (string line in lines)
{
int errorCounter = 0;
string[] items = line.Split('\t');
//Put all your logic back here...
if (errorCounter == 0)
{
var countryData = new CountryData()
{
FirstCountry = items[0],
ThirdCountry = items[2]
};
countryDataList.Add(countryData);
multiCountryDataList.Add( new MultiCountryData() { SeceondCountryOption = items[1].Split(',')});
}
//}
}
}
}
In your presentation project here is the button click code:
imports BusLogic;
private void button1_Click(object sender, EventArgs e)
{
var pf = new ProcessFiles();
pf.foo(#"C:\temp","countries.txt");
dataGridView2.AutoGenerateColumns = false;
dataGridView2.DataSource = pf.CountryDataList;
multiCountryDataBindingSource.DataSource = pf.MultiCountryDataList;
}
I set dataGridView2.AutoGenerateColumns = false; because I have added the 3 columns during design time; 1st text column, 2nd combobox column and 3rd text column.
The trick with binding the 2nd combobox column is a BindingSource. In design time > right click on the DataGridView > choose Edit Columns > select the second column > choose DataSource > click Add Project DataSource > choose Object > then tick the multiCountry class and click Finish.
Also set the 1st column's DataPropertyName to FirstCountry and the 3rd column's DataPropertyName to ThirdCountry, so when you bind the data the mapping is done automatically.
Finally, dont forget to set the BindingSource's DataMember property to the multiCountry class's SeceondCountryOption member.
Here is a code demo http://temp-share.com/show/HKdPSzU1A
I have table with 4 primary key fields. I load that in to drop down list in my WinForm application created by using C#.
On the TextChanged event of drop down list I have certain TextBox and I want to fill the information recived by the table for the certain field I selected by the drop down list.
So as I say the table having 4 fields. Can I get those all 4 fields into value member from the data set, or could you please tell me whether is that not possible?
Thank you.
Datatable dt=dba.getName();
cmb_name.ValueMember="id";
cmb_name.DisplayMember="name";
cmb_name.DataSource=dt;
this is normal format.. but i have more key fields.. so i need to add more key fields..
You can use DataSource property to bind your source data to the ComboBox (e.g. a List of Entities, or a DataTable, etc), and then set the DisplayMember property of the ComboBox to the (string) name of the field you want to display.
After the user has selected an Item, you can then cast the SelectedItem back to the original row data type (Entity, DataRow, etc - it will still be the same type as you put in), and then you can retrieve your 4 composite keys to the original item.
This way you avoid the SelectedValue problem entirely.
Edit:
Populate as follows:
cmb_name.DisplayMember = "name";
cmb_name.DataSource = dt;
// Ignore ValueMember and Selected Value entirely
When you want to retrieve the selected item
var selectedRow = (cmb_name.SelectedItem as DataRowView );
Now you can retrieve the 4 values of your PK, e.g. selectedRow["field1"], selectedRow["field2"], selectedRow["field3"] etc
If however you mean that you want to DISPLAY 4 columns to the user (i.e. nothing to do with your Table Key), then see here How do I bind a ComboBox so the displaymember is concat of 2 fields of source datatable?
cmb_name.DisplayMember = "name";
cmb_name.DataSource = dt;
DataRowView selectedRow = (cmb_name.SelectedItem as DataRowView );
The result will be here:
MessageBox.Show(selectedRow.Row[0].ToString());
MessageBox.Show(selectedRow.Row[1].ToString());
MessageBox.Show(selectedRow.Row[2].ToString());
MessageBox.Show(selectedRow.Row[3].ToString());
.....
If you want to get some data from a ComboBox in to a List you can use something like this
List<string> ListOfComboData = new List<string>();
ListOfComboData = yourComboBox.Items.OfType<string>().ToList<string>();
I have no real idea if this is what you mean as the question is very poorly structured. I hope this helps...
Edit: To put the selected text in to some TextBox use
yourTextBox.Text = youComboBox.Text;
in the SelectedIndexChanged event of your ComboBox.
You could follow the approach here with the following class:
public class ComboBoxItem
{
public string Text { get; set; }
public object[] PrimaryKey { get; set; }
}
private void Test()
{
ComboboxItem item = new ComboboxItem();
item.Text = "Item text1";
item.PrimaryKey = new object[] { primaryKey1, primaryKey2, primaryKey3, primaryKey4};
comboBox1.Items.Add(item);
comboBox1.SelectedIndex = 0;
MessageBox.Show((comboBox1.SelectedItem as ComboboxItem).Value.ToString());
}
I believe this question is kinda new-bie, but I can't solve it in correct way.
Brief description:
I have an inherited from ComboBox class that does some data bindings in constructor:
var mdl = new Model();
ValueMember = "id";
DisplayMember = "unit";
DataSource = mdl.getUnits();
All good here. The combobox is filled by required data.
Then I have another form with a function editIngridient. The function is following;
public bool editIngridient(int id)
{
currentId = id;
var row = mdl.getIngridient(id);
txtIngridient.Text = (string)row["ingridient"];
cmbUnit.ID = (int)row["unitId"];
numNotifyQty.Value = (int) row["notifyQty"];
ShowDialog();
return true;
}
Now, when the form popups, textbox and number box filled by needed values, while combobox is filled by first value.
If I will run the combobox data bind function as the first line inside editIngridient function - all works good.
Please point me to my stupidity.
Thanks a lot!
YOu didnt say what is your dataSource, but I assume thats DataTable, so you can do it:
DataRowView rowData = comboBox1.SelectedItem as DataRowView;
int id = Convert.ToInt32(rowData["id"]);
string name = rowData["unit"].ToString();
I'm having a Structure like
X={ID="1", Name="XX",
ID="2", Name="YY" };
How to dump this data to a DataGridView of two columns
The gridView is like
ID | Name
Can we use LINQ to do this. I'm new to DataGridView Pleaese help me to do this..
Thanks in advance
first you need to add 2 columns to datagrid. you may do it at design time. see Columns property.
then add rows as much as you need.
this.dataGridView1.Rows.Add("1", "XX");
Let's assume you have a class like this:
public class Staff
{
public int ID { get; set; }
public string Name { get; set; }
}
And assume you have dragged and dropped a DataGridView to your form, and name it dataGridView1.
You need a BindingSource to hold your data to bind your DataGridView. This is how you can do it:
private void frmDGV_Load(object sender, EventArgs e)
{
//dummy data
List<Staff> lstStaff = new List<Staff>();
lstStaff.Add(new Staff()
{
ID = 1,
Name = "XX"
});
lstStaff.Add(new Staff()
{
ID = 2,
Name = "YY"
});
//use binding source to hold dummy data
BindingSource binding = new BindingSource();
binding.DataSource = lstStaff;
//bind datagridview to binding source
dataGridView1.DataSource = binding;
}
My favorite way to do this is with an extension function called 'Map':
public static void Map<T>(this IEnumerable<T> source, Action<T> func)
{
foreach (T i in source)
func(i);
}
Then you can add all the rows like so:
X.Map(item => this.dataGridView1.Rows.Add(item.ID, item.Name));
LINQ is a "query" language (thats the Q), so modifying data is outside its scope.
That said, your DataGridView is presumably bound to an ItemsSource, perhaps of type ObservableCollection<T> or similar. In that case, just do something like X.ToList().ForEach(yourGridSource.Add) (this might have to be adapted based on the type of source in your grid).
you shoud do like this form your code
DataGridView.DataSource = yourlist;
DataGridView.DataBind();
I want to know how should we add columns and rows programmatically to a DataGrid in WPF. The way we used to do it in windows forms. create table columns and rows, and bind it to DataGrid.
I have No. of rows and columns which I need to draw in DataGrid so that user can edit the data in the cells.
To programatically add a row:
DataGrid.Items.Add(new DataItem());
To programatically add a column:
DataGridTextColumn textColumn = new DataGridTextColumn();
textColumn.Header = "First Name";
textColumn.Binding = new Binding("FirstName");
dataGrid.Columns.Add(textColumn);
Check out this post on the WPF DataGrid discussion board for more information.
I had the same problem. Adding new rows to WPF DataGrid requires a trick. DataGrid relies on property fields of an item object. ExpandoObject enables to add new properties dynamically. The code below explains how to do it:
// using System.Dynamic;
DataGrid dataGrid;
string[] labels = new string[] { "Column 0", "Column 1", "Column 2" };
foreach (string label in labels)
{
DataGridTextColumn column = new DataGridTextColumn();
column.Header = label;
column.Binding = new Binding(label.Replace(' ', '_'));
dataGrid.Columns.Add(column);
}
int[] values = new int[] { 0, 1, 2 };
dynamic row = new ExpandoObject();
for (int i = 0; i < labels.Length; i++)
((IDictionary<String, Object>)row)[labels[i].Replace(' ', '_')] = values[i];
dataGrid.Items.Add(row);
//edit:
Note that this is not the way how the component should be used, however, it simplifies a lot if you have only programmatically generated data (eg. in my case: a sequence of features and neural network output).
try this , it works 100 % : add columns and rows programatically :
you need to create item class at first :
public class Item
{
public int Num { get; set; }
public string Start { get; set; }
public string Finich { get; set; }
}
private void generate_columns()
{
DataGridTextColumn c1 = new DataGridTextColumn();
c1.Header = "Num";
c1.Binding = new Binding("Num");
c1.Width = 110;
dataGrid1.Columns.Add(c1);
DataGridTextColumn c2 = new DataGridTextColumn();
c2.Header = "Start";
c2.Width = 110;
c2.Binding = new Binding("Start");
dataGrid1.Columns.Add(c2);
DataGridTextColumn c3 = new DataGridTextColumn();
c3.Header = "Finich";
c3.Width = 110;
c3.Binding = new Binding("Finich");
dataGrid1.Columns.Add(c3);
dataGrid1.Items.Add(new Item() { Num = 1, Start = "2012, 8, 15", Finich = "2012, 9, 15" });
dataGrid1.Items.Add(new Item() { Num = 2, Start = "2012, 12, 15", Finich = "2013, 2, 1" });
dataGrid1.Items.Add(new Item() { Num = 3, Start = "2012, 8, 1", Finich = "2012, 11, 15" });
}
I found a solution that adds columns at runtime, and binds to a DataTable.
How do I bind a WPF DataGrid to a variable number of columns?
Unfortunately, with 47 columns defined this way, it doesn't bind to the data fast enough for me. Any suggestions?
xaml
<DataGrid
Name="dataGrid"
AutoGenerateColumns="False"
ItemsSource="{Binding}">
</DataGrid>
xaml.cs
using System.Windows.Data;
if (table != null) // table is a DataTable
{
foreach (DataColumn col in table.Columns)
{
dataGrid.Columns.Add(
new DataGridTextColumn
{
Header = col.ColumnName,
Binding = new Binding(string.Format("[{0}]", col.ColumnName))
});
}
dataGrid.DataContext = table;
}
edit: sorry, I no longer have the code mentioned below. It was a neat solution, although complex.
I posted a sample project describing how to use PropertyDescriptor and lambda delegates with dynamic ObservableCollection and DynamicObject to populate a grid with strongly-typed column definitions.
Columns can be added/removed at runtime dynamically. If your data is not a object with known type, you could create a data structure that would enable access by any number of columns and specify a PropertyDescriptor for each "column".
For example:
IList<string> ColumnNames { get; set; }
//dict.key is column name, dict.value is value
Dictionary<string, string> Rows { get; set; }
You can define columns this way:
var descriptors= new List<PropertyDescriptor>();
//retrieve column name from preprepared list or retrieve from one of the items in dictionary
foreach(var columnName in ColumnNames)
descriptors.Add(new DynamicPropertyDescriptor<Dictionary, string>(ColumnName, x => x[columnName]))
MyItemsCollection = new DynamicDataGridSource(Rows, descriptors)
Or even better, in case of some real objects
public class User
{
public string FirstName { get; set; }
public string LastName{ get; set; }
...
}
You can specify columns strongly typed (related to your data model):
var propertyDescriptors = new List<PropertyDescriptor>
{
new DynamicPropertyDescriptor<User, string>("First name", x => x.FirstName ),
new DynamicPropertyDescriptor<User, string>("Last name", x => x.LastName ),
...
}
var users = retrieve some users
Users = new DynamicDataGridSource<User>(users, propertyDescriptors, PropertyChangedListeningMode.Handler);
Then you just bind to Users collections and columns are autogenerated as you speficy them. Strings passed to property descriptors are names for column headers. At runtime you can add more PropertyDescriptors to 'Users' add another column to the grid.
If you already have the databinding in place John Myczek answer is complete.
If not you have at least 2 options I know of if you want to specify the source of your data. (However I am not sure whether or not this is in
line with most guidelines, like MVVM)
option 1: like JohnB said. But I think you should use your own defined collection
instead of a weakly typed DataTable (no offense, but you can't tell from the
code what each column represents)
xaml.cs
DataContext = myCollection;
//myCollection is a `ICollection<YourType>` preferably
`ObservableCollection<YourType>
- option 2) Declare the name of the Datagrid in xaml
<WpfToolkit:DataGrid Name=dataGrid}>
in xaml.cs
CollectionView myCollectionView =
(CollectionView)CollectionViewSource.GetDefaultView(yourCollection);
dataGrid.ItemsSource = myCollectionView;
If your type has a property FirstName defined, you can then do what John Myczek pointed out.
DataGridTextColumn textColumn = new DataGridTextColumn();
dataColumn.Header = "First Name";
dataColumn.Binding = new Binding("FirstName");
dataGrid.Columns.Add(textColumn);
This obviously doesn't work if you don't know properties you will need to show in your dataGrid, but if that is the case you will have more problems to deal with, and I believe that's out of scope here.
If you already have the databinding in place John Myczek answer is complete.
If not you have at least 2 options I know of if you want to specify the source of your data. (However I am not sure whether or not this is in line with most guidelines, like MVVM)
Then you just bind to Users collections and columns are autogenerated as you speficy them. Strings passed to property descriptors are names for column headers. At runtime you can add more PropertyDescriptors to 'Users' add another column to the grid.
To Bind the DataTable into the DataGridTextColumn in CodeBehind
xaml
<DataGrid
Name="TrkDataGrid"
AutoGenerateColumns="False"
ItemsSource="{Binding}">
</DataGrid>
xaml.cs
foreach (DataColumn col in dt.Columns)
{
TrkDataGrid.Columns.Add(
new DataGridTextColumn
{
Header = col.ColumnName,
Binding = new Binding(string.Format("[{0}]", col.ColumnName))
});
}
TrkDataGrid.ItemsSource= dt.DefaultView;