Possible to have a inner control on a custom server control? - asp.net

I would like to be able to do something like:
<ui:Tab Title="A nice title">
<TabTemplate>
<asp:Literal runat="server" ID="SetMe">With Text or Something</asp:Literal>
</TabTemplate>
</ui:Tab>
but also be able to do:
<ui:Tab Title="A nice title">
<TabTemplate>
<asp:DataList runat="server" ID="BindMe"></asp:DataList>
</TabTemplate>
</ui:Tab>
Answer code I eventually came up with:
[ParseChildren(true)]
public class Node : SiteMapNodeBaseControl, INamingContainer
{
private ITemplate tabTemplate;
[Browsable(false),
DefaultValue(null),
Description("The tab template."),
PersistenceMode(PersistenceMode.InnerProperty),
TemplateContainer(typeof(TabTemplate))]
public ITemplate TabTemplate
{
get { return tabTemplate; }
set { tabTemplate = value; }
}
protected override void CreateChildControls()
{
if (TabTemplate != null)
{
Controls.Clear();
TabTemplate i = new TabTemplate();
TabTemplate.InstantiateIn(i);
Controls.Add(i);
}
}
protected override void Render(HtmlTextWriter writer)
{
EnsureChildControls();
base.Render(writer);
}
}
public class TabTemplate : Control, INamingContainer
{
}

The ParseChildren attribute tells .NET whether to treat your control's children as properties or as controls. For your first example, you want to treat children as controls, so add
[ ParseChildren(ChildrenAsProperties = false) ]
For the second, you want ChildrenAsProperties=true, and a TabTemplate property of type ITemplate. There's some plumbing involved after that, which this MSDN sample describes. It doesn't add a lot of value if you only need one template, though.

Related

C#, Xamarin Forms: No Custom TextChangedEvent Raised on initialization

I'm creating an Xamarin.Forms MVVM App (only using Android) which needs certain buttons to be outlined red, whenever their text property holds a specific value. (Purpose: alert the user to press the button and select a value, which will change the Button Text Property and therefore remove the red outline)
To achieve this I've create the following documents:
A custom button CButton that extents the default Button:
public class CButton : Button
{
// this Hides the Default .Text-Property
public string Text
{
get => base.Text;
set
{
base.Text = value;
TextChangedEvent(this, new EventArgs());
}
}
// The Raised Event
protected virtual void TextChangedEvent(object sender, EventArgs e)
{
EventHandler<EventArgs> handler = TextChanged;
handler(sender, e);
}
public event EventHandler<EventArgs> TextChanged;
}
A custom behavior makes use of the raised TextChangedEvent
public class ButtonValBehavior : Behavior<CButton>
{
protected override void OnAttachedTo(CButton bindable)
{
bindable.TextChanged += HandleTextChanged;
base.OnAttachedTo(bindable);
}
void HandleTextChanged(object sender, EventArgs e)
{
string forbidden = "hh:mm|dd.mm.yyyy";
if (forbidden.Contains((sender as CButton).Text.ToLower()))
{
//Do when Button Text = "hh:mm" || "dd.mm.yyyy"
(sender as CButton).BorderColor = Color.Gray;
}
else
{
//Do whenever Button.Text is any other value
(sender as CButton).BorderColor = Color.FromHex("#d10f32");
}
}
protected override void OnDetachingFrom(CButton bindable)
{
bindable.TextChanged -= HandleTextChanged;
base.OnDetachingFrom(bindable);
}
}
The relevant parts of the ViewModel look the following:
public class VM_DIVI : VM_Base
{
public VM_DIVI(O_BasisProtokoll base)
{
Base = base;
}
private O_BasisProtokoll _base = null;
public O_BasisProtokoll Base
{
get => _base;
set
{
_base = value;
OnPropertyChanged();
}
}
Command _datePopCommand;
public Command DatePopCommand
{
get
{
return _datePopCommand ?? (_datePopCommand = new Command(param => ExecuteDatePopCommand(param)));
}
}
void ExecuteDatePopCommand(object param)
{
//launch popup
var p = new PP_DatePicker(param);
PopupNavigation.Instance.PushAsync(p);
}
}
The .xmal looks the following (b is the xmlns of the Namespace):
<b:CButton x:Name="BTN_ED_Datum"
Text="{Binding Base.ED_datum, Mode=TwoWay}"
Grid.Column="1"
Command="{Binding DatePopCommand}"
CommandParameter="{x:Reference BTN_ED_Datum}">
<b:CButton.Behaviors>
<b:ButtonValBehavior/>
</b:CButton.Behaviors>
</b:CButton>
This solution works fine whenever the input is caused by user interaction. However, when a Value is assigned during the initialization of the Page no red outline is created, in fact the TextChangedEvent isn't raised. By using breakpoints I noticed that during initialization the Text Property of CButton is never set, eventhough it actually will be in the view.
Despite fiddling around with my solution I cannot make this work on initialization. I tried to work around this issue by outlining every button by default in their constructor, however this will outline every button red, even when their text value doesn't require them to be.
How can I achieve my initial goal?
Many thanks in advance!
It's been a while but if I recall correctly what I ended up doing was:
Changing the new Text-Property of my custom Button to CText and
Making sure that I have Mode=TwoWay activated for any Element, that doesn't have it enabled by default. (Look up Binding modes on msdn for more)
making CText a bindable property of CButton
My custom button now looks the following:
using System;
using System.Collections.Generic;
using System.Text;
using Xamarin.Forms;
namespace EORG_Anton.Model
{
public class CButton : Button
{
public static readonly BindableProperty CTextProperty =
BindableProperty.Create(nameof(CText),
typeof(string),
typeof(CButton),
default(string),
BindingMode.TwoWay,
propertyChanged: OnTextChanged);
private static void OnTextChanged(BindableObject bindable, object oldValue, object newValue)
{
var control = (CButton)bindable;
var value = (string)newValue;
control.CText = value;
}
public string CText
{
get => base.Text;
set
{
base.Text = value;
TextChangedEvent(this, new EventArgs());
}
}
protected virtual void TextChangedEvent(object sender, EventArgs e)
{
EventHandler<EventArgs> handler = TextChanged;
handler(sender, e);
}
public event EventHandler<EventArgs> TextChanged;
}
}

RaisePostBackEvent called on wrong control

I have spent two days with another colleague investigating this. I was surprised as most solutions discussing this problem either have the wrong solution or a solution that works, I think, for the wrong reasons.
We have a custom button control that needs to raise a ServerClick event when it is pressed. Here is the summarised code:
public class MyButton : WebControl, IPostBackEventHandler
{
protected HtmlGenericControl _Button;
protected string _OnClick = "";
protected string _Name;
public event EventHandler ServerClick;
// etc...
public MyButton()
{
Width = Unit.Pixel(100);
_Button = new HtmlGenericControl("button");
Controls.Add(_Button);
}
protected override void Render(HtmlTextWriter writer)
{
_Button.Attributes.Add("id", string.IsNullOrEmpty(_Name) ? base.ID : _Name);
_Button.Attributes.Add("name", _Name);
// etc...
_OnClick = Page.ClientScript.GetPostBackEventReference(this, "");
_Button.Attributes.Add("onClick", _OnClick);
// etc...
ID = String.Empty;
Name = String.Empty;
AccessKey = String.Empty;
TabIndex = -1;
Width = Unit.Empty;
base.Render(writer);
}
protected virtual void OnServerClick()
{
if (this.ServerClick != null)
{
this.ServerClick(this, new EventArgs());
}
}
public void RaisePostBackEvent(string eventArgument)
{
this.OnServerClick();
}
}
On the browser end the code uses two of these buttons
<form>
<!-- etc ... -->
<div class="row actionBar">
<PGSC:MyButton Name="btnAccept" ID="btnAccept" LabelID="3244" TabIndex="70" runat="server" OnServerClick="AcceptClickHandler"/>
<PGSC:MyButton Name="btnClose" ID="btnClose" LabelID="349" OnClick="window.returnValue=frmMMD.hdnMmdId.value;window.close();" TabIndex="80" runat="server" />
</div>
</form>
The Problem:
The event is not raised on the accept button. Debugging reveals that RaisePostBackEvent is called but on the Close button, which does not have a ServerClick handler attached, hence nothing happens. No event handlers get called.
Notes:
The problem is not seen if there is only one MyButton on the page.
If the buttons are reordered such that the accept button is the last on the page, it starts working.
Moving the buttons outside of the form tag causes events to work as expected, and the accept buttons event handler is called correctly.
Implementing IPostBackDataHandler and calling RaisePostBackEvent() from IPostBackDataHandler::RaisePostDataChangedEvent() causes the event to be raised correctly on the accept button when inside the form tag.
Calling RegisterRequiresRaiseEvent(btnAccept) during PageLoad routes events correctly to the accept button.
The Question:
What is the correct solution from the ones that work above? Or is there another solution? We need it to work such that multiple buttons on the page can raise independent click events, without regard to their order or position on the page.
My Thoughts:
This problem seems to be discussed here: http://forums.asp.net/t/1074998.aspx?ASP+NET+RaisePostbackEvent+Issues
One is lead to believe that calling __doPostback() with the correct __EVENTTARGET should automatically route the event correctly to the button, but this is not happening in reality. It only happens if we also implement IPostBackDataHandler. Many solutions on the web seem to point to __doPostback, UniqueID etc as the culprit when actually implementing IPostBackDataHandler is what seemingly fixes the issue.
The Control implements IPostBackEventHandler but not IPostBackDataHandler. I think this is correct because the control does not need to raise any data driven events. So implementing IPostBackDataHandler to get it working seems like a hack.
Using RegisterRequiresRaiseEvent is unintuitive and besides will not work if multiple buttons on the page would like to raise events.
I wonder, how does an asp:Button do it?
I've simulated a situation.
Hope it helps.
There is the MyButton WebServerControl class:
[DefaultProperty("Text")]
[ToolboxData("<{0}:MyButton runat=server></{0}:MyButton>")]
public class MyButton : WebControl, IPostBackEventHandler
{
[Bindable(true)]
[Category("Appearance")]
[DefaultValue("")]
[Localizable(true)]
public string Text
{
get
{
String s = (String)ViewState["Text"];
return ((s == null) ? String.Empty : s);
}
set
{
ViewState["Text"] = value;
}
}
protected HtmlGenericControl _Button;
protected string _OnClick = "";
protected string _Name;
public event EventHandler ServerClick;
// etc...
public MyButton()
{
Width = Unit.Pixel(100);
_Button = new HtmlGenericControl("button");
Controls.Add(_Button);
}
protected override void Render(HtmlTextWriter writer)
{
_Button.Attributes.Add("id", string.IsNullOrEmpty(_Name) ? base.ID : _Name);
_Button.Attributes.Add("name", _Name);
// etc...
_OnClick = Page.ClientScript.GetPostBackEventReference(this, "");
_Button.Attributes.Add("onClick", _OnClick);
// etc...
ID = String.Empty;
//Name = String.Empty;
AccessKey = String.Empty;
TabIndex = -1;
Width = Unit.Empty;
base.Render(writer);
}
protected virtual void OnServerClick()
{
if (this.ServerClick != null)
{
this.ServerClick(this, new EventArgs());
}
}
public void RaisePostBackEvent(string eventArgument)
{
this.OnServerClick();
}
}
I then used my web server control in a project, let's say this is the default.aspx:
<div><cc1:MyButton ID="btnAccept" runat="server" TabIndex="70" OnServerClick="AcceptClickHandler" />
<cc1:MyButton ID="btnClose" Text="Close" Width="256px" LabelID="349" runat="server" TabIndex="80" /></div><div>
<asp:Label ID="Label1" runat="server" Text="Label"></asp:Label>
</div>
And in default.aspx.cs I've implemented simply the event:
protected void AcceptClickHandler(object sender, EventArgs e)
{
Label1.Text = DateTime.Now.ToLongTimeString();
}
The AcceptClickHandler fires only when clicking on the Accept button and not
on the Close button.
Sorry if I didnt get the problem right.

access WebControls markable properties in constructor asp net

Here is my custom control.It inherits [Height] property from WebControl class.I want to access it in constructor for calculating other properties.But its value is always 0.Any idea?
public class MyControl : WebControl, IScriptControl
{
public MyControl()
{
AnotherProperty = Calculate(Height);
.......
}
my aspx
<hp:MyControl Height = "31px" .... />
Markup values are not available in your control's constructor but they are available from within your control's OnInit event.
protected override void OnInit(EventArgs e)
{
// has value even before the base OnInit() method in called
var height = base.Height;
base.OnInit(e);
}
As #andleer said markup has not been read yet in control's constructor, therefore any property values that are specified in markup are not available in constructor. Calculate another property on demand when it is about to be used and make sure that you not use before OnInit:
private int fAnotherPropertyCalculated = false;
private int fAnotherProperty;
public int AnotherProperty
{
get
{
if (!fAnotherPropertyCalculated)
{
fAnotherProperty = Calculate(Height);
fAnotherPropertyCalculated = true;
}
return fAnotherProperty;
}
}

Custom DataControlField Class

I did some search but nothing is really useful in my case.
I want to inherit the DataControlField (System.Web.UI.WebControls) to be able to carry two label controls and then I want to color the two labels to get some sort of conditional formatting, I've got the conditional formatting part but how can I customize this class?
Where in my class should I define the two label controls?
How would I override the CreateField method?
P.S: I know I can accomplish this, in XHTML Markup, but I have so many columns that it would not be appropriate to include those markups in the page markup. Therefore I'm doing that in the CodeBehind page.
EDIT:
public class MyField : DataControlField
{
public MyField()
{
}
protected override DataControlField CreateField()
{
// What to put here?
}
protected override void CopyProperties(DataControlField newField)
{
((CalendarField)newField).DataField = this.DataField;
((CalendarField)newField).DataFormatString = this.DataFormatString;
((CalendarField)newField).ReadOnly = this.ReadOnly;
base.CopyProperties(newField);
}
public override void InitializeCell(DataControlFieldCell cell, DataControlCellType cellType, DataControlRowState rowState, int rowIndex)
{
// Call the base method
base.InitializeCell(cell, cellType, rowState, rowIndex);
// Initialize the contents of the cell quitting if it is a header/footer
if (cellType == DataControlCellType.DataCell)
InitializeDataCell(cell, rowState);
}
protected virtual void InitializeDataCell(DataControlFieldCell cell, DataControlRowState rowState)
{
}
}
See here. Hope this helps you.
public class MyField : DataControlField {
public MyField() { }
protected override DataControlField CreateField() {
// What to put here?
return new MyField();
}
protected override void CopyProperties(DataControlField newField) {
((CalendarField)newField).DataField = this.DataField;
((CalendarField)newField).DataFormatString = this.DataFormatString;
((CalendarField)newField).ReadOnly = this.ReadOnly;
base.CopyProperties(newField);
}
public override void InitializeCell(DataControlFieldCell cell, DataControlCellType cellType, DataControlRowState rowState, int rowIndex)
{
// Call the base method
base.InitializeCell(cell, cellType, rowState, rowIndex);
// Initialize the contents of the cell quitting if it is a header/footer
if (cellType == DataControlCellType.DataCell)
{
cell.DataBinding += new EventHandler(cell_DataBinding);
}
}
void cell_DataBinding(object sender, EventArgs e)
{
Control ctrl = sender as Control;
var container = ctrl.NamingContainer as IDataItemContainer;
// here what you would like to show in MyField
}
}

Add ability to provide list items to composite control with DropDownLIst

I'm creating a composite control for a DropDownList (that also includes a Label).
The idea being that I can use my control like a dropdown list, but also have it toss a Label onto the page in front of the DDL.
I have this working perfectly for TextBoxes, but am struggling with the DDL because of the Collection (or Datasource) component to populate the DDL.
Basically I want to be able to do something like this:
<ecc:MyDropDownList ID="AnimalType" runat="server" LabelText="this is what will be in the label">
<asp:ListItem Text="dog" Value="dog" />
<asp:ListItem Text="cat" Value="cat" />
</ecc:MyDropDownList>
The problem is, I'm not extending the DropDownList class for my control, so I can't simply work it with that magic. I need some pointers to figure out how I can turn my control (MyDropDownList), which is currently just a System.Web.UI.UserControl, into something that will accept List items within the tag and ideally, I'd like to be able to plug it into a datasource (the same functions that the regular DDL offers).
I tried with no luck just extending the regular DDL, but couldn't get the Label component to fly with it.
After doing some digging and searching I found a solution that works. Hopefully this will help someone else out in the future:
[ParseChildren(true, "Items")]
public class EDropDownList : CompositeControl, IValidatedFields
{
public string PromptingText { get; set; }
public string Value { get; set; }
public Label __Label { get; set; }
private ListItemCollection _items;
public DropDownList __DropDownList;
public ListItemCollection Items
{
get { return _items; }
set
{
if (_items != value)
{
_items = value;
}
}
}
public string Type { get { return "DropDownList"; } }
public EDropDownList()
{
__Label = new Label();
}
protected override void CreateChildControls()
{
__DropDownList = new DropDownList();
foreach (ListItem myItem in _items)
{
__DropDownList.Items.Add(myItem);
}
Controls.AddAt(0, __Label);
Controls.AddAt(1, __DropDownList);
}
protected override void OnLoad(EventArgs e)
{
// label section
__Label.Text = PromptingText+"<br />";
__Label.ForeColor = Color.Red;
__Label.Visible = false;
// ddl section
if (Page.IsPostBack)
Value = __DropDownList.SelectedValue;
}
}
The easiest thing would be to go back to your original option of extending the DropDownList control. What problems did you have getting the label to work with it? Those problems are probably easier to solve?

Resources