I'm working on an ASP.NET page, using VB.NET and I have this hierarchy:
Page A
- Web User Control 1
- Web User Control A
- Web User Control B
- Web User Control C
I need to raise an event from Web User Control B that Page A will receive (the event flow will be Web User Control B -> Web User Control 1 -> Page A).
My only approach so far has been this:
1) Add a custom event declaration to both Web User Control B and Web User Control 1 and simply RaiseEvent twice until it gets to Page A (this seems ugly and I don't particularly like it).
My other idea was to create a custom Event class that inhertis from some magical base Event class and create an instance of it in both Web User Control B and Web User Control 1, but that is proving fruitless because I can't find any event base classes (maybe b/c they're aren't any, since it appears to be a keyword, not a class name).
Any help would be appreciated! Thanks and happy coding!
You can use the BubbleEvent concept to do this. A BubbleEvent goes up the control hierarchy until someone handles it. The GridView and Repeater controls do this with their Row/ItemCommand events.
You could implement it into WebUserControl1, turning it into a standard event for the page (like the GridView does):
Class UserControl1 ' Parent
Protected Override Function OnBubbleEvent(sender as Object, e as EventArgs) as Boolean
Dim c as CommandEventArgs = TryCast(e, CommandEventArgs)
If c IsNot Nothing Then
RaiseEvent ItemEvent(sender, c)
Return True ' Cancel the bubbling, so it doesn't go up any further in the hierarchy
End If
Return False ' Couldn't handle, so let it bubble
End Function
Public Event ItemEvent as EventHandler(Of CommandEventArgs)
End Class
Class UserControlB ' Child
Protected Sub OnClicked(e as EventArgs)
' Raise a direct event for any handlers attached directly
RaiseEvent Clicked(Me, e)
' And raise a bubble event for parent control
RaiseBubbleEvent(Me, New CommandEventArgs("Clicked", Nothing))
End Sub
Protected Sub OnMoved(e as EventArgs)
' Raise a direct event for any handlers attached directly
RaiseEvent Moved(Me, e)
' And raise a bubble event for parent control
RaiseBubbleEvent(Me, New CommandEventArgs("Moved", Nothing))
End Sub
End Class
Class PageA
Sub UserControl1_ItemEvent(sender as Object, e as CommandEventArgs) Handles UserControl1.ItemEvent
Response.Write(sender.GetType().Name & " was " & e.CommandName)
End Sub
End Class
Or, do it directly in the page. UserControlB (Child) is the same as above, and UserControl1 (Parent) doesn't need to do anything special - OnBubbleEvent defaults to returning False, so the event bubbles up:
Class PageA
Protected Override Function OnBubbleEvent(sender as Object, e as EventArgs) as Boolean
If sender Is UserControlB Then
Dim c as CommandEventArgs = TryCast(e, CommandEventArgs)
If c IsNot Nothing Then
Response.Write(sender.GetType().Name & " was " & c.CommandName)
Else
Response.Write(sender.GetType().Name & " raised an event, with " & e.GetType().Name & " args)
End If
Return True ' Cancel the bubbling, so it doesn't go up any further in the hierarchy
End If
Return False ' Not handled
End Function
End Class
If your initial event is from a server control (like a Button.Click), then it will have been coded to already raise the bubble event - so UserControlB (Child) doesn't need to do anything to get that to the parent either. You just need to call RaiseBubbleEvent for any of your custom events, or if you want to transform the EventArgs in some way.
The real question here is is the actual action in Web UserControl B something that should notify both, OR, is WebUserControl1 responsible for some processing BEFORE notifying the page.
If each step of the chain has a specific action, your method of raising two events is proper. If it is in a manner where the event just needs to notify everyone you will want to look at different subscription methods to communicate.
Create a Assembly (or Namespace) that is referenced by everything.
Create a interface with the methods you need.
Create a class that manages objects that implemented the interface.
Have Page A implement the interface
Have Page A register itself with the manager class done in step #3
Now Web UserControl B can raise the event by retrieving the page from the manager and calling the method on the interface that raises the event you need.
You avoid tightly coupling the page to the webcontrol because you are using a interface.
Likely you will find that you will have a multiple interface for different areas of your project. For example in my CAM project I have a interface for the Setup Parameters UI, the Shape Entry UI, and the Cut Entry UI. On our website we have different product categories that uses different interfaces. (Software, Machinery, Services, etc).
You can create a public method in Page A which gets called from Web User Control B instead of raising an event up the entire control tree.
This would not be my first choice since this will cause tight coupling between those classes but I hope it solves your problem.
Sample Page:
Public Partial Class TestPage
Inherits Page
Public Sub PerformAction()
'Whatever needs to be done on Page A
End Sub
End Class
Sample User Control:
Public Partial Class TestControl
Inherits UserControl
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
'This will call the Page, obviously this will only
'work when the control is on TestPage
CType(Page, TestPage).PerformAction()
End Sub
End Class
Related
Building my first server control. Getting the "Value cannot be null. Parameter name: child" error in debug at the point indicated in my code. The textbox is created, then becomes nothing when the parent's page_load event is called. The page_load event fires between the OnInit and CreateChildControls in my control. Examples are hard to come by.
Public Class ContactForm
Inherits WebControl
#Region "Local variables"
Private _ShowFirstName As Boolean
Private tbFirstName As TextBox
#End Region
<Browsable(True), _
Bindable(True), _
Category("Appearance"), _
DefaultValue(True), _
Localizable(True), _
Description("Show First Name field")> _
Public Property ShowFirstName() As Boolean
Get
Return _ShowFirstName
End Get
Set(value As Boolean)
_ShowFirstName = value
End Set
End Property
Protected Overrides Sub OnInit(e As EventArgs)
If ShowFirstName = True Then
Dim tbFirstName As New TextBox
tbFirstName.ID = "tbFirstName"
tbFirstName.MaxLength = 30
tbFirstName.Text = "IM HERE"
End If
MyBase.OnInit(e)
End Sub
<<<<< --- NOTE: The Page_Load event of the parent fires here ->>>>>
Protected Overrides Sub CreateChildControls()
Me.Controls.Add(tbFirstName) <<---Error: Value cannot be null. Parameter name: child
If HasChildViewState Then
ClearChildViewState()
End If
MyBase.CreateChildControls()
End Sub
End Class
i haven't done this before but at the top you say, DefaultValue(True); does that mean it must have a default value? maybe if you set a default value OnInit (or make the prop false).
Okay, here is how I got it working.
I basically move the OnInit code into the CreateChildControls...duh!
I created a function (buildForm) that declares and adds all controls in one step like below.
Protected Overrides Sub CreateChildControls()
buildForm()
If HasChildViewState Then
ClearChildViewState()
End If
MyBase.CreateChildControls()
End Sub
SIDEBAR: Web User Control vs Custom Server Control
I spent the last 4-5 days comparing and experimenting with both controls. I've done a lot of User Controls over the years and just copied them into applications. Hated the maintenance that created.
I spent a full day trying to compile my current web controls into single dll that could be used kind of like server controls. I found it tedious and limited.
Many articles, including MSDN, had me scared of server controls saying it is much harder than a web control to create. I can say...so far...I am digging the Server Control. I wish I had done this in the beginning. True reuse. Don't fear the server control.
I have a server control that I am trying to get to save properties as control states but for some reason the properties are not persisting across partial postbacks.
The psuedo code is as follows:
Public Class FileUpload
Inherits ScriptControl
Implements INamingContainer, IPostBackEventHandler
Public Property newFileExt() As String
Get
Dim foundList As String = DirectCast(ViewState(Me.UniqueID & "_fileExt"), String)
If foundList IsNot Nothing Then
Return foundList
Else
Return String.Empty
End If
End Get
Set(ByVal value As String)
ViewState(Me.UniqueID & "_fileExt") = value
End Set
End Property
Protected Overrides Sub OnInit(ByVal e As System.EventArgs)
MyBase.OnInit(e)
Page.RegisterRequiresControlState(Me)
End Sub
Protected Overrides Function SaveControlState() As Object
Dim controlState(6) As Object
controlState(0) = MyBase.SaveControlState()
controlState(1) = newFileExt
Return controlState
End Function
Protected Overrides Sub LoadControlState(ByVal savedState As Object)
Dim controlState() As Object
controlState = CType(savedState, Object)
MyBase.LoadControlState(controlState(0))
newFileExt = CType(controlState(1), String)
End Sub
end class
On this control is an asyncFileUpload ajaxcontroltoolkit control and a button. I have an event for upload complete:
Protected Sub SaveUploadedFile(ByVal sender As Object, ByVal e As AjaxControlToolkit.AsyncFileUploadEventArgs) Handles asyncFileUpload.UploadedComplete
newFileExt= "Some Value"
end sub
Protected Sub bntSelectResults_click(ByVal sender As Object, ByVal e As EventArgs) Handles bntSelectResults.Click
If (newFileExt= "") Then
'this always returns as empty
End If
end sub
So, UploadedComplete is complete it should set the controls state. then, when the user click the button it should read it. Through debugging, I can see that it is set correctly in UploadedComplete event but null when read. Is this due to the cycle of the page or something?
Thanks
jason
EDIT
I traced out the path for how the page cycle is running:
User clicks the async file upload control's browse button and selects a file. This causes the upload process to start
a. OnInit gets called
b. LoadControlState gets called
c. OnLoad gets called
d. asyncFileUpload.UploadedComplete gets called and I set the newFileExt property
here.
e. SaveControlState gets called. newFileExt is set here properly
User clicks a button on the control that initiates another partial postback/update of the update panel
a. OnInit gets called
b. LoadControlState gets called. I can see that the newFileExt property is not set
c. OnLoad gets called
d. Buttons click event gets called and the property is read (which is no longer set)
e. SaveControlState gets called and cycle ends
So, as best as I can tell, the asyncFileUpload application has issues with ViewStates/ControlStates. I ended up just just using sessions.
I'm getting the 'Response is not available in this context error' calling the following function:
Private Sub ReloadPage(ByVal inNumber As Integer) Handles tempaux.Advertise
'Response.Redirect("tope.aspx?dep=" & CStr(inNumber))
Response.Write("<script>window.open('tope.aspx?dep= & CStr(inNumber)','topFrame');</script>")
End Sub
I've changed the line adding the System.Web.HttpContext.Current before Response.Write and I get 'Object reference not set to an instance of an object'.
To give some background: tope.aspx is, as you can see, opened in topframe. As soon as it loads it starts a CustomTimer object I've defined:
Public Class tope
Inherits System.Web.UI.Page
Public funciones As funciones = New funciones
Dim WithEvents tempaux As CustomTimer = Global.objCustomTimer
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Dim inUserProfile As Int64
Try
tempaux.StartTimer()
Catch ex As Exception
'bla bla
End Try
As you can see I've declared the CustomTimer in the Global.asax. The CustomTimer object raises an Advertise event every 5 seconds and passes 'inNumber' as a parameter for the tope.aspx page to refresh some label, a simple thing. CustomTimer is a class I made to manage the timer, it doesn't inherits any other class( For what I've learned in my search it has to inherit some httpthing but I'm not sure). I'm guessing that at some point the httpcontext is being lost (I've searched in google and I couldn't figure its lifecycle or whatever information that tells me why it 'dies). Can anyone help me to find out what is the problem?
thanks
Your timer exists outside of the tope page class, so it is possible that the timer event is firing after the response from the page is complete and there is no longer a HttpContext.Current instance.
It sounds like what you are trying to do is to change an advertising banner on a page every 5 seconds, once the page is loaded. You need to do that using a javascript timer, which would fire every 5 seconds and make a request back to your web server for a new advertisement.
Hi i have a user control that contains a button. I want to Over ride a custom function on click of that button like
Protected Sub btnUploadSpreadSheet_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnUploadSpreadSheet.Click
MyMethod()
End Sub
Public Overridable Sub MyMethod()
' this i want to over ride
End Sub
and in my page where i have added my control when i tried to over ride
Protected Overrides Sub MyMethod ()
End Sub
It is not finding that sub in the base class.
That's because the page is not a child of your UserControl(your page does not inherit from it) and this is the wrong approach anyway. Look up "Inheritance in Visual Basic": http://msdn.microsoft.com/en-us/library/5x4yd9d5%28v=vs.90%29.aspx
What you apparently want is to handle a custom UserControl's event in your page:
Your UserControl:
Class ExcelSheetUploader
Inherits UserControl
Public Event Uploaded(ctrl As ExcelSheetUploader)
Protected Sub btnUploadSpreadSheet_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnUploadSpreadSheet.Click
RaiseEvent Uploaded(Me)
End Sub
End Class
Now you can handle it in your page:
Protected Sub ExcelSheetUploader_Uploaded(ctrl As ExcelSheetUploader) Handles ExcelSheetUploader1.Uploaded
' Do something .... '
End Sub
MSDN: RaiseEvent
You cannot do this because your page is not extending the user control. Only in such a situation your implementation would be suitable
You can not directly override the methods or properties if some class
(e.g. Page) does not inherit You custom Class(Control). If you want to
do some functionality on some event on the page or button click then
create the public methods on the UserControl that raise some event or
execute some code.
Note - Must follow the page - life cycle.
Check this little code snippet that may be helpful to understand.
Private Class MyControl
Inherits NavronChartControl.UserControls.MyControlBase
Protected Overrides Sub OnChartTypeChanged(chartType As UserControls.ChartType)
MyBase.OnChartTypeChanged(chartType)
End Sub
///Call this OnChartTypeChanged(...) using some method
/// Or Create your public methods for your functionality
End Class
///Method at Parent Control
'OnChartTypeChanged is method that raise the event with argument
Protected Overridable Sub OnChartTypeChanged(chartType As ChartType)
'ChartTypeChang is an Event
RaiseEvent ChartTypeChanged(chartType)
End Sub
Check the Programming ASP.NET- Custom and User Controls 's Handling events in VB.NET section.
MSDN - Raising an Event - you can create your own custom event argument that hold some data that you want to pass through event. e.g. e.X
I have the following BasePage class...
Public Class BasePage
Inherits System.Web.UI.Page
Private litError As Literal
Protected SO As Session
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
SO = Session.Item("SO")
If SO Is Nothing Then
Session.Abandon()
Response.Redirect("~/timeout.htm")
End If
litError = Page.FindControl("litError")
If litError IsNot Nothing Then
litError.Visible = False
End If
End Sub
Protected Sub ShowMessage(ByVal Message As String)
Show(Message, "message")
End Sub
Protected Sub ShowError(ByVal Message As String)
Show(Message, "error message")
End Sub
Protected Sub ShowSuccess(ByVal Message As String)
Show(Message, "success message")
End Sub
Private Sub Show(ByVal Message As String, ByVal CssClass As String)
If litError IsNot Nothing Then
litError.Text = String.Format("<span class=""{0}"">{1}</span>", CssClass, HttpUtility.HtmlEncode(Message))
litError.Visible = True
End If
End Sub
End Class
Every page in this application inherits this class. The SO variable represents a custom session class, that is very simple and just holds a couple of basic settings to be used throughout the application. The problem is, my Page_Load in this base class does not fire if a natural postback occurs (in this case, it is a gridview postback by sorting/paging). Then later in my code when I reference SO, I get a null reference exception because it hasn't been pulled from session.
Why doesn't the base Page_Load fire?
Try moving your code into the Page_Init event.
Microsoft has some info on each event in the lifecycle http://msdn.microsoft.com/en-us/library/ms178472.aspx. This MSDN page tells you what types of things you should handle in each event.
You might want to think about implementing SO as a property, where the Get does (not sure if this is correct VB...)
Dim so As Session = Session.Item("SO")
If so Is Nothing Then
Session.Abandon()
Response.Redirect("~/timeout.htm")
End If
return so
It could be that something else is happening in the Init events that is causing it to fail. So rather than it not being called it just hasn't been called yet.
It could be that the autoevent wireup isn't wiring it up correctly, tend to override the OnInit event and attach the events manually myself, I have also read somewhere that this improves perfomance by not requiring the framework to do heaps of reflection on every post.
But back to your problem... try making the SO object private and create a property accessor for it that first checks that if the private is set, if not set it, before returning the private variable. If it isn't set and can't be found then it can abort the same way you are doing in the Load. This means that to load the variable you won't be dependent on the Page_Load from firing and thus the SO object should be available for you during the init routines, if you need it.