I have an ASCX user control that is being used in about 60 web forms pages. This control basically renders a series of nested drop down lists.
Inside the control, the method for populating the final list is like this:
Public Sub PopulateList()
Dim dt as DataTable = MyDAL.GetListValues()
For each dr as DataRow in dt.Rows
Dim li as new ListItem
' ...
myDDL.Items.Add(li)
Next
End Sub
In a small handful of pages, I need this method to work slightly differently (the list items are populated with more details, from a different table).
Is it possible somehow for the parent page of the control to somehow override the method? I read about Overriding on various MSDN pages, but can't figure this out. I can declare the method as...
Public Overridable Sub PopulateList()
... but then in VS2015, when I try to create an overriding method using Public Overrides the Intellisense menu does not contain any reference to the user control, or the method. I assume that this is because the control isn't actually being inherited by the page?
Is this possible to do, or is there another way please?
You cannot "override" the method in the parent page, because the page does not inherit from your control's class.
You could possibly create an event handler, or pass in a delegate to modify the behavior of the method.
For example:
Public Class Test1
Dim t2 As New Test2
Sub New()
' Call populateList with an action handler
t2.PopulateList(Sub(ddl)
' Do your logic here
Dim dt As DataTable = MyDAL.GetListValues()
For Each dr As DataRow In dt.Rows
Dim li As New ListItem
' ...
ddl.Items.Add(li)
Next
End Sub)
End Sub
End Class
Public Class Test2
Public Sub PopulateList(Optional handler As Action(Of DropDownList) = Nothing)
If handler Is Nothing Then
' Default behavior
Dim dt As DataTable = MyDAL.GetListValues()
For Each dr As DataRow In dt.Rows
Dim li As New ListItem
' ...
myDDL.Items.Add(li)
Next
Else
' Invoke action handler and pass a reference to the dropdown you want to add items to
handler(myDDL)
End If
End Sub
End Class
Example using an event:
Event MyCustomEvent As EventHandler(Of MyCustomEventArgs)
Public Sub PopulateList()
Dim args As New MyCustomEventArgs()
args.ListObject = myDDL
RaiseEvent MyCustomEvent(Me, args)
' Do default behavior if not handled by event code
If Not args.Handled Then
Dim dt As DataTable = MyDAL.GetListValues()
For Each dr As DataRow In dt.Rows
Dim li As New ListItem
' ...
myDDL.Items.Add(li)
Next
End If
End Sub
Custom event args class:
Public Class MyCustomEventArgs
Inherits EventArgs
Public Property Handled As Boolean
Public Property ListObject As DropDownList
End Class
Handled on your page:
Protected Sub MyControl_MyCustomEvent(sender As Object, e As MyCustomEventArgs) Handles MyControl.MyCustomEvent
e.Handled = True
' Do work on your list
Dim mylist = e.ListObject
End Sub
Related
I'm trying to do display multiple rows with two column values on a list box so when a user selects an option they have a little extra information.
It should look like this:
ej. 3 BestBuy
I use the same method to output data to my GridViews but it doesn't display anything on the listbox. What is the correct method to output data from a db to a listbox.
SQL Control Class Functions
Public Function ExecQuery(query As String) As DataTable
Dim DBDT = New DataTable
Using DBCon As New SqlConnection(ConStr),
DBCmd As New SqlCommand(query, DBCon)
Params.ForEach(Sub(p) DBCmd.Parameters.Add(p))
Params.Clear()
DBCon.Open()
DBDT.Load(DBCmd.ExecuteReader)
End Using
Return DBDT
End Function
'Add Params
Public Sub AddParam(Name As String, Value As Object)
Dim NewParam As New SqlParameter(Name, Value)
Params.Add(NewParam)
End Sub
How im trying to add data to the listbox
Protected Sub DivisionListBox_DataBinding(sender As Object, e As EventArgs) Handles DivisionListBox.DataBinding
Try
dt = SQL.ExecQuery("Select STR_GRP_ID, GROUP_DESC
FROM Store_Group_Desc ")
Catch ex As Exception
MsgBox(ex.Message)
Exit Sub
End Try
DivisionListBox.DataSource = dt
DivisionListBox.DataBind()
End Sub
What I would do is return the STR_GRP_ID as well as create an aliased column that concatenated the STR_GRP_ID and GROUP_DESC fields.
Then you would bind the DataTable to the ListBox like you're doing but specifying that the ListBox's DisplayMember is your aliased column and the ValueMember is the id:
Try
dt = SQL.ExecQuery("Select STR_GRP_ID, CONCAT_WS(' ', STR_GRP_ID, GROUP_DESC GROUP_DESC) AS DisplayText FROM Store_Group_Desc;")
Catch ex As Exception
MessageBox.Show(ex.Message)
Return
End Try
With DivisionListBox
.DataSource = dt
.DisplayMember = "DisplayText"
.ValueMember = "STR_GRP_ID"
End With
I don't think the DataBinding event will ever be triggered in you code. You can set a break point inside the event and see if it is ever triggered.
I chose to use the Page.Load event to fill the list box. I separated the user interface code that actually fills the list box from the data access code.
I had the server do the work to build the string your want to display. I assumed the id field was some type of number field so I cast it to a varchar. Then added a space and the description field. This new select field is called IDDesc.
IDDesc is the field name that I want to display in the list box.
Protected Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs) Handles Me.Load
If Not IsPostBack Then
FillListBox()
End If
End Sub
Private Sub FillListBox()
Dim ListBoxData = GetListBoxData()
ListBox1.DataTextField = "IDDesc"
ListBox1.DataSource = ListBoxData
ListBox1.DataBind()
End Sub
Private Function GetListBoxData() As DataTable
Dim DBDT = New DataTable
Dim Query = "Select Cast(STR_GRP_ID As varchar) + ' ' + GROUP_DESC As IDDesc
FROM Store_Group_Desc "
Using DBCon As New SqlConnection(ConStr),
DBCmd As New SqlCommand(Query, DBCon)
DBCon.Open()
DBDT.Load(DBCmd.ExecuteReader)
End Using
Return DBDT
End Function
The purpose of my detailsview is to collect report parameters for the user to enter when running a report. So I build the details view dynamically because report parameters are data driven, stored in the database per the report.
The object being bound to is created by reflection dynamically at run-time from the report parameters, with one property per each parameter, so it contains simple date or collection properties, which I turn into a combo box or date box dynamically by creating templates dynamically.
In the item_updating event, I simply want to get the values the user entered so I know how to run the report.
I looked in all the properties of e and also tried findcontrol to get to the values the user entered. both e and findcontrol are empty. This is how I got data back in the past so I don't know why they aren't there. It almost as if the controls themselves are not represented in the object model of the detailsview at that time.
<asp:DetailsView ID="DetailsView1" runat="server" AutoGenerateRows="false" DefaultMode="Edit" >
<Fields>
<asp:CommandField ButtonType="Button" UpdateText="Run" ShowCancelButton="false" ShowEditButton="true" />
</Fields>
</asp:DetailsView>
code behind
Private Sub BuildDetailView(DataSource As Object)
Dim Properties() As System.Reflection.PropertyInfo = DataSource.GetType.GetProperties
Dim Template As System.Web.UI.ITemplate
For Each PropertyInfo As System.Reflection.PropertyInfo In Properties
Template = Nothing
If PropertyInfo.PropertyType Is GetType(System.DateTime) Then
Template = New DateTemplate(DataSource, PropertyInfo)
ElseIf GetType(ICollection).IsAssignableFrom(PropertyInfo.PropertyType) Then
Template = New ListTemplate(DataSource, PropertyInfo, Report.InputReportParameters(Array.IndexOf(Properties, PropertyInfo)).Enumeration.MultiSelect)
End If
If Template IsNot Nothing Then
Dim TemplateField As New TemplateField
TemplateField.HeaderText = PropertyInfo.Name
TemplateField.ItemTemplate = Template
DetailsView1.Fields.Add(TemplateField)
End If
Next
End Sub
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
CheckAuthentication()
BuildDetailView(Report.ReportParametersDataObject)
If Report.ReportParameters.Any AndAlso Not Report.InputReportParameters.Any Then
Run() 'no parameters are for data entry
ElseIf Not IsPostBack Then
lblReportType.Text = String.Format("Report {0}", Report.Type)
DetailsView1.DataSource = New List(Of Object) From {Report.ReportParametersDataObject}
DetailsView1.DataBind()
End If
End Sub
Protected Sub DetailsView1_ItemUpdating(sender As Object, e As System.Web.UI.WebControls.DetailsViewUpdateEventArgs) Handles DetailsView1.ItemUpdating
Run(e.NewValues)
End Sub
List Template helper:
Public Class ListTemplate
Implements System.Web.UI.ITemplate
Public Sub New(DataSource As Object, PropertyInfo As System.Reflection.PropertyInfo, Multiselect As Boolean)
Me.DataSource = DataSource
Me.PropertyInfo = PropertyInfo
Me.Multiselect = Multiselect
End Sub
Private DataSource As Object
Private Multiselect As Boolean
Private PropertyInfo As System.Reflection.PropertyInfo
Public Sub InstantiateIn(container As System.Web.UI.Control) Implements System.Web.UI.ITemplate.InstantiateIn
Dim ListBox As New System.Web.UI.WebControls.ListBox
ListBox.DataSource = PropertyInfo.GetValue(DataSource, Nothing)
ListBox.DataTextField = "Text"
ListBox.DataValueField = "Value"
ListBox.SelectionMode = IIf(Multiselect, ListSelectionMode.Multiple, ListSelectionMode.Single)
ListBox.Rows = Math.Min(25, ListBox.DataSource.count)
ListBox.EnableViewState = True
container.Controls.Add(ListBox)
End Sub
End Class
DateTemplate Helper:
Public Class DateTemplate
Implements System.Web.UI.ITemplate
Public Sub New(DataSource As Object, PropertyInfo As System.Reflection.PropertyInfo)
Me.DataSource = DataSource
Me.PropertyInfo = PropertyInfo
End Sub
Private DataSource As Object
Private PropertyInfo As System.Reflection.PropertyInfo
Public Sub InstantiateIn(container As System.Web.UI.Control) Implements System.Web.UI.ITemplate.InstantiateIn
Dim Textbox As New System.Web.UI.WebControls.TextBox
Textbox.ID = PropertyInfo.Name.Replace(" ", String.Empty)
Textbox.EnableViewState = True
Dim CalendarExtender As New AjaxControlToolkit.CalendarExtender
CalendarExtender.TargetControlID = Textbox.ID
CalendarExtender.SelectedDate = PropertyInfo.GetValue(DataSource, Nothing)
CalendarExtender.DefaultView = AjaxControlToolkit.CalendarDefaultView.Months
CalendarExtender.Format = "MMMM yyyy"
container.Controls.Add(Textbox)
container.Controls.Add(CalendarExtender)
End Sub
End Class
In the absense of any answers or even comments, I worked around this issue by creating my own asp.net custom control.
The way it works is very simple. It has a DataSource property and it creates the UI adding an edit widget for each property. According to the propertyinfo.propertytype, it creates an editor for that type.
Once the user submits changes it raises a server-side event handing back the datasource object containing the changed values.
It's so simple, even I can understand how to use it.
I am building a custom ASP.Net gridview server control (not user control) with a custom pager. The custom pager contains a dynamically added dropdownlist which changes the gridview's pagesize (items per page). The dropdownlist is added in the RenderChildren method which I am overriding in the server control's code.
I am basically cloning the default pager row and adding the dropdownlist into the row, then adding the cloned pager row to the gridview and destroying the original.
The problem is that even though the dropdownlist's autopostback property is set to true and I am adding an event handler for it, I cannot get the event to fire when changing the selectedindex. I have tried several different approaches with no luck.
I believe the problem is that the dropdownlist has not yet been rendered by the time the selectedindex is changed in the calling page. But I don't know enough about server controls and the page lifecycle to figure out how to resolve it.
Here is an abbreviated version of the code:
Public Class CustomGridView
Inherits System.Web.UI.WebControls.GridView
Implements IPostBackEventHandler
Protected WithEvents mctlDropdownListPageSize As New DropDownList
Public Event PageSizeChanged(ByVal sender As Object, ByVal e As EventArgs)
Protected Sub dropdownListPageSize_Click(ByVal sender As Object, ByVal e As EventArgs)
RaiseEvent PageSizeChanged(Me.mctlDropdownListPageSize, e)
End Sub
'This sets up paging using the pageddatasource
Protected Overrides Sub InitializePager(row As GridViewRow, columnSpan As Integer, pagedDataSource As PagedDataSource)
pagedDataSource.AllowPaging = True
pagedDataSource.AllowCustomPaging = True
pagedDataSource.VirtualCount = TotalRows
pagedDataSource.CurrentPageIndex = CurrentPageIndex
Me.PageIndex = CurrentPageIndex
MyBase.InitializePager(row, columnSpan, pagedDataSource)
End Sub
Protected Overrides Sub RenderChildren(writer As HtmlTextWriter)
Try
If Me.Controls.Count = 0 Then
' nothing to render, use default
MyBase.RenderChildren(writer)
Return
End If
' select the header row in the grid
Dim gridViewTable As WebControl = DirectCast(Me.Controls(0), WebControl) ' the table
' the table->row(0) if no top paging, otherwise row(1)
Dim pagerRow As GridViewRow = gridViewTable.Controls(0)
'Get the pager cell -- we want only one cell containing all the elements
Dim pagerContainerCell As TableCell = pagerRow.Cells(0)
'Create the new table which will contain the title, results summary and pager
Dim newPagerTable As New Table
newPagerTable.CellPadding = 0
newPagerTable.CellSpacing = 0
newPagerTable.CssClass = "section_headers gridpagertable"
'Create a table row to contain the new cells
Dim newPagerRow As New TableRow
newPagerRow.CssClass = "pagerrow"
'Create 2 cells to hold the new controls + pager
Dim resultsCell As New TableCell
resultsCell.CssClass = "results"
Dim pagerCell As New TableCell
pagerCell.CssClass = "pager"
If Me.AllowPaging Then
'Add the pagesize dropdown and results summary text
'Create the results label
Dim mctlResultsLabelText As New LiteralControl
mctlResultsLabelText.Text = "<span>Results per page </span>"
'Create the pagesize dropdown container div
Dim mctlResultsDropdownListContainerDiv As New HtmlControls.HtmlGenericControl("div")
mctlResultsDropdownListContainerDiv.Attributes("class") = "dropdown_select"
Dim mctlResultsDropdownListLabel As New HtmlControls.HtmlGenericControl("label")
'Create the pagesize dropdownlist
mctlDropdownListPageSize.ID = "dropDownListPageSize"
mctlDropdownListPageSize.Items.Add(New ListItem("100", "100"))
mctlDropdownListPageSize.Items.Add(New ListItem("200", "200"))
mctlDropdownListPageSize.Items.Add(New ListItem("300", "300"))
mctlDropdownListPageSize.AutoPostBack = True
mctlDropdownListPageSize.EnableViewState = True
AddHandler mctlDropdownListPageSize.SelectedIndexChanged, AddressOf Me.dropdownListPageSize_Click
'Add the dropdown tot he dropdown container div
mctlResultsDropdownListLabel.Controls.Add(mctlDropdownListPageSize)
mctlResultsDropdownListContainerDiv.Controls.Add(mctlResultsDropdownListLabel)
'Add the results drpdown label
resultsCell.Controls.Add(mctlResultsLabelText)
'Add the pagesize dropdown
resultsCell.Controls.Add(mctlResultsDropdownListContainerDiv)
'Add the cell to the row
newPagerRow.Controls.Add(resultsCell)
End If
'Add the pager control and action icons
Dim mctlPagerContainerDiv As New HtmlControls.HtmlGenericControl("div")
mctlPagerContainerDiv.Attributes.Add("class", "pagination")
'Add the div to the pager cell
pagerCell.Controls.Add(mctlPagerContainerDiv)
If Me.AllowPaging Then
'Get the existing pager container table with the pager buttons
Dim tblPager As Table
tblPager = pagerContainerCell.Controls(0)
tblPager.CellPadding = 0
'Add the pager to the cell
pagerCell.Controls.Add(tblPager)
End If
'Add the cell to the row
newPagerRow.Controls.Add(pagerCell)
'Add the row to the table
newPagerTable.Controls.Add(newPagerRow)
'Render the new pager row (table+row+3 new cells with controls)
newPagerTable.RenderControl(writer)
If Me.AllowPaging Then
' remove the original (default) pager row, otherwise we have two
gridViewTable.Controls.RemoveAt(0)
End If
'Render the gridview
gridViewTable.RenderControl(writer)
Catch ex As Exception
ControlErrorHandler(ex, "RenderChildren")
End Try
End Sub
Private Sub ControlErrorHandler(ByVal ex As Exception, ByVal methodName As String)
Throw New ApplicationException(Me.GetType().ToString & "." & methodName & " failed due to the inner error - " & ex.Message, ex)
End Sub
<Description("The current page index of the gridview."), DefaultValue(""), Themeable(False), Category("Appearance")> _
Public Property CurrentPageIndex() As Integer
Get
If Not ViewState(VSKeyCurrPageIndex) Is Nothing Then
Return Integer.Parse(ViewState(VSKeyCurrPageIndex))
Else
Return 0
End If
End Get
Set(value As Integer)
ViewState(VSKeyCurrPageIndex) = value
End Set
End Property
End Class
The calling page has this:
Private Sub gridViewResults_PageSizeChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles gridViewResults.PageSizeChanged
'
End Sub
I have looked at almost every related issue here on Stackoverflow, as well as other possible solutions in posts and article online. Any insight or suggestions will be appreciated.
Dim zonename As String = DropDownList1.SelectedItem.Text
It always display the first value from the dropdownlist
try it binding in is not postback
Reference
If you bind inside page load you will always get the first value of the dropdown list.
private void Page_Load()
{
if (!IsPostBack)
{
//bind your dropdown here
}
}
In VB
Sub Page_Load
If Not IsPostBack
' bind your dropdown list
Validate()
End If
End Sub
Edit 1
Storing Connection string you can use web.config file
http://www.connectionstrings.com/Articles/Show/store-connection-string-in-web-config
use page.ispostback
within page load event...
if NOT page.isPostBack Then
Dim zonename As String = DropDownList1.SelectedItem.Text
End if
I have nested repeaters, each item in the nested repeater has a label and a button on it, i want to beable to access the label.text when the button is clicked, I think i'm nearly there as I can return the index of the repeater and nested repeater that is clicked, i'm just having some trouble finding the label itself.
You might be able to help me without me posting the repeater code. Here is my code behind for when the button is clicked.
Protected Sub btnEditUser_Click(ByVal sender As Object, ByVal e As System.EventArgs)
Dim btnEditUser As Button = DirectCast(sender, Button)
Dim reClient As RepeaterItem = DirectCast(btnEditUser.NamingContainer.Parent.Parent, RepeaterItem)
Dim reUser As RepeaterItem = DirectCast(btnEditUser.NamingContainer, RepeaterItem)
Dim selectedClient As Integer = reClient.ItemIndex
Dim selectedUser As Integer = reUser.ItemIndex
Dim UserId As Label = DirectCast(reClients.Items(selectedClient).FindControl("lUserName"), Label)
Response.Write(selectedClient & " " & selectedUser & " " & UserId.Text)
End Sub
I'm currently getting this error 'Object reference not set to an instance of an object.' when trying to write the value of UserId.Text so i think i've got it slightly wrong in this line:
Dim UserId As Label = DirectCast(reClients.Items(selectedClient).FindControl("lUserName"), Label)
This is just a guess, but sometimes you get errors like this when not all rows contain the control you're looking for. Often the code loops through the rows in order, hits a header row first that doesn't contain the relevant control, and fails.
Here is a good MSDN article - Locating a Control Inside a Hierarchy of Naming containers.
Private Function FindControlRecursive(
ByVal rootControl As Control, ByVal controlID As String) As Control
If rootControl.ID = controlID Then
Return rootControl
End If
For Each controlToSearch As Control In rootControl.Controls
Dim controlToReturn As Control =
FindControlRecursive(controlToSearch, controlID)
If controlToReturn IsNot Nothing Then
Return controlToReturn
End If
Next
Return Nothing
End Function
Try it,
Dim UserId As Label =DirectCast(FindControlRecursive(repClient,"lUserName"),Label)