Home | Contact Us | FAQ | Search & Site Map | Link to Us
Sign In | Join | Other 45 Sites in Network
HomeAnnouncementsFree MagazinesWhite PapersSubmit Content
Discussion GroupsASP.NETWindows FormsLanguages.NET FrameworkVisual Studio.NET
Articles.NET FrameworkASP.NETToolsWindows Forms
.NET DirectoryOpen Source ProjectsUser GroupsWeb Resources
Related Topics
Visual Basic 6SQL ServerMS AccessOther DB ProductsMS Server ProductsMore Topics ...

.NET Forum / Windows Forms / WinForm General / April 2005

Tip: Looking for answers? Try searching our database.

ComboBox unique ValueMember and non-unique DisplayMember

Thread view: 
Enable EMail Alerts  Start New Thread
Thread rating: 
cryolitte - 13 Apr 2005 00:51 GMT
I have a ComboBox bound to a DataTable. For example:

ID        Name
1        John
2        John
3        Bob

I then set ValueMember = "ID" and DisplayMember = "Name". All the data in
the ValueMember's column is unique, but the DisplayMember's column may not
be unique.

The problem:

User selects John (ID = 2), close the dropdown. User opens the dropdown
again, and the selectedIndex/selectedItem/selectedValue is set to John (ID =
1).

I've tracked this change to after the DropDown event. i.e. in the DropDown
event the selectedIndex is still correct. With the DrawMode =
OwnerDrawFixed, it seems that
The problem I am having is that, some time after the DropDown event, the
"change" occurs before DrawItem.

Unfortunately I could not find anything not "drawing" related between
DropDown and the actual drawing to reset the "real" SelectedIndex. Setting
it in the DrawItem method only lead to an infinite loop of DrawItem...

Has anyone else encountered this problem? That upon DropDown, the ComboBox
selects the first match based on the SelectedText, then the SelectedIndex?

BTW, this behavior is also exhibited in the unextended ComboBox, not just my
customized ComboBox...
Beth Massi [Architect MVP] - 13 Apr 2005 02:22 GMT
Does the problem still happen if the Names are different? If so, you
probably just need to call EndCurrentEdit on the CurrencyManager.

-B

>I have a ComboBox bound to a DataTable. For example:
>
[quoted text clipped - 30 lines]
> my
> customized ComboBox...
cryolitte - 13 Apr 2005 02:48 GMT
No, thank goodness this problem doesn't happen when the names in Name are
unique.

I had a look at the ComboBox class through Reflector, and I'm guessing it
could be the way the ListControl evaluates the items, based on DisplayMember
only... Looks like someone forgot to take the ValueMember into
consideration...

> Does the problem still happen if the Names are different? If so, you
> probably just need to call EndCurrentEdit on the CurrencyManager.
[quoted text clipped - 35 lines]
> > my
> > customized ComboBox...
cryolitte - 13 Apr 2005 03:08 GMT
Also, as you suggested, I tried calling CurrencyManager.EndCurrentEdit() in
the DropDown and DrawItem events. The ComboBox still selected the first
matching item based on DisplayValue.

> Does the problem still happen if the Names are different? If so, you
> probably just need to call EndCurrentEdit on the CurrencyManager.
>
> -B
Beth Massi [Architect MVP] - 13 Apr 2005 06:36 GMT
How are you setting up the databindings to your dataset? Are you doing
something like this?:

Me.ComboBox1.DataSource = MyLookupTable
Me.ComboBox1.ValueMember = "ID"
Me.ComboBox1.DisplayMember = "Name"
Me.ComboBox1.DataBindings.Add("SelectedValue", myDataSet, "Table1.MyID")

> Also, as you suggested, I tried calling CurrencyManager.EndCurrentEdit()
> in
[quoted text clipped - 5 lines]
>>
>> -B
cryolitte - 13 Apr 2005 12:05 GMT
1). Setting it up kinda like this:
DataTable dt = generateTestDataTable();    // in here column ID is set as
Unique
comboBox1.DataSource = dt;
comboBox1.ValueMember = "ID";
comboBox1.DisplayMember = "Name";

2). Then I added the data bindings, as suggested:
comboBox1.DataBindings.Add("SelectedValue", dt, "ID");

3). But then I start getting a ConstraintException whenever I make a
selection in the dropdown. Additional information: Column 'ID' is
constrained to be unique. Value '4' (or whatever was selected) is already
present.

Why is this being thrown? I am not trying to edit or insert into the
DataTable. I only wish to select a row in it (and get the Combo to remember
it!). When I click on the dropdown button, it is still automatically
re-selected to the first matching (based on DisplayValue) row.

4). Just to see what happens, went back, set dt.Columns["ID"].Unique =
false. Then, whenever I select something, it updates its ID with the current
SelectedValue...

5). Set Unique = true again. I then tried calling
CurrencyManager.EndCurrentEdit() again in both DropDown and DrawItem events,
and my selectedItem/Index/Value is still being overwritten.
ConstraintException was also thrown (because of the new data-binding, I
think).

Seriously, I can't be the first one asking this question...lol...

> How are you setting up the databindings to your dataset? Are you doing
> something like this?:
[quoted text clipped - 13 lines]
> >>
> >> -B
Beth Massi [Architect MVP] - 13 Apr 2005 18:05 GMT
Okay I think it will be easier to figure out if I just write you an example
;-). This example uses the SQL Northwind database. It selects Regions and
Territories, sets up a relation and has combobox that binds the selected
value to the Territories.RegionID field. I'm putting a duplicate row into
the Region table to simulate your scenario and it works fine. Note the call
to EndCurrentEdit in the SelectedIndexChanged event handler.

Public Class Form3
Inherits System.Windows.Forms.Form

#Region " Windows Form Designer generated code "

Public Sub New()
 MyBase.New()

 'This call is required by the Windows Form Designer.
 InitializeComponent()

 'Add any initialization after the InitializeComponent() call

End Sub

'Form overrides dispose to clean up the component list.
Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
 If disposing Then
  If Not (components Is Nothing) Then
   components.Dispose()
  End If
 End If
 MyBase.Dispose(disposing)
End Sub

'Required by the Windows Form Designer
Private components As System.ComponentModel.IContainer

'NOTE: The following procedure is required by the Windows Form Designer
'It can be modified using the Windows Form Designer.
'Do not modify it using the code editor.
Friend WithEvents TextBox2 As System.Windows.Forms.TextBox
Friend WithEvents TextBox1 As System.Windows.Forms.TextBox
Friend WithEvents Label1 As System.Windows.Forms.Label
Friend WithEvents Label2 As System.Windows.Forms.Label
Friend WithEvents Label4 As System.Windows.Forms.Label
Friend WithEvents ComboBox1 As System.Windows.Forms.ComboBox
Friend WithEvents Label3 As System.Windows.Forms.Label
Friend WithEvents TextBox3 As System.Windows.Forms.TextBox
Friend WithEvents Button1 As System.Windows.Forms.Button
<System.Diagnostics.DebuggerStepThrough()> Private Sub
InitializeComponent()
 Me.TextBox2 = New System.Windows.Forms.TextBox
 Me.TextBox1 = New System.Windows.Forms.TextBox
 Me.Label1 = New System.Windows.Forms.Label
 Me.Label2 = New System.Windows.Forms.Label
 Me.Label4 = New System.Windows.Forms.Label
 Me.TextBox3 = New System.Windows.Forms.TextBox
 Me.ComboBox1 = New System.Windows.Forms.ComboBox
 Me.Label3 = New System.Windows.Forms.Label
 Me.Button1 = New System.Windows.Forms.Button
 Me.SuspendLayout()
 '
 'TextBox2
 '
 Me.TextBox2.Location = New System.Drawing.Point(192, 56)
 Me.TextBox2.Name = "TextBox2"
 Me.TextBox2.Size = New System.Drawing.Size(160, 20)
 Me.TextBox2.TabIndex = 5
 Me.TextBox2.Text = "TextBox2"
 '
 'TextBox1
 '
 Me.TextBox1.Location = New System.Drawing.Point(192, 24)
 Me.TextBox1.Name = "TextBox1"
 Me.TextBox1.Size = New System.Drawing.Size(48, 20)
 Me.TextBox1.TabIndex = 4
 Me.TextBox1.Text = "TextBox1"
 '
 'Label1
 '
 Me.Label1.AutoSize = True
 Me.Label1.Location = New System.Drawing.Point(24, 24)
 Me.Label1.Name = "Label1"
 Me.Label1.Size = New System.Drawing.Size(112, 16)
 Me.Label1.TabIndex = 6
 Me.Label1.Text = "Territories.TerritoryID"
 '
 'Label2
 '
 Me.Label2.AutoSize = True
 Me.Label2.Location = New System.Drawing.Point(24, 56)
 Me.Label2.Name = "Label2"
 Me.Label2.Size = New System.Drawing.Size(157, 16)
 Me.Label2.TabIndex = 7
 Me.Label2.Text = "Territories.TerritoryDescription"
 '
 'Label4
 '
 Me.Label4.AutoSize = True
 Me.Label4.Location = New System.Drawing.Point(24, 120)
 Me.Label4.Name = "Label4"
 Me.Label4.Size = New System.Drawing.Size(109, 16)
 Me.Label4.TabIndex = 10
 Me.Label4.Text = "Territorries.RegionID"
 '
 'TextBox3
 '
 Me.TextBox3.Location = New System.Drawing.Point(192, 120)
 Me.TextBox3.Name = "TextBox3"
 Me.TextBox3.Size = New System.Drawing.Size(48, 20)
 Me.TextBox3.TabIndex = 8
 Me.TextBox3.Text = "TextBox4"
 '
 'ComboBox1
 '
 Me.ComboBox1.DropDownStyle =
System.Windows.Forms.ComboBoxStyle.DropDownList
 Me.ComboBox1.Location = New System.Drawing.Point(192, 88)
 Me.ComboBox1.Name = "ComboBox1"
 Me.ComboBox1.Size = New System.Drawing.Size(160, 21)
 Me.ComboBox1.TabIndex = 11
 '
 'Label3
 '
 Me.Label3.AutoSize = True
 Me.Label3.Location = New System.Drawing.Point(24, 88)
 Me.Label3.Name = "Label3"
 Me.Label3.Size = New System.Drawing.Size(40, 16)
 Me.Label3.TabIndex = 12
 Me.Label3.Text = "Region"
 '
 'Button1
 '
 Me.Button1.Location = New System.Drawing.Point(296, 144)
 Me.Button1.Name = "Button1"
 Me.Button1.Size = New System.Drawing.Size(96, 23)
 Me.Button1.TabIndex = 13
 Me.Button1.Text = "Show Changes"
 '
 'Form3
 '
 Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
 Me.ClientSize = New System.Drawing.Size(432, 182)
 Me.Controls.Add(Me.Button1)
 Me.Controls.Add(Me.Label3)
 Me.Controls.Add(Me.ComboBox1)
 Me.Controls.Add(Me.Label4)
 Me.Controls.Add(Me.TextBox3)
 Me.Controls.Add(Me.Label2)
 Me.Controls.Add(Me.Label1)
 Me.Controls.Add(Me.TextBox2)
 Me.Controls.Add(Me.TextBox1)
 Me.Name = "Form3"
 Me.Text = "Form3"
 Me.ResumeLayout(False)

End Sub

#End Region

Private MyDataset As DataSet

Private Class DataAccess
 Private Const SQL_CONNECTION_STRING As String = _
  "Data Source=localhost;" & _
  "Initial Catalog=Northwind;" & _
  "Integrated Security=SSPI"

 Public Shared Sub LoadData(ByRef ds As DataSet)
  ds = New DataSet
  Dim da, da2 As SqlDataAdapter
  Dim cnn As SqlConnection
  Try
   cnn = New SqlConnection(SQL_CONNECTION_STRING)

   da = New SqlDataAdapter("SELECT * FROM Region", cnn)
   da.Fill(ds, "Region")

   With ds.Tables("Region")
    If .Rows.Count > 0 Then
     '-- Load a "Duplicate" row. has a unique primary key,
     '-- but same description as first row.
     Dim values As Object() = {.Rows.Count + 1,
.Rows(0)("RegionDescription")}
     .LoadDataRow(values, True)
    End If
   End With

   da = New SqlDataAdapter("SELECT * FROM Territories", cnn)
   da.Fill(ds, "Territories")

   ds.Relations.Add("Region_Territories", _
    ds.Tables("Region").Columns("RegionID"), _
    ds.Tables("Territories").Columns("RegionID"))

   ds.DataSetName = "RegionTerritories"

  Catch Exp As Exception
   MessageBox.Show(Exp.Message)
  End Try
 End Sub

End Class

Private Sub Form3_Load(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles MyBase.Load
 DataAccess.LoadData(MyDataset)

 Me.ComboBox1.DataSource = MyDataset.Tables("Region")
 Me.ComboBox1.ValueMember = "RegionID"
 Me.ComboBox1.DisplayMember = "RegionDescription"

 Me.ComboBox1.DataBindings.Add("SelectedValue", MyDataset,
"Territories.RegionID")

 Me.TextBox1.DataBindings.Add("Text", MyDataset, "Territories.TerritoryID")
 Me.TextBox2.DataBindings.Add("Text", MyDataset,
"Territories.TerritoryDescription")
 Me.TextBox3.DataBindings.Add("Text", MyDataset, "Territories.RegionID")
End Sub

Private Sub ComboBox1_SelectedIndexChanged(ByVal sender As System.Object,
ByVal e As System.EventArgs) Handles ComboBox1.SelectedIndexChanged
 '-- This forces the comboxbox's value to be written to the dataset.
 Dim cm As CurrencyManager = Me.BindingContext(MyDataset, "Territories")
 cm.EndCurrentEdit()
End Sub

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles Button1.Click
 '-- View the diffgram in the web browser
 Try
  If Not (MyDataset Is Nothing) Then

   Dim cFileName As String =
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) +
"\Diff"
   Dim dsDiffgram As DataSet

   If MyDataset.HasChanges Then
    dsDiffgram = MyDataset.GetChanges()
    dsDiffgram.WriteXml(cFileName + MyDataset.DataSetName + ".xml",
XmlWriteMode.DiffGram)
    System.Diagnostics.Process.Start("file://" + cFileName +
MyDataset.DataSetName + ".xml")
   Else
    MessageBox.Show("Please make changes first.", "Show Changes",
MessageBoxButtons.OK, MessageBoxIcon.Information)
   End If
  End If
 Catch exp As Exception
 End Try
End Sub

End Class

> 1). Setting it up kinda like this:
> DataTable dt = generateTestDataTable();    // in here column ID is set as
[quoted text clipped - 49 lines]
>> >>
>> >> -B
cryolitte - 13 Apr 2005 23:04 GMT
Hi Beth,

Thanks again for your help.

After a few moments of head-scratching, I think I've narrowed it down to
this line:
Me.ComboBox1.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList
As it is, it works fine. Except for the fact that user cannot type anything
into the textbox portion of the ComboBox.

If I change it to:
Me.ComboBox1.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDown
The ComboBox then starts exhibiting the behavior that I have described.

Unfortunately I need to use the DropDown style to let users type into the
textbox portion.

TIA,
Cryo

> Okay I think it will be easier to figure out if I just write you an example
> ;-). This example uses the SQL Northwind database. It selects Regions and
[quoted text clipped - 303 lines]
> >> >>
> >> >> -B
Beth Massi [Architect MVP] - 14 Apr 2005 01:21 GMT
Okay I don't think I'm understanding your architecture. I was under the
impression that you are trying to use the combobox to display a lookup list
of values and store the value's ID into the data table you're editing. Is
that correct? If so then you will not be able to allow the user to type into
the combobox because there will be no associated ID with the text they type.
Now if you want to store the lookup table's Name field instead, then you can
allow them to type in the text, but you would need to bind to a string field
instead of an integer in the table you're editing. However, since you have
two duplicate Name values in the lookup table, the combobox would always
display the first one in the list that matched. Does that make sense? So for
instance you could do:

Me.ComboBox1.DataSource = MyLookupTable
Me.ComboBox1.ValueMember = "Name"
Me.ComboBox1.DisplayMember = "Name"

Me.ComboBox1.DataBindings.Add("SelectedValue", MyDataset,
"MyTable.LookupName")
Me.ComboBox1.DataBindings.Add("Text", MyDataset, "MyTable.LookupName")

Then I would move the call to EndCurrentEdit into the combobox's validating
event handler so you could pick up any direct edits the user made.

-B

> Hi Beth,
>
[quoted text clipped - 335 lines]
>> >> >>
>> >> >> -B
cryolitte - 14 Apr 2005 02:11 GMT
Beth,

Sorry for the confusion. What we wanted to achieve with an extended ComboBox
to:

1. Display a lookup list of values
2. Like Access 97 ComboBox control, display Text but use an ID for
SelectedValue
3. Allow user to type into the textbox portion so row filters can be placed
on the DataView to filter the results in a custom way (then of course the
user would make a selection from the filtered list)
4. Allow setting an initial SelectedIndex/Value programmatically
5. Get the SelectedValue, which is the ID of the thing of interest

We do _not_ intend the ComboBox to make _any_ modifications to the
DataSet/DataTable. Instead, when a button is clicked on the form, the
SelectedValue (which contains the ID of the thing) will be read. The ID will
then be used for a variety of things. e.g. calling stored procedure, or
calling the enterprise services to do the business logic.

In other words, it is just an interface to let the user make a selection and
for us to get the ID. Nothing to do with updating data.

Hope that's clearer,
Cryo

> Okay I don't think I'm understanding your architecture. I was under the
> impression that you are trying to use the combobox to display a lookup list
[quoted text clipped - 42 lines]
> > TIA,
> > Cryo
Beth Massi [Architect MVP] - 14 Apr 2005 05:46 GMT
Ah. Okay then you want it to be used more like a list control. Take a look
at my blog posting here:
http://bethmassi.blogspot.com/2005/04/combobox-databinding-woes.html and pay
attention to the second scenario. I think that will put you on the right
track.

> Beth,
>
[quoted text clipped - 83 lines]
>> > TIA,
>> > Cryo

Free Magazines

Get these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...

Oracle MagazineNetwork ComputingComputer WorldBio-IT WorldeWeekInformation WeekInfosecurity
 
Sign In
Join
My Latest Posts
My Monitored Threads
My Blog
My Photo Gallery
My Profile
My Homepage

Start New Thread
Enable EMail Alerts
Rate this Thread



©2008 Advenet LLC   Privacy Policy - Terms of Use
This website includes both content owned or controlled by Advenet as well as content owned or controlled by third parties.