M
Mike Hofer
Hi everyone.
I could really use some help.
First, the backstory:
=====================
I *really* need a 3-state checkbox for my ASP.NET application. Specifically, I need one that lets me set the image for the checkbox portion so that the control conforms to my site's visual style, and that lets me set the style for the text in both the enabled and disabled states.
As you might expect, I couldn't find one. So I decided to roll my own.
Now, the problem:
=================
The control works exactly as I expect it to, except for one problem. The control has two properties of type Style: DisabledTextStyle and TextStyle. When I change either of those properties in any way, such as by setting the CSSCLASS property of the style, the change appears immediately in the designer. However, if I switch to HTML view or run the application, the style information is lost.
My theory:
==========
I should be doing something to make the style information stick to the ASP.NET code in the designer. I'm not doing that. However, I can't find any information on what I should be doing. (I used the MSDN samples.)
For your consideration:
=======================
The complete source code for the control follows below. Please paste it into a project and compile it. Then drop the control onto a Web Form and try to change the style. You'll see what I mean.
Also, if you see any way for me to improve the control, feel free to chime in. I would really like to release this to the community when it's finished, so the better it works, the happier I'll be.
The Code:
=========
Imports System.ComponentModel
Imports System.Web.UI
Imports System.Web.UI.Design
Imports System.Web.UI.WebControls
' This control's design is based on the Checkbox documentation
' from the Avalon documentation on MSDN at the following URL:
' http://winfx.msdn.microsoft.com/lib...stem.windows.controls/c/checkbox/checkbox.asp
Public Enum CheckboxState
Checked = 1
Unchecked = 0
Indeterminate = 2
End Enum
<DefaultProperty("Text"), _
DefaultEvent("CheckedChanged"), _
ToolboxData("<{0}:Checkbox runat=server></{0}:Checkbox>")> _
Public Class Checkbox
Inherits WebControl
Implements INamingContainer, IPostBackEventHandler
Const OnClickEventArgument As String = "onclick"
Event CheckedChanged(ByVal sender As System.Object, ByVal e As System.EventArgs)
#Region "Checked"
Public Property Checked() As Boolean
Get
Return Me.CheckedState = CheckboxState.Checked
End Get
Set(ByVal Value As Boolean)
Me.CheckedState = CheckboxState.Unchecked
End Set
End Property
#End Region
#Region "CheckedImageUrl"
<DefaultValue(""), Editor(GetType(ImageUrlEditor), GetType(System.Drawing.Design.UITypeEditor))> _
Public Property CheckedImageUrl() As String
Get
Return CStr(Me.ViewState("CheckedImageUrl"))
End Get
Set(ByVal Value As String)
Me.ViewState("CheckedImageUrl") = Value
End Set
End Property
#End Region
#Region "CheckedState"
<Bindable(True), Category("Appearance"), DefaultValue(GetType(CheckboxState), "-1")> _
Public Property CheckedState() As CheckboxState
Get
Return CType(Me.ViewState("CheckedState"), CheckboxState)
End Get
Set(ByVal Value As CheckboxState)
If Value <> Me.CheckedState Then
If Value = CheckboxState.Indeterminate Then
If ThreeState = False Then
Throw New ApplicationException("Invalid state.")
End If
End If
Me.ViewState("CheckedState") = Value
OnCheckedChanged()
End If
End Set
End Property
#End Region
#Region "CreateChildControls"
Protected Overrides Sub CreateChildControls()
CreateControlHeirarchy()
End Sub
#End Region
#Region "CreateControlHeirarchy"
Private Sub CreateControlHeirarchy()
Dim img As New ImageButton
Dim table As New table
Dim tr As New TableRow
Dim lbl As New Label
Dim tdImage As New TableCell
Dim tdText As New TableCell
If Me.Enabled Then
Select Case Me.CheckedState
Case CheckboxState.Checked
img.ImageUrl = Me.CheckedImageUrl
Case CheckboxState.Unchecked
img.ImageUrl = Me.UncheckedImageUrl
Case Else
img.ImageUrl = Me.IndeterminateImageUrl
End Select
img.Attributes.Add("onclick", "javascript:" & Page.GetPostBackEventReference(Me, OnClickEventArgument))
Else
img.ImageUrl = Me.DisabledImageUrl
End If
If Me.Site Is Nothing Then
lbl.Text = Me.Text
ElseIf Me.Site.DesignMode Then
' We're in the designer. Display the control name
' if we don't have any text yet.
If Me.Text = String.Empty Then
lbl.Text = "[" & Me.ClientID & "]"
Else
lbl.Text = Me.Text
End If
Else
lbl.Text = Me.Text
End If
If Me.Enabled Then
lbl.ApplyStyle(Me.TextStyle)
lbl.Attributes.Add("onclick", "javascript:" & Page.GetPostBackEventReference(Me, OnClickEventArgument))
Else
lbl.ApplyStyle(Me.DisabledTextStyle)
End If
table.Style.Clear()
table.CellPadding = 0
table.CellSpacing = 0
table.BorderStyle = BorderStyle.None
table.ApplyStyle(Me.ControlStyle)
table.Style.Add("border-collapse", "collapse")
table.Style.Add("cursor", "hand")
tr.Style.Clear()
tr.Style.Add("margin", "0")
tr.Style.Add("padding", "0")
tr.Style.Add("border", "none")
tr.Style.Add("cursor", "hand")
tdImage.Style.Clear()
tdImage.Style.Add("margin", "0")
tdImage.Style.Add("padding", "0")
tdImage.Style.Add("border", "none")
tdImage.Style.Add("cursor", "hand")
tdText.Style.Clear()
tdText.Style.Add("margin", "0")
tdText.Style.Add("padding", "0")
tdText.Style.Add("border", "none")
tdText.Style.Add("cursor", "hand")
table.Controls.Add(tr)
tr.Controls.Add(tdImage)
tr.Controls.Add(tdText)
tdImage.Controls.Add(img)
tdText.Controls.Add(lbl)
Controls.Clear()
Controls.Add(table)
End Sub
#End Region
#Region "DisabledImageUrl"
<DefaultValue(""), Editor(GetType(ImageUrlEditor), GetType(System.Drawing.Design.UITypeEditor))> _
Public Property DisabledImageUrl() As String
Get
Return CStr(Me.ViewState("DisabledImageUrl"))
End Get
Set(ByVal Value As String)
Me.ViewState("DisabledImageUrl") = Value
End Set
End Property
#End Region
#Region "DisabledTextStyle"
Private m_DisabledTextStyle As Style
Public Overridable ReadOnly Property DisabledTextStyle() As Style
Get
ensurechildcontrols()
If m_DisabledTextStyle Is Nothing Then
m_DisabledTextStyle = New Style
If istrackingviewstate Then
CType(m_DisabledTextStyle, IStateManager).TrackViewState()
End If
End If
Return m_DisabledTextStyle
End Get
End Property
#End Region
#Region "ImageStyle"
Private m_imageStyle As Style
Public Overridable ReadOnly Property ImageStyle() As Style
Get
If m_imageStyle Is Nothing Then
m_imageStyle = New Style
If istrackingviewstate Then
CType(m_imageStyle, IStateManager).TrackViewState()
End If
End If
Return m_imageStyle
End Get
End Property
#End Region
#Region "IndeterminateImageUrl"
<DefaultValue(""), Editor(GetType(ImageUrlEditor), GetType(System.Drawing.Design.UITypeEditor))> _
Public Property IndeterminateImageUrl() As String
Get
Return CStr(Me.ViewState("IndeterminateImageUrl"))
End Get
Set(ByVal Value As String)
Me.ViewState("IndeterminateImageUrl") = Value
End Set
End Property
#End Region
#Region "LoadViewState"
Protected Overrides Sub LoadViewState(ByVal savedState As Object)
If Not savedState Is Nothing Then
Dim items() As Object = CType(savedState, Object())
' First item is always the control's viewstate
If Not items(0) Is Nothing Then
MyBase.LoadViewState(items(0))
End If
' Next item should be our control's viewstate
If Not items(1) Is Nothing Then
CType(Me.TextStyle, IStateManager).LoadViewState(items(1))
End If
If Not items(2) Is Nothing Then
CType(Me.ImageStyle, IStateManager).LoadViewState(items(2))
End If
If Not items(3) Is Nothing Then
CType(Me.DisabledTextStyle, IStateManager).LoadViewState(items(3))
End If
End If
End Sub
#End Region
#Region "OnCheckedChanged"
Protected Sub OnCheckedChanged()
RaiseEvent CheckedChanged(Me, System.EventArgs.Empty)
End Sub
#End Region
#Region "OnClick"
Private Sub OnClick()
Dim state As CheckboxState = Me.CheckedState
If state = CheckboxState.Indeterminate Then
Me.CheckedState = CheckboxState.Unchecked
ElseIf state = CheckboxState.Unchecked Then
Me.CheckedState = CheckboxState.Checked
ElseIf ThreeState Then
Me.CheckedState = CheckboxState.Indeterminate
Else
Me.CheckedState = CheckboxState.Unchecked
End If
End Sub
#End Region
#Region "RaisePostBackEvent"
Public Sub RaisePostBackEvent(ByVal eventArgument As String) Implements System.Web.UI.IPostBackEventHandler.RaisePostBackEvent
If eventArgument = OnClickEventArgument Then
OnClick()
End If
Stop
End Sub
#End Region
#Region "Render"
Protected Overrides Sub Render(ByVal writer As System.Web.UI.HtmlTextWriter)
CreateControlHeirarchy()
Me.RenderContents(writer)
End Sub
#End Region
#Region "SaveViewState"
Protected Overrides Function SaveViewState() As Object
Dim baseState As Object = MyBase.SaveViewState
Dim textStyleState As Object
Dim imageStyleState As Object
Dim disabledTextStyle As Object
Dim stateObjects(3) As Object
textStyleState = CType(Me.TextStyle, IStateManager).SaveViewState()
imageStyleState = CType(Me.ImageStyle, IStateManager).SaveViewState()
disabledTextStyle = CType(Me.DisabledTextStyle, IStateManager).SaveViewState()
stateObjects(0) = baseState
stateObjects(1) = textStyleState
stateObjects(2) = imageStyleState
stateObjects(3) = disabledTextStyle
Return stateObjects
End Function
#End Region
#Region "Text"
<Bindable(True), Category("Appearance"), DefaultValue("")> Property [Text]() As String
Get
Dim value As String = Me.ViewState("Text")
If value Is Nothing Then
value = String.Empty
End If
Return value
End Get
Set(ByVal Value As String)
Me.ViewState("Text") = Value
End Set
End Property
#End Region
#Region "TextStyle"
Private m_textStyle As Style
Public Overridable ReadOnly Property TextStyle() As Style
Get
If m_textStyle Is Nothing Then
m_textStyle = New Style
If IsTrackingViewState Then
CType(m_textStyle, IStateManager).TrackViewState()
End If
End If
Return m_textStyle
End Get
End Property
#End Region
#Region "ThreeState"
Public Property ThreeState() As Boolean
Get
Return CBool(Me.ViewState("ThreeState"))
End Get
Set(ByVal Value As Boolean)
If Value = False And Me.CheckedState = CheckboxState.Indeterminate Then
Me.CheckedState = CheckboxState.Unchecked
End If
Me.ViewState("ThreeState") = Value
End Set
End Property
#End Region
#Region "TrackViewState"
Protected Overrides Sub TrackViewState()
MyBase.TrackViewState()
CType(Me.DisabledTextStyle, IStateManager).TrackViewState()
CType(Me.TextStyle, IStateManager).TrackViewState()
End Sub
#End Region
#Region "UncheckedImageUrl"
<DefaultValue(""), Editor(GetType(ImageUrlEditor), GetType(System.Drawing.Design.UITypeEditor))> _
Public Property UncheckedImageUrl() As String
Get
Return CStr(Me.ViewState("UncheckedImageUrl"))
End Get
Set(ByVal Value As String)
Me.ViewState("UncheckedImageUrl") = Value
End Set
End Property
#End Region
End Class
I could really use some help.
First, the backstory:
=====================
I *really* need a 3-state checkbox for my ASP.NET application. Specifically, I need one that lets me set the image for the checkbox portion so that the control conforms to my site's visual style, and that lets me set the style for the text in both the enabled and disabled states.
As you might expect, I couldn't find one. So I decided to roll my own.
Now, the problem:
=================
The control works exactly as I expect it to, except for one problem. The control has two properties of type Style: DisabledTextStyle and TextStyle. When I change either of those properties in any way, such as by setting the CSSCLASS property of the style, the change appears immediately in the designer. However, if I switch to HTML view or run the application, the style information is lost.
My theory:
==========
I should be doing something to make the style information stick to the ASP.NET code in the designer. I'm not doing that. However, I can't find any information on what I should be doing. (I used the MSDN samples.)
For your consideration:
=======================
The complete source code for the control follows below. Please paste it into a project and compile it. Then drop the control onto a Web Form and try to change the style. You'll see what I mean.
Also, if you see any way for me to improve the control, feel free to chime in. I would really like to release this to the community when it's finished, so the better it works, the happier I'll be.
The Code:
=========
Imports System.ComponentModel
Imports System.Web.UI
Imports System.Web.UI.Design
Imports System.Web.UI.WebControls
' This control's design is based on the Checkbox documentation
' from the Avalon documentation on MSDN at the following URL:
' http://winfx.msdn.microsoft.com/lib...stem.windows.controls/c/checkbox/checkbox.asp
Public Enum CheckboxState
Checked = 1
Unchecked = 0
Indeterminate = 2
End Enum
<DefaultProperty("Text"), _
DefaultEvent("CheckedChanged"), _
ToolboxData("<{0}:Checkbox runat=server></{0}:Checkbox>")> _
Public Class Checkbox
Inherits WebControl
Implements INamingContainer, IPostBackEventHandler
Const OnClickEventArgument As String = "onclick"
Event CheckedChanged(ByVal sender As System.Object, ByVal e As System.EventArgs)
#Region "Checked"
Public Property Checked() As Boolean
Get
Return Me.CheckedState = CheckboxState.Checked
End Get
Set(ByVal Value As Boolean)
Me.CheckedState = CheckboxState.Unchecked
End Set
End Property
#End Region
#Region "CheckedImageUrl"
<DefaultValue(""), Editor(GetType(ImageUrlEditor), GetType(System.Drawing.Design.UITypeEditor))> _
Public Property CheckedImageUrl() As String
Get
Return CStr(Me.ViewState("CheckedImageUrl"))
End Get
Set(ByVal Value As String)
Me.ViewState("CheckedImageUrl") = Value
End Set
End Property
#End Region
#Region "CheckedState"
<Bindable(True), Category("Appearance"), DefaultValue(GetType(CheckboxState), "-1")> _
Public Property CheckedState() As CheckboxState
Get
Return CType(Me.ViewState("CheckedState"), CheckboxState)
End Get
Set(ByVal Value As CheckboxState)
If Value <> Me.CheckedState Then
If Value = CheckboxState.Indeterminate Then
If ThreeState = False Then
Throw New ApplicationException("Invalid state.")
End If
End If
Me.ViewState("CheckedState") = Value
OnCheckedChanged()
End If
End Set
End Property
#End Region
#Region "CreateChildControls"
Protected Overrides Sub CreateChildControls()
CreateControlHeirarchy()
End Sub
#End Region
#Region "CreateControlHeirarchy"
Private Sub CreateControlHeirarchy()
Dim img As New ImageButton
Dim table As New table
Dim tr As New TableRow
Dim lbl As New Label
Dim tdImage As New TableCell
Dim tdText As New TableCell
If Me.Enabled Then
Select Case Me.CheckedState
Case CheckboxState.Checked
img.ImageUrl = Me.CheckedImageUrl
Case CheckboxState.Unchecked
img.ImageUrl = Me.UncheckedImageUrl
Case Else
img.ImageUrl = Me.IndeterminateImageUrl
End Select
img.Attributes.Add("onclick", "javascript:" & Page.GetPostBackEventReference(Me, OnClickEventArgument))
Else
img.ImageUrl = Me.DisabledImageUrl
End If
If Me.Site Is Nothing Then
lbl.Text = Me.Text
ElseIf Me.Site.DesignMode Then
' We're in the designer. Display the control name
' if we don't have any text yet.
If Me.Text = String.Empty Then
lbl.Text = "[" & Me.ClientID & "]"
Else
lbl.Text = Me.Text
End If
Else
lbl.Text = Me.Text
End If
If Me.Enabled Then
lbl.ApplyStyle(Me.TextStyle)
lbl.Attributes.Add("onclick", "javascript:" & Page.GetPostBackEventReference(Me, OnClickEventArgument))
Else
lbl.ApplyStyle(Me.DisabledTextStyle)
End If
table.Style.Clear()
table.CellPadding = 0
table.CellSpacing = 0
table.BorderStyle = BorderStyle.None
table.ApplyStyle(Me.ControlStyle)
table.Style.Add("border-collapse", "collapse")
table.Style.Add("cursor", "hand")
tr.Style.Clear()
tr.Style.Add("margin", "0")
tr.Style.Add("padding", "0")
tr.Style.Add("border", "none")
tr.Style.Add("cursor", "hand")
tdImage.Style.Clear()
tdImage.Style.Add("margin", "0")
tdImage.Style.Add("padding", "0")
tdImage.Style.Add("border", "none")
tdImage.Style.Add("cursor", "hand")
tdText.Style.Clear()
tdText.Style.Add("margin", "0")
tdText.Style.Add("padding", "0")
tdText.Style.Add("border", "none")
tdText.Style.Add("cursor", "hand")
table.Controls.Add(tr)
tr.Controls.Add(tdImage)
tr.Controls.Add(tdText)
tdImage.Controls.Add(img)
tdText.Controls.Add(lbl)
Controls.Clear()
Controls.Add(table)
End Sub
#End Region
#Region "DisabledImageUrl"
<DefaultValue(""), Editor(GetType(ImageUrlEditor), GetType(System.Drawing.Design.UITypeEditor))> _
Public Property DisabledImageUrl() As String
Get
Return CStr(Me.ViewState("DisabledImageUrl"))
End Get
Set(ByVal Value As String)
Me.ViewState("DisabledImageUrl") = Value
End Set
End Property
#End Region
#Region "DisabledTextStyle"
Private m_DisabledTextStyle As Style
Public Overridable ReadOnly Property DisabledTextStyle() As Style
Get
ensurechildcontrols()
If m_DisabledTextStyle Is Nothing Then
m_DisabledTextStyle = New Style
If istrackingviewstate Then
CType(m_DisabledTextStyle, IStateManager).TrackViewState()
End If
End If
Return m_DisabledTextStyle
End Get
End Property
#End Region
#Region "ImageStyle"
Private m_imageStyle As Style
Public Overridable ReadOnly Property ImageStyle() As Style
Get
If m_imageStyle Is Nothing Then
m_imageStyle = New Style
If istrackingviewstate Then
CType(m_imageStyle, IStateManager).TrackViewState()
End If
End If
Return m_imageStyle
End Get
End Property
#End Region
#Region "IndeterminateImageUrl"
<DefaultValue(""), Editor(GetType(ImageUrlEditor), GetType(System.Drawing.Design.UITypeEditor))> _
Public Property IndeterminateImageUrl() As String
Get
Return CStr(Me.ViewState("IndeterminateImageUrl"))
End Get
Set(ByVal Value As String)
Me.ViewState("IndeterminateImageUrl") = Value
End Set
End Property
#End Region
#Region "LoadViewState"
Protected Overrides Sub LoadViewState(ByVal savedState As Object)
If Not savedState Is Nothing Then
Dim items() As Object = CType(savedState, Object())
' First item is always the control's viewstate
If Not items(0) Is Nothing Then
MyBase.LoadViewState(items(0))
End If
' Next item should be our control's viewstate
If Not items(1) Is Nothing Then
CType(Me.TextStyle, IStateManager).LoadViewState(items(1))
End If
If Not items(2) Is Nothing Then
CType(Me.ImageStyle, IStateManager).LoadViewState(items(2))
End If
If Not items(3) Is Nothing Then
CType(Me.DisabledTextStyle, IStateManager).LoadViewState(items(3))
End If
End If
End Sub
#End Region
#Region "OnCheckedChanged"
Protected Sub OnCheckedChanged()
RaiseEvent CheckedChanged(Me, System.EventArgs.Empty)
End Sub
#End Region
#Region "OnClick"
Private Sub OnClick()
Dim state As CheckboxState = Me.CheckedState
If state = CheckboxState.Indeterminate Then
Me.CheckedState = CheckboxState.Unchecked
ElseIf state = CheckboxState.Unchecked Then
Me.CheckedState = CheckboxState.Checked
ElseIf ThreeState Then
Me.CheckedState = CheckboxState.Indeterminate
Else
Me.CheckedState = CheckboxState.Unchecked
End If
End Sub
#End Region
#Region "RaisePostBackEvent"
Public Sub RaisePostBackEvent(ByVal eventArgument As String) Implements System.Web.UI.IPostBackEventHandler.RaisePostBackEvent
If eventArgument = OnClickEventArgument Then
OnClick()
End If
Stop
End Sub
#End Region
#Region "Render"
Protected Overrides Sub Render(ByVal writer As System.Web.UI.HtmlTextWriter)
CreateControlHeirarchy()
Me.RenderContents(writer)
End Sub
#End Region
#Region "SaveViewState"
Protected Overrides Function SaveViewState() As Object
Dim baseState As Object = MyBase.SaveViewState
Dim textStyleState As Object
Dim imageStyleState As Object
Dim disabledTextStyle As Object
Dim stateObjects(3) As Object
textStyleState = CType(Me.TextStyle, IStateManager).SaveViewState()
imageStyleState = CType(Me.ImageStyle, IStateManager).SaveViewState()
disabledTextStyle = CType(Me.DisabledTextStyle, IStateManager).SaveViewState()
stateObjects(0) = baseState
stateObjects(1) = textStyleState
stateObjects(2) = imageStyleState
stateObjects(3) = disabledTextStyle
Return stateObjects
End Function
#End Region
#Region "Text"
<Bindable(True), Category("Appearance"), DefaultValue("")> Property [Text]() As String
Get
Dim value As String = Me.ViewState("Text")
If value Is Nothing Then
value = String.Empty
End If
Return value
End Get
Set(ByVal Value As String)
Me.ViewState("Text") = Value
End Set
End Property
#End Region
#Region "TextStyle"
Private m_textStyle As Style
Public Overridable ReadOnly Property TextStyle() As Style
Get
If m_textStyle Is Nothing Then
m_textStyle = New Style
If IsTrackingViewState Then
CType(m_textStyle, IStateManager).TrackViewState()
End If
End If
Return m_textStyle
End Get
End Property
#End Region
#Region "ThreeState"
Public Property ThreeState() As Boolean
Get
Return CBool(Me.ViewState("ThreeState"))
End Get
Set(ByVal Value As Boolean)
If Value = False And Me.CheckedState = CheckboxState.Indeterminate Then
Me.CheckedState = CheckboxState.Unchecked
End If
Me.ViewState("ThreeState") = Value
End Set
End Property
#End Region
#Region "TrackViewState"
Protected Overrides Sub TrackViewState()
MyBase.TrackViewState()
CType(Me.DisabledTextStyle, IStateManager).TrackViewState()
CType(Me.TextStyle, IStateManager).TrackViewState()
End Sub
#End Region
#Region "UncheckedImageUrl"
<DefaultValue(""), Editor(GetType(ImageUrlEditor), GetType(System.Drawing.Design.UITypeEditor))> _
Public Property UncheckedImageUrl() As String
Get
Return CStr(Me.ViewState("UncheckedImageUrl"))
End Get
Set(ByVal Value As String)
Me.ViewState("UncheckedImageUrl") = Value
End Set
End Property
#End Region
End Class