1

So I have been browsing about and there are a lot of explanations on how to move data from listbox to listbox. I have a listbox bound to a source from my SQL server and another unbound. My aim is to move the data from the first (LBsearch) to the second (LBselect) and back. I have seen people say use LBselect.Items.Add(LBsearch.SelectedItem) however it doesn't return data and instead shows System.Data.DataRowView. I've tried many different suffixes and all show this apart from LBsearch.text. Then to remove the data from the first one I've been removing the databindingsource (PersonBindingSource) with PersonBindingSource.Remove(LBsearch.SelectedItem) but my issue is adding the data back again.

PersonBindingSource.Add(LBselect.SelectedItem) gives an error:

System.InvalidOperationException: Objects added to a BindingSource's list must all be of the same type.
   at System.Windows.Forms.BindingSource.Add(Object value)
   at Project_Program.Participants.btnremoveselect_Click(Object sender, EventArgs e) in E:\Documents\Visual Studio\Project Program\Project Program\Participants.vb:line 39

PersonBindingSource.Add(PersonBindingSource.Item(LBsearch.SelectedIndex)) gives an error:

System.ArgumentException: Cannot add external objects to this list.
   at System.Data.DataView.System.Collections.IList.Add(Object value)
   at System.Windows.Forms.BindingSource.Add(Object value)
   at Project_Program.Participants.btnremoveselect_Click(Object sender, EventArgs e) in E:\Documents\Visual Studio\Project Program\Project Program\Participants.vb:line 38

Any help would be appreciated. Thanks


Private Sub btnaddselect_Click(sender As Object, e As EventArgs) Handles btnaddselect.Click
    If LBsearch.Items.Count > 0 Then
        MsgBox(LBsearch.Text)
        ' PersonBindingSource.Remove(PersonBindingSource.Item(LBsearch.SelectedIndex))
        LBselect.Items.Add(LBsearch.Text)
        PersonBindingSource.Remove(LBsearch.SelectedItem)

        ' filter()
    End If
End Sub

Private Sub btnremoveselect_Click(sender As Object, e As EventArgs) Handles btnremoveselect.Click
    If LBselect.Items.Count > 0 Then
        Try
            'PersonBindingSource.Add(PersonBindingSource.Item(LBsearch.SelectedIndex))
            PersonBindingSource.Add(LBselect.SelectedItem)

            MsgBox(LBselect.SelectedItem.ToString())
            LBselect.Items.Remove(LBselect.SelectedItem)
        Catch ex As Exception
            TextBox1.Text = (ex.ToString)
        End Try
        'filter()
    End If
End Sub
ByteWelder
  • 5,464
  • 1
  • 38
  • 45
Jareth
  • 123
  • 1
  • 9
  • The one that is bound has db rows "mapped" to it thru a DataView. If you have the ValueMember set, then in the ValueChanged event the SelectedValue will give you that row. I dont know what you want to put on the other LB; If you want to add the row, set the DisplayMember and ValueNenber properties, or to show the text, get it from the SelectedValue - cast it to a DataRow and get whichever field you want – Ňɏssa Pøngjǣrdenlarp Apr 01 '16 at 13:56
  • Possible duplicate of [How to detect a mobile device with javascript?](http://stackoverflow.com/questions/6666907/how-to-detect-a-mobile-device-with-javascript) – ByteWelder Apr 01 '16 at 14:07

1 Answers1

1

A major problem with moving rows is that since they are DataRows, they will not display well in the unbound control. If you pull out something useful like a name, you will have to recreate the DataRow to return it to the original/bound/source control.

This is a problem because, now it is a new row, so a DataAdpter might add it to the database again! One way to avoid that is to clone the table. Also, if/when you move them back, they will appear at the bottom of the list and not in their original position.

There is a better way than duplicating the table data and moving anything anywhere.

Since the act of being selected can be represented with a simple Boolean, it can be coded so that the Selected ones show in one control, the unSelected ones in the other. For this, a new Selected column is needed. If possible, add one using your SQL:

' MS Access syntax 
Dim SQL = "SELECT a, b, c,..., False As Selected FROM tblFoo"

This will create the new column in your datatable with all the values initialized to False. You can also add the column manually.

' form level vars
Private dvSource As DataView
Private dvDest As DataView
...
' set up:
' *** Add a column manually if you cant use SQL
dtSample.Columns.Add("Selected", GetType(Boolean))

' we need to loop and set the initial value for an added column
For Each r As DataRow In dtSample.Rows
    r("Selected") = False
Next
' *** end of code for adding col manually

' when the column is added from SQL, you will need:
dtSample.Columns("Selected").ReadOnly = False


' create Source DV as Selected = False
dvSource = New DataView(dtSample,"Selected=False", "",
                                DataViewRowState.CurrentRows)
' create Dest DV as Selected = True
dvDest = New DataView(dtSample, "Selected=True", "",
                                DataViewRowState.CurrentRows)
' assign DS
lbSource.DataSource = dvSource
lbSource.DisplayMember = "Name"
lbSource.ValueMember = "Id"

lbDest.DataSource = dvDest
lbDest.DisplayMember = "Name"
lbDest.ValueMember = "Id"

Then in the click events:

' select
CType(lbSource.SelectedItem, DataRowView).Row("Selected") = True

' deselect:
CType(lbSource.SelectedItem, DataRowView).Row("Selected") = False

The two DataView objects will filter on Selected inversely. When you change the state of a row, it is instantly removed from one and appears in the other without actually being added/removed from any table (or collection or control). If you move it back to the Source it will appear in the same position as before.

In this case, the RowState of any item moved will be Modified, which of course it is (unlike the move-to-table approach where they would be new rows (Added) which they are not).

It is hard to illustrate without 5 or 6 images, but the idea:

enter image description here

It is actually a pretty simple method and more economical than one invovling actually moving rows. Using DataViews the rows/items look like they move to another table or control, but they do not, nor do they need to.

Ňɏssa Pøngjǣrdenlarp
  • 38,411
  • 12
  • 59
  • 178
  • Thanks for your input Plutonix! I tried doing what you wrote and now both ListBoxes show a blank. Could you have a look and see if you can spot my mistake? http://pastebin.com/Qc8hV0eZ – Jareth Apr 03 '16 at 12:47
  • I dont know what some of those things are, but assuming `All.Person` is a legitimate datatable, why would you use "CollegeID" as the DisplayMember? Does the underlying table even have that column? – Ňɏssa Pøngjǣrdenlarp Apr 03 '16 at 13:04
  • The 2 tables I am using are to select Student ID numbers to add to a booking reference. In SQL it has CollegeID. PersonID is the unique key the database uses. `All.Person` is the data source. – Jareth Apr 03 '16 at 14:20
  • The LB Display and ValueMembers allow you to show one thing (like a name to the user) but collect another from the SelectedValue for the code to use. If it is showing blanks, the display member is DBNull or not there (the second should throw an error). If you are selecting Student/Persons to assign to something the DisplayMember should be the name or something the user can identify them by, – Ňɏssa Pøngjǣrdenlarp Apr 03 '16 at 14:32
  • From what you are saying I can't understand why my code is showing blank. Here is a sneak peak at some relevant data in the SQL server http://imgur.com/DGfxUht As you can see it shouldn't return DBNull. The connection is definitely correct as my code before showed up the values in the first LB, it was just not working to transfer back from the second LB. – Jareth Apr 03 '16 at 15:05
  • What exactly are you trying to do? Assign selected students to one or another college? Since there are many colleges, how do you know which one they want to assign them to? Is there a college picker somewhere which specifies the destination? If you *are* trying to assign students to a college, the ValueMember should be StudentID - you want to know which students are in which group. DisplayMember should be their name so the user can see who they are pickings - AI Ids are rarely meaningful to users - do *you* know your SO PK? – Ňɏssa Pøngjǣrdenlarp Apr 03 '16 at 16:49
  • There is one college, but many external companies. It is essentially a room booking system. If a room is booked for a lesson it goes to a participants form so all the students that will be in the room can be selected. PK for the database is PersonID. College ID is used to identify students but first name could be used instead. – Jareth Apr 04 '16 at 15:24
  • Your code looks right, Be sure it is being called and especially if it is called from Form Load, step thru it to be sure every single lone executes. Failing that, try it with a simple table of fish or colors or names to see how it works. – Ňɏssa Pøngjǣrdenlarp Apr 04 '16 at 15:33
  • I put a messagebox in the For loop and it didn't show once. I put in ` MsgBox(All.Person.Rows.Count)` and it returned 0. Should I be using a table adaptor or binding source? Atm I believe "All" is the dataset. – Jareth Apr 04 '16 at 19:47
  • Plutonix, Thanks a million for all your help and support, and thanks for being patient with a bit of a derp. I figured out the problem... I did all the code you wrote before actually filling the table from the adaptor `Me.PersonTableAdapter.Fill(Me.All.Person)` Thanks for all your help and support, and the code works great now! Thanks!! Now I have just one small question. Before, to filter it I would use `PersonBindingSource.Filter` but that no longer works. What should I use now do you know? – Jareth Apr 04 '16 at 20:14
  • You can use the `.RowFilter` property to apply other criteria. Note that the ones above are created with one in place: `Selected = xxx` – Ňɏssa Pøngjǣrdenlarp Apr 04 '16 at 21:16