VB.NET web application - Update a data bound Listbox when underlying table / query changes - sql

I have a page in my web application that contains two listboxes with buttons to move items back & forth between them. Each listbox is bound to a SQL query, the results of which change as the selected items are added or removed from the corresponding lists. This all works fine, except I cannot get the list boxes to update their contents on the fly, however if I refresh the web page, the contents update correctly.
Basically the user selects items in LeftListbox and clicks the Add button which calls code to loop through the LeftListbox and for each selected item adds a new record to a table (Score). The RightListbox should then update to show that the items have been added to the table.
Here is a snippet of code from the Click event of the Add button:
Dim i As Integer
For i = 0 To (LeftListbox.Items.Count() - 1)
If LeftListbox.Items(i).Selected Then
Try
DbUtils.StartTransaction()
Dim rec As ScoreRecord = New ScoreRecord
rec.Player_ID = CInt(Me.LeftListbox.Items(i).Value)
rec.Save()
DbUtils.CommitTransaction()
Catch ex As Exception
DbUtils.RollBackTransaction()
Me.Page.ErrorOnPage = True
Finally
DbUtils.EndTransaction()
End Try
End If
Next i
'** Here is where I want to refresh the list **
I've searched quite a bit for a solution, but I can't find anything that works so any help would be much appreciated.
Andrew

Use the same method (or code) used to populate the "right listbox" in the first place. The right ListBox's DataSource will be the same as it was prior to this code snipped being ran, so it must be updated since the underlying data has changed.

Related

Getting Selected Items from a Listbox

Good Wednesday All.
I am running into a brick wall (easy for a shade tree coder to do) I have a Listbox that i populated with a datatable. I want to get the all LicenseID's from the selected items. In other words, if the user selects 3 out of 8 of the list box, I need to get the LicenseID for each of those 3.
Below is how I populated the listbox
Using cmd As New OleDbCommand(cmdText, conn)
conn.Open()
Dim reader As OleDbDataReader = cmd.ExecuteReader()
dt.Load(reader)
ListBox1License.DataSource = dt
ListBox1License.DisplayMember = "InstitutionTypeAbrev"
ListBox1License.ValueMember = "LicenseID"
End Using
I need to get the selected items from the listbox to use later.
I am thinking of adding the selected Items to an array.
I have searched around STackOverflow for some examples but none seem to work for me.
Any Help Appreciated
I'll show you how to derive the answer for this yourself:
I've set up a form:
Really simple; the listbox is like your listbox. The button is just there to give me an easy way to stop the code and examine what is going on.
I wrote some code to populate some things into my listbox. It's a screenshot because it doesn't matter that you have exactly this code, so you don't need to write this code (hence why I'm making it hard to copy paste):
I've double clicked my button to make a click handler. I haven't written any code, but I have put a breakpoint on the method declaration - see it's red? Click the margin where the dot is, to put breakpoints in your code. When you hit them, the code stops and waits for you to inspect:
I've run my app and clicked my button. The code has stopped and VS has switched to showing me the code, not the app:
I can now point to some variable that is in scope (like ListBox1) and see a tooltip, or I can open the Locals/Autos windows and see variables that are in scope and drill into them:
Expand you ListBox in the Autos/Locals window. It has a lot of properties. Scroll to SelectedItems:
SelectedItems is a collection of things.. We can tell partly because Microsoft is good at naming collections of things with a plural name, and because the inspector says "enumerate the enumerable" .. it means that it is a bunch of things that we can ForEach to look through
Expanding it we see that my selecteditems has only one thing selected (i truly did only have one selected item in my list when I clicked the button)
We can see that an entry in the SelectedItems collection is a DataRowView type of object. We can see that a DataRowView has a Row property that is a DataRow.. This Row is the DataRow in the DataTable to which the list is bound (you set the DataSource to a DataTable; this is a row from that table).
Every time you dig into the tree another level, that's like using either a dot or an indexer in your code. At this level we've gone listbox1.SelectedItems(0).Row..
So from this we can see that we need a code like:
' we will "enumerate the enumerable"
For Each drv as DataRowView in listbox1.SelectedItems
Dim originalRow = drv.Row 'we could do this to get the row...
Dim selectedAnimaId = row("AnimalID") ' ..and then index the row to get the animal ID ..
Dim selectedAnimalId = drv("AnimalID") ' ... or it's actually possible to index a DataRowView directly, so you can skip the row part
Next drv
It can be handy to write code while you're stopped on a breakpoint so you can look at the values of things as you're writing, and check you're going in the right direction. You might need to use F10 (or whatever key is associated with "step over"/"step into") to move the yellow bar along and execute code lines one by one:
You can only move the code execution along if you've written complete, legal code, but it doesn't have to be logically correct. You can back up and execute again by dragging the yellow arrow in the margin (or right clicking and choosing Set Next Statement). Here I've put some dummy statement in to move along to, so i can check that my animalID is correctly set in X like I expect . I point to X to see the value:
The standard ListBox won't help you with that, past getting the DataRowView objects from the SelectedItems collection. As an alternative, here's a custom control that you can use in place of a standard ListBox that will help you:
Public Class ListBoxEx
Inherits ListBox
Public Function GetItemValue(item As Object) As Object
Dim index = Me.Items.IndexOf(item)
If (index <> -1 AndAlso Me.DataManager IsNot Nothing) Then
Return Me.FilterItemOnProperty(Me.DataManager.List(index), Me.ValueMember)
End If
Return Nothing
End Function
End Class
You can then call GetItemValue and pass any item and get the same value back as you would if that was the SelectedItem and you got the SelectedValue. To get all the values in an array:
Dim licenseIDs = myListBoxEx.SelectedItems.
Cast(Of Object)().
Select(Function(o) CInt(myListBoxEx.GetItemValue(o)).
ToArray()
For more information, see here.
In case you're unaware, if you add a class to your project and it is a control or component, once you build, it will appear automatically at the top of the Toolbox window.
If you already have a standard ListBox in place and you don't want to have to delete it and add a new control, you can edit the designer code file by hand to change the existing control. To do that, open the Solution Explorer and select a node within your project, click the Show All Files button, expand the node for your form, double-click the designer code file and then replace ListBox with ListBoxEx (or whatever you call it) in the relevant places. I'd advise creating a backup copy or syncing with source control first, in case you mess it up.

Checkbox in a DataGridView column is not getting checked sometimes?

I use this code to check all the check-boxes in a data grid view in VB.NET (2010 - .NET Framework 4.0),
Dim i As Integer
For i = 0 To (DataGridView.Rows.Count - 1)
DataGridView.Rows(i).Cells(0).Value = True
Next
Sometimes when one of the row is selected (not checked but just the entire row is selected) while this code is running, it checks all other check-boxes in the column except the selected one. What is wrong here?
I also try adding this code,
DataGridView.SelectedCells(0).Cells(0).Value = True
Did not work.
Like I said in the comments, the issue is that the box is checked in the datasource, but the GUI hasn't refreshed that cell to reflect it. If you click off of it, the cell should change/refresh to your new value. After you have made your data changes, at the very end you should deselect cells/rows. You can do it by:
Datagridview.ClearSelection()
or
Datagridview.CurrentCell = Nothing 'deselects
You get the idea. This is what has worked for me in the past.

How to set default values for data bound controls for addition in VB.NET

I have a vb.net 2010 form with 22 data bound controls from two tables held in a dataset which is navigated by a bindingnavigator. This successfully adds deletes and updates. However what I need is when adding a new record I need some of the fields to be pre filled out. More specifically I have points balance fields etc which could be any value but will normally be 0 on a new customer so I want to initialise them to 0 when adding new records.
I located an AddingNew event on my datasource but this is called before the new item is added and thus all my initialisation is lost.
any help on this would be appreciated.
kind regards
Feldoh
Since you are using DataTables, you can manually set the DefaultValue property of the DataColumn:
Dim dt As New DataTable
Dim dc As New DataColumn("test", GetType(String))
dc.DefaultValue = "hello"
dt.Columns.Add(dc)
dt.Rows.Add()
Debug.WriteLine(dt.Rows(0)("test").ToString)
Result: hello
Answer including multiple linked tables solution:
Like LarsTech said using
dataset.table.Columns.Item("someField").DefaultValue = someValue
works nicely if you have a standard default but it also works for generated defaults, if you reset this default on saving to the next value you want if a new addition is made. However if you are using a dataset as your data source where a child table record cannot feasibly be generated (like in my case where an account id will be generated only on saving using a database trigger to link all different kinds of accounts) you can still set defaults for the other fields, the ones from the sibling (or non-existent parent) table but
dataset.siblingTable.Columns.Item("someField").DefaultValue = someValue
WILL NOT WORK as the binding navigator only generates the first sibling and the parent is not generated until the trigger. However on the BindingSource.PositionChanged event of the binding source that IS generated by clicking add you can freely put defaults into the actual controls on the screen and they will not be overwritten by nulls, clearly you need to restrict this so it only happens if the user pressed add by adding a variable to the Clicked event of the add button to tell you when add has been clicked and resetting this variable on save or roll-back. It is also possible to modify the bindings themselves which have an if null or empty default value.
Hope this helps someone else :)
Use the MouseUp event on the 'Add Row' button in the BindingNavigator instead of the "Click" event. This is becouse the new ROW does not exist in the datagridview until AFTER the click event is completed.
Private Sub BindingNavigatorAddNewItem_MouseUp(sender As Object, e As MouseEventArgs) Handles BindingNavigatorAddNewItem.MouseUp
'works with add row from the binding navigator
myDataGridView.Item(3, myDataGridView.CurrentRow.Index).Value = txtDefaultDescrip.Text.Trim
End Sub
to do this same thing when adding data directly in you DataGridView do this.
Private Sub myDataGridView_DefaultValuesNeeded(sender As Object, e As DataGridViewRowEventArgs) Handles myDataGridView.DefaultValuesNeeded
'works with add row inside a datagridview
e.Row.Cells(3).Value = txtDefaultDescrip.Text.Trim
End Sub
In this example I have a editable default value shown on my from. If you have a non-changing default value (e.g., is always 1 for column 3) then you should do that when setting up your datasource.

Updating a control on another form with the results of a dialog box

I made a windows form which contains a listbox (named VenueList). The listbox is pulling it's values from a list provided by a binding source VenuesBindingSource which is pulling in a ID for the value and a name for the text.
There is a button which is causing a DialogBox to appear which is asking for values to store in the database for a NEW venue. Once the values are filled and insert the database, what's supposed to happen is that the dialog box closes and goes back to the original form which invoked it.
However, instead of updating the list. The list stays the same. But if you close the form and reopen it, you see that a new value was added.
TournamentSettings.VenuesTableAdapter.InsertVenueQuery(Trim(VenueNameTxt.Text), Trim(VenueAddress1Txt.Text), Trim(VenueAddress2Txt.Text), Trim(VenueCityTxt.Text), Trim(VenueProvinceTxt.Text), Trim(VenueZipTxt.Text), Trim(CountryBox.SelectedValue), Trim(VenuePhoneNo.Text), VenueType.SelectedText, VenueWebAddress)
TournamentSettings.VenuesTableAdapter.Fill(TournamentSettings.VenueNameList.Venues)
In the above code, InsertVenueQuery is the name of a query from the designer which is invoked to add the values onto the tableadapter VenuesTableAdapter which is used to fill the combo box on load. I also sent the Fill command to refill the table with the new value.
So the question is, should I go about doing this another way, rather than feeding the Table adapter and sending a fill command on to the datatable? Or is there something that I'm not doing here which I should to force that value into the list. I'm tempted to redo everything outside of the designer but that's a lot of code since I have to essentially run two commands (one to insert the data, and another to get the ##IDENTITY value since this is run on an access database.)
Okay. This one I had to think about for a moment.
Instead of me creating a block of done on the load event, I instead created a sub function called "FillVenueList".
I used the following block of code:
Public Sub FillVenueList()
' Adding values from database to a datatable.
' From there will add to the list box.
Dim VenueConnection As New OleDb.OleDbConnection(DBconnection)
VenueConnection.Open()
Dim VenueConnectionQuery As New OleDb.OleDbCommand("SELECT VenueID, VenueName FROM Venues", VenueConnection)
Dim VenueDataAdapter As New OleDb.OleDbDataAdapter(VenueConnectionQuery)
Dim VenueDataSet As New DataSet
VenueDataAdapter.Fill(VenueDataSet, "Venues")
TrnVenueLst.DataSource = VenueDataSet.Tables("Venues")
TrnVenueLst.DisplayMember = "VenueName"
TrnVenueLst.ValueMember = "VenueID"
VenueConnection.Close()
End Sub
From there I called THIS sub on both the form AND the Add Venue window and I can safely see that this works. SO THAT is how you get a new value onto the form, don't use it as a part of the Load Event but rather call it from the load event block, then call it when you want to add to the list.

Problem with datagridview and AddNew

I've created a VB 2008 program to track work requests. It all works perfectly on a VISTA box, but I am having an issue with the program on an XP environment with adding new records.
Basically I've got 2 tabs: TAB 1 holds a datagridview with limited info and a calendar. Selecting dates on the calendar change the info in the datagridview. TAB 2 holds all the available info for that record in text/combo boxes. Both the datagridview and text boxes use the same Binding Source, so they are always in sync whenever the user selects a row from the datagridview. When you select the NEW button, TAB 2 appears with all the text boxes empty so the user can add data. If you look back on TAB 1, you see an empty, new row added to the datagridview (user can not directly add a row in the datagridview as AllowUserToAdd is set to false). If you let the app stay in the AddNew record state on VISTA, you remain on that new record until you select SAVE or CANCEL. On XP, however, after 1 minute time lapse, all the empty fields will eventually fill in with an existing record for that particular calendar day. When you look back on TAB 1, you no longer see the new empty row, you only see existing records previously saved.
Any ideas on how to resolve?? Thanks for any assistance.
Here is the code for adding new records:
Private Sub cmdNew_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdNew.Click
'Focus on Work tab
TabControl1.SelectedTab = tabWork
'Change the files from read-only
bEditMode = True
ChangeEditMode()
'Clear the current information stored in the fields
Try
Me.BindingContext(WorkRequestBindingSource).AddNew()
Catch ex As Exception
System.Windows.Forms.MessageBox.Show(ex.Message)
End Try
'Hidden text boxes populate with current selected calendar
'Used to populate TimeIn and DateNeed because if never clicked on, will populate as NULL on save
dtpDateNeed.Text = txtDate.Text
dtpTimeIn.Text = txtTime.Text
End Sub
This is definitely an environmental issue. To solve the problem I would need to know which browsers you are using on each machine and some of the settings on each.
It sounds like the XP machine is refreshing the page after a timeout period and therefore munging the new record. I have seen that happen before and it stinks.
You might need to consider saving some more state information in the viewstate to catch that kind of thing.
If the code is exactly the same I wonder if it is an environment issue e.g. something like different international options or version of framework?