How do I wrap header text if my data source is a DataTable? - asp.net

I have a GridView whose data source is built dynamically as a DataTable - how can I specify column-specific header wrapping considering I'm specifying the structure in code?
I've not found anything to deal with this specific situation as I need to wrap only some columns in specific places, e.g. wrapping the second column below after 'Long' but leaving others alone. Adding \n or <br /> don't work as they're just treated as literals.
var statsTable = new DataTable();
statsTable.Columns.Add("Run Date", typeof(DateTime));
statsTable.Columns.Add("Needlessly Long Test Header", typeof(string));
...etc
statsTable.Rows.Add(runDate, "example", ...)
gridView.DataSource = statsTable;
gridView.DataBind();
Not sure if this is relevant, but I've found that I need to keep AutoGenerateColumns = true on my GridView otherwise nothing shows up. This is confusing me as I thought specifying the columns would do the trick - if this is unrelated to this question I'll ask another later.
Using .Net 3.5, if that affects answers. It seems like it'd be a simple/common problem.

You could use a custom class to achieve that:
class CustomDataRow
{
public string ColumnHeader { get; set; }
public string ColumnName { get; set; }
public string ColumnValue { get; set; }
}
Then, instead of a DataTable, you could use a List to bind the grid. Then, in the ItemDataBound event you could cast the DataItem to a CustomDataRow. If e.Item.ItemType is header, set the header text. If it's an item, set the Text values.

Give something like this a shot:
Markup:
<asp:TemplateField>
<HeaderTemplate>
<%#HttpUtility.HtmlDecode(InsertBreaks(Eval("DataField")))%>
</HeaderTemplate>
</asp:TemplateField>
With a LiteralControl:
<asp:TemplateField>
<HeaderTemplate>
<asp:Literal ID="litHeader" Text='<%#HttpUtility.HtmlDecode(InsertBreaks(Eval("DataField")))%>' Mode="PassThrough"></asp:Literal>
</HeaderTemplate>
</asp:TemplateField>
Code-behind:
protected string InsertLineBreaks(string val)
{
return val.Replace("long", "long<br/>").replace("foo", "foo<br/>");
}

Related

Custom Gridview

I have Gridview and I want to retrieve some Information from data base(SQL). and according to them, show typical information. for example: I read "Meal" field from DB, but I want to show lunch in my gridview. What should I do?
You can use a TemplateField that change the values as you wish, for example:
<asp:TemplateField HeaderText="Infos">
<ItemTemplate ><%#GetTypicalInfo(Container.DataItem)%></ItemTemplate>
</asp:TemplateField>
and on code behind:
protected string GetTypicalInfo(object oItem)
{
string cInfo = DataBinder.Eval(oItem, "cDataField").ToString();
switch (cInfo)
{
case "Meal":
return "lunch";
default:
return cInfo;
}
}

Binding a nullable int to an asp:TextBox

I have a property int? MyProperty as a member in my datasource (ObjectDataSource). Can I bind this to a TextBox, like
<asp:TextBox ID="MyTextBox" runat="server" Text='<%# Bind("MyProperty") %>' />
Basically I want to get a null value displayed as blank "" in the TextBox, and a number as a number. If the TextBox is blank MyProperty shall be set to null. If the TextBox has a number in it, MyProperty should be set to this number.
If I try it I get an exception: "Blank is not a valid Int32".
But how can I do that? How to work with nullable properties and Bind?
Thanks in advance!
Well i've found a solution, which includes a FormView however and you don't specify if that fits your scenario.
Anyhow, in my case the DataBound-ed entity is a dto of my own (not that it should matter) and the trick is that when you update the formview you have to essentially attach on the pre-databound event and re-write the empty string as a null value so that the framework can property inject the value to the constructed object:
protected void myFormView_Updating(object sender, FormViewUpdateEventArgs e)
{
if (string.Empty.Equals(e.NewValues["MyProperty"]))
e.NewValues["MyProperty"] = null;
}
and similarly on insert
protected void myFormView_Inserting(object sender, FormViewInsertEventArgs e)
{
if (string.Empty.Equals(e.Values["MyProperty"]))
e.Values["MyProperty"] = null;
}
what makes this really fun is the fact that the error message ( is not a valid Int32) is actually wrong and it should write ( is not a valid Nullable) but then nullables would have been first class citizens wouldn't they?
I'm starting to believe it's not possible to bind a nullable value property. Til now I only can see the workaround to add an additional helper property to bind a nullable type:
public int? MyProperty { get; set; }
public string MyBindableProperty
{
get
{
if (MyProperty.HasValue)
return string.Format("{0}", MyProperty);
else
return string.Empty;
}
set
{
if (string.IsNullOrEmpty(value))
MyProperty = null;
else
MyProperty = int.Parse(value);
// value should be validated before to be an int
}
}
and then bind the helper property to the TextBox instead of the original:
<asp:TextBox ID="MyTextBox" runat="server"
Text='<%# Bind("MyBindableProperty") %>' />
I'd be happy to see another solution.
<asp:TextBox ID="MyTextBox" runat="server"
Text='<%# Bind("MyProperty").HasValue ? Bind("MyProperty") : "" %>' />
You could use HasValue to determine if the nullable type is null, and then set the Text property.

problem binding gridview 's bound columns datafield using the column name of my datatable

I'm binding my gridview's bound columns with datafield using the column name of my datatable. The problem is we have a scenario we need to put in a text where the datafield was int with value 0. I couldn't see any work around. Is there any easy way to do this?
If you don't like to use inline code in your aspx pages as David has suggested make a template with a literal control in it and implement the OnDataBinding event:
For example in your grid have the following template for your field:
<asp:TemplateField HeaderText="Your Header Name">
<ItemTemplate>
<asp:Literal runat="server" ID="litYourCustomField" OnDataBinding="litYourCustumField_DataBinding"></asp:Label>
</ItemTemplate>
</asp:TemplateField>
Then you implement the OnDataBinding in your code behind:
protected void litYourCustomField_DataBinding(object sender, System.EventArgs e)
{
Literal lit = (Literal)(sender);
int yourInt = Convert.ToInt32(Eval("YourNumber"));
lit.Text = (yourInt == 1) ? "It's a 1" : "It's something else";
}
I prefer this method to the inline code since it puts no code in your aspx pages. I usually have a #region defined in my .cs file that has all by databinding code. I am pretty sure performance wise they will be pretty much identical except for maybe the overhead of the literal control if you have the viewstate enable. Make sure to turn off viewstate when you don't need it.
If this is ASP.Net, you can make this a Template column and do the following:
<ItemTemplate>
<%# MyConversionFunction(Convert.ToInt32(DataBinder.Eval(Container.DataItem, "IntegerFieldName"))) %>
</ItemTemplate>
protected string MyConversionFunction(int ValueToCheck)
{
if(ValueToCheck.ToString() == "0")
{
return "SomeText";
}
else
{
return SomeValue.ToString();
}
}

How to set the RowStyle of a GridView row depending on a property of the Object that the row is being bound to

I'm currently using a GridView and I want to set the CssClass for the Row depending on a property of the object that the row is being bound to.
I tried the following but it does not work (see comments):
<asp:GridView id="searchResultsGrid" runat="server" AllowPaging="true" PageSize="20" AutoGenerateColumns="false">
<!-- The following line doesn't work because apparently "Code blocks
aren't allowed in this context: -->
<RowStyle CssClass="<%#IIF(DataBinder.Eval(Container.DataItem,"NeedsAttention","red","") %>
<Columns>
<!--............-->
</Columns>
</asp:GridView>
Now I could simply handle the GridView's RowDataBound event and change the css class of the row there...but I'm trying to keep a clear separation between the UI and the page/business logic layers.
I have no idea how to accomplish this and I'm looking forward to hearing any suggestions.
Thanks,
-Frinny
You cannot do this in declarative markup.
Nearly all of GridView's declarative properties (including GridView.RowStyle) are grid-level settings rather than row-level. Apart from TemplateFields , they are not bound data containers, so they don't have access to the data in their rows.
If you want to keep this logic in the .aspx template, your only real option is to use template fields and manipulate their contents:
<asp:TemplateField>
<ItemTemplate>
<span class="<%# ((string)Eval("property3")) == "NeedsAttention" ? "red" : string.Empty %>">
<%# Eval("property1") %>
</span>
</ItemTemplate>
</asp:TemplateField>
Depending on what you want to do, this may be awkward - you don't have access to the containing <td> (or <tr> for that matter) and you'll have to repeat the formatting for each cell.
The GridView class goes to a lot of lengths to hide the details of HTML and styling from you. After all you could create a GridView control adapter that wouldn't even render as HTML tables. (Unlikely though that may be.)
So even though you're trying to avoid it, you're probably best off dealing with this in a OnRowDataBound handler - or use a Repeater (if that's appropriate).
I know it has been almost a year, but if anyone else is trying this, try to subclass the GridView.
public class GridViewCSSRowBindable : GridView
{
public string DataFieldRowCSSClass { get; set; }
protected override void OnRowDataBound(GridViewRowEventArgs e)
{
base.OnRowDataBound(e);
if (!string.IsNullOrEmpty(DataFieldRowCSSClass))
{
//This will throw an exception if the property does not exist on the data item:
string cssClassString = DataBinder.Eval(e.Row.DataItem, DataFieldRowCSSClass) as string;
if (!string.IsNullOrEmpty(cssClassString))
{
string sep = string.IsNullOrEmpty(e.Row.CssClass) ? string.Empty : " ";
e.Row.CssClass += sep + cssClassString;
}
}
}
}
And then in your Page:
<custom:GridViewCSSRowBindable ID="gvExample" runat="server" DataFieldRowCSSClass="RowCSS">
</custom:GridViewCSSRowBindable>
The objects being bound to this example GridView should have a public string RowCSS property.
If you haven't used inherited controls before, you might have to look up how to set that up in your project.
foreach (TableCell gvc in gvRowPhistry.Cells)
{
gvc.ForeColor = System.Drawing.Color.Blue;
}

Getting an object back from my GridView rows

Basically, i want my object back...
I have an Email object.
public class Email{
public string emailAddress;
public bool primary;
public int contactPoint;
public int databasePrimaryKey;
public Email(){}
}
In my usercontrol, i a list of Email objects.
public List<Email> EmailCollection;
And i'm binding this to a GridView inside my usercontrol.
if(this.EmailCollection.Count > 0){
this.GridView1.DataSource = EmailCollection;
this.GridView1.DataBind();
}
It would be really awesome, if i could get an Email object back out of the GridView later.
How do i do this?
I'm also binding only some of the Email object's properties to the GridView as well and they're put into Item Templates.
<Columns>
<asp:TemplateField HeaderText="Email Address">
<ItemTemplate>
<asp:TextBox ID="TextBox1" runat="server" Text=<%# Eval("EmailAddress") %> Width=250px />
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Primary">
<ItemTemplate>
<asp:CheckBox runat="server" Checked=<%# Eval("PrimaryEmail") %> />
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Contact Point">
<ItemTemplate>
<CRM:QualDropDown runat="server" Type=ContactPoint InitialValue=<%# Eval("ContactPoint") %> />
</ItemTemplate>
</asp:TemplateField>
</Columns>
Can GridView even do this? Do i need to roll my own thing? It'd be really cool if it would do it for me.
To elaborate more.
I am saving the List collection into the viewstate.
What I'm eventually trying to get to, is there will be a Save button somewhere in the control, which when the event fires I'd like to create an Email object from a datarow in the GridView which to compare to my original List collection. Then if there's a change, then I'd update that row in the database. I was thinking that if I could put a List collection into a GridView, then perhaps I could get it right back out.
Perhaps I create a new constructor for my Email object which takes a DataRow? But then there's a lot of complexities that goes into that...
ASP.NET Databinding is a one-way operation in terms of object manipulation. However, the DataSource property will contain a reference to your EmailCollection throughout the response:
EmailCollection col = (EmailCollection)this.GridView1.DataSource;
But I have a feeling that what you really want is a control that manipulates your EmailCollection based on user input and retrieve it in the next request. Not even webforms can fake that kind of statefulness out of the box.
Well I ended up looping through my List EmailCollection, which was saved into the ViewState.
So in the page, a Save button is clicked, when the event is caught, I loop through my List Collection and grab the row from the GridView by index.
On the GridViewRow I have to use a GridView1.Rows[i].Cells[j].FindControl("myControl1") then get the appropriate value from it, be it a check box, text box, or drop down list.
I do see that a GridViewRow object has a DataItem property, which contains my Email object, but it's only available during the RowBound phase.
Unfortunately If/When i need to expand upon this Email Collection later, by adding or removing columns, it'll take a few steps.
protected void SaveButton_OnClick(object sender, EventArgs e){
for (int i = 0; i < this.EmailCollection.Count; i++)
{
Email email = this.EmailCollection[i];
GridViewRow row = this.GridView1.Rows[i];
string gv_emailAddress = ((TextBox)row.Cells[0].FindControl("EmailAddress")).Text;
if (email.EmailAddress != gv_emailAddress)
{
email.EmailAddress = gv_emailAddress;
email.Updated = true;
}
...
}
}
I'd still be open to more efficient solutions.
Just a thought, basically a roll your own but not that tricky to do:
Store the list that you use as a datasource in the viewstate or session, and have a hidden field in the gridview be the index or a key to the object that matches the row.
In other words, each row in the gridview "knows" which email object in the list that it is based on.
If you want to hold onto an object like this its easiest to use the viewstate, although you will be duplicating the data but for a small object it should be ok.
ViewState.Add("EmailObj", Email);
EMail email = (Email)ViewState["EmailObj"];

Resources