I'm hoping there is a pattern for verifying when a property is set for a custom control in asp.net.
Considering that there is a page life cycle, we keep having issues where the control can get into an invalid state. The best thing we can do is raise an exception with an instructional message for things like, setting values selected before adding data.
Please note, ideally the component wouldn't rely on things like ordering of when a property is set. Unfortunately I can only move the company to better practices one step at a time. There are too many components to re-write from scratch and is an unrealistic expectation.
That said, here's an example.
We have two properties. SelectedValues which will set the values that match a comma separated list and InsertAll which will insert "All" at the top of a list.
Potential issue: The developer sets the SelectedValues in the Page's PreInit event, but the InsertAll property, if true, will add the "All" value and select it during the control's Init event. The trick is, SelectedValues will directly set the values when set, not later during the life cycle. Which means, when they see the page, they think there is a bug in the component because they didn't set All to be selected, but it is.
Page:
protected override void OnPreInit(EventArgs e)
{
base.OnPreInit(e);
listBoxSelection.SelectedValues = "value1,value2";
listBoxSelection.InsertAll = true;
}
Control:
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
if (this.InsertAll)
{
ListBoxItem allItem = new ListBoxItem()
{
Text = "All",
Value = "0"
Selected = true
};
this.Items.Insert(0, allItem);
}
}
How can I ensure that the developer knows they goofed when using this control? This is a simple example, but I see it all the time and in many forms.
Related
My main page uses a TabbedPage to group existing news into different lists. The tabs aren't fixed; they're built from a data binding operations against a collection that's retried through a web service call.
I'd like to persist the selected tab across activity restarts, but it seems like I'm missing something. Since there's no selected tab property (which can be set through data binding), I've tried to handle the PageChanged and the CurrentPageChangedCommand events. I'm using the PageChanged to set the selected tab to the previous selected tab and the CurrentPageChangedCommand is being used to update the persisted selected tab (I'm using the Application.Properties to make sure the selected tab survives app restarts).
Unfortunately, the events generated by the tab will always set tab 0 as the selected tab! Here's what I'm seeing (let's assume that my app was killed white tab 3 was active):
When data is bound to the TabbedPage.ItemsSource property, the tab will automatically fire the CurrentPageChangedCommand, passing the first tab (tab at position 0).
My code handles the event and updates the current persisted selected tab by changing the selected tab in the Properties dictionary. So now, instead of 3 (which was the value persisted when my app was killed), it will have 0.
Then the tab will fire the PagesChanged
When my code handles this event, it will try to update the selected tab. However, when it access the selected tab from the Properties dictionary, it will get the default tab (0) and not 3. This happens because the CurrentPageChangedCommand was fired before the PagesChanged event (step 2), completely overriding the previously persisted tab index.
This default behaviour will also give a bad user experience when the user refreshes the current list (pull to refresh) because he always ends up seeing tab 0 list.
So, any clues on how to solve this? How have you guys solved this?
Thanks.
It seems it can't be achieved using MVVM as CurrentPage is not a bindable property and CurrentPageChanged is an event.
However, there's no need to handle the PagesChanged event. You could record the index in the changed event like:
private void MyTabbedPage_CurrentPageChanged(object sender, EventArgs e)
{
Application.Current.Properties["index"] = this.Children.IndexOf(CurrentPage);
Application.Current.SavePropertiesAsync();
}
Then you could set your tabbed page's current page after you have loaded all the tabs:
object index;
Application.Current.Properties.TryGetValue("index", out index);
if (index != null)
{
CurrentPage = Children[int.Parse(index.ToString())];
}
// Subscribe the event
CurrentPageChanged += MyTabbedPage_CurrentPageChanged;
I placed the code above in the custom tabbed page's constructor and it could change the selected tab at initial time.
Update:
If you want to change the tabbed page's children dynamically, you could define a property to avoid the event being fired when you change the children:
bool shouldChangeIndex = true;
private void MyTabbedPage_CurrentPageChanged(object sender, EventArgs e)
{
if (shouldChangeIndex)
{
var index = this.Children.IndexOf(CurrentPage);
Application.Current.Properties["index"] = index;
Application.Current.SavePropertiesAsync();
}
}
// Simulate the adjusting
shouldChangeIndex = false;
Children.Clear();
Children.Add(new MainPage());
Children.Add(new SecondPage());
shouldChangeIndex = true;
object index;
Application.Current.Properties.TryGetValue("index", out index);
if (index != null)
{
CurrentPage = Children[int.Parse(index.ToString())];
}
Unfortunately, I had to abandon the MVVM approach...In the end, I had to resort to code and a couple of flags to control when the generated tab events should be handled.
I've been using hardcoded hyperlinks for my web app navigation, but the app has grown since and managing it is becoming a real pain. I've decided to replace what I have with the TreeView control, however I want to make several changes to the way it looks.
Is there any property that needs to be set, that would allow user to expand the TreeView node by clicking its text instead of +/- ?
I've already set ShowExpandColapse to 'false'.
I want my final result to end up as something similar to the TreeView on the left of the MSDN site.
Could anyone point me at the right direction please?
Set TreeNode.SelectAction to either Expand, or SelectExpand.
you can use xml data source or direct binding from db to treview
in the TreeView DataBound event we can write d recursive function as below to fetch each node and assign expand action to them.
protected void TreeView1_DataBound(object sender, EventArgs e)
{
foreach (TreeNode node in TreeView1.Nodes)
{
node.SelectAction = TreeNodeSelectAction.Expand;
PrintNodesRecursive(node);
}
}
public void PrintNodesRecursive(TreeNode oParentNode)
{
// Start recursion on all subnodes.
foreach(TreeNode oSubNode in oParentNode.ChildNodes)
{
oSubNode.SelectAction = TreeNodeSelectAction.Expand;
PrintNodesRecursive(oSubNode);
}
}
I think you just have to do this in code: handle the Click event, determine the currently-selected tree node, and toggle its Expanded property (I think that's what it's called here).
You can do this only this way! http://geekswithblogs.net/rajiv/archive/2006/03/16/72575.aspx
With respect,
Alexander
I'm working with dynamic fields in ASP.NET due to a very specifc and rigid end-user requirement that would take 2 hours just to explain. Suffice it to say, I can't make the requirement go away.
Anyway, I have a working solution in place; no problems with controls loading, rendering or maintaining their ViewState. This is what my OnLoad looks like:
public void override OnLoad(EventArgs e){
//don't need to check IsPostback, we have to load the controls on every POST
FormDefinition initialFormDefinition = ServiceLayer.GetFormDefinition(id);
BuildControls(initialFormDefinition);
}
In order to implement some biz logic around which dynamic fields are required, disabled or optional, I need to get the posted values (i.e. the ViewState) of my dynamic controls before I can actually add them to the page control hierarchy.
It's sort of a chicken/egg problem I suppose. ASP.NET won't automagically associate ViewState with the proper dynamic control until I've added them all to the page. On the other hand, I can't add these controls to the page until my service layer has applied biz rules that hinge on their current values. I tried to get around this rather unpleasant problem by writing this bit of pseudo-code :
public void override OnLoad(EventArgs e){
FormDefinition initialFormDefinition = ServiceLayer.GetFormDefinition(id);
BuildControls(initialFormDefinition);
if (IsPostBack){
PushControlValuesIntoForm(initialFormDefinition);
var updatedFormDefinition = ServiceLayer.ApplyBizRules(initialFormDefinition);
ReBuildControls(updatedFormDefinition); //remove controls and re-add them
}
}
Unfortunately, when you clear a control and re-add it, the ViewState is lost, even if the control type and ControlID are exactly the same, so this solution is a bust. Any reasonable ideas on how to accomplish what I'm after are welcome!
One way could be to load your controls and then decide if you need form definition to be be updated and if yes then re-initiate page life cycle again. See the below sample code:
public void override OnLoad(EventArgs e){
var updatedFormDef = Context.Items["UpdatedDef"] as FormDefinition;
if (null != updatedFormDef)
{
// Updated form def, rebuild controls
BuildControls(updatedFormDef);
}
else
{
// load initial form def
var initialFormDefinition = ServiceLayer.GetFormDefinition(id);
BuildControls(initialFormDefinition);
// check whether we need to update form def
if (IsPostBack){
PushControlValuesIntoForm(initialFormDefinition);
var updatedFormDefinition = ServiceLayer.ApplyBizRules(initialFormDefinition);
if (null != updatedFormDefinition)
{
// we have to update UI, transfer to self
Context.Items["UpdatedDef"] = updatedFormDefinition;
try
{
Server.Transfer(this.Request.RawUrl, true);
}
catch(ThreadAbortException)
{
// Do nothing
}
}
}
}
I'm building a custom server control derived from CompositeControl.
The control contains a number of child controls (Labels, DropDownList, ListSearchExtender, etc). All of them reside inside an UpdatePanel.
The control also publishes events. For this I added two Properties: EnableCallBacks and CallBacksAsPostBacks. Those two properties should configure the postback behaviour of the update panel.
Any ideas what a correct implementation should look like?
I'm getting some problems with the way I implemented it:
the PostBackTrigger does not always get rendered into the output html.
Having both Triggers.Add(trigger) and Controls.Add(_updatePanel) inside the CreateChildControls methods leads to the PostBackTrigger always being rendered, even if I remove it later on (e.g. within RenderControl() or PreRender()). If I do not add the trigger here but later on, then it does never get rendered. At this stage I do not have the correct values of all my properties yet (e.g. EnableCallBacks and CallBacksAsPostBacks).
It is not possible to place the statement of Controls.Add(_updatePanel) inside the RenderControl-method due to it beeing too late for AJAX (latest ist PreRender() otherwise I get an exception).
Ideally I would instantiate all controls in CreateChildControls() and then set their values later on in e.g. PreRender or RenderControl
Having both statements in the PreRender method results in, that the trigger gets rendered corretly depending on my settings in the containing page, but I don't get the DropDownList populated with its data from the ViewState (on call/postbacks).
protected override void CreateChildControls()
{
base.CreateChildControls();
_updatePanel = new UpdatePanel();
_updatePanel.ID = "FprDropDownList_UpPnl";
_updatePanel.UpdateMode = UpdatePanelUpdateMode.Conditional;
_label = new FprLabel();
_label.ID = "FprDropDownList_Lbl";
_updatePanel.ContentTemplateContainer.Controls.Add(_label);
_dropDownList = new DropDownList();
_dropDownList.ID = "FprDropDownList_Ddl";
_dropDownList.CssClass = "fprDropDownList";
_dropDownList.AutoPostBack = true;
_updatePanel.ContentTemplateContainer.Controls.Add(_dropDownList);
_label.AssociatedControlID = _dropDownList.ClientID;
_listSearchExtender = new ListSearchExtender();
_listSearchExtender.ID = "FprDropDownList_Lse";
_listSearchExtender.TargetControlID = _dropDownList.ClientID;
_listSearchExtender.PromptPosition = ListSearchPromtPosition;
_listSearchExtender.PromptCssClass = "fprListSearchExtender";
_updatePanel.ContentTemplateContainer.Controls.Add(_listSearchExtender);
_ddlPostBackTrigger = new PostBackTrigger();
_ddlPostBackTrigger.ControlID = _dropDownList.ClientID;
//_updatePanel.Triggers.Add(_ddlPostBackTrigger);
Controls.Add(_updatePanel);
}
protected override void OnPreRender(EventArgs pE)
{
if (EnableCallBacks)
{
_dropDownList.SelectedIndexChanged += DropDownList_SelectedIndexChanged;
}
if (EnableCallBacks && CallBacksAsPostBacks)
{
_updatePanel.Triggers.Add(_ddlPostBackTrigger);
}
//Controls.Add(_updatePanel);
base.OnPreRender(pE);
}
public override void RenderControl(HtmlTextWriter pWriter)
{
// Do some things... like set Enable-state of child controls
base.RenderControl(pWriter);
}
You should add your dynamic controls in PreInit for the events to fire properly.
Use this event for the following:
Check the IsPostBack property to
determine whether this is the first
time the page is being processed. The
IsCallback and IsCrossPagePostBack
properties have also been set at this
time.
Create or re-create dynamic
controls.
Set a master page
dynamically.
Set the Theme
property dynamically.
Read or set
profile property values.
I have a DataGrid that I have bound to a property:
<cd:DataGrid
Name="myDataGrid"
ItemsSource="{Binding Mode=OneWay,Path=Thingies}"
VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Auto">
...
When the Thingies property changes, once all rows in the DataGrid have been populated with the new contents of Thingies, I want the DataGrid to scroll to the bottom row.
In WinForms, I would have done this by subscribing to the DataBindingComplete event. MSDN Forums contains several suggestions on how to do this with Silverlight 4.0 but they range from completely evil to just plain fugly:
start a 100ms timer on load, and scroll when it elapses
count rows as they're added, and scroll to the bottom when the number of added rows equals the number of entities in the data source
Is there an idiomatic, elegant way of doing what I want in Silverlight 4.0?
I stumbled upon this while searching for a resolution to the same problem. I was finding that when I attempted to scroll the selected item into view after filter and sort changes that I frequently received a run time error (index out of bounds). I knew instinctively that this was because the grid was not populated at that particular moment.
Aaron's suggestion worked for me. When the grid is defined, I add an event listener:
_TheGrid.LayoutUpdated += (sender, args) => TheGrid.ScrollIntoView(TheGrid.SelectedItem, TheGrid.CurrentColumn);
This solved my problem, and seems to silently exit when the parameters are null, too.
Why not derive from DataGrid and simply create your own ItemsSourceChanged event?
public class DataGridExtended : DataGrid
{
public delegate void ItemsSourceChangedHandler(object sender, EventArgs e);
public event ItemsSourceChangedHandler ItemSourceChanged;
public new System.Collections.IEnumerable ItemsSource
{
get { return base.ItemsSource; }
set
{
base.ItemsSource = value;
EventArgs e = new EventArgs();
OnItemsSourceChanged(e);
}
}
protected virtual void OnItemsSourceChanged(EventArgs e)
{
if (ItemSourceChanged != null)
ItemSourceChanged(this, e);
}
}
Use the ScrollIntoView method for achieving this.
myDataGrid.ItemSource = Thingies;
myDataGrid.UpdateLayout();
myDataGrid.ScrollIntoView(MyObservableCollection[MyObservableCollection.Count - 1], myDataGrid.Columns[1]);
You don't need to have any special event for this.
I think the nice way to do it, in xaml, is to have the binding NotifyOnTargetUpdated=true, and then you can hook the TargetUpdated to any event of your choice.
<ThisControl BindedProperty="{Binding xxx, NotifyOnTargetUpdated=true}"
TargetUpdated="BindingEndedHandler">