Custom Component and Custom Textbox binding

Discussion in 'ASP .Net Building Controls' started by Bill Anderson, Dec 10, 2003.

  1. Hi,

    I am trying to accomplish the following.

    Here is what I want to do:
    1. Drop a custom business object component onto the
    design surface and have it show up in the area below the
    designer grid in VisStudio (like a dataadapter or dataset
    does).

    2. Drag a textbox webcontrol and change a custom
    datasource property to the component from step 1. The
    property grid will display a drop down of available
    datasources (i want the textbox to work like a dropdown
    control).

    3. Based on the datasource chosen, change a
    DataTextField property to a valid property of the
    component. The property grid will show a drop down of
    available Public Properties (fields) from the component
    selected (basically, binding values to the textbox work
    like the dropdown box does when binding to a dataset).

    4. The Datasource and DataTextField values chosen will
    persist as attributes in HTML on the custom component.

    5. The componet is a basic business object. It does not
    implement IEnumerable or IListsource (it does implement
    IComponent). It just exposes public properties.

    I have all of this working fine if the custom component
    is of type Collection. But I don't want a Collection,
    just the ability to bind to Public Properties of a basic
    class.

    I think have taken care of most of the items as follow,
    but I am stuck on #5:
    1. Implement IComponent on the business class.
    2. Created custom control derived from Textbox webcontrol.
    3. Created custom Designer that implements
    IDatasourceprovider, and attach to custom textbox control.
    4. Put designerserialization attribute of visible on
    properties of custom textbox control.
    5. Can't get to work unless the component Implements
    IEnumerable, and then it only shows up in the datasource
    drop down. I can never get the properties to list out
    unless i have a stongly typed collection of busines
    objects.

    here is some sample code(i shortened syntax for brevity):

    <Designer(Gettype(MyCustomDesigner)), otherattributes...>
    _
    Public Class MyTextBox : Inherits from TextBox

    Private mDataSource as string = ""
    Private mDataTextField as string = ""

    <otherattributes, designerserialation...(visible)> _
    Public Property DataSource as string
    get
    return mDataSource
    end get
    set(Value as string)
    mDataSource = Value
    end set
    end property

    'Repeat for the DataTextField Property

    End Class

    Public Class MyAddressObject : Implements IComponent,
    IEnumberable 'IEnumerable causes this to become a valid
    datasource in the datasource drop down. Should I use
    something else?.

    Private mStreet as string
    Private mCity as string
    Private mSite as ISite
    'etc.

    'Implement IComponet stuff here
    'If IEnumberable is needed, how to implement
    GetEnumerator?

    Public Property Street as string
    get
    return mStreet
    end get
    set(Value as string)
    mStreet = Value
    end set
    end property

    'Implement the rest of the properties

    End Class

    Public Class WebDataBoundDesigner : Inherits
    ControlDesigner
    Implements IDataSourceProvider

    Public Property DataTextField() As String
    Get
    Return CType(Me.Component,
    MyTextBox).DataTextField
    End Get
    Set(ByVal Value As String)
    CType(Me.Component, MyTextBox).DataTextField
    = Value
    End Set
    End Property

    Public Property DataSource() As String
    Get
    Return CType(Me.Component,
    MyTextBox).DataSource
    End Get
    Set(ByVal Value As String)
    CType(Me.Component, MyTextBox).DataSource =
    Value
    OnBindingsCollectionChanged("DataSource")
    End Set
    End Property

    Public Function GetResolvedSelectedDataSource() As
    System.Collections.IEnumerable Implements
    System.Web.UI.Design.IDataSourceProvider.GetResolvedSelect
    edDataSource
    If Me.DataSource.Length > 0 Then
    Return DesignTimeData.GetSelectedDataSource
    (Me.Component, Me.DataSource, Me.DataTextField)
    End If
    Return Nothing
    End Function

    Public Function GetSelectedDataSource() As Object
    Implements
    System.Web.UI.Design.IDataSourceProvider.GetSelectedDataSo
    urce
    If Me.DataSource.Length > 0 Then
    Return DesignTimeData.GetSelectedDataSource
    (Me.Component, Me.DataSource)
    End If
    Return Nothing
    End Function

    Protected Overrides Sub PreFilterProperties(ByVal
    properties As System.Collections.IDictionary)
    MyBase.PreFilterProperties(properties)
    Dim prop As PropertyDescriptor

    prop = CType(properties("DataSource"),
    PropertyDescriptor)
    prop = TypeDescriptor.CreateProperty
    (Me.GetType, "DataSource", GetType(String), New Attribute
    () {New TypeConverterAttribute(GetType
    (DataSourceConverter))})
    properties("DataSource") = prop

    prop = CType(properties("DataTextField"),
    PropertyDescriptor)
    prop = TypeDescriptor.CreateProperty
    (Me.GetType, "DataTextField", GetType(String), New
    Attribute() {New TypeConverterAttribute(GetType
    (DataFieldConverter))})
    properties("DataTextField") = prop
    End Sub
    End Class
     
    Bill Anderson, Dec 10, 2003
    #1
    1. Advertising

  2. I think I achieved what I wanted to do, so here is a
    follow up in case any one wants to know.

    I found Rick Strahl's article on two way databinding.
    This works great, except you have to type the object name
    you want to bind to in the BindingSourceObject property
    (the value is a valid instance variable declared in code
    behind). Then you have to type the property of that
    object you want to bind to in the BindingSourceProperty.
    If a designer is not familar with the properties of an
    object, they would have to find them out somehow. I
    wanted this to be more RAD. I wanted to have these
    properties as dropdown boxes showing valid values.

    Here is what I did to achieve that.

    I created two custom typeconverters.

    The first type converter iterates through the code behind
    class of the active document that the textbox is sitting
    on. It finds all family level fields (protected) using
    reflection. Then, by attaching the typeconverter
    attribute to the bindingsourceobject property of the
    textbox, this creates a drop down at design time that
    shows all valid objects from the codebehind this textbox
    can bind to.

    The second type converter looks at the selected value
    from above. Based on the object selected, it uses
    reflection to get all public properties of the selected
    object and displays them in a drop down box. I attached
    this typeconverter to the BindingSourceProperty object.

    There are a couple of caveats. The code behind type has
    to be the same name as the web form (by default this is
    always the case when you create a new web form).

    Here is the code of the inherited textbox and the type
    converters.....

    ' *******************************************
    ' TypeConverter Classes - WebBindingTypeConverters.vb
    '********************************************

    Imports System.ComponentModel
    Imports EnvDTE
    Imports VSLangProj
    Imports System.Web.UI.Design
    Imports System.Web.UI
    Imports System.Reflection

    Public Class TypeInstanceConverter : Inherits
    DataSourceConverter
    Public Overloads Overrides Function GetStandardValues
    (ByVal context As
    System.ComponentModel.ITypeDescriptorContext) As
    System.ComponentModel.TypeConverter.StandardValuesCollecti
    on
    Dim list1 As ArrayList
    Dim array1() As Object
    Dim sAssemblyName As String = ""
    Dim sTypeName As String = ""
    Dim a As System.Reflection.Assembly
    sAssemblyName =
    ReflectionHelperFunctions.GetProjectAssemblyName(context)
    sTypeName =
    ReflectionHelperFunctions.GetActiveDocumentType(context)
    list1 = New ArrayList
    If Not context Is Nothing Then
    Dim ass As System.Reflection.Assembly
    Try
    ass = System.Reflection.Assembly.Load
    (sAssemblyName)
    Catch ex As Exception
    ass = Nothing
    End Try
    If Not ass Is Nothing Then
    Dim t As System.Type
    Try
    t = ass.GetType(sAssemblyName & "." &
    sTypeName)
    Catch ex As Exception
    MsgBox(ex.Message)
    t = Nothing
    End Try
    If Not t Is Nothing Then
    For Each fi As FieldInfo In
    t.GetFields(ReflectionHelperFunctions.MemberAccess)
    If fi.IsFamily Then
    list1.Add(fi.Name)
    End If
    Next
    End If
    End If
    End If
    array1 = list1.ToArray
    Array.Sort(array1, Comparer.Default)
    Return New StandardValuesCollection(array1)
    End Function
    End Class


    Public Class TypePropertyNameConverter : Inherits
    DataSourceConverter
    Public Overloads Overrides Function GetStandardValues
    (ByVal context As
    System.ComponentModel.ITypeDescriptorContext) As
    System.ComponentModel.TypeConverter.StandardValuesCollecti
    on

    Dim list1 As ArrayList
    Dim array1() As Object
    Dim sAssemblyName As String
    Dim sTypeName As String

    sAssemblyName =
    ReflectionHelperFunctions.GetProjectAssemblyName(context)
    sTypeName =
    ReflectionHelperFunctions.GetActiveDocumentType(context)

    list1 = New ArrayList
    If Not context Is Nothing Then
    If TypeOf context.Instance Is IWebDataControl
    Then
    Dim c As IWebDataControl = CType
    (context.Instance, IWebDataControl)
    If Not c Is Nothing Then
    If Trim(c.BindingSourceObject).Length
    > 0 Then

    Dim ass As
    System.Reflection.Assembly
    ass =
    System.Reflection.Assembly.Load(sAssemblyName)
    Dim t As Type
    Try
    t = ass.GetType(sAssemblyName
    & "." & sTypeName, True)
    Catch ex As Exception
    t = Nothing
    End Try
    If Not t Is Nothing Then
    Try
    For Each f As FieldInfo
    In t.GetFields(ReflectionHelperFunctions.MemberAccess)
    If f.IsFamily Then
    If f.Name =
    c.BindingSourceObject Then
    For Each p As
    PropertyInfo In f.FieldType.GetProperties
    Try

    list1.Add(p.Name)
    Catch ex
    As Exception
    End Try

    Next
    Exit For
    End If
    End If

    Next
    Catch ex As Exception
    MsgBox(ex.Message)
    End Try
    End If
    End If
    End If
    End If
    End If
    array1 = list1.ToArray
    Array.Sort(array1, Comparer.Default)
    Return New StandardValuesCollection(array1)
    End Function
    End Class


    Public Class ReflectionHelperFunctions
    Public Const MemberAccess As BindingFlags =
    BindingFlags.Public Or _

    BindingFlags.NonPublic Or _

    BindingFlags.Static Or _

    BindingFlags.Instance Or _

    BindingFlags.IgnoreCase

    Public Shared Function GetProjectAssemblyName(ByVal
    context As ITypeDescriptorContext) As String
    Dim item As ProjectItem
    Dim sAssemblyName As String

    item = CType(context.GetService(GetType
    (EnvDTE.ProjectItem)), ProjectItem)

    If Not item Is Nothing Then
    Dim project As VSProject
    project = CType
    (item.ContainingProject.Object, VSProject)
    If Not project Is Nothing Then
    For i As Int32 = 1 To
    project.Project.Properties.Count
    If project.Project.Properties.Item
    (i).Name = "AssemblyName" Then
    sAssemblyName =
    project.Project.Properties.Item(i).Value
    Exit For
    End If
    Next
    End If
    Else
    sAssemblyName = ""
    End If
    Return sAssemblyName
    End Function

    Public Shared Function GetActiveDocumentType(ByVal
    context As ITypeDescriptorContext) As String
    Dim sTypeName As String = ""
    Dim item As EnvDTE._DTE
    item = CType(context.GetService(GetType
    (EnvDTE._DTE)), EnvDTE._DTE)
    sTypeName = item.ActiveDocument.Name
    Return Trim(Left(sTypeName, sTypeName.IndexOf
    (".aspx")))
    End Function
    End Class

    ' **********************************
    ' TextBox Class - WebTextBox.vb
    ' ***********************************

    Imports System.Web.UI
    Imports System.Web.UI.Design
    Imports System.Web.I.WebControls
    Imports System.ComponentModel
    Imports System.Reflection
    Imports System.Drawing

    <DefaultProperty("BindingSourceObject"), _
    ToolboxBitmap(GetType(TextBox)), _
    ToolboxData("<{0}:WebTextBox runat=server></
    {0}:WebTextBox>")> _
    Public Class WebTextBox : Inherits TextBox
    Implements IWebDataControl

    Private mBindingSourceObject As String = ""
    Private mBindingSourceProperty As String = ""
    Private mBindingProperty As String = "Text"
    Private mDisplayFormat As String = ""
    Private mUserFieldName As String = ""
    Private mBindingErrorMessage As String = ""

    <TypeConverter(GetType(TypeInstanceConverter))> _
    Public Property BindingSourceObject() As String
    Implements IWebDataControl.BindingSourceObject
    Get
    Return mBindingSourceObject
    End Get
    Set(ByVal Value As String)
    mBindingSourceObject = Value
    End Set
    End Property

    <TypeConverter(GetType(TypePropertyNameConverter))> _
    Public Property BindingSourceProperty() As String
    Implements IWebDataControl.BindingSourceProperty
    Get
    Return mBindingSourceProperty
    End Get
    Set(ByVal Value As String)
    mBindingSourceProperty = Value
    End Set
    End Property

    Public Property BindingErrorMessage() As String
    Implements IWebDataControl.BindingErrorMessage
    Get
    Return mBindingErrorMessage
    End Get
    Set(ByVal Value As String)
    mBindingErrorMessage = Value
    End Set
    End Property

    <DefaultValue("Text")> _
    Public Property BindingProperty() As String
    Implements IWebDataControl.BindingProperty
    Get
    Return mBindingProperty
    End Get
    Set(ByVal Value As String)
    mBindingProperty = Value
    End Set
    End Property

    Public Property DisplayFormat() As String Implements
    IWebDataControl.DisplayFormat
    Get
    Return mDisplayFormat
    End Get
    Set(ByVal Value As String)
    mDisplayFormat = Value
    End Set
    End Property

    Public Overrides Property Text() As String Implements
    IWebDataControl.Text
    Get
    Return MyBase.Text
    End Get
    Set(ByVal Value As String)
    MyBase.Text = Value
    End Set
    End Property

    Public Property UserFieldName() As String Implements
    IWebDataControl.UserFieldName
    Get
    Return mUserFieldName
    End Get
    Set(ByVal Value As String)
    mUserFieldName = Value
    End Set
    End Property

    Public Sub UnbindData(ByVal WebForm As
    System.Web.UI.Page) Implements IWebDataControl.UnbindData
    WebDataHelper.ControlUnbindData(WebForm, Me)
    End Sub

    Public Sub BindData(ByVal WebForm As
    System.Web.UI.Page) Implements IWebDataControl.BindData
    WebDataHelper.ControlBindData(WebForm, Me)
    End Sub
    End Class


    >-----Original Message-----
    >Hi,
    >
    >I am trying to accomplish the following.
    >
    >Here is what I want to do:
    >1. Drop a custom business object component onto the
    >design surface and have it show up in the area below the
    >designer grid in VisStudio (like a dataadapter or

    dataset
    >does).
    >
    >2. Drag a textbox webcontrol and change a custom
    >datasource property to the component from step 1. The
    >property grid will display a drop down of available
    >datasources (i want the textbox to work like a dropdown
    >control).
    >
    >3. Based on the datasource chosen, change a
    >DataTextField property to a valid property of the
    >component. The property grid will show a drop down of
    >available Public Properties (fields) from the component
    >selected (basically, binding values to the textbox work
    >like the dropdown box does when binding to a dataset).
    >
    >4. The Datasource and DataTextField values chosen will
    >persist as attributes in HTML on the custom component.
    >
    >5. The componet is a basic business object. It does

    not
    >implement IEnumerable or IListsource (it does implement
    >IComponent). It just exposes public properties.
    >
    >I have all of this working fine if the custom component
    >is of type Collection. But I don't want a Collection,
    >just the ability to bind to Public Properties of a basic
    >class.
    >
    >I think have taken care of most of the items as follow,
    >but I am stuck on #5:
    >1. Implement IComponent on the business class.
    >2. Created custom control derived from Textbox

    webcontrol.
    >3. Created custom Designer that implements
    >IDatasourceprovider, and attach to custom textbox

    control.
    >4. Put designerserialization attribute of visible on
    >properties of custom textbox control.
    >5. Can't get to work unless the component Implements
    >IEnumerable, and then it only shows up in the datasource
    >drop down. I can never get the properties to list out
    >unless i have a stongly typed collection of busines
    >objects.
    >
    >here is some sample code(i shortened syntax for brevity):
    >
    ><Designer(Gettype(MyCustomDesigner)),

    otherattributes...>
    >_
    >Public Class MyTextBox : Inherits from TextBox
    >
    >Private mDataSource as string = ""
    >Private mDataTextField as string = ""
    >
    ><otherattributes, designerserialation...(visible)> _
    >Public Property DataSource as string
    >get
    >return mDataSource
    >end get
    >set(Value as string)
    >mDataSource = Value
    >end set
    >end property
    >
    >'Repeat for the DataTextField Property
    >
    >End Class
    >
    >Public Class MyAddressObject : Implements IComponent,
    >IEnumberable 'IEnumerable causes this to become a valid
    >datasource in the datasource drop down. Should I use
    >something else?.
    >
    >Private mStreet as string
    >Private mCity as string
    >Private mSite as ISite
    >'etc.
    >
    >'Implement IComponet stuff here
    >'If IEnumberable is needed, how to implement
    >GetEnumerator?
    >
    >Public Property Street as string
    >get
    >return mStreet
    >end get
    >set(Value as string)
    >mStreet = Value
    >end set
    >end property
    >
    >'Implement the rest of the properties
    >
    >End Class
    >
    >Public Class WebDataBoundDesigner : Inherits
    >ControlDesigner
    > Implements IDataSourceProvider
    >
    >Public Property DataTextField() As String
    > Get
    > Return CType(Me.Component,
    >MyTextBox).DataTextField
    > End Get
    > Set(ByVal Value As String)
    > CType(Me.Component, MyTextBox).DataTextField
    >= Value
    > End Set
    > End Property
    >
    >Public Property DataSource() As String
    > Get
    > Return CType(Me.Component,
    >MyTextBox).DataSource
    > End Get
    > Set(ByVal Value As String)
    > CType(Me.Component, MyTextBox).DataSource =
    >Value
    > OnBindingsCollectionChanged("DataSource")
    > End Set
    > End Property
    >
    >Public Function GetResolvedSelectedDataSource() As
    >System.Collections.IEnumerable Implements
    >System.Web.UI.Design.IDataSourceProvider.GetResolvedSelec

    t
    >edDataSource
    > If Me.DataSource.Length > 0 Then
    > Return DesignTimeData.GetSelectedDataSource
    >(Me.Component, Me.DataSource, Me.DataTextField)
    > End If
    > Return Nothing
    > End Function
    >
    >Public Function GetSelectedDataSource() As Object
    >Implements
    >System.Web.UI.Design.IDataSourceProvider.GetSelectedDataS

    o
    >urce
    > If Me.DataSource.Length > 0 Then
    > Return DesignTimeData.GetSelectedDataSource
    >(Me.Component, Me.DataSource)
    > End If
    > Return Nothing
    > End Function
    >
    >Protected Overrides Sub PreFilterProperties(ByVal
    >properties As System.Collections.IDictionary)
    > MyBase.PreFilterProperties(properties)
    > Dim prop As PropertyDescriptor
    >
    > prop = CType(properties("DataSource"),
    >PropertyDescriptor)
    > prop = TypeDescriptor.CreateProperty
    >(Me.GetType, "DataSource", GetType(String), New Attribute
    >() {New TypeConverterAttribute(GetType
    >(DataSourceConverter))})
    > properties("DataSource") = prop
    >
    > prop = CType(properties("DataTextField"),
    >PropertyDescriptor)
    > prop = TypeDescriptor.CreateProperty
    >(Me.GetType, "DataTextField", GetType(String), New
    >Attribute() {New TypeConverterAttribute(GetType
    >(DataFieldConverter))})
    > properties("DataTextField") = prop
    >End Sub
    >End Class
    >.
    >
     
    Bill Anderson, Dec 15, 2003
    #2
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Karuppasamy

    com+ component and Component Service

    Karuppasamy, Jan 13, 2004, in forum: ASP .Net
    Replies:
    0
    Views:
    637
    Karuppasamy
    Jan 13, 2004
  2. Karuppasamy

    com+ component and Component Service

    Karuppasamy, Jan 13, 2004, in forum: ASP .Net
    Replies:
    1
    Views:
    2,569
  3. Scott Holmes

    event binding and component.config

    Scott Holmes, Aug 16, 2003, in forum: Python
    Replies:
    1
    Views:
    349
    Greg McFarlane
    Aug 27, 2003
  4. JcFx
    Replies:
    0
    Views:
    319
  5. Miguel Minora
    Replies:
    1
    Views:
    164
    Miguel Minora
    Jan 11, 2005
Loading...

Share This Page