I need some help in rendering a dynamic gridview.
Using the source code below, I can add the grid to the page and it generates one row for each entry in the datasource.
The problem is that the controls only appears in the last row. If I add another entry to the datasource, I get one more row in the Gridview but only the last is showing the controls that I've added using the ItemTemplate.
When I debug the solution I can see that it instatiates a new ItemTemplate foreach collumn and that it adds all the controls, but after the databinding of the grid, all the cells are empty, except the ones of the last row.
Please help if you can. Thanks
private void BuildGridInterface()
{
TBL_PARAM_01 xpto = new TBL_PARAM_01();
GridView grid = new GridView();
grid.ID = "grd";
grid.AutoGenerateColumns = false;
grid.ShowHeader = true;
PropertyInfo[] props = xpto.GetType().GetProperties();
foreach (PropertyInfo info in props)
{
TemplateField field = new TemplateField();
object[] attributes = info.GetCustomAttributes(true);
var att = attributes.SingleOrDefault(a => a.GetType() == typeof(Domain));
WebControl control;
if (att != null)
{
control = new DropDownList();
control.ID = "drp" + info.Name;
((DropDownList)control).Items.Add(new ListItem() { Text = "XXXXX", Value = "1" });
((DropDownList)control).Items.Add(new ListItem() { Text = "XXXX2", Value = "2" });
((DropDownList)control).Items.Add(new ListItem() { Text = "XXXX3", Value = "3" });
((DropDownList)control).Items.Add(new ListItem() { Text = "XXXX4", Value = "4" });
((DropDownList)control).Items.Add(new ListItem() { Text = "XXXX5", Value = "5" });
((DropDownList)control).Items.Add(new ListItem() { Text = "XXXX6", Value = "6" });
}
else
{
control = new TextBox();
control.ID = "txt" + info.Name;
}
field.ItemTemplate = new ItemTemplate(control, false, info.Name);
field.HeaderText = ((MatrixFieldLabel)attributes.Single(a => a.GetType() == typeof(MatrixFieldLabel))).Value;
grid.Columns.Add(field);
}
FillGrid(grid);
placer.Controls.Add(grid);
}
public class ItemTemplate : ITemplate
{
WebControl Control { get; set; }
bool Enabled { get; set; }
string ColumName { get; set; }
public ItemTemplate(WebControl control, bool enabled, string columname)
{
this.Control = control;
this.Enabled = enabled;
this.ColumName = columname;
control.DataBinding += new EventHandler(Control_DataBinding);
}
public void InstantiateIn(Control container)
{
((WebControl)container).Enabled = Enabled;
container.Controls.Add(this.Control);
}
void Control_DataBinding(object sender, EventArgs e)
{
GridViewRow container = (GridViewRow)((Control)sender).NamingContainer;
object dataValue = DataBinder.Eval(container.DataItem, this.ColumName);
if (sender.GetType() == typeof(TextBox))
((TextBox)sender).Text = dataValue.ToString();
else if (sender.GetType() == typeof(DropDownList))
((DropDownList)sender).SelectedValue = dataValue.ToString();
}
private void FillGrid(GridView target)
{
List<TBL_PARAM_01> list = new List<TBL_PARAM_01>();
list.Add(new TBL_PARAM_01() { IDEntityRecordProduct = 1, IDEntityRecordX = 2, Output = "XPTO 3" });
list.Add(new TBL_PARAM_01() { IDEntityRecordProduct = 2, IDEntityRecordX = 3, Output = "XPTO 4" });
list.Add(new TBL_PARAM_01() { IDEntityRecordProduct = 3, IDEntityRecordX = 4, Output = "XPTO 5" });
list.Add(new TBL_PARAM_01() { IDEntityRecordProduct = 4, IDEntityRecordX = 5, Output = "XPTO 6" });
list.Add(new TBL_PARAM_01() { IDEntityRecordProduct = 5, IDEntityRecordX = 6, Output = "XPTO 7" });
target.DataSource = list;
target.DataBind();
}
}
Problem solved. I've had to rewrite the method InstantiateIn of my ItemTemplate class.
Thanks.
you appear to be replacing the datasource for your target gridview every time you call FillGrid, and rebinding it. You could remove your DataSource assignment and DataBind calls outside of the FillGrid method and between FillGrid(grid); and placer.Controls.Add(grid); lines in the BuildGridInterface method?
Let us know if that helps.. :)
Related
I would like to resize any ComboBox dropdown width based on the longest string in the dropdown items. I want this to work on any ComboBox with items which means one whose items are strings, some object with DisplayMember set, or a DataTable. I found some code which works for strings, but not when a DisplayMember is set
static void resizeCombo(ComboBox cb)
{
cb.DropDownWidth = cb.Items.Cast<string>().Max(x => TextRenderer.MeasureText(x, cb.Font).Width);
}
In the following three examples, the first which is just strings will work, but the following two don't work (cb.Items.Cast<string>() cast to string fails), and demonstrate that the DisplayMember can vary when bound to sources of different classes, so I can't just use "Name" for example
var c = new string[] { "Name1", "Name2" };
comboBox.DataSource = c.ToList();
resizeCombo(comboBox);
var c1 = new Class1[] { new Class1() { ID = 1, Name1 = "Name1" }, new Class1() { ID = 2, Name1 = "Name2" } };
comboBox1.DisplayMember = "Name1";
comboBox1.ValueMember = "ID";
comboBox1.DataSource = c1.ToList();
resizeCombo(comboBox1);
var c2 = new Class2[] { new Class2() { ID = 2, Name2 = "Name1" }, new Class2() { ID = 2, Name2 = "Name2" } };
comboBox2.DisplayMember = "Name2";
comboBox2.ValueMember = "ID";
comboBox2.DataSource = c2.ToList();
resizeCombo(comboBox2);
I could reflect the DisplayMember, and find the strings by name, and it may solve the List<class> case, but not DataTable.
I am looking for a method to get all the strings in the ComboBox regardless of how they are added. Is there one?
Combobox has GetItemText method that returns the string representation of item.
This should work:
static void resizeCombo(ComboBox cb)
{
if (cb.Items.Count == 0) cb.DropDownWidth = cb.Width;
else
{
int maxWidth = int.MinValue;
for (int i = 0; i < cb.Items.Count; i++)
{
maxWidth = Math.Max(maxWidth, TextRenderer.MeasureText(cb.GetItemText(cb.Items[i]), cb.Font).Width);
}
if (cb.Items.Count > cb.MaxDropDownItems) maxWidth += SystemInformation.VerticalScrollBarWidth
cb.DropDownWidth = maxWidth;
}
}
To test with DataTable:
DataTable t = new DataTable();
t.Columns.Add(new DataColumn("ID", typeof(int)));
t.Columns.Add(new DataColumn("Name2", typeof(string)));
t.Rows.Add(new object[] { 1, "Somename" });
t.Rows.Add(new object[] { 2, "Some other name" });
comboBox2.DisplayMember = "Name2";
comboBox2.ValueMember = "ID";
comboBox2.DataSource = t;
resizeCombo(comboBox2);
NB! For this to work, do not use the resize function in a form's constructor. Use it from the Load event or similar, once the form is already up and running.
So I have a TableLayoutPanel that on my form, named tlpSSMappings. I am populating it based on columns in a data table that I store in memory via a foreach loop. Here is my code currently:
tlpSSMappings.CellBorderStyle = TableLayoutPanelCellBorderStyle.Single;
tlpSSMappings.RowCount = 0;
foreach (DataColumn col in dt.Columns)
{
tlpSSMappings.GrowStyle = TableLayoutPanelGrowStyle.AddRows;
// For Add New Row (Loop this code for add multiple rows)
if (tlpSSMappings.RowCount == 1)
{
//tlpSSMappings.RowStyles.Add(new RowStyle(SizeType.AutoSize));
tlpSSMappings.Controls.Add(new Label() { Text = col.ToString() }, 3, tlpSSMappings.RowCount-1);
tlpSSMappings.Controls.Add(new ComboBox() { DataSource = configFields }, 1, tlpSSMappings.RowCount-1 );
tlpSSMappings.Controls.Add(new CheckBox() { Text = "Mapped" },2, tlpSSMappings.RowCount - 1);
tlpSSMappings.RowCount = tlpSSMappings.RowCount + 1;
}
else
{
List<string> copyOfConfigDS = new List<string>();
foreach (string cfg in configFields)
{
copyOfConfigDS.Add(cfg);
}
ComboBox c = new ComboBox()
{
DataSource = copyOfConfigDS,
};
CheckBox chkBox = new CheckBox()
{
Text = "Mapped"
};
tlpSSMappings.RowStyles.Add(new RowStyle(SizeType.AutoSize));
tlpSSMappings.Controls.Add(new Label() { Text = col.ToString() }, 3, tlpSSMappings.RowCount-1);
tlpSSMappings.Controls.Add(c, 1, tlpSSMappings.RowCount-1);
tlpSSMappings.Controls.Add(chkBox, 2, tlpSSMappings.RowCount - 1);
tlpSSMappings.RowCount = tlpSSMappings.RowCount + 1;
}
}
You can see that there are 3 controls per row in tlpSSMappings being created, 1 ComboBox, 1 Label and 1 Checkbox.
I wish to have code that works as such: When I change the value of a combobox, the checkbox within that row will automatically be checked. How can I do this?
Thanks for the help!
attach an event handler to each comboBox and set IsChecked property of related checkBox
ComboBox c = new ComboBox()
{
DataSource = copyOfConfigDS,
};
CheckBox chkBox = new CheckBox()
{
Text = "Mapped"
};
c.SelectedIndexChanged += (eventSender, args) =>
{
chkBox.IsChecked = c.SelectedIndex >= 0;
};
ok so in my switch(comboBox1.SelectedIndex) in case 1 it creates some Labels and comboBoxes dynamically and adds them to tabPage1, but I want those dynamically created controls to be removed when case 2 is selected
public void comboBox2_SelectedIndexChanged(object sender, EventArgs e)
{
switch (comboBox2.SelectedIndex)
{
case 0:
{
//do nothing.
break;
}
case 1:
{
Label label16 = new Label();
tabPage1.Controls.Add(label16);
label16.Left = 465;
label16.Top = 111;
label16.Text = "Time:";
label16.Size = new Size(60, 13);
ComboBox comboBox13 = new ComboBox();
tabPage1.Controls.Add(comboBox13);
comboBox13.Left = 533;
comboBox13.Top = 108;
comboBox13.Size = new Size(104, 21);
comboBox13.DropDownStyle = ComboBoxStyle.DropDownList;
comboBox13.DisplayMember = "Text";
comboBox13.ValueMember = "Value";
var ComboBox13Items = new[] {
new { Text = "1 Second", Value = "1" },
new { Text = "2.5 Seconds", Value = "2.5" },
new { Text = "5 Seconds", Value = "5" },
new { Text = "7.5 Seconds", Value = "7.5" },
new { Text = "10 Seconds", Value = "10" }
};
comboBox13.DataSource = ComboBox13Items;
break;
}
case 2:
{
foreach (Control TimeLabel in tabPage1.Controls.OfType<Controls>())
{
if (TimeLabel.Name == "label16")
tabPage1.Controls.Remove(TimeLabel);
}
foreach (Control TimeComboBox in tabPage1.Controls.OfType<Controls>())
{
if (TimeComboBox.Name == "comboBox13")
tabPage1.Controls.Remove(TimeComboBox);
}
break;
}
I also tried changing OfType<Controls> to OfType<Label> and OfType<ComboBox>, still no luck :/
Label label16 = new Label();
tabPage1.Controls.Add(label16);
label16.Left = 465;
label16.Top = 111;
label16.Text = "Time:";
label16.Size = new Size(60, 13);
Does not create a button called "label16" it creates an unnamed button.
you would need to add
labal16.Name = "label16";
Names should be unique, keep a counter or something if theres chance that more than one set would be added and use the counter to make a unique name.
I'm struggling with DataGridViewComboBoxCell. On some cases (let's say, events) I must preselect a value of ComboBox in my Form's DataGridView. When user changes one box, I am able to change another programatically like this:
var item = ItemName.Items.GetListItem(name);
if (item != null)
{
_loading = true; // that's needed to come around another CellValueChanged events
itemView.Rows[e.RowIndex].Cells["ItemName"].Value = item;
_loading = false;
}
I'm populating ItemName.Items like this:
foreach (var item in _model.DefaultData.ItemList)
{
if (item.Value.Code.HasValue()) ItemCode.Items.Add(new ListItem(item.Key, item.Value.Code));
ItemName.Items.Add(new ListItem(item.Key, item.Value.Name));
}
GetListItem method:
public static ListItem GetListItem(this DataGridViewComboBoxCell.ObjectCollection col, string name)
{
ListItem retItem = null;
foreach (ListItem item in col)
{
if (item.Name == name) { retItem = item; break; }
}
return retItem;
}
That works fine, BUT...
Now I want to add rows to DataGridView on form load like that:
foreach (var item in _model.SelectedItems)
{
var row = new DataGridViewRow();
row.Cells.Add(new DataGridViewTextBoxCell { Value = item.Id });
row.Cells.Add(new DataGridViewComboBoxCell { Value = ItemCode.Items.GetListItem(item.Code) });
row.Cells.Add(new DataGridViewComboBoxCell { Value = ItemName.Items.GetListItem(item.Name) });
row.Cells.Add(new DataGridViewTextBoxCell { Value = item.Units });
...
itemView.Rows.Add(row);
}
and that throws beloved DataGridViewComboBox value is not valid exception. Please help, I'm out of thoughts on that. I don't want to use DataSource or something like that. ItemCode and ItemName column items are populated and returned properly through GetListItem(). I don't understand why it works normally, but on form load it doesn't work (on shown it doesn't work either).
EDIT: sorry, forgot to add.
My ListItem class:
public class ListItem
{
public int Id { get; set; }
public string Name { get; set; }
public ListItem(int sid, string sname)
{
Id = sid;
Name = sname;
}
public override string ToString()
{
return Name;
}
}
I already put that too on form load:
ItemName.ValueMember = "Id";
ItemName.DisplayMember = "Name";
ItemCode.ValueMember = "Id";
ItemCode.DisplayMember = "Name";
Ok, I managed to solve it myself.
Apparently, it's not enough for DataGridViewComboBoxColumn.Items to contain possible items. You must also add items to DataGridViewComboBoxCell.Items if you're adding a new row programatically.
So if someone else tries to use my approach of not having data bindings like DataTable etc., here's my solution:
foreach (var item in _model.SelectedItems)
{
var row = new DataGridViewRow();
var codeCell = new DataGridViewComboBoxCell();
codeCell.Items.AddRange(ItemCode.Items);
codeCell.Value = ItemCode.Items.GetListItem(item.Code);
var nameCell = new DataGridViewComboBoxCell();
nameCell.Items.AddRange(ItemName.Items);
nameCell.Value = ItemName.Items.GetListItem(item.Name);
row.Cells.Add(new DataGridViewTextBoxCell { Value = item.Id });
row.Cells.Add(codeCell);
row.Cells.Add(nameCell);
row.Cells.Add(new DataGridViewTextBoxCell { Value = item.Units });
row.Cells.Add(new DataGridViewTextBoxCell { Value = item.Quantity });
row.Cells.Add(new DataGridViewTextBoxCell { Value = item.PriceLt });
row.Cells.Add(new DataGridViewTextBoxCell { Value = item.PriceEu });
itemView.Rows.Add(row);
}
DataGridViewColumn[] dgCol =
{
new DataGridViewTextBoxColumn()
{
HeaderText = "Conviction",
SortMode = DataGridViewColumnSortMode.NotSortable,
Width = Convert.ToInt32(0.25f * grdConvictions.Width - 19)
},
new DataGridViewTextBoxColumn()
{
HeaderText = "Points" ,
SortMode = DataGridViewColumnSortMode.NotSortable,
Width = Convert.ToInt32(0.125f * grdConvictions.Width - 19)
},
new DataGridViewTextBoxColumn()
{
HeaderText = "Date",
SortMode = DataGridViewColumnSortMode.NotSortable,
Width = Convert.ToInt32(0.125f * grdConvictions.Width - 19),
},
new DataGridViewComboBoxColumn ()
{
HeaderText = "Galletas",
Width = Convert.ToInt32(0.25 * grdConvictions.Width - 19),
DataPropertyName = "Galletas",
DataSource = new List<String>{"",
"Maiz",
"Pera",
"Manzana",
"Sandia",
"Fresa",
"Melon",
"Melocoton",
"Maracuya",
"Cereza",
"Frambuesa",
"Mora",
"Kiwi",
"Anona",
"Guayaba"
},
SortMode = DataGridViewColumnSortMode.NotSortable,
MaxDropDownItems = 10,
DisplayStyle = DataGridViewComboBoxDisplayStyle.Nothing,
ReadOnly = false
}
};
YourDataGridView.Columns.AddRange(dgCol);
Note: The DisplayStyle = DataGridViewComboBoxDisplayStyle.ComboBox in order to see the combo. I use as "nothing", and...:
private void grdConvictions_CellEnter(object sender, DataGridViewCellEventArgs e)
{
int colIndex = e.ColumnIndex;
int rowIndex = e.RowIndex;
if (rowIndex < 0 || colIndex < 0)
{
return;
}
DataGridView grd = (DataGridView)sender;
if (grd[e.ColumnIndex, e.RowIndex].GetType().Name != "DataGridViewComboBoxCell")
{
((DataGridViewComboBoxCell)grd.CurrentCell).DisplayStyle = DataGridViewComboBoxDisplayStyle.DropDownButton;
}
}
private void grdConvictions_CellLeave(object sender, DataGridViewCellEventArgs e)
{
int colIndex = e.ColumnIndex;
int rowIndex = e.RowIndex;
if (rowIndex < 0 || colIndex < 0)
{
return;
}
DataGridView grd = (DataGridView)sender;
if (grd[e.ColumnIndex, e.RowIndex].GetType().Name != "DataGridViewComboBoxCell")
{
((DataGridViewComboBoxCell)grd.CurrentCell).DisplayStyle = DataGridViewComboBoxDisplayStyle.Nothing;
}
}
I have managed this in list view for grouping items in list view i have customer table have columns with
category id
category name
categories
-----------
category name 1
category name 2
category name 3
price ranges
-----------
ALL
0-500
500-1000
I have done above task but i have problem with checking selected item in groups in list view ..
my problem is how we fire the event like if i select the first item in first group in list view i want to do something ....
and if i select the first item in second group in list view i want do something...
and some where i have to use the selected item text in events.....
how i find the checking ...
can any one help on this .....
Many thanks....
and this is my code
private void categorieslist()
{
lstviewcategories.View = View.Details;
lstviewcategories.Columns.Add(new ColumnHeader() { Width = lstviewcategories.Width - 20 });
lstviewcategories.HeaderStyle = ColumnHeaderStyle.None;
lstviewcategories.Sorting = SortOrder.Ascending;
lstviewcategories.Dock = DockStyle.None;
ListViewGroup categorygroup = new ListViewGroup("Category Types",HorizontalAlignment.Center);
lstviewcategories.Groups.Add(categorygroup);
var categorytypes = (from categories in abc.categories
select categories.category_Name).ToList();
lstviewcategories.Items.Add(new ListViewItem() { Text = "ALL", Group = categorygroup });
foreach (string item in categorytypes)
{
lstviewcategories.Items.Add(new ListViewItem() { Text = item.ToString(), Group = categorygroup });
}
ListViewGroup pricerangegroup = new ListViewGroup("Price Ranges", HorizontalAlignment.Center);
lstviewcategories.Groups.Add(pricerangegroup);
lstviewcategories.Items.Add(new ListViewItem() { Text = "ALL", Group = pricerangegroup });
lstviewcategories.Items.Add(new ListViewItem() { Text = "0-500", Group = pricerangegroup });
lstviewcategories.Items.Add(new ListViewItem() { Text = "500-1000", Group = pricerangegroup });
lstviewcategories.Items.Add(new ListViewItem() { Text = "1000+", Group = pricerangegroup });
}
EDIT :
private void lstviewcategories_SelectedIndexChanged(object sender, EventArgs e)
{
// int index = 0;
if (lstviewcategories.SelectedItems.Count > 0 &&lstviewcategories.SelectedItems[0].Group.Name == "Category Types")
{
string text = lstviewcategories.SelectedItems[0].Text.ToString();
var categorywithids = (from categorytypes in abc.categories
where categorytypes.category_Name.Equals(text)
select categorytypes.category_Id).SingleOrDefault();
var productcategoty = from productswithcatgories in abc.product1
where productswithcatgories.category_Id.Equals(categorywithids)
select new
{
productid = productswithcatgories.product_Id, //0
productnam = productswithcatgories.product_Name, //1
productimage = productswithcatgories.product_Image, //2
productprice = productswithcatgories.product_Price,//3
productdescr = productswithcatgories.product_Description,//4
};
productbindingsource.DataSource = productcategoty;
productgridview.DataSource = productbindingsource;
productgridview.Columns[0].Visible = false;
productgridview.Columns[4].Visible = false;
}
}
Try creating a class derived from ListViewItem and add an enumeration property which you can query in the SelectedIndexChanged event:
public class CustomListViewItem : ListViewItem
{
public CustomListViewItemType Type { get; set; }
}
public enum CustomListViewItemType
{
Type1 = 0,
Type2 = 1
}
lstviewcategories.Items.Add(new CustomListViewItem() { Text = "ALL", Group = pricerangegroup, Type = CustomListViewItemType.Type2 });
void lstviewcategories_SelectedIndexChanged(object sender, EventArgs e)
{
if (lstviewcategories.SelectedItems.Count > 0)
{
CustomListViewItem customListItem = (CustomListViewItem)lstviewcategories.SelectedItems[0];
switch (customListItem.Type)
{
case CustomListViewItemType.Type1:
{
//...
}break;
case CustomListViewItemType.Type2:
{
//...
}break;
}
}
}
You can get SelectedItems for example in SelectedIndexChanged event and check group like below:
private void lstviewcategories_SelectedIndexChanged(object sender, EventArgs e)
{
if(lstviewcategories.SelectedItems.Count > 0 && lstviewcategories.SelectedItems[0].Group.Name == "group name")
//do smth
}
if MultiSelect property is enabled and for example you want to check selected items on some kind button click, loop through all selected items:
private void button_Click(object sender, EventArgs e)
{
foreach (ListViewItem item in lstviewcategories.SelectedItems)
{
if(item.Group.Name == "group name")
//do smth
}
}