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.
Related
how do you properly call methods inside custom binding expressions? Are there complications because the dropdownlist is inside a detailsview?
asp.net code:
<asp:DropDownList ID="ddlExceptionEditStatus" runat="server"
DataSourceID="odsExceptionsStatus"
DataTextField="Name"
DataValueField="StatusID"
SelectedValue='<%# Bind("StatusID") %>'
BackColor="<%# SetBackColorProp(Container.DataItem) %>">
</asp:DropDownList>
code behind:
protected System.Drawing.Color SetBackColorProp(object o)
{
System.Drawing.Color statusColor = System.Drawing.Color.White;
string statusName = o as string;
if (statusName != null)
{
statusColor = System.Drawing.ColorTranslator.FromHtml(FISBLL.StatusColors.GetColor(statusName));
return statusColor;
}
else
{
return statusColor;
}
}
Doesn't change the backcolor. but doesn't throw an exception.
So, I had two mistakes:
1) I needed to cast the Container.DataItem to the class object i was using for the ObjectDataSource. After casting, the BackColor for each item in the dropdownlist matched the StatusID of the casted Container.DataItem.
2) Unfortunately this gave all the items the same color, where as I wanted each item's color to reflect the their own value attached to the dropdownlist. This is because the dropdownlist has an objectdatasource outside the DetailsView that it's inside of. Therefor the selectedValue item of the dropdownlist dictated the colors for all the other items.
I decided to go with Tim's suggestion and tie the BackColor setting for each item in the databound event:
protected string GetColor(string name)
{
return FISBLL.StatusColors.GetColor(name);
}
protected void ddlExceptionEditStatus_DataBound(object sender, EventArgs e)
{
foreach (ListItem item in ((DropDownList)sender).Items)
{
item.Attributes.Add("style", "background-color:" + GetColor(item.Text));
}
}
And the correct behavior is shown:
I have a FormView and I need to access some Divs and other controls that are inside it. My apsx code looks similar to this:
<asp:FormView ID="Edit_FV" runat="server" DataKeyNames="IDproceso" DefaultMode="Edit" DataSourceID="SqlDS_Procesos">
<EditItemTemplate>
<div id="second_info" runat="server">
<div id="second_info_left" runat="server">
<div id="alcance" class="report_field" runat="server">
<p class="container-title">
Alcance:</p>
<asp:TextBox ID="TextBox14" runat="server" TextMode="multiline" Width="400px" Height="120px" Text='<%# Bind("alcance") %>' />
</div>
</div>
<div id="second_info_right" runat="server">
<div class="valores-container" id="tipo_ahorro" runat="server">
<asp:CheckBox ID="ahorro_state" runat="server" Checked='<%# Bind("tipo_ahorro") %>' />
</div>
</div>
</EditItemTemplate>
</asp:FormView>
Now, say I want to access the CheckBox with id = ahorro_state, I tried with Edit_FV.FindControl("ahorro_state") and got a Null reference. I also tried with Edit_FV.FindControl("MainContent_Edit_FV_ahorro_state") because this is how the ID actually gets named in the final HTML document, but I got a Null reference too. The same happened when I tried accessing any of the divs (with IDs second_info,tipo_ahorro, etc..). I feel I'm doing a dumb mistake but I looked around a bit and haven't found and answer.
Any ideas how to solve this?
EDIT: Added Code where I'm calling FindControl.
I tried both calling DataBind() from the Page_Load():
protected void Page_Load(object sender, EventArgs e)
{
DataBind();
if (Edit_FV.CurrentMode == FormViewMode.Edit)
{
Control c = Edit_FV.FindControl("ahorro_state");//c is null here.
}
}
And also tried setting the OnDataBound attribute of Edit_FV: OnDataBound="onBound"
protected void onBound(object sender, EventArgs e)
{
if (Edit_FV.CurrentMode == FormViewMode.Edit)
{
ControlCollection a = Edit_FV.Controls;
Control c = Edit_FV.FindControl("ahorro_state");//c is null here
}
}
Although the default mode is set "Edit", the form view won't switch to that mode until the control is DataBound. Try calling DataBind() first, then use FindControl using the ID of your element (not the ClientID, as you tried in your second example).
See FormView.FindControl(): object reference error for examples of where to put your FindControl logic.
EDIT:
There is also the possibility that your data source is not returning any data. This will result in the EditItemTemplate being empty which might explain your null reference errors. Try checking for a Edit_FV.DataItemCount > 0 before switching into Edit mode.
I have had similar problems with 'FindControl'. I found a piece of code that has helped me a) Find controls recursively, and b) the debug statement has been very help to see why I am not finding the control in question. To help me find the controls I have to give them ID values when I am looking for them if they don't have one by default:
public static class General_ControlExtensions
{
//From: http://www.devtoolshed.com/content/find-control-templatefield-programmatically
/// <summary>
/// recursively finds a child control of the specified parent.
/// USAGE:
/// Control controlToFind = DetailsView1.fn_ReturnControl_givenControlID("txtName");
/// </summary>
/// <param name="rootControl"></param>
/// <param name="ID"></param>
/// <returns></returns>
public static Control fn_ReturnControl_givenControlID(this Control rootControl, string ID)
{
if (rootControl.ID == ID)
{
return rootControl;
}
foreach (Control control in rootControl.Controls)
{
Debug.WriteLine("FindByID - child.id: " + control.ID);
Control foundControl = fn_ReturnControl_givenControlID(control, ID);
if (foundControl != null)
{
return foundControl;
}
}
return null;
}
Here is an example of its usage:
using System.Diagnostics; // for debug
TextBox txt_LastName = (TextBox)fv_NewHire_DetailsForm.fn_ReturnControl_givenControlID("INSERT_txt_LastName");
In addition, I have found it helpful for this type of problem to preface the controls in the 'insertitemtemplate' with 'INSERT_", and controls in the 'edititemtemplate' with 'EDIT_" to quickly tell them apart in the debug output.
I have a custom DropDownList control:
<cc1:CountriesControl ID="DdlCountry" TabIndex="69" runat="server" DefaultCountry="USA" OnSelectedIndexChanged="DdlCountryIndexChanged"
CssClass="DefaultDropdown" AutoPostBack="true" />
The DropDownList has a custom property called DefaultCountry. As you can see, the default value is set to "USA". However in my subclass, DefaultCountry is always null.
How do I get it to set the DefaultCountry to what is in the ASP.NET markup?
[DefaultProperty("Text"), ToolboxData("<{0}:CountriesControl runat=server></{0}:CountriesControl>")]
public class CountriesControl : DropDownList
{
[Bindable(true), Category("Appearance"), DefaultValue("")]
private String defaultCountry;
[
Category("Behavior"),
DefaultValue(""),
Description("Sets the default country"),
NotifyParentProperty(true)
]
public String DefaultCountry
{
get
{
return defaultCountry;
}
set
{
defaultCountry = value;
}
}
public CountriesControl()
{
this.DataSource = CountriesDataSource();
this.DataTextField = "CountryName";
this.DataValueField = "Country";
this.SelectedIndex = 0;
this.DataBind();
// DefaultCountry is always null?
this.Items.Insert(0, new ListItem(this.DefaultCountry, "--"));
}
// more code here
}
you need to use Selected property to true for the item which you want to set it as default. look here for examplesDropdownlist
// this is the normal syntax. to get the default value for dropdownlist
<asp:DropDownList ID="DropDownList1" runat="server" width="145px">
<asp:ListItem Text="SomeText" Value="SomeValue" Selected="true"></asp:ListItem>
</asp:DropDownList>
but in your case. you may try like this, i m not sure, but a guess.
this.Selected=true
The solution was to not bind the data in the constructor, but call it from the code behind on my page. It appears that the value of the attributes (e.g. #DefaultCountry) is not set until the RenderControl method is called on the control.
I have an <ItemTemplate> with an asp:LinkButton in, and I want to set the property Enabled to be true or false depending on a property I Eval(), something like this
<asp:LinkButton ID="btnEdit" runat="server" Text="Edit" CommandName="Edit"
Enabled='<%# (int?)Eval("KittenFriendlyStatus") == (int)KittenEnum.Awwwww %>' />
However my syntax is completely wrong, can anyone straighten it out for me, or suggest a better way of doing this? I've never done it with C# before, only Javascript.
You cannot use data-binding syntax on server-controls. To solve this problem, you need to find an event of the template control that happens when it binds. It will depend on the control. So, let's say the template control has a ItemDataBound event that passes the object beind bound. Add an event handler like this:
public void Control1_ItemDataBound(Control sender, Object data, EventArgs args) {
sender.FindControl("btnEdit").Enabled = (int?)DataBinder.Eval(data, "KittenFriendlyStatus") == (int)KittenEnum.Awwwww;
}
should be like...
Enabled='<%# (int?)Eval("KittenFriendlyStatus") == (int)KittenEnum.Awwwww ? true : false %>'
In your instance, #Chiwee's suggestion of using the ItemDataBound event is likely what you need.
If you need to provide the ability to manage this in multiple places, you can use a property to manage things cleanly like this:
protected bool EnableEdit
{
get { return btnEdit.Enabled; }
set { btnEdit.Enabled = value; }
}
or if you need to manage multiple buttons at once:
protected bool EnableEdit
{
set
{
btn1.Enabled = value;
btn2.Enabled = value;
//...
}
}
One way I use often is a function in code-behind to set property like:
Enabled=='<%# DoEnable(Eval("KittenFriendlyStatus"))%>'
//in code-behind add this function:
public bool DoEnable(object status)
{
bool enable = //decide here
return enable;
}
I am trying to add a template to a simplified composite control containing a Label and a TextBox. I want my mark up to look something like this:
<test:FormItem ID="fi" runat="server" Title="MyTitle" Text="My Text!">
<TestTemplate>
<i>
<%# Container.Title) %></i>
<br />
<%# Container.Text %>
</TestTemplate>
</test:FormItem>
I have a templateContainer class that has properties for the TextBox and Label.
public class TemplateContainer : WebControl, INamingContainer
{
public TextBox Text { get { return m_item.Text; } }
public Label Title { get { return m_item.Title; } }
private FormItem m_item;
public TemplateContainer(FormItem item)
{
m_item = item;
}
}
In the main FormItem class I have a CreateControlHierarchy() method that is being called from CreateChildControls():
protected virtual void CreateControlHierarchy()
{
m_itemTemplateContainer = new TemplateContainer(this);
TestTemplate.InstantiateIn(m_itemTemplateContainer);
Controls.Add(m_itemTemplateContainer);
}
What I WANT is for the Template to render the actual control. Instead, it's calling ToString() on the control and displaying System.Web.UI.WebControls.Label and System.Web.UI.WebControls.TextBox. Is there a way to make the template add the controls to it's collection instead of just calling ToString() on them?
Note: I've also tried adding the textbox and label to the controls collection of the container which does the same thing.
Ok. So I tried a few things and I came up with an OK solution.
First, I tried to use methods in the data binding expression and then keep track of where in the container's Control collection the textbox or label would go. However, the CompiledTemplateBuilder (which is what .Net internally builds for ITemplates specified in mark up) put all of the markup before and after both binding expressions into one DataBoundLiteral control and the Control collection was already built when the method was called.
What did work was to create a new WebControl which serves as a place holder for the controls within the composite control. It has one property Control and when set, it add the control to it's Controls Collection.
public class FormItemPlaceHolder : WebControl, INamingContainer
{
public WebControl Control
{
get
{
if(Controls.Count == 0)
return null;
return Controls[0] as WebControl;
}
set
{
if (Controls.Count != 0)
Controls.Clear();
Controls.Add(value);
}
}
}
Then in the mark up, I create a control of this type and bind it's Control property to the correct property in the container.
<test:FormItem ID="fi" runat="server" Title="MyTitle" Text="My Text!">
<TestTemplate>
<i>
<test:FormItemPlaceHolder ID="ph" runat="server"
Control='<%# Container.Title %>' />
</i>
<br />
<test:FormItemPlaceHolder ID="ph2" runat="server"
Control='<%# Container.Text %>' />
</TestTemplate>
</test:FormItem>
Does anyone have a better solution?
The container should not define the controls, just the data.
It is in the markup that you should define the actual controls of the data, and assign them the values in from the container.
E.g.
public class TemplateContainer : UserControl
{
public string Text { get { return m_text; } }
public string Title { get { return m_title; } }
private string m_text;
private string m_title;
private FormItem m_item;
public TemplateContainer(FormItem item)
{
m_item = item;
}
}
And in the markup:
<test:FormItem ID="fi" runat="server" Title="MyTitle" Text="My Text!">
<TestTemplate>
<i><asp:Label runat="server" Text='<%# Container.Title) %>' /></i>
<br />
<asp:TextBox runat="server" Text='<%# Container.Text %>' />
</TestTemplate>
</test:FormItem>
If you are trying to create a composite control that does not require controls to be added in the markup, then why are you using a Template? If it is just for styling then perhaps creating your own Style object may be more effective?