I am working on a Customer Server Control that extends another control. There is no problem with attaching to other controls on the form.
in vb.net: Parent.FindControl(TargetControlName)
I would like to pass a method to the control in the ASPX markup.
for example: <c:MyCustomerControl runat=server InitializeStuffCallback="InitializeStuff">
So, I tried using reflection to access the given method name from the Parent.
Something like (in VB)
Dim pageType As Type = Page.GetType
Dim CallbackMethodInfo As MethodInfo = pageType.GetMethod( "MethodName" )
'Also tried
sender.Parent.GetType.GetMethod("MethodName")
sender.Parent.Parent.GetType.GetMethod("MethodName")
The method isn't found, because it just isn't apart of the Page. Where should I be looking? I'm fairly sure this is possible because I've seen other controls do similar.
I forgot to mention, my work-around is to give the control events and attaching to them in the Code-behind.
If you want to be able to pass a method in the ASPX markup, you need to use the Browsable attribute in your code on the event.
VB.NET
<Browsable(True)> Public Event InitializeStuffCallback
C#
[Browsable(true)]
public event EventHandler InitializeStuffCallback;
Reference:
Design-Time Attributes for Components and BrowsableAttribute Class
All the events, properties, or whatever need to be in the code-behind of the control with the browsable attribute to make it so you can change it in the tag code.
Normally you wouldn't need to get the method via reflection. Inside your user control, define a public event (sorry I do not know the vb syntax so this will be in c#)
public event EventHandler EventName;
Now, inside your aspx page, or whatever container of the user control, define a protected method that matches the EventHandler:
protected void MyCustomerControl_MethodName(object sender, EventArgs e) { }
Now, inside your markup, you can use
<c:MyCustomerControl id="MyCustomerControl" runat=server OnEventName="MyCustomerControl_MethodName">
Your workaround is actually the better answer. If you have code that you must run at a certain part of your control's lifecycle, you should expose events to let the container extend the lifecycle with custom functionality.
buyutec and Jesse Dearing both have an acceptable answer.
[Browsable(true)]
lets you see the property in the Properties window. However, the event doesn't show up, which makes no difference to me.
The thing I overlooked earlier was the fact that when you reference a control's even from the tag, it prep-ends On.
Every ASP.NET page is class of its own inherited from Page as in:
class MyPage : Page
Therefore, to find that method via Reflection, you must get the correct type, which is the type of the page class that stores the page code.
I suppose you need to support multiple pages for this control to be instantiated in I believe you can find the child type of any instance of Page via Reflection, but I do not remember how, but you should be able to do it.
but... like everyone else has said, such case is what events are for.
Related
For logging user actions in my WPF forms, I added some global event handlers
I want to log exactly which control fire the event, is there some unique identifier for a wpf UIElement like ClientId in ASP.Net?
Why don't you use the Hash Code.
You can compare the values to make sure they are the same object, and its easy to get them with .GetHashCode()
Edit
Obviously this is different every time you run the program, so actually this is prolly a bad idea, unless you want to update the log each time the process is logged. Still possible though
I mean you could store a hash value for each object at the time the log is created, but i don't know if I like that
Seems I found answer to my question, the answer is No, now way to do that, As noted in MSDN here (http://msdn.microsoft.com/en-us/magazine/dd483216.aspx)
Notice that the top-level Window control definition does not contain a
Name attribute. This is significant because, as we'll see shortly,
when you write test automation, an easy way to get a reference to a
control using the MUIA library is to access the AutomationId property,
which is generated by the compiler from the control's Name attribute.
Controls without a XAML Name attribute will not receive an
AutomationId property. This idea is a specific, low-level example of
the importance of considering application design issues for things
such as security, extensibility, and test automation.
One way you can do this is with a custom attribute. Like so...
The UIElement you want to log (UserControl for example):
[UserInterfaceID(ID = "{F436E9B3-C2F6-4CF8-8C75-0A2A756F1C74}")]
public partial class MyUserControl : UserControl
{
InitializeComponent();
// or whatever...
}
Then you need the custom attribute class
[System.AttributeUsage(AttributeTargets.Class)]
public class UserInterfaceIDAttribute : Attribute
{
public Guid ID { get; set; }
}
Now in your code, you can do something like this:
MyUserControl control = new MyUserControl();
foreach(object att in control.GetCustomAttributes(typeof(UserInterfaceAttribute),false))
{
UserInterfaceAttribute uiAtt = (UserInterfaceAttribute)att;
Guid theID = uiAtt.ID;
}
Because you are tagging the control with an attribute in the code, the unique identifier never changes no matter how many times you kill / launch the application.
Of course this is a basic example that shows how to access the ID but you will probably want to use some type of Aspect Oriented Programming. I do exactly this kind of thing using Castle Windsor Interceptors, but that is out of the scope of this post.
Ideally, you will be accessing this ID when there is some kind of event that gets fired. Using interceptors allows you go capture method calls before they are invoked wherein you can look up the ID as shown above and log the action. Alternatively, you can just use
this.GetCustomAttributes(...)
in some method when an event is fired on the control and embed your Logging code there. This pattern is not the best because you're sprinkling cross-cutting concerns all over making some type of Aspect Oriented Programming approach better...but again I digress and it is out of the scope of this post...but you get the idea.
Hope this helps.
You're just looking at adding a Name or x:Name so that the Window/UserControl/Page exposes the control to the rest of the class with the specified name.
<Window ...>
<Grid>
...
<!-- These controls are named, so you can access them directly by name -->
<Button x:Name="btnMyNamedButton" ... />
<Button Name="btnMyOtherNamedButton" ... />
<!-- This control is not named, so you can not directly access it by name -->
<Button ... />
<Grid>
</Window>
public partial class MyWindow : Window
{
public MyWindow()
{
InitializeComponent();
//btnMyNamedButton can be accessed
//btnMyOtherNamedbutton can also be accessed
//The third button can be accessed, but not directly by name.
}
}
Also, you can always use the FrameworkElement.Tag object. It's meant to store arbitrary information, so you can use this as a unique identifier if you want to.
I believe, for logging user actions, you can use the UIAutomation tree and the AutomationElement.AutomationId property, because this approach is supported in all standard UI controls by default. Many third-party controls are also supports the AutomationId for their elements (e.g. grid cells). An AutomationId is useful for creating test automation scripts.
see FrameworkElement.Tag Property
The Tag property can be used. It is of type object and can be set to anything.
I think UIElement.Uid should work. Assign it in XAML and access it in code.
<Label Uid="FirstName"/>
OR in a style property
<Setter Property="Uid" Value="SecondName"/>
Then refer in C# code:
if (Keyboard.FocusedElement is UIElement uiElement)
{
Debug.WriteLine(this, $"{uiElement.Uid}");
}
I have created sharepoint 2010 visual webpart in VisualStudio2010 with three user controls (.ascx). I want to dynamically change usercontrol in the webpart by clicking some button at currently loaded usercontrol. The main problem consist in the fact that buttonClick event is handled only after execution CreateChildControls method (where I try to get needed usercontrol using ViewData). Could anyone please help me to solve this problem?
Lee's response is basically right and may work well for you. However, you should not just use __doPostBack and rely that it will be always "magically" there for you. This method and variables mentioned by Lee are internal to ASP.NET and they are not meant to be used directly. Also, if you do not place any postback-ing control on your page this method will actually not be generated and your code calling it would fail.
Luckily, the code to cause and handle a generic postback is very simple. Instead of using built-in event handlers of input controls (which need to be constructed before being triggered - hence the call to CreateChildControls before your handler is called) you would target the postback to the Web Part itself:
public class MyWebPart : WebPart, IPostBackEventHandler {
protected override void CreateChildControls() {
Control clickable = ...; // Create a clickable control.
// Get JavaScript expression to send postback "test" to "this" web part.
var postBack = Page.ClientScript.GetPostBackEventReference(this, "test");
clickable.Attributes["onclick"] = postBack + "; return false";
Controls.Add(clickable);
}
void IPostBackEventHandler.RaisePostBackEvent(string eventArgument) {
if (eventArgument == "test") { // Recognize and handle our postback.
...
}
}
}
The GetPostBackEventReference will generate the necessary JavaScript expression for you. (And actually, just calling it makes the __doPostBack "magically" appear on the page.) The RaisePostBackEvent will be called between OnLoad and OnPreRender. Make sure not to cause child controls be created before that (by calling EnsureChildControls, for example, or by any other means). If you need multiple postback-ing controls the eventArguments parameter will let you differ among them.
You need the postback triggers in your user controls and not directly in the Web Part. I showed it in the Web Part just to keep it simple. You can put the result of GetPostBackEventReference to any control providing you use the right Page and Web Part instances when calling it.
--- Ferda
A way to do this would be have the button call a javascript function that in turn calls the following:
__doPostBack('LoadControl', 'ControlName');
You can then use the server variables __EVENTTARGET and __EVENTARGUMENT to find out which control to load within your CreateChildControls event handler.
I had that problem too.
Add this to the event handler (after executing your code inside the handler)
this.Page.Response.Redirect(HttpContext.Current.Request.Url.AbsoluteUri, true);
Regards,
Pedro
There's loads of posts on this subject on the net, but I cant find one that fits my situation;
I've have a BasePage class, which my .aspx inherit from; I also have BaseLabel & BaseDDL classes, which extend Label & Dropdownlist respectively. On top of this I have a ReadyDDL class, which combines BaseLabel & BaseDDL into a single control (but this is a class, not a user control) and renders them with their own Div, Table, TableRow, TableCells, & another Label. The ReadyDDL class enables me to define label & dropdownlist & layout in a single html statement as per:
<moc:ReadyDDL ID="Person" runat="server" Member="#UserID" Caption="Create strike for"
DataSourceSQL="SELECT ID, UserName FROM [User] WHERE isDeleted = 0 AND ClientID = 3" TextField="UserName" ValueField="ID"
OnSelectedIndexChanged="ddl_SelectedIndexChanged" />
However I have a problem or two:
a) The event doesnt fire. The posts I have read on this subject say that the dropdown must be recreated OnInit & all will be fine. BUT -
I'm not dynamically creating a dropdownlist, but a custom extension of one - thus the code which creates the dropdownlist isnt in my aspx, where the event handler is defined, but is in a separate .cs file and accordingly, I cannot write
ddl.SelectedIndexChanged += new EventHandler(X);
because X doesnt exist in the class, only the page.
The only way I've found to get around this is to expose a string property (OnSelectedIndexChanged) which sets another property in BaseDDL, and when BaseDDL is rendered, to add the OnSelectedIndexChanged property to the markup produced.
The html produced looks ok, and on screen it looks ok, and it does postback when I change the selection in the control, but the eventhandler doesnt fire: it currently just contains a couple of assignment statements, which I have a breakpoint on, and which isnt reached.
On reflection, I suppose, rendering the handler only adds the event to the control in so far as the client is concerned, and the server doesnt know about it - but how can I overcome this, and define the handler at control initialisation, when the handler isnt in the same source code file as the initialisation code?
Does anyone have any ideas on either (1) getting the event to fire, or (2) how I can define the event in code, rather than via rendering?
Any questions please ask. Any help or suggestions will be appreciated, and I will mark Q as answered if suitable information comes.
b) the selected value is lost on postback. I know I have to do something with Viewstate, but I havent figured out just what, yet. If you know how I can implement a solution to this, a short example would be much appreciated.
Appears that your are developing a composite control - the correct way to go about this is to inherit from CompositeControl class and override CreateChildControls to add your child controls. This method is called by ASP.NET early in life-cycle and that would eliminate your view-state related issues.
See this article for developing composite control. For event, string typed property is not going to work - you must define the event at your composite control level. You can bubble up the child's event by raising your own event in the handler (this is shown in the article). Another way would be short-circuit the event handlers. For example, define the event in your composite control such as
public event EventHandler SelectedIndexChanged
{
add
{
childDdl.SelectedIndexChanged += value;
}
remove
{
childDdl.SelectedIndexChanged -= value;
}
}
childDll is reference to your child ddl control.
I want to create my own naming convention for page events rather than AutoEventWireUp but I couldn't find Component Initialize methods any where ? Should I override it ? But in which class it is defined ?
Thanks...
Edit :
For example : I don't want to use Page_Load but LoadThisPage naming. So It should be like
Load += new LoadThisPage(sender,e);
I was expecting a InitializeComponent method where I can initialize page,controls etc. events handlers...But it turned out to be Constructor function :)
So what confused me is I thought there should have been a method like InitializeComponent which does things for me already created by Designer itself so I thought I could define my own event handler names within this method by overriding it in the say Default.aspx.cs .
But the answer was simple :) Thanks...
Your question isn't really clear as to what you're trying to do.
Page events are defined in the System.Web.UI.Page class, some of which are inherited from System.Web.UI.Control. You don't need to use AutoEventWireUp if you don't want to, and you're free to override all of the Page methods that would normally raise the lifecycle events (OnInit, OnLoad, OnPreRender, etc.) and then not call the base methods, effectively squelching the events from being raised.
You can see some limited discussion around this on this blog post.
As Hogan noted, this sounds like a bad idea. Could you expand on what you're trying to accomplish?
This sounds like a bad idea to me.
However, you should be able to find auto-created code by selecting the project display options, I believe it is right click in project explorer, and selecting display hidden files or display all files. Then you will see additional .vb files created by the system. You might also have click on the little plus sign.
additonal added notes
Most of the events (btw) are defined in the base class(es) in System.Web.UI.Page and not in the code created for a specific instance.
I have a Form and a UserControl. The UserControl has a menu, and the form has a tabstrip (General, Food, Vitamins etc).
In the UserControl, I have the following code: (Form name is frmForm, the tab names in the form are tabGeneral,tabFood, tabVitamins)
frmForm fm=new frmForm();
fm.tabMain.Selected=tabVitamins;
I call these line from the UserControl to capture the tab to get selected on the form, but it does not select the vitamins tab.
Where am I going wrong? I have access specifier as Protected Internal for tabs in the form.
Please advice.
Thanks,
Karthick
When you write new frmForm(), you're creating a completely new instance of frmForm, which is then discarded.
To get the frmForm instance that holds your control, call the FindForm() method and cast to frmForm.
For example:
frmForm myForm = FindForm() as frmForm;
if(myForm != null)
myForm.tabMain.SelectedTab = myForm.tabVitamins;
If the control is on some other form, this code won't do anything.
By the way, Hungarian notation is frowned upon in .Net.
Your form should probably be named something like MainForm.
SLaks has correctly pointed out your fundamental error, and given you a valid example of a way, via a call to the method 'FindForm, to get the Form the UserControl is sited on.
It may be valuable to you to keep in mind that a UserControl (and all Controls) also has a 'Parent property, but, of course, a UserControl could be placed inside another Control on a Form (like your UserControl could be inside a Panel on the Form) : in that case the UserControl's Parent would be the control it's inside on the Form (like, a Panel), not the Form itself, but 'FindForm will do the right thing to get you the Form it's on.
However you are calling a Method every time you use 'FindForm, and "best practice" suggests that what you want to do is to "inject" a reference to the Form into the UserControl at run-time so that it can always access its Form property easily, without calling a 'Method.
In your example, on a practical level, this (calling the Method) may make almost no difference in performance, but, imho, as you get to a place with WinForms and .NET where you might have a UserControl that will need access to its Parent Form very frequently, this will pay off, and it's a better way to structure your code in the long run, for maintenance.
Wes showed you one way you can "embed" (inject) the UserControl's hosting Form : using an overloaded constructor for the UserControl. But that requires you to modify the Designer.cs file in standard WinForms, and I strongly advise you against that, even though it will work. Particularly if you are just "getting your feet on the ground" in .NET, I strongly advise you against modifying it, or anything having to do with the Form's constructor and its internal call to : InitializeComponent();
Also, as you progress with WinForms you are going to meet many situations where you are going to want instances of "objects" (a Control, a Form, an instance of a Class) to contain references to other instances of "objects.
If you can understand and use one simple use of "injection" here, you are going to make progress to make yourself ready to handle more complex .Net programming in the future.
Another way is to put a Public Property in the UserControl that can be set in code from the MainForm. In the UserControl something like :
private frmForm ParentForm;
public frmForm UCParentForm
{
set { ParentForm = value; }
}
So then in your main form's code, perhaps in the Load event like this :
private void frmForm_Load(object sender, EventArgs e)
{
TheUserControl.UCParentForm = this;
}
or when you need to, you set the UserControl's 'ParentForm property once. So you have eliminated using the method 'FindForm().
In this case, if you only want access to a specific control on the UserControl's Parent Form, like a TabControl, you might consider that you want to make the Property you set of type TabControl, rather than Form : the same coding technique shown above can be used in the UserControl :
private TabControl mainFormTabControl;
public TabControl MainFormTabControl
{
set { mainFormTabControl = value; }
}
imho, it is when you are creating UserControls dynamically at run-time, using an overloaded constructor, as Wes suggests, is the best strategy. And using overloaded constructors has many, many others uses in .NET that you'll get into.
good luck !
You should not be creating a new frmForm() inside the user control. You could pass a reference to the frmForm to the user control.
In your user control constructor try something like this.
private frmForm fm;
public YourUserControl(frmForm fm)
{
this.fm = fm;
}
Then you could use.
fm.tabMain.Selected=tabVitamins;
Does that help?