I have a custom Data Bound control that I am building. But on postback I lose the all state and I can't figure out why state isn't coming back.
This control presents a list of tasks in the left pane, this is my "frozen" pane. On the right is a panel with a scrollbar. Across the top is a header with a column for each day in the selected date range. Then for each Task per day there is another control, derived from the textbox, where the user can enter in data. Currently no saving of this data is being attempted, one step at a time.
I am re-generating the controls that are supposed to be there, as you will see in the code, but they always just come out empty.
Any help would be greatly appreciated.
--Peter
Code (better to much then not enough):
Public Class EstimatedDoseDisplayer
Inherits System.Web.UI.WebControls.DataBoundControl
Public Property StartDate() As DateTime
Get
Dim o As Object = ViewState("StartDate")
If o Is Nothing Then
Return DateTime.MinValue
Else
Return Convert.ToDateTime(o)
End If
End Get
Set(ByVal value As DateTime)
ViewState("StartDate") = value
If (Initialized) Then
OnDataPropertyChanged()
End If
End Set
End Property
Public Property EndDate() As DateTime
Get
Dim o As Object = ViewState("EndDate")
If o Is Nothing Then
Return DateTime.MinValue
Else
Return Convert.ToDateTime(o)
End If
End Get
Set(ByVal value As DateTime)
ViewState("EndDate") = value
If (Initialized) Then
OnDataPropertyChanged()
End If
End Set
End Property
'Master Panel that will surround the two table
Protected ParentPanel As Panel
'Main Table used for layout
Protected LayoutTable As Table
'Row and Cells for layout table
Protected LayoutRow As TableRow
Protected TasksCell As TableCell
Protected DatesCell As TableCell
'Main Tasks table
Protected TasksTable As Table
'Scrolling Panel that DatesTable goes in
Protected DatesPanel As Panel
'Main Dates Table
Protected DatesTable As Table
Public Sub New()
End Sub
Protected Overrides Sub PerformSelect()
' Call OnDataBinding here if bound to a data source using the
' DataSource property (instead of a DataSourceID), because the
' databinding statement is evaluated before the call to GetData.
If Not IsBoundUsingDataSourceID Then
OnDataBinding(EventArgs.Empty)
End If
' The GetData method retrieves the DataSourceView object from
' the IDataSource associated with the data-bound control.
GetData().Select(CreateDataSourceSelectArguments(), _
AddressOf OnDataSourceViewSelectCallback)
' The PerformDataBinding method has completed.
RequiresDataBinding = False
MarkAsDataBound()
' Raise the DataBound event.
OnDataBound(EventArgs.Empty)
End Sub
Private Sub OnDataSourceViewSelectCallback(ByVal retrievedData As IEnumerable)
' Call OnDataBinding only if it has not already been
' called in the PerformSelect method.
If IsBoundUsingDataSourceID Then
OnDataBinding(EventArgs.Empty)
End If
' The PerformDataBinding method binds the data in the
' retrievedData collection to elements of the data-bound control.
PerformDataBinding(retrievedData)
End Sub
Protected Overrides Sub PerformDataBinding(ByVal retrievedData As IEnumerable)
'Ensure child controls have been created
Me.EnsureChildControls()
MyBase.PerformDataBinding(retrievedData)
' Verify data exists.
If Not retrievedData Is Nothing And Me.Visible Then
CreateControlHierarchy(True, retrievedData)
End If
End Sub
Protected Sub CreateHeaders(ByVal StartDate As DateTime, ByVal EndDate As DateTime, ByRef TasksTable As Table, ByRef DatesTable As Table)
'Basic objects to use for table creation
Dim tr As TableRow = Nothing
Dim td As TableCell = Nothing
'--Add in Work Orders Header--
tr = New TableRow()
td = New TableCell()
td.Text = "Tasks"
'make "Work Orders" bold
td.Style.Add("font-weight", "bold")
'Add the cell to the row
tr.Cells.Add(td)
'add the "header" row to the work orders table
TasksTable.Rows.Add(tr)
'--Add in Dates Header for this month--
'New table row to hold date cells
tr = New TableRow()
'Used to hold current date
Dim CurrentDate As DateTime = StartDate
'While we have not passed the end date
While CurrentDate <= EndDate
td = New TableCell()
td.Text = CurrentDate.ToShortDateString()
'Handle Holiday/Weekend coloring/marking
HandleSpecialDays(td, CurrentDate)
'Make Date bold
td.Style.Add("font-weight", "bold")
'Add date to header row
tr.Cells.Add(td)
'Increment the date by 1
CurrentDate = CurrentDate.AddDays(1)
End While
'Add in the Dates header
DatesTable.Rows.Add(tr)
End Sub
Protected Sub HandleSpecialDays(ByRef TD As TableCell, ByVal SelectedDate As DateTime)
'Later on we will add a Special Days table to the database so we can highlight special days for reporting/etc
If SelectedDate.DayOfWeek = DayOfWeek.Saturday Or SelectedDate.DayOfWeek = DayOfWeek.Sunday Then
TD.BackColor = Drawing.Color.LightSkyBlue
TD.ToolTip = "Weekend"
End If
End Sub
Protected Sub AutoGenerateDates(ByVal Seed As DateTime)
StartDate = New DateTime(Seed.Year, Seed.Month, 1)
'We also need to generate an End Date if this is the case, start with the 28th of the month, then move forward
EndDate = New DateTime(StartDate.Year, StartDate.Month, 28)
'move end date forward until the last day of the month
While True
'If adding a day will not take us outside of this month then add a day,
' else exit the loop and stick with the date we've got.
If EndDate.AddDays(1).Month = StartDate.Month Then
EndDate = EndDate.AddDays(1)
Else
Exit While
End If
End While
End Sub
Protected Overrides Sub CreateChildControls()
'Clear out old controls
Controls.Clear()
'Make all the new controls
MyBase.CreateChildControls()
'Create the Control Hierarchy for the data
'If the viewstate exists we'll use that
If Not ViewState("ItemCount") Is Nothing Then
CreateControlHierarchy(False, Nothing)
End If
End Sub
Protected Sub CreateControlHierarchy(ByVal UseDataSource As Boolean, ByRef RawData As IEnumerable(Of Labor))
'Create the Parent Panel
ParentPanel = New Panel()
'Create Layout Table
LayoutTable = New Table()
LayoutRow = New TableRow()
TasksCell = New TableCell()
DatesCell = New TableCell()
'Main Tasks table
TasksTable = New Table()
'Scrolling Panel that DatesTable goes in
DatesPanel = New Panel()
'Main Dates Table
DatesTable = New Table()
'--Put Controls Together--'
'Add parent Panel to parent control
Controls.Add(ParentPanel)
'Add layout table to ParentPanel
ParentPanel.Controls.Add(LayoutTable)
'Add layout row
LayoutTable.Rows.Add(LayoutRow)
'Add layout cells
LayoutRow.Cells.Add(TasksCell)
LayoutRow.Cells.Add(DatesCell)
'Load Tasks Table into Parent Panel
TasksCell.Controls.Add(TasksTable)
'Load Dates Panel into Parent Panel
DatesCell.Controls.Add(DatesPanel)
'Load Dates Table into DatesPanel
DatesPanel.Controls.Add(DatesTable)
Dim DataContext As New DoseReportingDataContext()
'Convert our datasource into the appropriate object type
Dim Data As IEnumerable(Of Labor) = Nothing
Dim RecordCount As Integer = 0
If UseDataSource Then
Data = CType(RawData, IEnumerable(Of Labor))
RecordCount = Data.Count()
ViewState("ItemCount") = RecordCount
Else
RecordCount = ViewState("ItemCount")
Data = New List(Of Labor)(RecordCount)
End If
'if no data then return
If RecordCount < 1 Then
'We should have a No Data Template sort of thing that gets displayed here
Return
End If
'Auto generate dates if needed
'Check if we have a default date
If StartDate = DateTime.MinValue Or EndDate = DateTime.MinValue Then
'if we don't have a start date or we don't have an end date
' then get the first entry's start date and use it as a seed
Dim l As Labor = Data.First()
AutoGenerateDates(l.StartDate)
End If
'--Create and add together all of the major controls--'
'Set Tasks Table cell padding/spacing
TasksTable.CellPadding = 0
TasksTable.CellSpacing = 0
'Set scrollbars and width of dates panel
DatesPanel.ScrollBars = ScrollBars.Horizontal
DatesPanel.Style.Add("width", "600px")
'Set cell padding/spacing of Dates Table
DatesTable.CellPadding = 0
DatesTable.CellSpacing = 0
'Create Headers
CreateHeaders(StartDate, EndDate, TasksTable, DatesTable)
'Get count of distinct Labor entries
Dim count As Integer = RecordCount
'--Create Rows and Cells--'
'Basic objects to use for table creation
Dim tr As TableRow = Nothing
Dim td As TableCell = Nothing
'Create Table stuff for this work order
Dim LaborCurrentCount As Integer = 0 'used to track our progress through the list
While LaborCurrentCount < count
'--Create the entry in the Tasks table--
'Create new row for work orders table
tr = New TableRow()
'Add new cell with Work Order name/number
td = New TableCell()
'Set standard height on the cell
td.Height = New WebControls.Unit(30, UnitType.Pixel)
'Get current workorder
Dim CurrentLabor As Labor = Nothing
If UseDataSource Then
CurrentLabor = Data(LaborCurrentCount)
'set cell text
td.Text = CurrentLabor.AlaraTask.WorkOrderTask.WorkOrder + ":" + CurrentLabor.AlaraTask.TaskNumber.ToString("00") + ":" + CurrentLabor.AlaraTask.TaskNumber.ToString("00")
td.ToolTip = CurrentLabor.AlaraTask.AlaraDescription + Environment.NewLine + CurrentLabor.AlaraTask.AlaraSubDescription
End If
'Add cell to row
tr.Cells.Add(td)
'Add row to work orders table
TasksTable.Rows.Add(tr)
'--Load per date info if present--
'Copy the Start Date
Dim TaskDate As DateTime = StartDate
'Create table row for this work orders date info
tr = New TableRow()
'while we have not passed the end date
While TaskDate <= EndDate
'new table cell for this day
td = New TableCell()
'set to uniform height
td.Height = New WebControls.Unit(30, UnitType.Pixel)
'Check for holidays/weekend
HandleSpecialDays(td, TaskDate)
'Will instantiate this once we know how we are going to do it
Dim dData As EstimatedDoseItem = Nothing
'if we are using the datasource
If UseDataSource Then
'Query if there is any data for this day/Labor Entry
' for this query we ONLY have to look for the day and work order number
' in our previous query we already slimmed it down to department and month/year
Dim wodData = From c In CurrentLabor.DoseEstimates _
Where c.DateForEstimate.Date = TaskDate.Date
'if we have data then load it into the EstimatedDoseItem, if no data then input placeholders
If wodData.Count > 0 Then
dData = New EstimatedDoseItem(wodData.First(), Page)
Else
dData = New EstimatedDoseItem(TaskDate, CurrentLabor.LaborID, CurrentLabor.DepartmentID, Page)
End If
Else
dData = New EstimatedDoseItem()
End If
'Add event handler for when the text is changed
'AddHandler dData.DoseChanged, AddressOf DataUpdated
'Add the Estimated Dose Item to the TD
td.Controls.Add(dData)
'Add cell to the row
tr.Cells.Add(td)
'Increment the date by 1
TaskDate = TaskDate.AddDays(1)
End While
'Add in the Row
DatesTable.Rows.Add(tr)
'Move onto the next work order
LaborCurrentCount += 1
End While
End Sub
End Class
Public Class EstimatedDoseItem
Inherits TextBox
Public Event DoseChanged As EventHandler
Public ReadOnly Property DoseEstimate() As DoseEstimate
Get
Return _DoseEstimateStore.GetDoseEstimate()
End Get
End Property
Protected _DoseEstimateStore As DoseEstimateStore
Public Sub New(ByVal EstimateDate As DateTime, ByVal LaborID As Integer, ByVal Department As Integer, ByVal ParentPage As Page)
'Load all values into a DoseEstimate object
Dim DoseEstimate As New DoseEstimate()
DoseEstimate.DateForEstimate = EstimateDate
DoseEstimate.LaborID = LaborID
DoseEstimate.EstimatedDose = 0D
_DoseEstimateStore = New DoseEstimateStore(DoseEstimate)
'ParentPage.RegisterRequiresControlState(Me) 'Let the page know that this control needs it's state saved
End Sub
Public Sub New(ByVal DoseEstimate As DoseEstimate, ByVal ParentPage As Page)
'Load in provided Dose Estimate object
_DoseEstimateStore = New DoseEstimateStore(DoseEstimate)
'ParentPage.RegisterRequiresControlState(Me) 'Let the page know that this control needs it's state saved
End Sub
Public Sub New()
_DoseEstimateStore = New DoseEstimateStore()
End Sub
Protected Overrides Sub OnLoad(ByVal e As System.EventArgs)
'On load change the width of the txt box
Me.Columns = 5
'Center the text
Me.Style.Add("text-align", "center")
MyBase.OnLoad(e)
End Sub
Protected Overrides Sub OnTextChanged(ByVal e As System.EventArgs)
RaiseEvent DoseChanged(Me, e)
MyBase.OnTextChanged(e)
End Sub
'Override Text property of textbox so that it references the Estimated Dose
Public Overrides Property Text() As String
Get
Return _DoseEstimateStore.EstimatedDose.ToString()
End Get
Set(ByVal value As String)
_DoseEstimateStore.EstimatedDose = Convert.ToDecimal(value)
End Set
End Property
Protected Overrides Function SaveControlState() As Object
Dim state(2) As Object 'object array to hold data
state(0) = MyBase.SaveControlState() 'Save the base controls state
state(1) = _DoseEstimateStore 'Save dose estimate object
Return state 'Return the object array for saving
End Function
Protected Overrides Sub LoadControlState(ByVal savedState As Object)
MyBase.LoadControlState(savedState(0)) 'First load the base controls state
_DoseEstimateStore = savedState(1) 'Load date for estimate
End Sub
'This function saves the data in the cell back to the database
Public Sub Save(ByRef DataContext As DoseReportingDataContext)
'if this is an existing entry then update it
If _DoseEstimateStore.EstimateID <> 0 Then
'Get Dose Estimate object
Dim DoseEstimate As DoseEstimate = _DoseEstimateStore.GetDoseEstimate()
'Attach it, with modified set to true so it will get updated
DataContext.DoseEstimates.Attach(DoseEstimate, True)
Else
'if this is a new entry, and it has data
If _DoseEstimateStore.EstimatedDose <> 0D Then
DataContext.DoseEstimates.InsertOnSubmit(_DoseEstimateStore.GetDoseEstimate())
End If
End If
End Sub
End Class
<Serializable()> _
Public Class DoseEstimateStore
Public EstimateID As Integer = 0
Public DateForEstimate As DateTime = DateTime.MinValue
Public LaborID As Integer = 0
Public EstimatedDose As Decimal = 0D
Public Sub New()
End Sub
Public Sub New(ByRef DoseEstimate As DoseEstimate)
With DoseEstimate
EstimateID = .EstimateID
DateForEstimate = .DateForEstimate
LaborID = .LaborID
EstimatedDose = .EstimatedDose
End With
End Sub
Public Function GetDoseEstimate() As DoseEstimate
Dim DoseEstimate As New DoseEstimate()
With DoseEstimate
.EstimateID = EstimateID
.DateForEstimate = DateForEstimate
.LaborID = LaborID
.EstimatedDose = EstimatedDose
End With
Return DoseEstimate
End Function
End Class