.NET Forum / Windows Forms / WinForm General / April 2005
ComboBox unique ValueMember and non-unique DisplayMember
|
|
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 MagazinesGet 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 ...
|
|
|