Home All Groups Group Topic Archive Search About

Data Binding Question

Author
5 Nov 2007 5:51 PM
Terry
Think it is great the way that you can set up a datsource, value member, and
display member for a combobox, and map a 'code' to a 'description' and back
again by binding the combobox to a datasource that contains the code.  Now
suppose that you want it to be read-only?  That is, you have the 'code' in
your DB, want the associated 'description' to display, but not let the user
change it on this particular form.  There is no read-only property for a
combobox like with a textbox, and there are no datasource, display member,
value member properties for a textbox or label controls.  Is there some easy
way to accomplish this?  The only way I can think to do this is to use the
binding class 'format' event to lookup the description myself.  Any other
ideas?

--
Terry

Author
5 Nov 2007 6:13 PM
Cor Ligthert[MVP]
Terry,

I thought that it was easily just this one, however I am in doubt.

Dropdownlist

http://msdn2.microsoft.com/en-us/library/system.windows.forms.comboboxstyle(VS.71).aspx

Cor
Author
5 Nov 2007 6:22 PM
Terry
The user can still change the selected index. And while I could change the
'Data Source Update Mode' (under advanced binding) to Never, this would be
confusing.  Really want to show it in a label or readonly text box so the
user is aware that he/she can not change it (here).
--
Terry


Show quoteHide quote
"Cor Ligthert[MVP]" wrote:

> Terry,
>
> I thought that it was easily just this one, however I am in doubt.
>
> Dropdownlist
>
> http://msdn2.microsoft.com/en-us/library/system.windows.forms.comboboxstyle(VS.71).aspx
>
> Cor
>
>
Author
6 Nov 2007 10:05 AM
Linda Liu[MSFT]
Hi Terry,

If we could custom draw the input box part of a ComboBox as well as its
items in the drop down list, a possible workaround may be custom drawing
the ComboBox to get what you want.

Unfortunately, we can not custom draw the input box part of a ComboBox.

IMO, the solution to use the Binding class's Format event to look up the
description youself you have mentioned in your first reply is good. In
addition, I think it would be better to create a custom control to
encapsulate the above logic. Then you could use this custom control
wherever you want.

The following is a sample of this custom control. Note that you shoud set
the DataSource, ValueMember and DisplayMember properties of the derived
Label before bind the Text property of the control to a data source.

Public Class MyLabel
    Inherits Label

    Private _datasource As IList
    Private _valuemember As String
    Private _displaymember As String

    Public Property DataSource() As IList
        Get
            Return _datasource
        End Get
        Set(ByVal value As IList)
            _datasource = value
        End Set
    End Property

    Public Property ValueMember() As String
        Get
            Return _valuemember
        End Get
        Set(ByVal value As String)
            _valuemember = value
        End Set
    End Property

    Public Property DisplayMember() As String
        Get
            Return _displaymember
        End Get
        Set(ByVal value As String)
            _displaymember = value
        End Set
    End Property

    Sub New()
        AddHandler Me.DataBindings.CollectionChanged, AddressOf
DataBindings_CollectionChanged
    End Sub

    Private Sub DataBindings_CollectionChanged(ByVal sender As Object,
ByVal e As System.ComponentModel.CollectionChangeEventArgs)
        Dim b As Binding = CType(e.Element, Binding)
        If (Not (b Is Nothing)) Then
            If (b.PropertyName = "Text") Then
                If (e.Action = CollectionChangeAction.Add) Then
                    AddHandler b.Format, AddressOf b_Format
                    b.ReadValue()
                ElseIf (e.Action = CollectionChangeAction.Remove) Then
                    RemoveHandler b.Format, AddressOf b_Format
                End If
            End If
        End If
    End Sub

    Private Sub b_Format(ByVal sender As Object, ByVal e As
ConvertEventArgs)
        If (Not (_datasource Is Nothing) And _datasource.Count > 0) Then
            Dim pdc As PropertyDescriptorCollection =
TypeDescriptor.GetProperties(_datasource(0))
            For i As Integer = 0 To _datasource.Count - 1
                If (pdc(_valuemember).GetValue(_datasource(i)).ToString() =
e.Value.ToString()) Then
                    e.Value = pdc(_displaymember).GetValue(_datasource(i))
                    Exit For
                End If
            Next
        Else
            Throw New Exception("Please set the DataSource, DisplayMember
and ValueMember properties first")
        End If
    End Sub

End Class

Sincerely,
Linda Liu
Microsoft Online Community Support

==================================================
Get notification to my posts through email? Please refer to
http://msdn.microsoft.com/subscriptions/managednewsgroups/default.aspx#notif
ications.

Note: The MSDN Managed Newsgroup support offering is for non-urgent issues
where an initial response from the community or a Microsoft Support
Engineer within 1 business day is acceptable. Please note that each follow
up response may take approximately 2 business days as the support
professional working with you may need further investigation to reach the
most efficient resolution. The offering is not appropriate for situations
that require urgent, real-time or phone-based interactions or complex
project analysis and dump analysis issues. Issues of this nature are best
handled working with a dedicated Microsoft Support Engineer by contacting
Microsoft Customer Support Services (CSS) at
http://msdn.microsoft.com/subscriptions/support/default.aspx.
==================================================

This posting is provided "AS IS" with no warranties, and confers no rights.
Author
8 Nov 2007 3:28 PM
Terry
Hi Linda,
   I was able to accomplish what I wanted by using the Format event.  As a
learnig exercise for myself, I am going to try the custom control aproach
that you have suggested here.  I have never done this before and have a few
general questions for you.  I realize I could just add the code you have
given me here as a new class in the project and then reference it through
code in my form.  What approach do I take to make it available to other
projects?  Should I build a seperate project for it?  If so, is it a class
library or a windows control project?  How would I get it available in the
toolbox so that I can drop and position it on my form?  Maybe you could point
me to something I could read.
Thanks for all your help!
--
Terry


Show quoteHide quote
"Linda Liu[MSFT]" wrote:

> Hi Terry,
>
> If we could custom draw the input box part of a ComboBox as well as its
> items in the drop down list, a possible workaround may be custom drawing
> the ComboBox to get what you want.
>
> Unfortunately, we can not custom draw the input box part of a ComboBox.
>
> IMO, the solution to use the Binding class's Format event to look up the
> description youself you have mentioned in your first reply is good. In
> addition, I think it would be better to create a custom control to
> encapsulate the above logic. Then you could use this custom control
> wherever you want.
>
> The following is a sample of this custom control. Note that you shoud set
> the DataSource, ValueMember and DisplayMember properties of the derived
> Label before bind the Text property of the control to a data source.
>
> Public Class MyLabel
>     Inherits Label
>
>     Private _datasource As IList
>     Private _valuemember As String
>     Private _displaymember As String
>
>     Public Property DataSource() As IList
>         Get
>             Return _datasource
>         End Get
>         Set(ByVal value As IList)
>             _datasource = value
>         End Set
>     End Property
>
>     Public Property ValueMember() As String
>         Get
>             Return _valuemember
>         End Get
>         Set(ByVal value As String)
>             _valuemember = value
>         End Set
>     End Property
>
>     Public Property DisplayMember() As String
>         Get
>             Return _displaymember
>         End Get
>         Set(ByVal value As String)
>             _displaymember = value
>         End Set
>     End Property
>
>     Sub New()
>         AddHandler Me.DataBindings.CollectionChanged, AddressOf
> DataBindings_CollectionChanged
>     End Sub
>
>     Private Sub DataBindings_CollectionChanged(ByVal sender As Object,
> ByVal e As System.ComponentModel.CollectionChangeEventArgs)
>         Dim b As Binding = CType(e.Element, Binding)
>         If (Not (b Is Nothing)) Then
>             If (b.PropertyName = "Text") Then
>                 If (e.Action = CollectionChangeAction.Add) Then
>                     AddHandler b.Format, AddressOf b_Format
>                     b.ReadValue()
>                 ElseIf (e.Action = CollectionChangeAction.Remove) Then
>                     RemoveHandler b.Format, AddressOf b_Format
>                 End If
>             End If
>         End If
>     End Sub
>
>     Private Sub b_Format(ByVal sender As Object, ByVal e As
> ConvertEventArgs)
>         If (Not (_datasource Is Nothing) And _datasource.Count > 0) Then
>             Dim pdc As PropertyDescriptorCollection =
> TypeDescriptor.GetProperties(_datasource(0))
>             For i As Integer = 0 To _datasource.Count - 1
>                 If (pdc(_valuemember).GetValue(_datasource(i)).ToString() =
> e.Value.ToString()) Then
>                     e.Value = pdc(_displaymember).GetValue(_datasource(i))
>                     Exit For
>                 End If
>             Next
>         Else
>             Throw New Exception("Please set the DataSource, DisplayMember
> and ValueMember properties first")
>         End If
>     End Sub
>
> End Class
>
> Sincerely,
> Linda Liu
> Microsoft Online Community Support
>
> ==================================================
> Get notification to my posts through email? Please refer to
> http://msdn.microsoft.com/subscriptions/managednewsgroups/default.aspx#notif
> ications.

> Note: The MSDN Managed Newsgroup support offering is for non-urgent issues
> where an initial response from the community or a Microsoft Support
> Engineer within 1 business day is acceptable. Please note that each follow
> up response may take approximately 2 business days as the support
> professional working with you may need further investigation to reach the
> most efficient resolution. The offering is not appropriate for situations
> that require urgent, real-time or phone-based interactions or complex
> project analysis and dump analysis issues. Issues of this nature are best
> handled working with a dedicated Microsoft Support Engineer by contacting
> Microsoft Customer Support Services (CSS) at
> http://msdn.microsoft.com/subscriptions/support/default.aspx.
> ==================================================

> This posting is provided "AS IS" with no warranties, and confers no rights.
>
>
Author
9 Nov 2007 9:14 AM
Linda Liu[MSFT]
Hi Terry,

Thank you for your feedback!

You could create a Windows Control Library project for the custom control
and build the control library project, which generates an assembly(a .dll
file). Then you can add a reference to the assembly in other WinForm
application projects to use the custom control.

To add the custom control into the Toolbox, right click on the Toolbox and
choose 'Choose Items..'. In the 'Choose Toolbox Items' dialog, switch to
the '.NET Framework Components' tab and click the Browse button. Navigate
to the assembly containing the custom control and press Open button. The
custom control should appear in the listbox with the checkbox before it
selected. Press OK to close the dialog.

For more information on authoring a Windows Forms control, you may refer to
the following MSDN document:
'Developing Windows Forms Controls at Design Time'
http://msdn2.microsoft.com/en-us/library/w29y3h59.aspx

Hope this helps.
If you have any question, please feel free to let me know.

Sincerely,
Linda Liu
Microsoft Online Community Support
Author
20 Nov 2007 6:39 PM
Terry
Hi Linda,
   I finally had a few moments to give this 'Custom Control' approach a try
and am having a few issues.
1) When I try to set the DataSource property in the properties page, I get a
"Object Collection Editor" dialog that wants me to add system.object's to a
collection.  Not what I had in mind.  So I decided to set the datasource in
code to the bindingsource I am using for this purpose.  Which led me too....
2)I get a null reference exception in the line....
   If (pdc(_valuemember).GetValue(_datasource(i)).ToString() =
e.Value.ToString()) Then
_datsource is a bindingsource and has 7 elements.  pdc has 2, yet if I type
?pdc.count in the immediate window it says a null pointer was returned, but
when I hover the mouse over it it says its count is 2.  I think (guess) the
problem is with 'GetValue' which is marked as 'MustOverride'.  What am I
missing?

I tried changing the type of _datasource from Ilist to BindingSource.  That
allowed me to set the DataSource in the properties window, but I still got
the same null exception on the same line.

Thanks again for all your help.
--
Terry


Show quoteHide quote
"Linda Liu[MSFT]" wrote:

> Hi Terry,
>
> Thank you for your feedback!
>
> You could create a Windows Control Library project for the custom control
> and build the control library project, which generates an assembly(a .dll
> file). Then you can add a reference to the assembly in other WinForm
> application projects to use the custom control.
>
> To add the custom control into the Toolbox, right click on the Toolbox and
> choose 'Choose Items..'. In the 'Choose Toolbox Items' dialog, switch to
> the '.NET Framework Components' tab and click the Browse button. Navigate
> to the assembly containing the custom control and press Open button. The
> custom control should appear in the listbox with the checkbox before it
> selected. Press OK to close the dialog.
>
> For more information on authoring a Windows Forms control, you may refer to
> the following MSDN document:
> 'Developing Windows Forms Controls at Design Time'
> http://msdn2.microsoft.com/en-us/library/w29y3h59.aspx
>
> Hope this helps.
> If you have any question, please feel free to let me know.
>
> Sincerely,
> Linda Liu
> Microsoft Online Community Support
>
>
Author
21 Nov 2007 4:08 AM
Linda Liu[MSFT]
Hi Terry,

About your first question, you could add some attributes on the DataSource,
DisplayMember and ValueMember properties of the custom control to make the
design time behavior of these three properties like that of the DataSource,
DisplayMember and ValueMember properties of a ComboBox.

The following is a sample:

Imports System.Drawing.Design

Public Class MyLabel
    Inherits Label
    <DefaultValue(CType(Nothing, String)),
AttributeProvider(GetType(IListSource))> _
    Public Property DataSource() As IList
        ...
     End Property

     <Editor("System.Windows.Forms.Design.DataMemberFieldEditor,
System.Design, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b03f5f7f11d50a3a", _
     GetType(UITypeEditor)), DefaultValue("")> _
        Public Property ValueMember() As String
          ...
        End Property

        <DefaultValue(""), _
       TypeConverter("System.Windows.Forms.Design.DataMemberFieldConverter,
System.Design, Version=2.0.0.0, Culture=neutral,           
PublicKeyToken=b03f5f7f11d50a3a"),
Editor("System.Windows.Forms.Design.DataMemberFieldEditor, System.Design,
Version=2.0.0.0, Culture=neutral,     PublicKeyToken=b03f5f7f11d50a3a",
GetType(UITypeEditor))> _
        Public Property DisplayMember() As String
             ...
         End Property
       ...
End Class

Build the project and open the form containing the custom control in the
designer. Select the custom control on the form and go to the DataSource
entry in the Properties window, a drop down arrow should appear on the
right hand. If you click it, a drop down window should open with all data
sources available in the project listed in it. Then go to the DisplayMember
or ValueMember entry in the Properties window and click the drop down arrow
on the right, and you should see a drop down window opening with all
properites available in the selected data source.

About your second question, are all the objects in the data source of the
same type? If the problem is still not solved, please send me a simple
project that could just reproduce the problem. To get my actual email
address, remove 'online' from my displayed email address.

Sincerely,
Linda Liu
Microsoft Online Community Support
Author
21 Nov 2007 7:23 PM
Terry
Hi Linda,
   First, let me thank you for the ‘Attribute’ stuff – worked like a charm! 
When I get a chance I will look at it some more to figure out what it does!
   Now for the second problem, I have figured out what was going on, but I’m
not sure of the best way to fix the problem.  Let me try to explain.  I am
using custom classes, and the main one is called Subscription.  There are
several ‘codes’ for things like status, tax exemption code, cancel code etc. 
Some of these can be ‘empty’, Null.  My ‘lookup’ tables (code/description)
have entries like (Null/<None>) or (Null/No Exemption) etc.  This all works
fine when binding to a normal combobox, but with the ‘boundlabel’ you helped
me create, I get the Null reference exception I mentioned earlier on the line:
  If (pdc(_valuemember).GetValue(_datasource(i)).ToString() =
e.Value.ToString) Then
Due to e.value being ‘Nothing’.  So let me take you through the steps as the
record comes from the DB.  IN this case we are dealing with the CancelCode. 
The Subscription record is retrieved; the class instantiated and the
properties are being set:
….
..CancelCode = dr.Item(CN_CancelCode).ToString

The property looks like…

    Private _CancelCode As String
    Public Property CancelCode() As String
        Get
            Return _CancelCode
        End Get
        Set(ByVal value As String)
            If (_CancelCode <> value) Then
                Me.DataStateChanged(EntityStateEnum.Modified)
                _CancelCode = value
            End If
        End Set
    End Property

_CancelCode is Nothing, Value is “”, and _CancelCode<>Value is False! ie.
Nothing = “” and so CancelCode remains Nothing.

So later we get the exception when the routine tries to look up the code.  
The left side (pdc(_valuemember…..) evaluates  to “” (DBNull tostring), while
the right side is Nothing (tostring) which throws the exception.  Seems like
in some places,  Nothing and “” are the same and in others they are not!

So I changed the declaration of the backing variable to
_CancelCode as String = “”
this fixed the problem (here) but when  I now bind the field to a real
combobox, the “” does not match the lookup table!  I finally worked around
the problem by adding
if e.value = Nothing then
   e.value = “”
end if
as the first thing in the parsing routine.  This works but seems like a
hack.  The combobox is able to match a ‘Nothing’ coming from the property and
a DBNull coming from the DataTable.  Have any ideas?

--
Terry


Show quoteHide quote
"Linda Liu[MSFT]" wrote:

> Hi Terry,
>
> About your first question, you could add some attributes on the DataSource,
> DisplayMember and ValueMember properties of the custom control to make the
> design time behavior of these three properties like that of the DataSource,
> DisplayMember and ValueMember properties of a ComboBox.
>
> The following is a sample:
>
> Imports System.Drawing.Design
>
> Public Class MyLabel
>     Inherits Label
>     <DefaultValue(CType(Nothing, String)),
> AttributeProvider(GetType(IListSource))> _
>     Public Property DataSource() As IList
>         ...
>      End Property
>
>      <Editor("System.Windows.Forms.Design.DataMemberFieldEditor,
> System.Design, Version=2.0.0.0, Culture=neutral,
> PublicKeyToken=b03f5f7f11d50a3a", _
>      GetType(UITypeEditor)), DefaultValue("")> _
>         Public Property ValueMember() As String
>           ...
>         End Property
>
>         <DefaultValue(""), _
>        TypeConverter("System.Windows.Forms.Design.DataMemberFieldConverter,
> System.Design, Version=2.0.0.0, Culture=neutral,           
> PublicKeyToken=b03f5f7f11d50a3a"),
> Editor("System.Windows.Forms.Design.DataMemberFieldEditor, System.Design,
> Version=2.0.0.0, Culture=neutral,     PublicKeyToken=b03f5f7f11d50a3a",
> GetType(UITypeEditor))> _
>         Public Property DisplayMember() As String
>              ...
>          End Property
>        ...
> End Class

> Build the project and open the form containing the custom control in the
> designer. Select the custom control on the form and go to the DataSource
> entry in the Properties window, a drop down arrow should appear on the
> right hand. If you click it, a drop down window should open with all data
> sources available in the project listed in it. Then go to the DisplayMember
> or ValueMember entry in the Properties window and click the drop down arrow
> on the right, and you should see a drop down window opening with all
> properites available in the selected data source.
>
> About your second question, are all the objects in the data source of the
> same type? If the problem is still not solved, please send me a simple
> project that could just reproduce the problem. To get my actual email
> address, remove 'online' from my displayed email address.
>
> Sincerely,
> Linda Liu
> Microsoft Online Community Support
>
>
Author
22 Nov 2007 7:09 AM
Linda Liu[MSFT]
Hi Terry,

Thank you for your reply!

I understand your second question now.

Firstly, the reason why " Nothing = "" " returns true is that string
comparisons treat Nothing as "" (an empty string).

Secondly, it seems that the internal implementation of the ComboBox regards
Nothing equal to DBNull.Value.

So we can modify the code in the b_Format method in the derived Label class
as follows to get the same behavior of the ComboBox:

Public Class MyLabel
    Inherits Label
   ...
   Private Sub b_Format(ByVal sender As Object, ByVal e As ConvertEventArgs)
        If (Not (_datasource Is Nothing) And _datasource.Count > 0) Then
            Dim pdc As PropertyDescriptorCollection =
TypeDescriptor.GetProperties(_datasource(0))
            For i As Integer = 0 To _datasource.Count - 1
                Dim valueinDS As Object =
pdc(_valuemember).GetValue(_datasource(i))
                If ((valueinDS Is Nothing Or valueinDS Is DBNull.Value) And
(e.Value Is Nothing Or e.Value Is DBNull.Value)) Then
                    e.Value = pdc(_displaymember).GetValue(_datasource(i))
                    Exit For
                ElseIf (valueinDS IsNot Nothing And valueinDS IsNot
DBNull.Value And e.Value IsNot Nothing And e.Value IsNot DBNull.Value) Then
                    If (valueinDS.ToString() = e.Value.ToString()) Then
                        e.Value =
pdc(_displaymember).GetValue(_datasource(i))
                        Exit For
                    End If
                End If
            Next
        Else
            Throw New Exception("Please set the DataSource, DisplayMember
and ValueMember properties first")
        End If
    End Sub

End Class

Please try it in your project to see if it solves the problem and let me
know the result.

Sincerely,
Linda Liu
Microsoft Online Community Support
Author
22 Nov 2007 1:33 PM
Terry
Hi Linda,
Thanks!  Works like a charm!  I did make one small change, which I will
share...
If (Not (_datasource Is Nothing) AndAlso _datasource.Count > 0) Then
                                                      | so the second half
wont be evaluated if nothing

If you are in the US - Happy Thanksgiving!  If not, and in any case - Have a
Great Day and thanks again for all your help!

--
Terry


Show quoteHide quote
"Linda Liu[MSFT]" wrote:

> Hi Terry,
>
> Thank you for your reply!
>
> I understand your second question now.
>
> Firstly, the reason why " Nothing = "" " returns true is that string
> comparisons treat Nothing as "" (an empty string).
>
> Secondly, it seems that the internal implementation of the ComboBox regards
> Nothing equal to DBNull.Value.
>
> So we can modify the code in the b_Format method in the derived Label class
> as follows to get the same behavior of the ComboBox:
>
> Public Class MyLabel
>     Inherits Label
>    ...
>    Private Sub b_Format(ByVal sender As Object, ByVal e As ConvertEventArgs)
>         If (Not (_datasource Is Nothing) And _datasource.Count > 0) Then
>             Dim pdc As PropertyDescriptorCollection =
> TypeDescriptor.GetProperties(_datasource(0))
>             For i As Integer = 0 To _datasource.Count - 1
>                 Dim valueinDS As Object =
> pdc(_valuemember).GetValue(_datasource(i))
>                 If ((valueinDS Is Nothing Or valueinDS Is DBNull.Value) And
> (e.Value Is Nothing Or e.Value Is DBNull.Value)) Then
>                     e.Value = pdc(_displaymember).GetValue(_datasource(i))
>                     Exit For
>                 ElseIf (valueinDS IsNot Nothing And valueinDS IsNot
> DBNull.Value And e.Value IsNot Nothing And e.Value IsNot DBNull.Value) Then
>                     If (valueinDS.ToString() = e.Value.ToString()) Then
>                         e.Value =
> pdc(_displaymember).GetValue(_datasource(i))
>                         Exit For
>                     End If
>                 End If
>             Next
>         Else
>             Throw New Exception("Please set the DataSource, DisplayMember
> and ValueMember properties first")
>         End If
>     End Sub
>
> End Class
>
> Please try it in your project to see if it solves the problem and let me
> know the result.
>
> Sincerely,
> Linda Liu
> Microsoft Online Community Support
>
>