Click here to Skip to main content
15,885,365 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
See more:
Hello wonderful people of Code Project!

So here's my situation:

I have a custom textbox web control I've made that is designed to accept user input of a percentage and store them in its decimal equivalence (e.g. "13.5" => "0.135"). Simillarly, when the textbox is bound it will take the decimal value from the datasource and display it as its percentage equivalence (e.g. "0.135" => "13.5").

VB
<DefaultProperty("Text"),
ToolboxData("<{0}:PercentageTextBox  runat="server"></{0}:PercentageTextBox>")> _
Public Class PercentageTextBox
    Inherits TextBox
    Implements IValidator

#Region "Properties"
    Public Property Editable As Boolean
    Private ReadOnly Property InputFormatType As PercentageType
        Get
            Return PercentageType.InPercent ''Can be InDecimal if required
        End Get
    End Property

    <Bindable(True),
    Category("Appearance"),
    DefaultValue(""),
    Description("The name of the percentage"),
    DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
    PersistenceMode(PersistenceMode.InnerProperty)>
    Public Overridable Property Name As String

    <Bindable(True),
    Category("Appearance"),
    DefaultValue(""),
    Localizable(True)>
    Overrides Property Text() As String
        Get
            Dim s As String = CStr(ViewState("Text"))

            If s Is Nothing Then
                Return String.Empty
            Else
                Return s
            End If
        End Get

        Set(ByVal Value As String)
            Dim output As String = Value
            Try
                ''Convert percentage input to decimal.
                Dim TextVal = CDec(Value)
                Dim converted As New PercentageValue(TextVal, _
                                                     PercentageType.InDecimal)
                output = CStr(converted.Value)
            Catch ex As Exception
                output = Value
            Finally
                ViewState("Text") = output
            End Try
        End Set
    End Property
#End Region

    Protected Overrides Sub Render(writer As HtmlTextWriter)
        Try
            If Editable Then
                ''Convert decimal value to percentage
                Dim val = CDec(ViewState("Text"))
                val = System.Math.Round(val * 10000, 2)
                ViewState("Text") = val
            End If

        Catch ex As Exception
            ''Do something?
        Finally
            MyBase.Render(writer)
        End Try
    End Sub
End Class

N.B. I stripped out the validation code added to this control; also, you don't see the math in the control itself for converting the input as it uses the class PercentageValue to convert the value based upon the enumerator PercentageType. The idea behind this was that this could be interacted with to switch method based on the InputFormatType property in the future.

Now this is my first custom control I've made so I'm willing to try and improve how this works:

Firstly: I am not sure about where my conversion takes place in the control. Should I be doing the input conversion in the Text() method and the inverse in the Render() method. Reason I say this is that when the control is editing a stored value the Set property is called unnecessarily (resulting in my conversion in Render() having to x10000 to undo the previous set call).

Secondly: I've creating the property Editable which needs to be set manually when I use the control, such as in an EditItemTemplate in a DetailsView. This is to distinguish it from a user inputting a new value. Is there some property of the inherited textbox that I can infer this from instead of having to set it as such manually?

Any pointers or advice would be much appreciated.

Thanks,
Posted

1 solution

The approach I've used in the past is to add a new bindable property to the control which takes care of parsing and converting the value in the getter, and converts the value back to a string in the setter. That way, you don't need to touch the Text property or the Render method.

If you're binding to a database, it's also handy to have a second property typed as Object, which handles the conversion from DBNull.

Something like this:
VB.NET
<DefaultProperty("Value"), ToolboxData("<{0}:PercentageTextBox  runat="server">")> _
Public Class PercentageTextBox : Inherits TextBox
    <Category("Data")>
    Public Property Value() As Nullable(Of Decimal)
        Get
            Dim value As Decimal
            If Not Decimal.TryParse(Me.Text, value) Then
                Return Nothing
            Else
                Return value / 100
            End If
        End Get
        Set(ByVal value As Nullable(Of Decimal))
            If value Is Nothing Then
                Me.Text = String.Empty
            Else
                value *= 100
                Me.Text = value.ToString()
            End If
        End Set
    End Property
    
    <Bindable(True, BindingDirection.TwoWay), Category("Data")>
    Public Property BoundValue() As Object
        Get
            Return Me.Value
        End Get
        Set(ByVal value As Object)
            If value Is Nothing OrElse Convert.IsDBNull(value) Then
                Me.Value = Nothing
            Else
                Me.Value = Convert.ToDecimal(value, CultureInfo.CurrentCulture)
            End If
        End Set
    End Property
End Class

You can then use the Value property from code to read or set the value in the correct type, and bind to the BoundValue property in markup:
aspx
<uc:PercentageTextBox id="SomePercentage" runat="server"
    BoundValue='<%# Bind("SomePercentageFromTheDatabase") %>'
/>
 
Share this answer
 
Comments
GetReQ 8-May-15 9:36am    
Wow, that's a really elegant solution for my problem, it handles both edit and insert values nicely.

Thanks!
Sergey Alexandrovich Kryukov 8-May-15 10:35am    
5ed.
—SA

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900