How To Bind an Array/List's Elements To DataGridViewTextBoxColumn's - arrays

In DataGridView, how to bind an array or list (with n elements) to n DataGridViewTextBoxColumn's?
Maybe it's not so clear, for example, I have a class:
public class DynamicNumberFieldsClass
{
public string FullName { get; set; }
public int[] Years { get; set; }
}
The expected form a DataGridView should display:
FullName Year1 Year2 Year3...
Peter 11 12 13
Bryan 21 22 23
If I have to use reflection, it's okay for me.
Any idea?
Thanks!
Peter
P.S.: We can assume the array field will never be null.
we can even assume that the number of elements in the array is fixed once a session is started, but the user can change it in Settings, so in the next session, the number of elements might not be the same.

I am assuming you are working in Windows Forms. I was able to at least display the data in the format you've shown by putting the DataGridView in Virtual Mode. You had best follow the entire walkthrough for a complete solution, but here is what I did in a quick test (hacked up from the samples in the walkthough):
CellValueNeeded handler:
private void dataGridView1_CellValueNeeded(object sender, DataGridViewCellValueEventArgs e)
{
// If this is the row for new records, no values are needed.
if (e.RowIndex == this.dataGridView1.RowCount - 1) return;
DynamicNumberFieldsClass objectInRow = (DynamicNumberFieldsClass)this._people[e.RowIndex];
// Set the cell value to paint using the DynamicNumberFieldsClass object retrieved.
switch (this.dataGridView1.Columns[e.ColumnIndex].Name)
{
case "FullName":
e.Value = objectInRow.FullName;
break;
default:
e.Value = objectInRow.Years[e.ColumnIndex - 1];
break;
}
}
where _people is a collection of DynamicNumberFieldsClass.
Form Load:
private void Form1_Load(object sender, EventArgs e)
{
this.dataGridView1.VirtualMode = true;
this.dataGridView1.CellValueNeeded += new DataGridViewCellValueEventHandler(dataGridView1_CellValueNeeded);
// Add columns to the DataGridView.
DataGridViewTextBoxColumn fullNameColumn = new
DataGridViewTextBoxColumn();
fullNameColumn.HeaderText = "FullName";
fullNameColumn.Name = "FullName";
this.dataGridView1.Columns.Add(fullNameColumn);
for (int i = 0; i < _numYears; i++)
{
DataGridViewTextBoxColumn yearIColumn = new
DataGridViewTextBoxColumn();
yearIColumn.HeaderText = "Year" + (i+1);
yearIColumn.Name = "Year" + (i+1);
this.dataGridView1.Columns.Add(yearIColumn);
}
this.dataGridView1.AutoSizeColumnsMode =
DataGridViewAutoSizeColumnsMode.AllCells;
// Set the row count, including the row for new records.
this.dataGridView1.RowCount = 3; //two objects in _people and the empty row
}
where _numYears is the fixed value you mentioned. If you have at least one instance of the object available, you could use the size of the Years array in that instance as the loop limiter, so that it is completely dynamic.
You would need to expand the switch statement if any other properties are included in the class, and of course add more error checking in general. The walkthrough shows how to support editing in the DataGridView, as well.
(I know you asked how to bind the columns, not force their values, so you probably wanted to be able to decorate the class definition, assign the data source, and go. To that end, I first looked into doing this with Reflection via CustomTypeDescriptor and TypeDescriptionProvider, etc., which is how second-level properties can be bound. Of course, the individual elements of the array are not exposed as properties, so this doesn't work. I can't think of a way to support auto-generation of the columns the way you want, but maybe someone else will find one.)

Related

Sort Group summary in Xaml Infragistics grid

I have requirement to sort group summary field.
Ex. I have 3 columns in the grid.
Step 1 : I have group by Id by dragging the Id column in Group by area.
Step 2: Add Sum,Count,Average on column.
Now i want to sort sum or count or average by clicking on that ,so that the whole grouped is sorted by sum like 100,200,300.
please help
The sort order is controlled by the GroupByComparer of the FieldSettings class and this can be accomplished by creating a custom IComparer for the field that is grouped. Note that grouping is actually also a sort so I am going to assume that you still want the default sort to happen when the column is first grouped.
In the following example group by records can be sorted by a single summary result when it is clicked on. This was accomplished by using a custom IComparer for the groups that sorts by the value of the tag if it is set and if not set falls back to the value of the group by record:
public class SummarySortComparer : IComparer
{
public int Compare(object x, object y)
{
GroupByRecord xRecord = x as GroupByRecord;
GroupByRecord yRecord = y as GroupByRecord;
IComparable xValue = xRecord.Value as IComparable;
object yValue = yRecord.Value;
if (xRecord.Tag != null)
{
xValue = xRecord.Tag as IComparable;
yValue = yRecord.Tag;
}
return xValue.CompareTo(yValue);
}
}
This is set on the grid using the following:
this.XamDataGrid1.FieldSettings.GroupByComparer = new SummarySortComparer();
Use the PreviewMouseLeftButtonDown of the grid to get the summary that was clicked on if there was one and set the tag of the group by records to be the value of that summary and refresh the sort of the grid:
void XamDataGrid1_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
SummaryResultPresenter summaryResultPresenter =
Utilities.GetAncestorFromType(e.OriginalSource as DependencyObject, typeof (SummaryResultPresenter), false) as
SummaryResultPresenter;
if (summaryResultPresenter != null)
{
GroupBySummariesPresenter groupBySummariesPresenter =
Utilities.GetAncestorFromType(summaryResultPresenter,
typeof(GroupBySummariesPresenter), false) as GroupBySummariesPresenter;
if (groupBySummariesPresenter != null)
{
SummaryResult summaryResult = summaryResultPresenter.SummaryResult;
int summaryResultIndex = summaryResult.ParentCollection.IndexOf(summaryResult);
foreach (GroupByRecord groupRecord in groupBySummariesPresenter.GroupByRecord.ParentCollection)
{
groupRecord.Tag = groupRecord.ChildRecords.SummaryResults[summaryResultIndex].Value;
}
this.XamDataGrid1.Records.RefreshSort();
}
}
}
Note that there are a few limitations in this example in that I haven't implemented any way to clear what summary is sorted so that is something that if desired would still need to be implemented by you. I also didn't include logic to change the sort direction and used the direction that the field is currently sorted by so if you also want to update the direction this will need to be added as well.

Assigning values to array of 'Combo Boxes' in a 'Group Box' using for each loop in c#

I have 10 comboBox in a groupBox
for I just want to display a calculated value in respective comboBox like this say if I set a varible double i=08.00; then on button click cmboBox should display values like this
CB1-08.00
CB2-09.50
CB3-10.00
CB4-10.50
CB5-11.00
CB6-11.50
.... and so on upto CB10 But I am getting output like this
And Code
private void button1_Click(object sender, EventArgs e)
{
double i=08.00;
foreach (var comboBox in groupBox1.Controls.OfType<ComboBox>())
{
comboBox.Text = i.ToString("00.00");
i = i + 0.5;
}
}
Your combobox order is different in the collection so it inserts the numbers randomly. May be you can name your combobox for instance like cmb1,cmb2,cmb3 etc. and if you update your code it will run.
Your controls in the Controls collection are not sorted by their appearance on the form. You will need to find a way to sort them if you need different values in each based on their position.
Foreach loop doesn't give the collection in the order you wanted. The way to go forward is to give a tag id to each combo box, then you can use that to assign a value to them them.
So your first combo box will start with tag id 0, and the last one will have 8,
double val = 08.00;
for (int i = 0; i < groupBox1.Controls.Count; ++i)
{
var combobox = groupBox1.Controls[i] as ComboBox;
int tag = int.Parse(combobox.Tag.ToString());
double value = val + (0.5 * tag);
combobox.Text = value.ToString("00.00");
}
Make sure you tag the cobbo box in the order you wanted them.

Windows Form Combobox.Items.Clear() leaves empty slots

I am working on a Windows Form that has multiple comboboxes. Depending on what is chosen in the first combobox determines what items are filled into the second combobox. The issue I am running into is if I choose ChoiceA in ComboBox1, ComboBox2 is clear()ed, then is filled with ChoiceX, ChoiceY, and ChoiceZ. I then choose ChoiceB in ComboBox1, ComboBox2 is clear()ed, but there are no choices to add to ComboBox2, so it should remain empty. The issue is, after choosing ChoiceB, there's a big white box with three empty slots in ComboBox2. So, basically, however many items are cleared N, that's how many empty slots show up after choosing ChoiceB.
This might be a tad confusing, I hope I explained it well enough.
-- EDIT Adding Code, hope it helps clear things up. BTW, mainItemInfo is another "viewmodel" type class. It interfaces back into the form to make updates.
private void cmbType_SelectedIndexChanged(object sender, EventArgs e)
{
DropDownItem item = (DropDownItem)cmbType.SelectedItem;
if (!String.IsNullOrWhiteSpace(item.Text))
{
cmbBrand.Enabled = true;
btnAddBrand.Enabled = true;
mainItemInfo.FillBrands(new Dictionary<string, string> { { "Type", item.Text } });
mainItemInfo.SyncBrands(this);
}
}
public void FillBrands(Dictionary<string, string> columnsWhere)
{
// Clear list
Brands.Clear();
// Get data
StorageData storage = new StorageData(File.ReadAllLines(ItemsFilePath));
// Fill Brands
foreach (string type in storage.GetDistinctWhere(columnsWhere, "Brand"))
{
Brands.Add(type, new DropDownItem(type, type));
}
}
public void SyncBrands(IPopupItemInfo form)
{
form.ClearcmbBrand();
var brands = from brand in Brands.Keys
orderby Brands[brand].Text ascending
select brand;
foreach (var brand in brands)
{
form.AddTocmbBrand(Brands[brand]);
}
}
public void AddTocmbBrand(DropDownItem brand)
{
cmbBrand.Items.Add(brand);
}
public void ClearcmbBrand()
{
cmbBrand.Items.Clear();
}
Simply, you can add an item then clear the combobox again:
cmbBrand.Items.Clear();
cmbBrand.Items.Add(DBNull.Value);
cmbBrand.Items.Clear();
You should able to set the datasource of listbox2 to null to clear it, then set it again with the new data.
So, in pseudo-code, something like:
ItemSelectedInListBox1()
{
List futureListbox2Items = LoadOptionsBaseOnSelectedItem(item)
Listbox2.Datasource = null
Listbox2.Datasource = futureListBox2Items
}
That should refresh the list of items displayed in Listbox2 with no white spaces.
I was able to fix the extra space. I changed the Add and Clear methods to:
public void AddTocmbModel(DropDownItem model)
{
cmbModel.Items.Add(model);
cmbModel.DropDownHeight = cmbModel.ItemHeight * (cmbModel.Items.Count + 1);
}
public void ClearcmbModel()
{
cmbModel.Items.Clear();
cmbModel.DropDownHeight = cmbModel.ItemHeight;
}

How do I position a datagridview to the searched text input

Using Windows forms and linq to Sql, I bound a datagridview to Products Table, I added to the form 1 Textbox to input the searched text.
I wonder how to position the datagridview according to the text entered to find a given ProductName.
Here I do not want to filter rows, I only want to reposition datagrid after each character entered, the used code:
private void textBox1_TextChanged(object sender, EventArgs e)
{
var searchValue = textBox1.Text.Trim().ToUpper();
var qry = (from p in dc.Products
where p.ProductName.ToUpper().StartsWith(searchValue)
select p).ToList();
int itemFound = productBindingSource.Find("ProductName", searchValue);
productBindingSource.Position = itemFound;
}
The execution of code give the next error: System.NotSupportedException was unhandled at the ligne:
int itemFound = productBindingSource.Find("ProductName", searchValue);
Any idea please ?
The MSDN documentation for BindingSource has the answer:
The Find method can only be used when the underlying list is an
IBindingList with searching implemented. This method simply refers the
request to the underlying list's IBindingList.Find method. For
example, if the underlying data source is a DataSet, DataTable, or
DataView, this method converts propertyName to a PropertyDescriptor
and calls the IBindingList.Find method. The behavior of Find, such as
the value returned if no matching item is found, depends on the
implementation of the method in the underlying list.
When you call this method on a BindingSource whose underlying data source does not implement IBindingList then you see the exception (thrown by the default implementation of IBindingList.FindCore:
System.NotSupportedException: The specified method is not supported.
You don't show what you bind the binding source to but clearly it doesn't implement this method.
Annoyingly, BindingList<T> the recommended list type to use for your data source does not provide a FindCore implementation.
If you are using BindingList you will need to create your own custom type. Here is the code for an absolutely bare bones implementation of a BindingList that supports find:
public class FindableBindingList<T> : BindingList<T>
{
public FindableBindingList()
: base()
{
}
public FindableBindingList(List<T> list)
: base(list)
{
}
protected override int FindCore(PropertyDescriptor property, object key)
{
for (int i = 0; i < Count; i++)
{
T item = this[i];
if (property.GetValue(item).Equals(key))
{
return i;
}
}
return -1; // Not found
}
}
You can do lots with your own implementations of BindingList such as supporting sorting. I've left my answer as just the minimum to support the find method. Search for SortableBindingList if you want to know more.
To use this class do something like this:
var qry = (from p in dc.Products
where p.ProductName.ToUpper().StartsWith(searchValue)
select p).ToList();
FindableBindingList<YourType> list = new FindableBindingList<YourType>(qry);
dataGridView1.DataSource = list;

NHibernate, WinForms, and DataBinding - do they play well together?

I've been using WinForms databinding to display data from a database mapped with Fluent NHibernate, and that's been working great.
For example, I can just set a DataGridView's DataSource property from an entity's IList property, and voila - there's all the data!
But now I need to start adding and saving new data rows, and that's not going so well. I thought I'd be able to just enable the grid's AllowUserToAddRows property, and new rows would get added to the underlying IList in the entity, but that didn't work.
Then, after a little searching, I tried setting the DataSource property to a BindingList that was populated from the IList, but that's not being updated with new rows either.
During the course of my searches, I also came upon a few people reporting difficulty with WinForms and DataBinding in general, which makes me wonder if I should pursue that approach any further.
Is the DataBinding approach worth continuing? If so, can anyone suggest where I'm going wrong?
Or is it better to just handle all the DataGridView events associated with adding a new row, and writing my own code to add new objects to the IList property in my entity?
Other suggestions? (though I don't think switching to WPF is going to be an option, no matter how much better the databinding may be)
Can you load (or copy) your nHibernate entities into a generic List? If so, I have had good success in with two-way binding using a DataGridView bound to a generic List.
The key points are:
The generic list contains list objects where each is an instance of your custom class.
Your custom class must implement public properties for each of the fields to bind. Public fields didn't work for me.
Use a BindingSource to wrap the actual generic list.
The BindingSOurce allows you to set the AllowNew property to true. Binding directly to the List almost works, but the DataGridVieww does not display the "New row" line, even if AllowUsersToAddRows = true.
For example, add this code to a Form with a dataGridView1:
private List<MyObject> m_data = new List<MyObject>();
private BindingSource m_bs =new BindingSource();
private void Form1_Load(object sender, EventArgs e)
{
m_data.Add(new MyObject(0,"One",DateTime.Now));
m_data.Add(new MyObject(1, "Two", DateTime.Now));
m_data.Add(new MyObject(2, "Three", DateTime.Now));
m_bs.DataSource = m_data;
m_bs.AllowNew = true;
dataGridView1.DataSource = m_bs;
dataGridView1.AutoGenerateColumns = true;
dataGridView1.AllowUserToAddRows = true;
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
for (int i = 0; i < m_data.Count ; i++)
{
Console.WriteLine(string.Format("{0} {1} {2}", m_data[i].ID, m_data[i].Name, m_data[i].DOB));
}
}
}
public class MyObject
{
// Default ctor, required for adding new rows in DataGridView
public MyObject()
{
}
public MyObject(int id, string name, DateTime dob)
{
ID = id;
Name = name;
DOB = dob;
}
private int m_id;
public int ID
{
get
{
return m_id;
}
set
{
m_id = value;
}
}
private string m_name;
public string Name
{
get
{
return m_name;
}
set
{
m_name = value;
}
}
private DateTime m_dob;
public DateTime DOB
{
get
{
return m_dob;
}
set
{
m_dob = value;
}
}
}
When the form closes, the contents of the bound List are printed to the Output window.

Resources