Inherit from DropDownList and add a Validator, possible? - c#

I've already created my custom textbox control inheriting from it and all is fine and dandy. However, I'm having an issue trying the same thing with a DropDownList. I've searched for about 2 hours in Google and all the results I get are either crappy links or some (uncomplete) suggestions on creating a composite control and adding the DropDownList inside, but that also means that I have to expose all the events and properties that I use, which I find pretty overkill for what I need to do, which is to add a validator of any kind next to my DropDownList control.
To illustrate, this is what I'm attempting to do:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI.WebControls;
namespace Blah
{
public class ExtendedDropDownList : DropDownList
{
private DropDownList _self;
private CustomValidator _cv;
public bool Required { get; set; }
public String FieldName { get; set; }
protected override void OnInit(EventArgs e)
{
_cv = new CustomValidator()
{
ControlToValidate = ID,
EnableClientScript = false,
ValidationGroup = ValidationGroup,
ValidateEmptyText = true
};
_cv.ServerValidate += new ServerValidateEventHandler(_cv_ServerValidate);
Controls.Add(_cv);
}
private void _cv_ServerValidate(object source, ServerValidateEventArgs args)
{
if (Required && String.IsNullOrWhiteSpace(args.Value))
{
args.IsValid = false;
_cv.ErrorMessage = "The field <strong>" + FieldName + "</strong> is required.";
return;
}
}
}
}
And it throws an exception that the DropDownList cannot have secondary controls. How come? if the TextBox allows it?
Is there any way to do this same thing without creating the composite control and rewriting the wheel? (yes, pun intended :P). I assume I could get away with creating the control and then writing it in the rendering phase AFTER the DropDownList is rendered, but I can't find out how to do it and if it's even possible (though a hack, I'm short on time on a form generator I need to finish and this is taking too long and making me feel really tired :(... you guys know I come to SO when I've used up all the resources available).
Thanks in advance! :D

Okay, I managed to do it the way I wanted, but with a bit of help from the container's page:
I had to add this to my aspx page:
<asp:PlaceHolder runat="server" ID="phValidators" />
And then here's the finished control, which adds the validator to the Page's validators placeholder I created, not the control itself:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI.WebControls;
namespace Blah
{
public class ExtendedDropDownList : DropDownList
{
private CustomValidator _cv;
public bool Required { get; set; }
public String FieldName { get; set; }
public String ValidatorPlaceHolder { get; set; }
protected override void OnInit(EventArgs e)
{
_cv = new CustomValidator()
{
ControlToValidate = ID,
EnableClientScript = false,
ValidationGroup = ValidationGroup,
Display = ValidatorDisplay.None,
ValidateEmptyText = true
};
_cv.ServerValidate += new ServerValidateEventHandler(_cv_ServerValidate);
if (Parent.FindControl(ValidatorPlaceHolder) != null)
Parent.FindControl(ValidatorPlaceHolder).Controls.Add(_cv);
else
throw new Exception("Cannot find asp:PlaceHolder inside parent with ID '" + ValidatorPlaceHolder + "'");
base.OnInit(e);
}
private void _cv_ServerValidate(object source, ServerValidateEventArgs args)
{
if (Required && String.IsNullOrWhiteSpace(args.Value))
{
args.IsValid = false;
_cv.ErrorMessage = "The field <strong>" + FieldName + "</strong> is required.";
return;
}
}
}
}
I hope this helps someone out :)

Related

Winforms How to Bind to a List<long> DatagridView and have it show correctly

So I need to show in a Grid a list of numbers that people enter to help them double check their work. It almost works except it doesn't show the numbers. My setup is simple. I have a textbox, they enter the number, when the add button is clicked its added to a BindingList and then that is used as the datasource for the DataGridView.
So, with some help from this Stackoverflow Post I was able to get this halfway working.
Unfortunately, even though it appears to add a row the Grid each time it does not correctly show the value. It shows the new rows as empty.
Here is my code.
public partial class ManualEntry : Form
{
BindingList<long> ProjectIDs;
public ManualEntry()
{
InitializeComponent();
ProjectIDs = new BindingList<long>();
}
When the add button is clicked, this gets executed.
private void AddButton_Click(object sender, EventArgs e)
{
try
{
long temp = long.Parse(textBox1.Text);
ProjectIDs.Add(temp);
ProjectsGrid.DataSource = ProjectIDs;
textBox1.Text = "";//clear the textbox so they can add a new one.
}
catch//bring up the badinput form
{
BadInput b = new BadInput();
b.Show();
}
}
And so here is the result of adding a few numbers.
If you need any other code from me to help you answer the question, just ask.
You haven't told the DataGridViewColumn what to bind to.
Normally you bind to a public property of the bound data type. In this case your data type is long which does not have an appropriate property to bind to.
Wrap your long in a custom class and expose it as a public property.
public class Data
{
public long Value { get; set; }
}
Bind your column to the Value property. You can do this in the designer, but here is the code:
Column1.DataPropertyName = "Value";
Now instead of long you use Data:
ProjectIDs = new BindingList<Data>();
...
long temp = long.Parse(textBox1.Text);
ProjectIDs.Add(new Data { Value = temp });
The following post discusses this:
DataGridView bound to BindingList does not refresh when value changed
It looks like your data type in the binding list needs to support the INotifyPropertyChanged interface:
http://msdn.microsoft.com/en-us/library/system.componentmodel.inotifypropertychanged.aspx
NOTE: Revised - my use of INotifyPropertyChanged was incorrect and also appears not to be needed.
Now this answer is basically just like Igby's - so I think his is the better one :)
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
ProjectIDs = new BindingList<AwesomeLong>();
var source = new BindingSource( ProjectIDs, null );
dataGridView1.DataSource = source;
dataGridView1.Columns.Add( new DataGridViewTextBoxColumn() );
}
BindingList<AwesomeLong> ProjectIDs;
private int i = 0;
private void button1_Click( object sender, EventArgs e )
{
i++;
ProjectIDs.Add(new AwesomeLong(i));
}
}
public class AwesomeLong
{
public long LongProperty { get; set; }
public AwesomeLong( long longProperty )
{
LongProperty = longProperty;
}
}
}

Simple Custom Web Control

Hello I am designing a custom .net web control that inherits from the aspx dropdownlist.
The idea is to have a dropdownlist that will display year values up until the current year. I want to be able to set a "StartYear" property that the control can start from or else use a default date. I am able to create this control but it is always using the defult date. It seems I am unable to use the property setting in the aspx code in the code behind. My front end code is ....
<customControls:YearDropDownList StartYear="2000" ID="ddlYear" runat="server"/>
and the code behind is
using System;
using System.ComponentModel;
using System.Globalization;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace customControls {
[ToolboxData("<{0}:ServerControl1 runat=server></{0}:ServerControl1>")]
[DefaultProperty("StartYear")]
public class YearDropDownList : DropDownList
{
public YearDropDownList() {
for (int i = Int32.Parse(StartYear); i <= DateTime.Now.Year; i++)
{
this.Items.Add(new ListItem(i.ToString(), i.ToString()));
}
}
public string StartYear {
get{
String s = (String)ViewState["StartYear"];
return ((s == null) ? "2009":s);
}
set{
ViewState["StartYear"] = value;
}
}
}
}
To my way of thinking, it doesn't make sense to regenerate the list in the constructor. At the time the control is instantiated, ViewState probably won't be populated yet (though I am not sure of this) and therefore you'll always get your default value being used.
Here's what I'd do:
[ToolboxData("<{0}:ServerControl1 runat=server></{0}:ServerControl1>")]
[DefaultProperty("StartYear")]
public class YearDropDownList : DropDownList
{
public string StartYear
{
get
{
String s = (String)ViewState["StartYear"];
return ((s == null) ? "2009" : s);
}
set
{
ViewState["StartYear"] = value;
RegenerateList();
}
}
// Defer regenerating the list until as late as possible
protected void OnPreRender(EventArgs e)
{
RegenerateList();
base.OnPreRender(e);
}
public void RegenerateList()
{
// Remove any existing items.
this.Items.Clear();
for (int i = Int32.Parse(StartYear); i <= DateTime.Now.Year; i++)
{
this.Items.Add(new ListItem(i.ToString(), i.ToString()));
}
}
}
you need to regenerate the list when the property is set like so;
[ToolboxData("<{0}:YearDropDownList runat=\"server\" StartYear=\"[StartYear]\"></{0}:YearDropDownList>")]
[DefaultProperty("StartYear")]
public class YearDropDownList : DropDownList
{
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
RegenerateList();
}
public string StartYear
{
get
{
String s = (String)this.ViewState["StartYear"];
return s ?? "2009";
}
set
{
ViewState["StartYear"] = value;
RegenerateList();
}
}
public void RegenerateList()
{
Items.Clear();
for (int i = Int32.Parse(this.StartYear); i <= DateTime.Now.Year; i++)
{
this.Items.Add(new ListItem(i.ToString(), i.ToString()));
}
}
}
I have tested and verified the code above and it most definitely works. An interesting thing that I did notice was that I was able to reproduce your issue for a while in that the property setter was not being hit. I resolved this by right-clicking the solution and clicking Clean. After the solution was cleaned, I right-clicked on the solution again but this time selected Rebuild. That seemed to solve the property setter issue.

Google Adword and asp.net master pages

I'm trying to add Google Adword conversion code script to certain aspx pages in our website but I'm running into an issue with the site using master pages. The Google instruction said to place the code before the body tag but with master pages being used the code will be on all the pages using the master page. I would like setup it up where certain pages use individual conversion codes with others not using anything. Any suggestions or examples would be appreciated. Also, I'm using C#.
Jamal
There are lots of different ways to communicate with controls on Master pages from individual pages. One of them is to create some simple custom controls and use the same pattern .NET uses with it's ScriptManager/ScriptManagerProxy controls. Basically, you put can a ScriptManager control on a Master page with default settings, then if you need to override the defaults on a page, you use a ScriptManagerProxy control.
I don't really know all that's involved with Adwords conversion code, but you could create the custom controls something like this:
AdwordConversionControl:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace SATest
{
[DefaultProperty("ConversionCode")]
[ToolboxData("<{0}:AdwordConversion runat=server></{0}:AdwordConversion>")]
public class AdwordConversion : Control
{
private const string _conversionCodeKey = "cc";
private const string _includeScriptKey = "ic";
[Category("Behavior")]
[DefaultValue("")]
public string ConversionCode
{
get { return (String)(ViewState[_conversionCodeKey] ?? "" ); }
set { ViewState[_conversionCodeKey] = value; }
}
[Category("Behavior")]
[DefaultValue(false)]
public bool IncludeScript
{
get { return (bool)(ViewState[_includeScriptKey] ?? false ); }
set { ViewState[_includeScriptKey] = value; }
}
protected override void Render(HtmlTextWriter writer)
{
if ( !IncludeScript ) { return; }
string js = "<script type=\"text/javascript\">...Insert conversion code here: var code = " + ConversionCode + ";</script>";
writer.Write( js );
}
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
if ( Page.Items.Contains( typeof(AdwordConversion) ) )
{
throw new ApplicationException( "There can be only one AdwordConversion control defined on a page. Use AdwordConversionProxy." );
}
Page.Items[typeof(AdwordConversion)] = this;
}
}
}
AdwordConversionProxy Control:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace SATest
{
[DefaultProperty("ConversionCode")]
[ToolboxData("<{0}:AdwordConversionProxy runat=server></{0}:AdwordConversionProxy>")]
public class AdwordConversionProxy : Control
{
private string _conversionCode;
private bool? _includeScript;
public string ConversionCode
{
get { return _conversionCode; }
set { _conversionCode = value; }
}
public bool IncludeScript
{
get { return ( _includeScript.HasValue ) ? _includeScript.Value : false; }
set { _includeScript = value; }
}
protected override void Render(HtmlTextWriter writer)
{
}
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
AdwordConversion current = Page.Items[typeof(AdwordConversion)] as AdwordConversion;
if ( current == null )
{
throw new ApplicationException( "AdwordConversionProxy requires that an AdwordConversion control already exist on a page." );
}
if ( _conversionCode != null )
{
current.ConversionCode = _conversionCode;
}
if ( _includeScript.HasValue )
{
current.IncludeScript = _includeScript.Value;
}
}
}
}
Then you would just put an AdwordConversion control on your master page with default values, and you would put AdwordConversionProxy controls on the individual pages that needed their own settings.

Create custom panel control

A Server based control is not good solution for me, since my panel should by default always contain a asp checkbox which will allow the user to hide and show the panels content.
I created my Panel as a templated user control but now I have the problem that I cannot declare variables in it.
[ParseChildren(true)]
public partial class MyPanel: System.Web.UI.UserControl
{
private ITemplate messageTemplate = null;
[TemplateContainer(typeof(MessageContainer))]
[PersistenceMode(PersistenceMode.InnerProperty)]
public ITemplate Content
{
get
{
return messageTemplate;
}
set
{
messageTemplate = value;
}
}
void Page_Init()
{
MessageContainer container = new MessageContainer();
messageTemplate.InstantiateIn(container);
PlaceHolder1.Controls.Add(container);
}
[ParseChildren(true)]
public class MessageContainer : Control, INamingContainer
{
internal MessageContainer()
{
}
}
}
If I do the following in MyPage.aspx then the control definitions are not inserted into MyPage.aspx.designer.cs a they do normally:
<my:MyPanel>
<Content>
<asp:TextBox id = "foo" runat="server" />
</Content>
</my:MyPanel>
Therefore foo is not created as control variable by the designer, so I have no access to it.
How can I create my own Panel which allows declaration of controls in it?
EDIT:
I now tried with [ParseChildren(false)]. Variables for contained variables are now generated in the designer code of the form. The problem is now that messageTemplate.InstantiateIn(container) throws an exception.
You haven't given code for the control. In general, it needs to implement INamingContainer and should have properties of type ITemplate to accept templates.
Check on MSDN on how to develop one. And here's the sample code from MSDN. Also check this article for data bound templated control.
First of all, you need to use the runat="server" attribute.
<asp:TextBox id = "foo" runat="server"/>
Afterwards you can try
var textbox = this.MyCustomPanel.FindControl("foo") as TextBox;
Instead of using FindControl I guess it is possible to achieve this behaviour by setting an attribute on the designer settings of the INamingTemplate Container of your Usercontrol
You don't need to create a templated control, just create a Composite Web Control. Create a Panel & Checkbox, add them to the control collection of the composite control, adjust the rendering to display it as you want, and run with it.
look here
* EDIT **
Here is a working implementation of what you need. Make to create a reference for the Web.dll.
CustomPanel.cs
using System;
using System.ComponentModel;
using System.Drawing;
using System.Security.Permissions;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace Web
{
[ AspNetHostingPermission(SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Minimal),
AspNetHostingPermission(SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal),
ToolboxData("<{0}:CustomPanel runat=\"server\"> </{0}:CustomPanel>"),
]
public class CustomPanel : CompositeControl
{
private Panel panelContainer;
private CheckBox chkHideContent;
private Panel panelInnerContainer;
[Bindable(true),
Category("Appearance"),
DefaultValue(""),
Description("The text to display with the checkbox.")]
public string CheckBoxText
{
get
{
EnsureChildControls();
return chkHideContent.Text;
}
set
{
EnsureChildControls();
chkHideContent.Text = value;
}
}
[Bindable(true)]
[Category("Data")]
[DefaultValue("")]
[Localizable(true)]
public bool IsCheckBoxChecked
{
get
{
return chkHideContent.Checked;
}
}
[Bindable(true)]
[Category("Data")]
[DefaultValue("")]
[Localizable(true)]
public bool HideInnerPanel
{
set
{
EnsureChildControls();
panelInnerContainer.Visible = value;
}
}
[Bindable(true)]
[Category("Data")]
[DefaultValue("")]
[Localizable(true)]
public ControlCollection InnerPanelControls
{
get
{
EnsureChildControls();
return panelInnerContainer.Controls;
}
}
protected virtual void OnCheckboxChanged(EventArgs e)
{
if (chkHideContent.Checked)
{
panelInnerContainer.Visible = false;
}
else
{
panelInnerContainer.Visible = true;
}
}
private void _checkbox_checkChanged(object sender, EventArgs e)
{
OnCheckboxChanged(EventArgs.Empty);
}
protected override void RecreateChildControls()
{
EnsureChildControls();
}
protected override void CreateChildControls()
{
Controls.Clear();
panelContainer = new Panel();
panelContainer.ID = "panelContainer";
chkHideContent = new CheckBox();
chkHideContent.ID = "chkHideContent";
chkHideContent.CheckedChanged += new EventHandler(_checkbox_checkChanged);
chkHideContent.AutoPostBack = true;
panelInnerContainer = new Panel();
panelInnerContainer.ID = "panelInnerContainer";
this.Controls.Add(panelContainer);
this.Controls.Add(chkHideContent);
this.Controls.Add(panelInnerContainer);
}
protected override void Render(HtmlTextWriter writer)
{
panelContainer.RenderBeginTag(writer);
chkHideContent.RenderControl(writer);
panelInnerContainer.RenderControl(writer);
panelContainer.RenderEndTag(writer);
}
}
}
Default.aspx
<%# Register assembly="Web" namespace="Web" tagprefix="cc1" %>
<cc1:CustomPanel ID="CustomPanel1" runat="server" />
Default.aspx.cs
protected void Page_Load(object sender, EventArgs e)
{
Label lbl = new Label();
lbl.Text = "IT WORKS!";
CustomPanel1.CheckBoxText = "Hide my innards!";
CustomPanel1.InnerPanelControls.Add(lbl);
}

When TextBox TextChanged event is fired?

My question is :
As we know ViewState is not responsible for storing and restoring TextBox,CheckBox and such controls Values. This is done by LoadPostData() method to controls that implement IPostBackDataHandler interface.
And we also know after Load stage,RaisePostBackEvent stage occurs and raise corresponding events such Button Click or if Text changed in a TextBox, its TextChanged event will be fired.
So how does system track the text changed if ViewState is not responsible for that and which mechanism actually fires TextBox TextChanged event ?
I am actually confused at this point.
Thanks in advance.
I think it is working in this way :
TextBox control implements IPostBackDataHandler instead of IPostBackEventHandler because it's fired by its text state. So if any changes happened in postedValue which is determined
if (presentValue == null || !presentValue.Equals(postedValue)) {
Text = postedValue;
return true;
}
portion then it returns true and keep executing so finally TextChanged fired. Pff confusing but looks easy tho.
using System;
using System.Web;
using System.Web.UI;
using System.Collections;
using System.Collections.Specialized;
namespace CustomWebFormsControls {
[System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Demand, Name="FullTrust")]
public class MyTextBox: Control, IPostBackDataHandler {
public String Text {
get {
return (String) ViewState["Text"];
}
set {
ViewState["Text"] = value;
}
}
public event EventHandler TextChanged;
public virtual bool LoadPostData(string postDataKey,
NameValueCollection postCollection) {
String presentValue = Text;
String postedValue = postCollection[postDataKey];
if (presentValue == null || !presentValue.Equals(postedValue)) {
Text = postedValue;
return true;
}
return false;
}
public virtual void RaisePostDataChangedEvent() {
OnTextChanged(EventArgs.Empty);
}
protected virtual void OnTextChanged(EventArgs e) {
if (TextChanged != null)
TextChanged(this,e);
}
protected override void Render(HtmlTextWriter output) {
output.Write("<INPUT type= text name = "+this.UniqueID
+ " value = " + this.Text + " >");
}
}
}

Categories

Resources