I have a set of User controls shown in a Page (ABC.aspx) in 5 to 6 tabs. Each of the User control has many Textboxes, Drop Downs etc.. I need to detect any user changes on any of the fields in those user controls and do some processing on Parent page (ABC.aspx) on which these user controls reside.
The User Controls are implemented as follows.
There is an XMl file for each of User controls which is read and creates controls accordingly.
The render method in ascx.cs reads this xml file and renders the UI Acordingly for that User Control.
Lets say that sample XMl for a User control is
<uigroup groupname ="Schematic(Logical) Symbol Request">
<uirow>
<uicontrol displayname="Request Type" datatype="DropDown" isrequired="false" domainname="RequestType" dropdownEventHandler="OnScenarioChange(this)" key="Schematic Scenario" defaultValue="New"></uicontrol>
</uirow>
<uirow>
<uicontrol displayname="Logical Name" datatype="TextBox" isrequired="false" supportedscenarios="New,Use Existing,Update Existing" key="Schematic Symbol Name"></uicontrol>
<uicontrol displayname="Similar Symbol" datatype="TextBox" isrequired="false" supportedscenarios="New" key="Similar Schematic Symbol"></uicontrol>
</uirow>
<uirow>
<uicontrol displayname="Type of Change" datatype="DropDown" isrequired="false" domainname="Type of Schematic Change" supportedscenarios="Update Existing" key="Type of Schematic Change"></uicontrol>
<uicontrol displayname="Layout Preference" datatype="DropDown" isrequired="false" domainname="Schematic Layout Preference" supportedscenarios="New,Update Existing" key="Schematic Layout Preference"></uicontrol>
</uirow>
<uirow>
<uicontrol displayname="Justification" datatype="DropDown" isrequired="false" domainname="Schematic Justification" supportedscenarios="Update Existing" key="Schematic Justification"></uicontrol>
<uicontrol displayname="Logical Directory" datatype="DropDown" isrequired="false" domainname="ICL Logical Directory" supportedscenarios="New" key="ICL Logical Directory"></uicontrol>
</uirow>
Usercontrol.ascx.cs has
protected void Page_Load(object sender, EventArgs e)
{
}
protected void Page_Init(object sender, EventArgs e)
{
this.Render();
}
public void Render()
{
this.Render(this.XmlPath);
}
and my render function does the following
public void Render(string _filePath)
{
UITab tab = this.GetDataFromXML(_filePath);
foreach (UIGroup g in tab.Groups)
{
//Add a panel
Panel groupPanel = this.AddPanel(g);
foreach (UIRow r in g.Rows)
{
Table table = new Table();
groupPanel.Controls.Add(table);
TableRow tableRow = new TableRow();
table.Rows.Add(tableRow);
foreach (UIControl c in r.Controls)
{
switch (c.DataType)
{
case UIDataType.Textbox:
this.AddTextBox(groupPanel, c, tableRow);
break;
case UIDataType.Dropdown:
this.AddDropdown(groupPanel, c, tableRow);
break;
case UIDataType.LabelInfo:
this.AddLabelInfo(groupPanel, c,tableRow);
break;
case UIDataType.Label:
this.AddLabel(groupPanel, c,tableRow);
break;
}
}
}
}
}
We are rendering other user controls similar way. we need to find a way to detect user changes on any of these user controls (like a text box changed or dropdown changed etc..) and do process the Parent page.
I am not sure if Delegates and Events might be a good fit and if yes, how would they fit in this architecture are my biggest questions here. Any constructive inputs would really help.
Thanks,
Yes, creating a custom event for your UserControl makes a lot of sense.
Here's an example (note: the example shows the basic idea and does not use an XML document as a datasource; nonetheless, the example should be sufficiently instructive that you can apply it to what you've built):
Code for a simple User Control that dynamically injects nested controls:
...in the ascx file:
<%# Control Language="C#" AutoEventWireup="true" CodeBehind="TabControl.ascx.cs" Inherits="StackOverflowAspNetQuestionAnswers.Controls.TabControl" %>
<asp:Panel ID="panel" runat="server">
</asp:Panel>
...in the code-behind file for the user control:
public partial class TabControl : System.Web.UI.UserControl
{
public event EventHandler<ControlChangedEventArgs> ControlUpdated;
protected void Page_Load(object sender, EventArgs e)
{
TextBox textBox = new TextBox();
textBox.AutoPostBack = true;
textBox.ID = "textBox";
textBox.TextChanged += textBox_TextChanged;
DropDownList dropDown = new DropDownList();
dropDown.Items.Add(new ListItem("Option 1", "Option 1"));
dropDown.Items.Add(new ListItem("Option 2", "Option 2"));
dropDown.AutoPostBack = true;
dropDown.TextChanged += dropDown_TextChanged;
panel.Controls.Add(textBox);
panel.Controls.Add(dropDown);
}
void dropDown_TextChanged(object sender, EventArgs e)
{
ControlChangedEventArgs args = new ControlChangedEventArgs();
args.ControlID = ((DropDownList)sender).ID;
args.ControlValue = ((DropDownList)sender).SelectedValue;
ControlUpdated(this, args);
//CODE EDIT:
UnhookEventHandlers();
}
void textBox_TextChanged(object sender, EventArgs e)
{
ControlChangedEventArgs args = new ControlChangedEventArgs();
args.ControlID = ((TextBox)sender).ID;
args.ControlValue = ((TextBox)sender).Text;
ControlUpdated(this, args);
//CODE EDIT:
UnhookEventHandlers();
}
public virtual void OnControlUpdated(ControlChangedEventArgs e)
{
EventHandler<ControlChangedEventArgs> handler = ControlUpdated;
if (handler != null)
{
handler(this, e);
}
//CODE EDIT:
UnhookEventHandlers();
}
//CODE EDIT:
private void UnhookEventHandlers()
{
foreach (var c in panel.Controls.OfType<DropDownList>())
{
c.TextChanged -= dropDown_TextChanged;
}
foreach (var c in panel.Controls.OfType<TextBox>())
{
c.TextChanged -= textBox_TextChanged;
}
}
}
public class ControlChangedEventArgs : EventArgs
{
public string ControlID { get; set; }
public string ControlValue { get; set; }
}
Here's the code for the parent page that uses this simple tab control:
...in the .aspx file:
<%# Page Language="C#" AutoEventWireup="true" CodeBehind="RaisingEventFromUserControl_Question.aspx.cs" Inherits="StackOverflowAspNetQuestionAnswers.RaisingEventFromUserControl_Question" %>
<%# Register Src="~/Controls/TabControl.ascx" TagPrefix="uc1" TagName="TabControl" %>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:Label ID="message" runat="server" />
<uc1:TabControl runat="server" id="TabControl" />
</div>
</form>
</body>
</html>
...in the code-behind file of the parent page:
public partial class RaisingEventFromUserControl_Question : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
TabControl.ControlUpdated += TabControl_ControlUpdated;
}
void TabControl_ControlUpdated(object sender, ControlChangedEventArgs e)
{
//EDIT: add code to update some data source to lock the form from other users
message.Text = String.Format("A child control with an ID of '{0}' was updated. It now has the value of '{1}'.", e.ControlID, e.ControlValue);
}
}
As you can see in the example, I have a custom event named "ControlUpdated," which I fire anytime there is a change to either the nested TextBox or DropDownList that I dynamically inject into the UserControl at runtime. Also, keep in mind that I set the "AutoPostBack" property for both the TextBox and DropDownList to ensure that the client-side code generated by ASP.Net executes whenever I change the value of the textbox / dropdown.
Also, you can see that for my custom event, I also created a custom EventArgs class so that I could pass along pertinent information from the nested control that is updated (that is, both the ID and the changed value).
Related
I have a master page which contains a label for status messages. I need to set the status text from different .aspx pages. How can this be done from the content page?
public partial class Site : System.Web.UI.MasterPage
{
public string StatusNachricht
{
get
{
return lblStatus.Text;
}
set
{
lblStatus.Text = value;
}
}
protected void Page_Load(object sender, EventArgs e)
{
}
}
I have tried this, but was unsuccessful in making it work:
public partial class DatenAendern : System.Web.UI.Page
{
var master = Master as Site;
protected void Page_Load(object sender, EventArgs e)
{
if (master != null)
{
master.setStatusLabel("");
}
}
protected void grdBenutzer_RowCommand(object sender, GridViewCommandEventArgs e)
{
try
{
//some code
if (master != null)
{
master.setStatusLabel("Passwort erfolgreich geändert.");
}
}
catch (Exception ex)
{
if (master != null)
{
master.setStatusLabel("Passwort konnte nicht geändert werden!");
}
}
}
}
}
In the MasterPage.cs file add the property of Label like this:
public string ErrorMessage
{
get
{
return lblMessage.Text;
}
set
{
lblMessage.Text = value;
}
}
On your aspx page, just below the Page Directive add this:
<%# Page Title="" Language="C#" MasterPageFile="Master Path Name"..... %>
<%# MasterType VirtualPath="Master Path Name" %> // Add this
And in your codebehind(aspx.cs) page you can then easily access the Label Property and set its text as required. Like this:
this.Master.ErrorMessage = "Your Error Message here";
In Content page you can access the label and set the text such as
Here 'lblStatus' is the your master page label ID
Label lblMasterStatus = (Label)Master.FindControl("lblStatus");
lblMasterStatus.Text = "Meaasage from content page";
It Works
To find master page controls on Child page
Label lbl_UserName = this.Master.FindControl("lbl_UserName") as Label;
lbl_UserName.Text = txtUsr.Text;
I have a helper method for this in my System.Web.UI.Page class
protected T FindControlFromMaster<T>(string name) where T : Control
{
MasterPage master = this.Master;
while (master != null)
{
T control = master.FindControl(name) as T;
if (control != null)
return control;
master = master.Master;
}
return null;
}
then you can access using below code.
Label lblStatus = FindControlFromMaster<Label>("lblStatus");
if(lblStatus!=null)
lblStatus.Text = "something";
You cannot use var in a field, only on local variables.
But even this won't work:
Site master = Master as Site;
Because you cannot use this in a field and Master as Site is the same as this.Master as Site. So just initialize the field from Page_Init when the page is fully initialized and you can use this:
Site master = null;
protected void Page_Init(object sender, EventArgs e)
{
master = this.Master as Site;
}
This is more complicated if you have a nested MasterPage. You need to first find the content control that contains the nested MasterPage, and then find the control on your nested MasterPage from that.
Crucial bit: Master.Master.
See here: http://forums.asp.net/t/1059255.aspx?Nested+master+pages+and+Master+FindControl
Example:
'Find the content control
Dim ct As ContentPlaceHolder = Me.Master.Master.FindControl("cphMain")
'now find controls inside that content
Dim lbtnSave As LinkButton = ct.FindControl("lbtnSave")
If you are trying to access an html element: this is an HTML Anchor...
My nav bar has items that are not list items (<li>) but rather html anchors (<a>)
See below: (This is the site master)
<nav class="mdl-navigation">
<a class="mdl-navigation__link" href="" runat="server" id="liHome">Home</a>
<a class="mdl-navigation__link" href="" runat="server" id="liDashboard">Dashboard</a>
</nav>
Now in your code behind for another page, for mine, it's the login page...
On PageLoad() define this:
HtmlAnchor lblMasterStatus = (HtmlAnchor)Master.FindControl("liHome");
lblMasterStatus.Visible =false;
HtmlAnchor lblMasterStatus1 = (HtmlAnchor)Master.FindControl("liDashboard");
lblMasterStatus1.Visible = false;
Now we have accessed the site masters controls, and have made them invisible on the login page.
For your information (this was my original problem webforms : add dynamically in javascript option to a dropdownlist, solved thanks to ConnorsFan).
My goal is to having a infragistics dropdownlist enabling multi selection and at each selection I want an event fired server side without refreshing the whole page.
This is my aspx page :
<%# Register assembly="Infragistics45.Web.v16.1, Version=16.1.20161.1000, Culture=neutral, PublicKeyToken=7dd5c3163f2cd0cb" namespace="Infragistics.Web.UI.ListControls" tagprefix="ig" %>
<asp:Content ID="BodyContent" ContentPlaceHolderID="MainContent" runat="server">
<ig:WebDropDown ID="WebDropDown1" runat="server" Width="200px" OnSelectionChanged="WebDropDown1_SelectionChanged" EnableMultipleSelection="true" EnableClosingDropDownOnSelect="false" AutoPostBack="true">
</ig:WebDropDown>
<asp:UpdatePanel runat="server" UpdateMode="Conditional">
<Triggers>
<asp:AsyncPostBackTrigger ControlId="WebDropDown1" EventName="SelectionChanged"/>
</Triggers>
</asp:UpdatePanel>
This is my code-behind page :
private List<string> allPossiblechoices = new List<string>() { "a", "b", "c","d","e" };
private List<string> defaultChoices = new List<string>() { "a", "b", "c" };
protected void Page_Load(object sender, EventArgs e)
{
if (!this.IsPostBack)
{
foreach(var choice in allPossiblechoices)
{
WebDropDown1.Items.Add(
new DropDownItem()
{
Text = choice,
Value = choice,
Selected = defaultChoices.Contains(choice)
}
);
}
}
}
protected void WebDropDown1_SelectionChanged(object sender, DropDownSelectionChangedEventArgs e)
{
// I put a breakpoint here to see what e.NewSelection and e.OldSelection are
}
By default, when the page is requested for the first time, the dropdown is composed of a,b,c,d,e and only a,b,c are selected.
When I select d, a request is indeed send to the server (I put a breakpoint in my event handler) and the results are correct :
EventArgs e.OldSelection contains a,b,c.
EventArgs e.NewSelection contains a,b,c,d.
Then, I deselect d and the results are the following :
EventArgs e.OldSelection contains a,b,c.d.
EventArgs e.NewSelection contains a,b,c,d.
I don't understand why EventArgs e.NewSelection contains d even if I deselected it.
The fact that it's even more strange, is that I have done the same thing without the updatePanel and everything works fine, the selection (new and old) are correct.
Thanks in advance for your help.
You can call the RegisterStartupScript static method of the ScriptManager class to add some Javascript code to be executed after the event handler has returned. In the code below, I assume that the ID of the UpdatePanel is UpdatePanel1.
protected void WebDropDown1_SelectionChanged(object sender, DropDownSelectionChangedEventArgs e)
{
WebDropDown wdd = sender as WebDropDown;
string scriptCode = string.Format("document.getElementById('{0}').openDropDown();", wdd.ClientID);
ScriptManager.RegisterStartupScript(UpdatePanel1, UpdatePanel1.GetType(), "WDDScript1", scriptCode, true);
}
If it works, you will probably see the WebDropDown closing/opening when the panel is updated (unfortunately).
This is probably really simple, but I cant seem to get my values picked up at all from the parent page?
Aspx page is within a master page and ContentPwith the code for example
<asp:Content ID="Content3" ContentPlaceHolderID="ContentPlaceHolder1" runat="server">
<asp:TextBox ID="tbFromDate" runat="server" ></asp:TextBox>
<asp:Button ID="butDisplayInfo" runat="server" Text="Display info" OnClick="butDisplayInfo_Click"
CssClass="button-gray" />
<asp:PlaceHolder ID="placeHolderContent" runat="server">
</asp:PlaceHolder>
What I want is when the button is pressed it loads the UserControl and loads data based on the date put in the text box which I can then use to load data to the control?
.aspx.cs
protected void butDisplayInfo_Click(object sender, EventArgs e)
{
var ctrl = LoadControl("~/Controls/DailyShiftStats.ascx");
ctrl.ID = "ucUserCtrl1";
placeHolderContent.Controls.Add(ctrl);
}
Usercontrol ascx.cs
protected void Page_Load(object sender, EventArgs e)
{
TextBox tb = (TextBox)this.Parent.Page.FindControl("ContentPlaceHolder1").FindControl("tbFromDate");
Response.Write(tb.Text);
}
public void getShiftInfo(DateTime shiftDate)
{
//load my data
}
As per my comment - you would be better defining a property in your control and then have the page pass this in, I have not done this in a while but the basic idea is -
In your control
public DateTime? ShiftDate
{
set { this.shiftDate = value; }
}
private DateTime? shiftDate;
Then you can use shiftDate anywhere in your control where it is needed, if you make it Nullable as above then you can check to see if it has been set and throw an error (or whatever is appropriate) if not.
In your page when creating your control you would then have (Note: you need to cast your control to correct type)
var ctrl = (DailyShiftStats)LoadControl("~/Controls/DailyShiftStats.ascx");
ctrl.ID = "ucUserCtrl1";
//TODO: Handle an invalid date
DateTime shiftDate;
if (DateTime.TryParse(tbFromDate.Text, out shiftDate))
{
ctrl.ShiftDate = shiftDate;
}
placeHolderContent.Controls.Add(ctrl);
I have an Order page with 4 textboxes that are inside an ajax updatepanel. All the 4 have TextChanged events. None of the controls in this page have TabIndex property set. When I enter text in textbox1 & press the tab key, it causes postback, but the next focus is not on textbox2 as I want. The focus is on the page instead. Similarly with all the textboxes.
This Order page uses a master page.
Master page:
<form id = "form1" runat="server">
<asp:ScriptManager ID="ScriptManager1 " runat="server" />
Order page:
<asp:content id ="bodycontent" contentplaceholderID="maincontent" runat="server">
// 4 text boxes
</asp:content>
I cannot add another form or scriptmanager tag in the order page as it errors out saying there can be only instance of them.
So ,there is no FormOrder or ScriptManagerOrder in the Order page's code behind, but I would like to do something of the foll. way.
How can I do this.
protected void textbox1_TextChanged(object sender, EventArgs e)
{
//someFunction();
TextBox tb = (TextBox)FormOrder.FindControl("textbox2");
ScriptManagerOrder.SetFocus(tb);
}
Try this
protected void textbox1_TextChanged(object sender, EventArgs e)
{
//someFunction();
TextBox tb = (TextBox)FormOrder.FindControl("textbox2");
tb.focus();
}
Add following script in js file called i.e focus.js:
var lastFocusedControlId = "";
function focusHandler(e) {
document.activeElement = e.originalTarget;
}
function appInit() {
if (typeof(window.addEventListener) !== "undefined") {
window.addEventListener("focus", focusHandler, true);
}
Sys.WebForms.PageRequestManager.getInstance().add_pageLoading(pageLoadingHandler);
Sys.WebForms.PageRequestManager.getInstance().add_pageLoaded(pageLoadedHandler);
}
function pageLoadingHandler(sender, args) {
lastFocusedControlId = typeof(document.activeElement) === "undefined"
? "" : document.activeElement.id;
}
function focusControl(targetControl) {
if (Sys.Browser.agent === Sys.Browser.InternetExplorer) {
var focusTarget = targetControl;
if (focusTarget && (typeof(focusTarget.contentEditable) !== "undefined")) {
oldContentEditableSetting = focusTarget.contentEditable;
focusTarget.contentEditable = false;
}
else {
focusTarget = null;
}
targetControl.focus();
if (focusTarget) {
focusTarget.contentEditable = oldContentEditableSetting;
}
}
else {
targetControl.focus();
}
}
function pageLoadedHandler(sender, args) {
if (typeof(lastFocusedControlId) !== "undefined" && lastFocusedControlId != "") {
var newFocused = $get(lastFocusedControlId);
if (newFocused) {
focusControl(newFocused);
}
}
}
Sys.Application.add_init(appInit);
Reference it using Scriptmanager like below:
<ajax:ScriptManager ID="ScriptManager1" runat="server">
<Scripts>
<ajax:ScriptReference Path="~/Js/FixFocus.js" />
</Scripts>
</ajax:ScriptManager>
For more information check out below link:
http://couldbedone.blogspot.in/2007/08/restoring-lost-focus-in-update-panel.html
It's not a good practice to use server-side controls for a Tab. Why don't you use some jQuery/Bootstrap?
With your current approach you use to many useless Posts/Postbacks overloading your server with a useless work.
I'm having a hard time figuring this out and I hope you guys would help me.
I have a page called Index.aspx with a DropDownList that is a separate UserControl class (because it will be used in other pages). Here's the code for that:
UcSelecionarLocal.ascx:
<%# Control Language="C#" AutoEventWireup="true"
CodeBehind="UcSelecionarLocal.ascx.cs"
Inherits="QuickMassage.uc.UcSelecionarLocal" %>
<asp:DropDownList ID="ddlLocais" runat="server"
CssClass="span4 dropdown-toggle" AutoPostBack="true">
</asp:DropDownList>
UcSelecionarLocal.ascx.cs:
public partial class UcSelecionarLocal : UserControl {
protected void Page_Load(object sender, EventArgs e) {
if (!this.IsPostBack) {
PreencherLocais();
}
}
private void PreencherLocais() {
ddlLocais.Items.Clear();
ddlLocais.Items.Add(new ListItem("Selecione", "0"));
ControleLocal controle = new ControleLocal();
DataTable tab = controle.ListarLocais();
foreach (DataRow row in tab.Rows) {
ddlLocais.Items.Add(new ListItem(row["Descricao"].ToString(),
row["ID"].ToString()));
}
}
}
This control is placed in Index.aspx and loads its values correctly. The form that it's contained in, has the action set to agendamentos.aspx. When I change the ddlist, the page is submitted to the forms action page, as it should be.
On the other page the problems begin: I get the parameters posted to this page and one of them is the ddlist value. In the immediate window, I check the value and it's there, let's say that it is 1.
To make long story short, I have this code:
agendamentos.aspx.cs:
protected void Page_Load(object sender, EventArgs e) {
DropDownList locais = ObterComponenteListaLocais();
try {
locais.SelectedIndex =
int.Parse(HttpContext.Current.Request["ucSelLocal$ddlLocais"]);
}
While debugging, I see that locais.SelectedIndex is -1. After the assignment it remains -1. The page loads and then I change the ddlist value again to 2. When debugging the same code above, I see that the locais.SelectedIndex is now 1. Again, setting it to 2, as it would normally be, produces no effect. If I change the ddlist again to 3, the SelectedIndex becomes 2 and does not take the value 3.
In other words: the value of the index in a newly loaded page is the value of the page that was loaded before.
Could you guys help me?
This is because the Page_Load event is firing in your page before the user control is loading. Do this:
public partial class UcSelecionarLocal : UserControl
{
protected void Page_Load(object sender, EventArgs e)
{
}
public void PreencherLocais()
{
ddlLocais.Items.Clear();
ddlLocais.Items.Add(new ListItem("Selecione", "0"));
ControleLocal controle = new ControleLocal();
DataTable tab = controle.ListarLocais();
foreach (DataRow row in tab.Rows)
{
ddlLocais.Items.Add(new ListItem(row["Descricao"].ToString(), row["ID"].ToString()));
}
}
}
Then in your aspx page:
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
this.idOfYourUserControl.PreencherLocais();
DropDownList locais = ObterComponenteListaLocais();
try {
locais.SelectedIndex =
int.Parse(HttpContext.Current.Request["ucSelLocal$ddlLocais"]);
}
}
Also because your question is a little confusing, an important note is that Page_Load fires before data is captured from controls that post back data. So that's a bad place to get their information because it will be what it was previously. That's why you need to create a function that fires on something like a button click that will execute after the controls data have been loaded.