Nested markup inside custom control - c#

I have created a custom control to implement a table we use all over the place. It has been working fine for a while and now I was asked to add a new column type that would allow any valid asp.net markup to be put in it and that markup will be rendred within that table column.
I created the following class:
[PersistChildren(false), ParseChildren(true)]
public class UserDefinedMarkupColumn : GridColumnBase
{
private Collection<WebControl> _innerMarkup = new Collection<WebControl>();
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
[PersistenceMode(PersistenceMode.InnerProperty)]
public virtual Collection<WebControl> InnerMarkup { get { return _innerMarkup; } }
public override System.Web.UI.WebControls.TableCell CreateCell(object dataSourceObject, GridColumnBase col, out CustomGridRow extraRow, Action<object, System.Web.UI.WebControls.CommandEventArgs> handler, bool isMobileLayout = false)
{
TableCell newCell = new TableCell();
foreach (var ctrl in _innerMarkup)
{
newCell.Controls.Add(ctrl);
}
extraRow = null;
return newCell;
}
}
and this markup for the column:
<CustomGrid:UserDefinedMarkupColumn ID="markup" HeaderText="Some Markup">
<InnerMarkup>
<asp:TextBox runat="server" />
</InnerMarkup>
</CustomGrid:UserDefinedMarkupColumn>
The problem is that when I hit CreateCell the _innerMarkup collection is always empty. I saw several samples using a ControlsCollection but the issue is that GridColumnBase does not inherit from Control, it creates a TableCell which does.
EDIT: I also tried adding a PlaceHolder control and using it's ControlsCollection, but it is empty as well.
private PlaceHolder _innerControl = new PlaceHolder();
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
[PersistenceMode(PersistenceMode.InnerDefaultProperty)]
public virtual ControlCollection InnerMarkup { get { return _innerControl.Controls; } }

Related

DropDownList displaying no value despite appearing to be populated

I am reading a setting from an XML document, converting it to a string array and then looping through each string and adding them to a DropDownList.
Everything appears to be working fine, until I actually go and look at the DropDownList itself. No matter what I do the DropDownList is empty even though when I am debugging through my code everything appears to be adding itself perfectly.
If anyone could shed a little light on why nothing is displaying despite the fact from the code's point of view it is being populated, I would appreciate it.
My code can be found below (Please note I have also tried populating it via Data Binding but I am still having the same issue.):
public class InstrumentDropDownList : DropDownList
{
public InstrumentDropDownList()
{
PopulateDropDown();
}
public void PopulateDropDown()
{
string unsplitList = Fabric.SettingsProvider.ReadSetting<string>("Setting.Location");
string[] instrumentList = unsplitList.Split(',');
DropDownList instrumentsDropDown = new DropDownList();
if (instrumentList.Length > 0)
{
foreach (string instrument in instrumentList)
{
instrumentsDropDown.Items.Add(instrument);
}
}
}
}
You're creating a new DropDownList and adding items to it. The problem is, your not doing anything with the new DropDownList you create. You are just adding the items to the wrong list.
public void PopulateDropDown()
{
string unsplitList = Fabric.SettingsProvider.ReadSetting<string>("Setting.Location");
string[] instrumentList = unsplitList.Split(',');
if (instrumentList.Length > 0)
{
foreach (string instrument in instrumentList)
{
this.Items.Add(instrument);
}
}
}
As an alternative you should be able to do this as well. You would obviously want to put in some more validation, but this is just to show that you can use the DataSource/DataBind
public void PopulateDropDown()
{
this.DataSource = fabric.SettingsProvider.ReadSetting<string>("Setting.Location").Split(',');
this.DataBind();
}
Why are you creating a new instance of the DropDownList when you are inheriting from the same class. Shouldn't you be doing something like. base.Items.Add() ??
You need to call instrumentsDropDown.DataBind after the foreach statement..
public class InstrumentDropDownList : DropDownList
{
public InstrumentDropDownList()
{
PopulateDropDown();
}
public void PopulateDropDown()
{
string unsplitList = Fabric.SettingsProvider.ReadSetting<string>("Setting.Location");
string[] instrumentList = unsplitList.Split(',');
DropDownList instrumentsDropDown = new DropDownList();
if (instrumentList.Length > 0)
{
foreach (string instrument in instrumentList)
{
instrumentsDropDown.Items.Add(instrument);
}
instrumentsDropDown.DataBind();
}
}
}

asp.net CompositeDataBoundControl for a single object

I have created a CompositeDataBoundControl that i can databind perfectly well.
Now i want to do the same thing, but not for a list of objects, but for a single object.
Reasons is that i want my colleagues to have the ability to simply use the <%# Eval("X") %> in their front end code.
The problem is that the CompositeDataBoundControl has a method that i have to override, which only accepts a collection as a datasource
CreateChildControls(System.Collections.IEnumerable dataSource, bool dataBinding)
Is there a way to do the same thing for a single object?
You need to override the DataSource property
public class SingleObjectView : CompositeDataBoundControl
{
private object dataSource;
public override object DataSource
{
get { return new List<object> { dataSource }; }
set { dataSource = value; }
}
protected override int CreateChildControls(System.Collections.IEnumerable dataSource, bool dataBinding)
{
SingleItem singleItem = null;
if (dataBinding)
{
var it = dataSource.GetEnumerator();
it.MoveNext();
singleItem = new SingleItem(it.Current);
}
else
{
singleItem = new SingleItem(null);
}
ItemTemplate.InstantiateIn(singleItem);
Controls.Add(singleItem);
if (dataBinding)
singleItem.DataBind();
return 1;
}
[PersistenceMode(PersistenceMode.InnerProperty), TemplateContainer(typeof(SingleItem))]
public ITemplate ItemTemplate { get; set; }
}
public class SingleItem : Control, IDataItemContainer
{
public SingleItem(object dataItem)
{
DataItem = dataItem;
}
public object DataItem { get; set; }
public int DataItemIndex
{
get { return 0; }
}
public int DisplayIndex
{
get { return 0; }
}
}
edit to show the usage
<ctrl:SingleObjectView ID="productView" runat="server">
<ItemTemplate>
<%# Eval("ProductName") %>
<br />
<%# Eval("Price","{0:c}") %>
</ItemTemplate>
</ctrl:SingleObjectView>
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
productView.DataSource = new { ProductName="My Product", Price="10"};
productView.DataBind();
}
}
So the DataSource receives an object which doesn't implement IEnumerable, but overriding the DataSource property of the SingleObjectView control, ensures that CreateChildControls is called.
What about create a list of control, add one single object and then call the method?
I use FormView to allow an instance of a single object to be templated:
protected void fvProduct_Init(object sender, EventArgs e)
{
// Load product template
fvProduct.ItemTemplate = LoadTemplate("Templates/ProductTemplate.ascx");
// Bind product to data source
fvProduct.DataSource = new[] { Product };
fvProduct.DataBind();
}
Then in ProductTemplate.ascx you can do stuff like this:
<h1><%# Eval("ProductName") %></h1>
<p><%# Eval("Description") %></p>
<h4><%# Eval("Price", "{0:C}")%></h4>
In your scenario you mentioned you were using a custom control but the idea is basically the same as the answers above. You just create an enumerable with one item and bind it. The reason for using a FormView is because it is designed to only display exactly one item at a time.

Programmatically create columns in a View

This should be easy. I want to populate a grid with a custom data source at runtime. For some reason, it simply does not work.
Running via a unit test
[TestMethod]
public void Runtest() {
TestForm form = new TestForm();
TestControl control = new TestControl();
form.Controls.Add(control);
control.LoadData();
form.ShowDialog();
}
The relevant Control code
public void LoadData() {
SourceRecord[] original = new SourceRecord[] {
new SourceRecord("1"), new SourceRecord("3"), new SourceRecord("9") };
gridControl1.DataSource = original;
GridColumn col = gridView1.Columns.AddVisible("SomeColumn");
col.FieldName = "SomeName";
//gridControl1.ForceInitialize();
}
Record info
public class SourceRecord {
public string SomeName = "";
public SourceRecord(string Name) {
this.SomeName = Name;
}
}
I end up with some column just called "Column" which displays 3 rows reading ClassLibrary1.SourceRecord. Then my custom column "Some Name" has no data. According to the devexpress walkthrough I only need to populate the DataSource with a class that implements IList, which I did with an Array.
How can I display just my custom column and give it the data?
The grid control will bind columns to properties only. Try this:
public class SourceRecord
{
public string SomeName { get; set; }
public SourceRecord(string Name)
{
SomeName = Name;
}
}
public void LoadData()
{
SourceRecord[] original = new SourceRecord[] { new SourceRecord("1"), new SourceRecord("3"), new SourceRecord("9") };
GridColumn col = gridView1.Columns.AddVisible("SomeColumn");
col.FieldName = "SomeName";
gridControl1.DataSource = original;
}

Custom Label does not show the Text string

I needed to make my own label to hold some value, that is diferent from the value displayed to user
public class LabelBean : Label {
private string value;
public LabelBean(string text = "", string value = ""): base() {
base.Text = text;
this.value = value;
}
public string Value {
get { return value; }
set { this.value = value; }
}
}
but now id in the form constructor I replace the control with my class
this.lbAttributeType = new LabelBean();
and later after the form is created, but before it is shown I set the text through setter
(this.lbAttributeType as LabelBean).Value = value;
this.lbAttributeType.Text = Transform(value);
but in the form I have always "label1" text... what is wrong with it?
thanks
UPDATE
I added the solution here to find it easier:
public class MyLabel : Label {
public MyLabel()
: base() {
}
public string Value {
set {
this.Text = value;
}
}
}
and the form with Widnows.Forms.Label label1 control
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
this.Controls.Remove(this.label1);
this.label1 = new MyLabel();
this.Controls.Add(this.label1);
(this.label1 as MyLabel).Value = "oh";
}
}
the bug was in the Controls.Remove and Controls.Add,
thanks all for their time :)
My guess is because, since you're doing the work in the constructor, the InitializeComponent code, automatically generated by the designer, is overwriting the control instance, as it's most likely called after your initialisation.
If the class is part of the project, you will find it on the toolbox; meaning you can simply drag and drop your new control on the form in place of the existing one - this is what you should do.
This ensures that the designer-generated property is of your LabelBean type, and not simply Label.
Also - you should consider changing your Value setter as demonstrated by WoLfulus (+1 there)
Update
In response to the comment you put on WoLfulus' answer - here's a couple of alternatives:
1) If the form is the 'clever' bit here - consider just writing a helper method in it, and setting the value of the label through it, leveraging the Tag property:
public void SetLabelBean(Label target, string value)
{
Label.Tag = value;
Label.Text = Transform(value);
}
public string GetLabelBean(Label target)
{
return target.Tag as string;
}
2) Continue using your sub-classed LabelBean type (adding it via the designer as I've already mentioned) - but use an abstraction to give it access to the form's Transform method:
public interface ITransformProvider
{
string Transform(string);
}
Make your form class implement this interface, with the Transform method you elude to.
Now, in your LabelBean class:
public ITransformProvider Transformer
{
get{
//searches up the control hierarchy to find the first ITransformProvider.
//should be the form, but also allows you to use your own container controls
//to change within the form. The algorithm could be improved by caching the
//result, invalidating it if the control is moved to another container of course.
var parent = Parent;
ITransformProvider provider = parent as ITransformProvider;
while(provider == null){
parent = parent.Parent;
provider = parent as ITransformProvider;
}
return provider;
}
}
And then, finally, using WoLfulus' code, but slightly changed, you can do this:
public string Value
{
get
{
return value;
}
set
{
this.value = value;
var transformer = Transformer;
if(transformer != null) this.Text = transformer.Transform(value);
}
}
That, I think, addresses your issues with that answer.
Try this:
Create a new delegate outside the label class:
public delegate string LabelFormatDelegate( string val );
Add this to your label class:
public LabelFormatDelegate ValueFormatter = null;
public string Value
{
get
{
return value;
}
set
{
this.value = value;
if (this.ValueFormatter != null)
{
this.Text = this.ValueFormatter(value); // change the label here
}
else
{
this.Text = value;
}
}
}
Place a new common label to your form (lets name it "label1")
Goto to Form1.Designer.cs and search for "label1" declaration.
Rename the "Label" type to your own label type (Ex: "MyLabel")
Change the initialization code of label on InitializeComponent function on designer code to match the new type "MyLabel"
Example:
this.label1 = new Label();
Change to:
this.label1 = new MyLabel();
In the Form_Load event, specify the format function:
this.label1.ValueFormatter = new LabelFormatDelegate(this.Transform);
Notes: You'll need to remove the "Text" setter call too from here:
(this.lbAttributeType as LabelBean).Value = value;
// this.lbAttributeType.Text = Transform(value);
This will keep your value/text in sync but remember not to set "Text" property by hand.
I agree with WoLfulus and Andreas Zoltan and would add a symmetrical functionality to Text if there exists a unambiguous reverse transformation:
public string Value
{
get { return value; }
set
{
if (this.value != value) {
this.value = value;
this.Text = Transform(value);
}
}
}
public override string Text
{
get { return base.Text; }
set
{
if (base.Text != value) {
base.Text = value;
this.value = TransformBack(value);
}
}
}
Note the if checks in order to avoid an endless recursion.
EDIT:
Assigning your label to lbAttributeType is not enough. You must remove the old label from the Controls collection before the assignment and re-add it after the assignment.
this.Controls.Remove(lbAttributeType); // Remove old label
this.lbAttributeType = new LabelBean();
this.Controls.Add(lbAttributeType); // Add new label
Your form was still displaying the old label! Why did I not see it earlier?

Validator not passing ErrorMessage to ValidationSummary

I have written my own Validator and although the validator appears to be working (as it does display the Text property when invalid) the ValidationSummary does not display the ErrorMessage property, or anything, when validation fails. Interestingly, it appears that it fails to even display the Text property when I add another control with a validator to the page. What am I doing wrong?
public class RequiredCheckBoxListValidator : BaseValidator
{
private CheckBoxList _list;
private int _requiredCount = 1;
public int RequiredCount
{
get { return _requiredCount; }
set { _requiredCount = value; }
}
public RequiredCheckBoxListValidator()
{
EnableClientScript = false;
}
protected override bool ControlPropertiesValid()
{
Control control = FindControl(ControlToValidate);
if (control != null)
{
_list = (CheckBoxList)control;
return (_list != null);
}
else
{
return false;
}
}
protected override bool EvaluateIsValid()
{
return (_list.Items.Cast<ListItem>().Where(li => li.Selected).Count() == _requiredCount);
}
}
It would help to see your clientside info.
Without that, my guesses are to check ShowSummary on the validtorsummary to make sure it is not hiding the summary, and to see if the validators and summary are in separate UpdatePanels.

Categories