WinForms context menu really slow adding 150 Toolstrip Menu Items - vb.net

I have this code that add items to a context menu's sub-menu:
CTX_VALUE.Enabled = True
CTX_VALUE.Visible = True
CTX_VALUE.Text = "List Values"
For k As Integer = 0 To CELL.VALUE_LIST.Count - 1
CTX_VALUE.DropDownItems.Add(CELL.VALUE_LIST(k))
Next k
Where CTX_VALUE is a ToolStripMenuItem
and CELL.VALUE_LIST is an ArrayList (yeah, old code!) of ToolStripMenuItems
When it comes to add about 150 items, it becomes really slow, about 2.5 seconds.
Visibility before adding doesn't matter, i tried moving it after.
BTW, note that the context menu is not on screen when adding items!
I also tried suspending layout of CTX_VALUE before adding. No luck.

you should add these using CTX_VALUE.DropDownItems.AddRange() method

Before the add items loop I used both
ts_filter.DropDown.SuspendDrawing (see addendum note below)
and
ts_filter.DropDown.SuspendLayout
After the loop I used the corresponding resume methods.
This made a huge difference to my program moving it from unworkable to instant.
Addendum:-
The resumedrawing (alone) was preventing my custom textbox (inherited from toolstriptextboxfrom) from showing. I found the suspendlayout and resumelayout to be ok alone though and it kept the speed up.

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.

Loop through items in "Bar styles", reading items

I'm basically interested in checking if the user is showing any baselines in his active view, and if yes, which one.
If for instance "Baseline1 Start" is used in Bar Styles for the active view, I'd find the matching item number.
If I were able to loop through each bar style item, such as the built-in list MS Project shows lists under "Bar styles", that would solve my problem.
I've tried using the GanttBarEditEx method, but as the documentation explains, it only returns boolean.
If I for instance want to delete an item, this code will do:
GanttBarStyleDelete Item:="41"
My problem is that I don't know what Item I'm looking for. I don't know how to read data about Item:= "41"
This code is not working, but illustrates what I want:
For i = 1 to 200
if GanttBarEditEx(item:=i, from:"Baseline1 start") = True Then MsgBox i
Next
I'm able to figure out the item count using the same logic, when item = i returns an error, that means there's no such item and I have found max. That's how far I've gotten.
There's also a code for GanttBarStyleBaseline which could be of help!
If it's impossible to list the items, I might be able to write a code that's sets each baseline to false, then check if a change was made to the active view, and hence find the active shown baseline...Help?
Update: I noticed that BarBoxStyles opens the "Bar styles" window. But I'm guessing that can't be turned into a collection for looping.

Wait for 1 second before starting code again - VB.NET

I desperately need help with a game I am making. For a bit of context, i am making a memory game and i have the following piece of code that is being troublesome. I have a bunch of labels on the form, 16 to be exact, with 1 randomly generated symbol placed in each. Each symbol appears in the labels twice.
------------------------------Continued----------------------------------------
'MsgBox("hello") 'used to check if the second inccorect press shows up - it does show but instantly changes colour
'''''''''''''''''NEED SOME CODE THAT PAUSES IT HERE'''''''''''''''
labels(0).ForeColor = Color.DarkRed
sender.ForeColor = Color.DarkRed
End If
flips = 1
End If
End If
tmrmemory.Enabled = True ' starts the timer after the user clicks the first label
End Sub
What's supposed to happen is that when the labels clicked don't match, it should show both the clicked labels for a short period before changing them both back to "DarkRed" which is the colour of the form's background.
I have tried using a timer but then i can't use sender.forecolor=color.darkred because it is not declared globally.
I have also tried using the command Threading.Thread.Sleep(500) but it still doesn't show the second incorrect click. I know that the code i have used works because when i use the message box, i can see both symbols and when the two clicks are correct, it stays.
Threading.Thread.Sleep(500) will actually pause your code for half a second. However during this time it won't do anything, not even refresh your controls. To get the effect you want, you need to call the YourControl.Refresh method before calling Threading.Thread.Sleep to force the control to redraw immediately.
On a side note, I would advise you not to call Threading.Thread.Sleep on UI thread. It will give a feeling of program hang. Instead do your work on a separate thread. You can either do all the work yourself right from creating a separate thread to destroying it, or use the BackgroundWorker control which has all the functionality built in.
Here is the link to an article I wrote a long time ago regarding BackgroundWorker that might be useful for you:
http://www.vbforums.com/showthread.php?680130-Correct-way-to-use-the-BackgroundWorker
Declare a variable outside the sub that stores what label should be flipped when the timer ends.
Label click sets
storedLabel = sender
Timer tick sets storedLabel.ForeColor = Color.DarkRed

Removing items from listbox using backgroundworker

I want to delete all the items of a listbox that does NOT contain "mysite" and here's my code that works fine without backgroundworker.
Do Work Event:
Dim relevantSite As Integer = 0
Do Until relevantSite = lstLinks.Items.Count
If lstLinks.Items.Item(relevantSite).ToString.Contains("mysite") Then
relevantSite += 1
Else
bgWorker.ReportProgress(relevantSite)
End If
Loop
ProgressChanged Event:
lstLinks.Items.RemoveAt(CInt(e.ProgressPercentage))
What it does is, it removes alot of items, sometimes all items. I know I'm making some terrible mistake with e and reportProgress thing.
Please explain them to me, I searched various sites but could not understand it...
Rather than directly changing the items in the list you should create a new list in your background worker. This way you can add remove items from the list and return it to the UI once all the processing is completed and rebind the drop down.
I want to delete all the items of a listbox that does NOT contain
"mysite"
Walk the ListBox backwards and delete the offending items as you go. Wrap the process in BeginUpdate() and EndUpdate() so the ListBox only refreshes once when you're all done:
lstLinks.BeginUpdate()
Dim NumItems As Integer = lstLinks.Items.Count - 1
For i As Integer = NumItems To 0 Step -1
If Not lstLinks.Items(i).ToString.Contains("mysite") Then
lstLinks.Items.RemoveAt(i)
End If
Next
lstLinks.EndUpdate()
lstLinks.Refresh()
You are expecting the code to act as if it is synchronized. But multi-threading does not work that way.
Your code in do work will be processing the next record before report progress has completed. In other words the loop will not pause and wait for report progress to complete. This is a problem because when you call out to remove a item from the list, you reuse the index assuming the item is gone. After a few removals, the index passed in will not indicate the correct item. If you were to use an identifier rather than an index it would work. But the whole thing seems wrong to me since you are not doing any heavy lifting in the do work method.

Re-Creating Dynamic Controls

I have a VB.Net WinForm Program.
I dynamically create panels with controls.
Each panel has:
2 Labels
1 DataGridView
1 Button
Everything works fine the first time I create the panels.
Everything gets created, and everything is functional.
If I have to re-create the form, I get rid of the existing panels (and their controls) with this code:
For P = 0 To Panels.Count - 1
For Each PControl In Panels(P).controls
Panels(P).controls.remove(PControl)
Next
Me.Controls.Remove(Panels(P))
Next
Panels.Clear()
DataGrids.Clear()
lblCounts.Clear()
Where:
Panels, DataGrids, & lblCounts are ArrayLists holding controls
When I re-create the panels, I get the panels and all of their controls except Buttons
When I step through the debugger, I see the buttons being removed, and I see them being created, but they don't appear in the panel
Any ideas?
Your question is regarding a button not appearing when you are adding the controls, but you are only showing the removal process, which is flawed.
Make a UserControl that holds your Labels, Grid and Button. Add that to your form. That's what UserControls are for.
Also, when you are done using it, just call:
MyControl.Dispose()
Otherwise, I suspect you are leaking memory. Remove does not destroy the object.
For Each PControl In Panels(P).controls
Panels(P).controls.remove(PControl)
Next
This part may kick you out of your code. The 'For Each' does not like it when its items change during execution. check it with Breakpoints. if is is really a problem , you could do..
lazy method, by just adding .ToList
For Each PControl In Panels(P).controls.ToList
Panels(P).controls.remove(PControl)
Next
similar to:
Dim AllControls as New List(Of control)
AllControls.AddRange(Panels(P).controls)
For Each PControl in AllControls
Panels(P).controls.remove(PControl)
Next
or:
For i as integer = Panels(P).controls.count -1 to 0 step -1
Dim PControl as control = Panels(P).controls(i)
PControl.parent.remove(PControl)
Next
Try this
WHILE Panels(P).controls.count > 0
Panels(P).controls.removeAt(1)