Custom Component and Custom Textbox binding

B

Bill Anderson

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
 
B

Bill Anderson

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
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
 

Ask a Question

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

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,995
Messages
2,570,236
Members
46,821
Latest member
AleidaSchi

Latest Threads

Top