Empty Datagridview cell with bound datasourse - c#

I have a DataGridView with "Type" and "Value" columns. The user selects a data type, and then enters a value compatible with that type, some types (e.g. "Text") accept any string. Other types (e.g. "Yes/No") are restricted to a list of possible values. For each row, I set the cell in the "Value" column to be either a Textbox for freeform types, or a combobox for list types. The DataGridView is bound to a DataTable.
The problem comes in if the user enters a value for one type, but then switches the row to a different type for which the current value is not allowed. No matter what I try, I cannot clear the Value cell. Then when I assign a combobox to the cell, I get a DataError, because the current value of the cell is incompatible. How can I clear the value before changing the cell from a textbox to a combobox?
public enum DataType
{
Text,
YesNo
}
public class IndexedItem
{
public string Name { get; set; }
public int ID {get; set; }
public string strID
{
get { return ID.ToString(); }
}
//constructors & other methods;
}
public static DataTable ParameterTable;
public List<IndexedItem> YesNoList;
In the form constructor (dgvInputs is the DataGridView):
ParameterTable = new DataTable("ParameterTable");
ParameterTable.Columns.Add("Type", typeof(DataType));
ParameterTable.Columns.Add("Value", typeof(string));
YesNoList = new List<IndexedItem>();
YesNoList.Add(new IndexedItem("Yes", 1));
YesNoList.Add(new IndexedItem("No", 0));
var D = (DataGridViewComboBoxColumn)dgvInputs.Columns[0];
D.ValueMember = "Value";
D.DisplayMember = "Display";
D.DataSource = new DataType[] {
DataType.Text,
DataType.YesNo
}.Select(x => new { Display = x.ToString(), Value = (int)x }).ToList();
BindingSource ParamSource = new BindingSource();
ParamSource.DataSource = ParameterTable;
dgvInputs.AutoGenerateColumns = false;
dgvInputs.DataSource = ParamSource;
dgvInputs.Columns[0].DataPropertyName = "Type";
dgvInputs.Columns[1].DataPropertyName = "Value";
And Events:
private void dgvInputs_CurrentCellDirtyStateChanged(object sender, EventArgs e) {
if (dgvInputs.IsCurrentCellDirty) {
dgvInputs.CommitEdit(DataGridViewDataErrorContexts.Commit);
}
}
private void dgvInputs_CellValueChanged(object sender, DataGridViewCellEventArgs e) {
if (e.RowIndex >= 0 && e.ColumnIndex == 0) {
var cb = (DataGridViewComboBoxCell)dgvInputs[0, e.RowIndex];
if (cb.Value != null && cb.Value != DBNull.Value) {
DataType Type = (DataType)cb.Value;
dgvInputs[1, e.RowIndex].Value = string.Empty;
dgvInputs.CommitEdit(DataGridViewDataErrorContexts.Commit);
switch (Type) {
case DataType.YesNo:
dgvInputs[1, e.RowIndex].Dispose();
var newBox = new DataGridViewComboBoxCell();
newBox.DisplayMember = "Name";
newBox.ValueMember = "strID";
newBox.DataSource = YesNoList;
dgvInputs[1, e.RowIndex] = newBox;
break;
default:
dgvInputs[1, e.RowIndex] = new DataGridViewTextBoxCell();
break;
}
}
}
}
If you have it set to "text" and enter something arbitrary, then switch to "YesNo", it gives an error "System.ArgumentException: DataGridViewComboBoxCell value is not valid.", that will reappear any time the cursor is over the cell. Changing it back to a text row causes the original value to reappear.
I am assuming that the problem is that the value is saved in ParameterTable, but I can't get it to propagate my clearing of the original value to ParameterTable. I've tried null and DBNull.Value instead of string.Empty, but neither one made any difference. I added the "CommitEdit" line in hopes of getting it to make the change, but that made no difference either.
Edit:
As it turns out, the problem was this code that I had in the cell change event:
string Default = dgvInputs[4, e.RowIndex].Value as string;
// code switching out text box and combo box above
try
{
dgvInputs[4, e.RowIndex].Value = Default;
} catch (Exception e2) {
MessageBox.Show(e2.GetType().ToString());
}
The idea had been to preserve the value if possible, and I had the messagebox to show me the specific exception I needed to catch, as I was not sure. But apparently this assignment does not immediately induce the exception. That only occurs later, apparently during some other event I am not handling.
It is obvious in hindsight that I should have included this code in the sample. I have no idea now how I overlooked it. My apologies to everybody I led on a wild goose chase by leaving out the critical information. I do appreciate all of your assistance.

You problem is not with clearing the value but with the YesNoList.
The grid compbobox tries to find the value for the current record and there is no empty neither null value in your YesNoList.
You will even get an error if you try to add a new record and first set the DataType without setting the Value.
You can solve this by either adding an empty item to your YesNoList or by setting a default value to the existing record when switching DataType.

Edit:
I know below does not specifically answer your question as it states, but the example may help you out. Consider having two controllers in one cell.
Original
I am not sure if this will help you or not, but I tried to make a very basic program that you discussed. A dataset is created with 2 entries. The first column is the DataType, the second is of Value. If the DataType Text is chosen, the Value cell turns into a Textbox. If the DataType Yes/No is chosen, it hides the Textbox and shows a DropDownList. The idea is to hide one component when not needed.
Default.aspx.cs
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace DataGridViewBounds
{
public partial class _Default : Page
{
public enum DataType
{
Text,
YesNo
}
public class IndexedItem
{
public string Name
{ get; set; }
public int ID
{ get; set; }
public string strID
{ get { return ID.ToString(); } }
}
protected void Page_Load (object sender, EventArgs e)
{
if (!IsPostBack)
{
Bind();
}
for (int i = 0; i < dg.Items.Count; ++i)
{
bool ShowText = ((DropDownList)dg.Items[i].Cells[0].Controls[1]).SelectedValue.Equals("text");
((DropDownList)dg.Items[i].Cells[1].Controls[1]).Visible = !ShowText;
((TextBox)dg.Items[i].Cells[1].Controls[3]).Visible = ShowText;
}
}
private void Bind ()
{
DataTable ParameterTable = new DataTable("ParameterTable");
ParameterTable.Columns.Add("", typeof(string));
ParameterTable.Columns.Add("Type", typeof(DataType));
ParameterTable.Columns.Add("Value", typeof(string));
List<ListItem> YesNoList = new List<ListItem>(); // Should be ListItem, not IndexedItem
YesNoList.Add(new ListItem("Yes", "1"));
YesNoList.Add(new ListItem("No", "0"));
DataRow row = ParameterTable.NewRow();
row["Type"] = DataType.Text;
row["Value"] = "Some text";
DataRow row2 = ParameterTable.NewRow();
ParameterTable.Rows.Add(row);
row2["Type"] = DataType.YesNo;
row2["Value"] = "false";
ParameterTable.Rows.Add(row2);
dg.DataSource = ParameterTable;
dg.DataBind();
dg.ShowHeader = true;
dg.Visible = true;
for (int i = 0; i < dg.Items.Count; ++i)
{ // Showing 2 ways to bind the DropDownList items
((DropDownList)dg.Items[i].Cells[0].Controls[1]).Items.Add(new ListItem("Text", "text"));
((DropDownList)dg.Items[i].Cells[0].Controls[1]).Items.Add(new ListItem("Yes/No", "bool"));
((DropDownList)dg.Items[i].Cells[1].Controls[1]).DataSource = YesNoList;
((DropDownList)dg.Items[i].Cells[1].Controls[1]).DataBind();
}
}
}
}
And the Default.aspx page
<%# Page Title="Home Page" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="DataGridViewBounds._Default" %>
<asp:Content ID="BodyContent" ContentPlaceHolderID="MainContent" runat="server">
<asp:DataGrid ID="dg" runat="server" AutoGenerateColumns="false">
<Columns>
<asp:TemplateColumn HeaderText="Type">
<ItemTemplate>
<asp:DropDownList runat="server" ID="ddListType" AutoPostBack="true"></asp:DropDownList>
<asp:Label id="TypeLabel" runat="server" Visible="false"></asp:Label>
</ItemTemplate>
</asp:TemplateColumn>
<asp:TemplateColumn HeaderText="Value">
<ItemTemplate>
<asp:DropDownList runat="server" ID="ddListValue" AutoPostBack="true" Visible="false"></asp:DropDownList>
<asp:TextBox id="ValueLabel" runat="server" Visible="false"></asp:TextBox>
</ItemTemplate>
</asp:TemplateColumn>
</Columns>
</asp:DataGrid>
</asp:Content>
This is the best I could do at the moment without seeing more of the code, but may you can use it. One suggestion, dgvInputs_CurrentCellDirtyStateChanged appears to commit code. I am assuming this is SQL code. You may want to wait until committing until a final 'Submit' button or 'Accept Changes' button has been pressed so that you don't have to call SQL so much, but also if there is an error that occurs between the start of the first SQL call and the last. If an interruption occurs between the two, you may not necessarily want to commit.

Related

How do I select a checkbox bound to a checkbox control from within c# code?

BillingEntity = (string)rdr["BillingEntity"];
string[] split = BillingEntity.Split(',');
for (int i = 0; i < split.Length; i++)
{
split[i] = split[i].Trim();
if (split[i] == "Clinic")
{
BillingEntityCheckBox.Items[i].Checked = true;
Session["billingEntitySessionVar"] = Session["billingEntitySessionVar"]
+ " Clinic";
}
How can I check an item in a checkbox list from the underlying code?
I know with a single checkbox you use checkbox.checked = true. I had that working fine in my code, but I need to link the checkboxes together in a control so that I can trigger an event based on whether any of them has been changed by the user.
To give a little background, I'm pulling in data from a SQL database and outputting to the user interface through a WebForm.
I think you're missing the concept of databinding, is this what you're looking for?:
public partial class _Default : Page
{
protected void Page_Load(object sender, EventArgs e)
{
string[] myStrings = new string[] { "Example Value", "Not a Thing", "Many Things", "All the Thing" };
cblThingy.DataSource = myStrings;
cblThingy.DataBind();
}
protected void btnPushMe_Click(object sender, EventArgs e)
{
//for all the things
foreach(ListItem itemThing in cblThingy.Items)
{
//set a default of not selected
itemThing.Selected = false;
//if the text is the same
//you can have any check you like here
if (itemThing.Text == txtThing.Text)
//say that it's selected
itemThing.Selected = true;
}
}
}
^The "code behind"
<asp:Label ID="lblThing" runat="server" Text="If you type the text and push the button the thing with select." />
<asp:TextBox ID="txtThing" runat="server" Text="Example Value" />
<asp:Button ID="btnPushMe" runat="server" Text="Push My Button" OnClick="btnPushMe_Click" />
<asp:CheckBoxList ID="cblThingy" runat="server" />
^The webform xml
Hopefully that helps!
p.s. I think it would be a good idea to make a readonly property on the BillingEntity type, which would return a bool which does the check for "clinic" inside that class, making it more object oriented..

C# button click event does not fire

I have seen a couple of similar questions but none of the solutions worked for me. I have a table and i dinamically add a couple of buttons to it like the following, and i want a single common listener for all buttons:
Button b; //This is defined as a variable in class, not in a function
...
...
void someFunction( . . . )
{
foreach (DataRow row in table.Rows)
{
try
{
string starthour = row["starthour"].ToString(), endhour = row["endhour"].ToString(), day = row["day"].ToString(), building = row["building"].ToString(), room = row["room"].ToString();
int start = Convert.ToInt32(starthour.Substring(0, starthour.Length - 2));
int end = Convert.ToInt32(endhour.Substring(0, endhour.Length - 2));
int startindex = getHourIndex(start);
int endindex = getHourIndex(end);
int dayindex = getDayIndex(day);
for (int i = startindex; i < endindex; i++)
{
b = new Button();
b.Text = subj + numb + " " + section;
Color clr = getCourseColor(subj + numb + section, courses);
b.BackColor = clr;
b.Enabled = true;
b.Click += new EventHandler(button_Click);
table_filter_instructor_schedule.Rows[i].Cells[dayindex].Controls.Add(b);
}
}
catch (Exception)
{
}
}
}
And here is the event handler:
protected void button_Click(object sender, EventArgs e)
{
Response.Redirect("Default.aspx");
}
but the problem is, ebent handler function is never called. Can anyone help me with this*
Thanks
EDIT: Here is how the page looks, and i want to add listener for those buttons:
If you want dynamic controls to work and also raise events, you need to recreate them early in the page lifecycle (Page_Init, Page_Load at the latest). It is important that you assign the same IDs when you recreate the controls and also wire up the event handlers.
Using dynamically created controls usually adds a lot of complexity and you should check whether there is no other way that is simpler. In your function, your buttons are created based on a data table. So it might be a good approach to use a repeater instead of the dynamically created buttons. This allows to wire up events statically.
For a detailed sample on how to create controls dynamically, see this link. However, it also suggests to favor a static approach if possible:
Existing controls can often provide the functionality you get from
creating controls dynamically. For example, controls such as the
Repeater, DataList, and RadioButtonList controls can dynamically
create rows or other control elements when the page runs.
How to avoid adding controls dynamically in your case
In your specific case (based on your image), I'd propose the following static approach:
Add a Repeater to your page that creates the table header in the header template, the rows in the ItemTemplate and the table footer in the FooterTemplate. Add a button for each day in the ItemTemplate. Wire an event to the buttons.
Create a data class that represents one line in the repeater, e.g. time and the data for each day.
When you retrieve the data, convert it to a list of the data class and bind the repeater to it.
Handle the OnItemDataBound event of the repeater to adjust the visibility of the buttons and set the text.
The following sample shows the main parts (I've added only columns for three days):
Repeater
This Repeater creates a very basic HTML table. Please note the buttons in the table rows and the static registration of an event handler.
<asp:Repeater ID="rptTimeTable" runat="server" OnItemDataBound="rptTimeTable_ItemDataBound">
<HeaderTemplate>
<table>
<thead>
<tr>
<td>Time</td>
<td>Mon</td>
<td>Tue</td>
<td>Wed</td>
</tr>
</thead>
<tbody>
</HeaderTemplate>
<ItemTemplate>
<tr>
<td><%# Eval("Time", "{0:t}") %></td>
<td>
<asp:Button ID="btnMon" runat="server" OnClick="btn_ClickHandler" />
</td>
<td>
<asp:Button ID="btnTue" runat="server" OnClick="btn_ClickHandler" />
</td>
<td>
<asp:Button ID="btnWed" runat="server" OnClick="btn_ClickHandler" />
</td>
</tr>
</ItemTemplate>
<FooterTemplate>
</tbody>
</table>
</FooterTemplate>
</asp:Repeater>
Data Class
The data class stores a text for each button that I put on the button later on.
public class RepeaterData
{
public DateTime Time { get; set; }
public string MonText { get; set; }
public string TueText { get; set; }
public string WedText { get; set; }
}
Data binding
I've placed this in Page_Load (only if it is not a PostBack), but you can run this whenever you like.
var data = new List<RepeaterData>();
data.Add(new RepeaterData() { Time = DateTime.Today.AddHours(9), MonText = "123", TueText = null, WedText = null });
data.Add(new RepeaterData() { Time = DateTime.Today.AddHours(10), MonText = null, TueText = "456", WedText = "789" });
data.Add(new RepeaterData() { Time = DateTime.Today.AddHours(11), MonText = null, TueText = null, WedText = null });
data.Add(new RepeaterData() { Time = DateTime.Today.AddHours(12), MonText = "123", TueText = null, WedText = null });
rptTimeTable.DataSource = data;
rptTimeTable.DataBind();
OnItemDataBound handler
protected void rptTimeTable_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
{
var data = (RepeaterData)e.Item.DataItem;
SetButtonText(e.Item, "btnMon", data.MonText);
SetButtonText(e.Item, "btnTue", data.TueText);
SetButtonText(e.Item, "btnWed", data.WedText);
}
}
private void SetButtonText(RepeaterItem repeaterItem, string btnId, string btnText)
{
var btn = repeaterItem.FindControl(btnId) as Button;
if (btn != null)
{
if (!string.IsNullOrEmpty(btnText))
btn.Text = btnText;
else
btn.Visible = false;
}
}
Button click handler
protected void btn_ClickHandler(object sender, EventArgs e)
{
// Do whatever you like
}
The problem is that you generate dynamic buttons on your asp page, the ids generated have to be the same over the lifetime of the page. If not the button can not be found at serverside on a postback. The cause can be that you build your table multiple times in the request handling.

Why are components of my custom control not initiated?

I'm making a GenericTable as a custom implementation of GridView that will display the values of any list of objects that's inserted.
To use the control on an aspx page it needs to be a UserControl, so the GridView is included as a component in the GenericTable:
<%# Control Language="C#" AutoEventWireup="true" CodeBehind="GenericTable.ascx.cs" Inherits="CASH.WebApplication.Controls.GenericTable" %>
<div style="width: 100%; overflow: scroll">
<asp:GridView ID="grid" runat="server"></asp:GridView>
</div>
This works fine for the first use of my control, it's added on the aspx page. It seems that doing that adds some sort of magic that initiates the control components.
When the user clicks on an item that has properties of it's own, the GenericTable should insert a row below the current row and spawn a new GenericTable that will show said properties. table is the DataTable that I use to set the GridView contents:
var data = table.NewRow();
var child = new GenericTable();
data[0] = child;
table.Rows.InsertAt(data, row);
grid.DataSource = table;
grid.DataBind(); // The extra row is displayed now, initialize components in the aspx code?
child.MakeTable(); // Throws exception because it's `grid` property is null.
When I try to activate the newly made GenericTable, after this code, it's grid is null.
Is there a way to initialize the same magic that happens when this control is located in the aspx code?
Update: Maybe the problem lies in how the table is stored between postbacks, currently I'm using the session, maybe there's a better way to remember user input?
The whole GenericTable code:
using Project.DomainModel.Models;
using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace CASH.WebApplication.Controls
{
public partial class GenericTable : UserControl
{
private PropertyInfo[] properties;
//private GridView gridView;
private DataTable table = new DataTable();
private Dictionary<int, int> ingedrukt = new Dictionary<int, int>();
protected void Page_Init(object sender, EventArgs e)
{
grid.RowCommand += WeergaveDossiers_RowCommand;
}
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
for (int i = 0; i < grid.Rows.Count; i++)
{
grid.Rows[i].Cells[0].ColumnSpan = 0;
}
}
else
{
properties = (PropertyInfo[])Session["properties"];
table = (DataTable)Session["table"];
ingedrukt = (Dictionary<int, int>)Session["ingedrukt"];
foreach (var knop in ingedrukt)
{
DetailRijToevoegen(knop.Key, knop.Value);
}
}
grid.DataBind();
}
protected void SaveInSession()
{
Session["properties"] = properties;
Session["table"] = table;
Session["ingedrukt"] = ingedrukt;
}
protected void WeergaveDossiers_RowCommand(object sender, GridViewCommandEventArgs e)
{
int row = int.Parse((string)e.CommandArgument) + 1;
int col = GetKolomIndex(e.CommandName) + 1;
if (ingedrukt.ContainsKey(row))
{
if (ingedrukt[row] != col)
{
//DetailRijVerwijderen(row);
//ingedrukt.Remove(row);
//ingedrukt[row] = col;
}
}
else
{
ingedrukt[row] = col;
}
//DetailRijToevoegen(row, col);
SaveInSession();
}
protected void DetailRijToevoegen(int row, int col)
{
var data = table.NewRow();
var child = new GenericTable();
child.grid = new GridView();
data[0] = child;
table.Rows.InsertAt(data, row);
grid.DataSource = table;
grid.DataBind();
var cells = grid.Rows[row].Cells;
// Only keep the first cell
while (cells.Count > 1)
{
cells.RemoveAt(1);
}
child.MaakTable(new List<object>() { table.Rows[row][col] });
grid.Columns[0].Visible = true;
grid.Rows[row].Cells[0].ColumnSpan = table.Columns.Count;
}
protected void DetailRijVerwijderen(int row)
{
}
protected int GetKolomIndex(string naam)
{
for (int i = 0; i < properties.Length; i++)
{
if (properties[i].Name == naam)
{
return i;
}
}
throw new InvalidDataException("Kolom naam " + naam + " niet bekend");
}
public void MaakTable(IEnumerable<object> data)
{
properties = data.First().GetType().GetProperties().Where(p => p.CanRead).ToArray();
grid.AutoGenerateColumns = false;
var details = new BoundField();
details.DataField = "Details";
grid.Columns.Add(details);
table.Columns.Add(new DataColumn("Details", typeof(object)));
foreach (var veld in properties)
{
table.Columns.Add(new DataColumn(veld.Name, (veld.Name == "Id" ? typeof(object) : veld.PropertyType)));
grid.Columns.Add(MaakKolom(veld));
}
foreach (var entry in data)
{
var row = table.NewRow();
int col = 0;
foreach (var veld in properties)
{
row[++col] = veld.GetValue(entry);
}
table.Rows.Add(row);
}
grid.DataSource = table;
SaveInSession();
}
protected DataControlField MaakKolom(PropertyInfo veld)
{
DataControlField field;
if (typeof(Entity).IsAssignableFrom(veld.PropertyType))
{
field = new ButtonField();
((ButtonField)field).DataTextField = veld.Name;
((ButtonField)field).ButtonType = ButtonType.Button;
((ButtonField)field).CommandName = veld.Name;
}
else if (veld.PropertyType == typeof(bool))
{
field = new CheckBoxField();
((CheckBoxField)field).DataField = veld.Name;
}
else if (veld.PropertyType.IsEnum)
{
field = new TemplateField();
//((TemplateField)field).ItemTemplate = (ITemplate)new Label()
//{
// Text = "#DataBinder.Eval(\"" + veld.Name + "\")",
//};
}
else if (veld.PropertyType == typeof(DateTime))
{
field = new TemplateField();
//field.DatePicker = true;
}
else
{
field = new BoundField();
((BoundField)field).DataField = veld.Name;
}
field.HeaderText = veld.Name;
return field;
}
protected void OnRowDataBound(object sender, GridViewRowEventArgs e)
{
if (e.Row.RowType == DataControlRowType.DataRow)
{
}
}
}
}
I'm not exactly sure what you're trying to acheive, I just know that what you're doing is fundamentally wrong (well, in terms of the ASP.NET view of the world...).
You're adding your control to the input data, rather than adding it as a sub-control of the grid.
You're not instantiating the GridView control in the consructor of your control.
Session is absolutely the wrong place to store lots of page-related data.
I'll start with the last point first: If this is data that needs to persist between visits to the site, then you must put it into a database. If this is data that only exists from the time someone logs in, to the time they logout, then yes, Session may be the right place for it. Otherwise, if it's specific to the page and should be discarded when the user visits a different one, then you should either reload it from the database every time or maybe store it in the ViewState.
Next, are all the objects of the same type / do they have the same fields? If so, then the default behaviour (controlled explicitly by AutoGenerateColumns will do the job for you, no extra work required:
<asp:GridView runat="server"
ID="grid"
AutoGenerateColumns="true" />
If the do not have the same columns, then they should be in seperate grids; a GridView is a way of creating an HTTP <table> element. A table element should only contain related data; you wouldn't use a single table for displaying prices of fish and colours of cars. It follows from this, that if you have different tables, with unrelated data, then you should have different data sources... a more simple solution that means you don't need to implement the control you're trying to implement.
Finally, for completeness, when you define a control, you're just creating a class. If you want to be able to instantiate a control in the way you are trying, then you need to make sure that all its data members are a instantiated in the constructor or that any references are guarded by null-reference checks:
if (grid != null)
{
// Do stuff with grid
}
Try using the Static datatable option and update the datatable first and rebind it to the grid without loosing data.
I'm not seeing how your gridview "grid" is connected to your "child" custom control. It won't have the grid property if your don't have them linked together?
You could be doing this behind the scenes in something in your custom control but that definately needs to be done.
My logic goes: Something you think is defined is null. Did you define it?
I believe what you need to achieve can simply be achieved by creating a class inheriting from the grid view. That way you will get all the features for Grid View and your additional properties without worrying about the implementation details for custom control.
If you still think you need to create a custom control then, post the code for your custom control that will help understand the issue.
If you do not wish to post the code for some reason then visit this link where I answered a question related custom control only. I think you would be able to find a solution to your problem there.
Hope this helps.

Clear grid with static and dynamic columns

I have a grid where I am adding a couple of columns into the template field through the aspx page.
<asp:TemplateField>
<ItemTemplate>
<asp:LinkButton ID="lnkBtnDown" runat="server" CommandName="Download" Text="Download"></asp:LinkButton>
<asp:LinkButton ID="LnkBtnMan" runat="server" CommandName="Manual" Text="Manual"
Style="margin-left: 10px"></asp:LinkButton>
</ItemTemplate>
</asp:TemplateField>
The grid displays search result from a table and there are a few columns that I have to hide, such as the filename column etc, but I do need the data...so I hide them using css styles.
private void generateSearchGrid(DataTable dt)
{
if (dt == null || dt.Rows.Count == 0)
return;
dgvSearchResults.DataSource = dt;
dgvSearchResults.AutoGenerateColumns = false;
BoundField bfName = new BoundField();
bfName.DataField = dt.Columns["OsmProjectName"].ToString();
bfName.HeaderText = "Project Name";
BoundField bfProjID = new BoundField();
bfProjID.DataField = dt.Columns["OsmProjectID"].ToString();
bfProjID.HeaderText = "ID";
BoundField bfProjFile = new BoundField();
bfProjFile.DataField = dt.Columns["OsmProjectFile"].ToString();
bfProjFile.HeaderText = "Project File";
BoundField bfProjManual = new BoundField();
bfProjManual.DataField = dt.Columns["OsmProjectManual"].ToString();
bfProjManual.HeaderText = "Project Manual";
BoundField bfProjType = new BoundField();
bfProjType.DataField = dt.Columns["OsmProjectType"].ToString();
bfProjType.HeaderText = "Project Type";
dgvSearchResults.Columns.Add(bfProjID);
dgvSearchResults.Columns.Add(bfName);
dgvSearchResults.Columns.Add(bfProjType);
// WARNING : Keep these two at the end all the time in the same order.
dgvSearchResults.Columns.Add(bfProjFile);
dgvSearchResults.Columns.Add(bfProjManual);
dgvSearchResults.DataBind();
// Assigning a css where display has been set to none.
bfProjManual.HeaderStyle.CssClass = "hiddenCols";
bfProjID.HeaderStyle.CssClass = "hiddenCols";
bfProjFile.HeaderStyle.CssClass = "hiddenCols";
bfProjManual.ItemStyle.CssClass = "hiddenCols";
bfProjID.ItemStyle.CssClass = "hiddenCols";
bfProjFile.ItemStyle.CssClass = "hiddenCols";
}
At the beginning of every search button click event I refresh the grid by,
dgvSearchResults.DataSource = null;
dgvSearchResults.DataBind();
dgvSearchResults.Columns.Clear();
But this clears all the columns including the ones that I bound in the aspx page..as expected. If I don't clear them, the columns from the search result just keeps piling up.
Is there a way where I can clear only the columns bound dynamically?
If you know how many you've added in the aspx page and how many you add dynamically you could do this.
//if total added in aspx page is 2, first one is in index 0, second in index 1
dgvSearchResults.Columns.RemoveAt(2); // this will remove the 3rd column
dgvSearchResults.Columns.RemoveAt(3); // this will remove the 4th columns
// you can continue till column x
One option is to disable viewstate of dgvSearchResults GridView. In this case, every postback, you need to bind dgvSearchResults with data. If you will not bind, dynamically added columns will be removed automatically (not persisted).
This will happen because in your case, dynamically added data (and also columns) are kept postback to postback in hidden element (viewstate).
So this will become:
private void Page_Load(object sender, EventArgs e)
{
if(IsPostBack) // If postback, then bind GridView with previous search result
{
dgvSearchResults.DataSource = Session["dgvSearchResultsData"];
dgvSearchResults.DataBind();
}
}
private void generateSearchGrid(DataTable dt)
{
// ...
Session["dgvSearchResultsData"] = dt; // Save result into session
dgvSearchResults.DataSource = dt;
// ...
}
ASPX:
<asp:GridView EnableViewState="false"> ... </GridView>

Passing two values through CommandArgument and splitting it

I'm using the commandArgument property of the LinkButton ( Which is wrapped inside a repeater ) to pass two values -as one string- to a second repeater then I try to split them into two values, So I could use them as parameters in my ADO.NET Code (SqlCommand Parameters)....after testing my queries don't return any results but If I passed fixed values for the parameter or change the source of the parameter (just for test from a textbox or querystring or something) I get my results, so I think the problem is in splitting.
I Conduct some arugment values from the ArgumentCommand property of the LinkButton -which is wrapped inside a repeater:
<ItemTemplate>
<asp:LinkButton id="sort_lnkbtn" Text='<%# Eval("value")%>'
CommandArgument='<%#string.Format("{0}|{1}",Eval("arrange_by_id"),Eval("value"))%>' runat="server">
</asp:LinkButton>
</ItemTemplate>
Then I receive these values and cut them into two pieces of information:
string sortByAndArrangeBy = (e.CommandArgument).ToString();
char[] separator = { '|' };
string[] sortByAndArrangeByArray = sortByAndArrangeBy.Split(separator);
Now the ado.net code uses this values as a
using (SqlConnection cn1 = new SqlConnection(ConfigurationManager.ConnectionStrings["testConnectionString"].ConnectionString))
{
using (SqlCommand cm1 = new SqlCommand("SELECT [name] FROM brands WHERE (name like #SearchString + '%' )", cn1))
{
cm1.Parameters.Add("#SearchString", System.Data.SqlDbType.Char);
cm1.Parameters["#SearchString"].Value = sortByAndArrangeByArray[1];
cn1.Open();
using (SqlDataReader dr1 = cm1.ExecuteReader())
{
List_rpt.DataSource = dr1;
List_rpt.DataBind();
}
}
}
Here is a simple solution:
Wrap your item template for the repeater in a control. The control will have the same markup as your item template without the bindings:
Control Markup:
<div>
<asp:LinkButton ID="LnkBtnSort" runat="server" Text="Sort" OnClick="LnkBtnSort_Clicked"/>
</div>
Control Code:
public class SomeControl
{
public event EventHandler Click;
public string ArrangeById
{
set { ViewState["byid"] = value; }
get { return ViewState["byid"].ToString(); }
}
public string Value
{
set { ViewState["val"] = value; }
get { return ViewState["val"].ToString(); }
}
protected void LnkBtnSort_Clicked(object sender, EventArgs e)
{
if( Click != null )
{
this.Click(this, EventArgs.Empty);
}
}
}
So now in the repeater all you have to do is bind an instance of that control to the Container.DataItem:
<ItemTemplate>
<ctrl:SomeControl
ID="someControl"
runat="server"
OnClick="SomeControl_Clicked"
ArrangeById='<%# Eval("arrange_by_id") %>'
Value='<%# Eval("value") %>' />
</ItemTemplate>
The page/control that has the repeater will have one simple method:
protected void SomeControl_Clicked(object sender, EventArgs e)
{
//Here cast the sender to the type of control you made:
SomeControl ctrl = (SomeControl)sender;
string byId = ctrl.ArrangeById;
string val = ctrl.Value;
}
Note: this code may not be 100% correct but it illustrates the point. The flow is simple - the control binds its public properties to whatever you need to bind. When the link is clicked (inside your control) the control doesn't propagate this event to the page. Instead it fires its own event (Click) thus sending a signal to the page that an event occured. however by doing so, it changes the source of the event to itself instead of the actual link button. The page handles the event and everyone is happy.
This way you don't have to care what the CommandArgument is... If this comes up empty, it means that either your data source is empty... or something else happened in the code.

Categories

Resources