Behind-the-scenes VBA activity interfering with listboxes - vba

Background:
I have a VBA project in an Excel spreadsheet. In it, I have a form that contains a frame that contains multiple labels/listboxes.
When the application is loaded, these listboxes are populated with data. (Nearly) everything works as expected, and this is what the form looks like:
My Problem:
My problem is that when a user clicks on an item in a listbox, that listbox is automatically "jumped into view" or something. For example; if I click on list item 5 like so, the frame scrolls down so that the listbox is at the top:
Before Click:
After Click:
You can see that the list item under my click has been selected, but the list was scrolled up beforehand so it's not actually the intended selection. This is not being done by any of my code.
If I put a "Stop" as the "Enter" and "Click" event handlers, the correct list item is selected and the list has not jumped at "Enter", but has by "Click".
More information:
If I continue to click on other elements from the same listbox, the listbox does not "jump". I can scroll up and down, and the listbox will not jump. As soon as I click in a DIFFERENT listbox (so "listbox1_Exit" and "listbox2_Enter" have been raised), the new listbox is "jumped" into view.
My Question:
My Question is: what "behind-the-scenes" code is VBA executing between these two events, and how can I prevent this from occuring? Or is there a way to "monitor" events?
(VBA appears to just resume execution if you "step" past the end of an event handler, rather than move to the next executing line of code - which would REALLY be helpful here...)
Edit:
Minimal example - plonk a frame on an empty form, add 2 listboxes to the frame and execute this code in the form:
Private Sub UserForm_Initialize()
Dim lb1 As Control
Dim lb2 As Control
Dim Frame1 As Control
Set Frame1 = Me.Controls("Frame1")
Set lb1 = Me.Controls("ListBox1")
Set lb2 = Me.Controls("ListBox2")
lb1.Height = 500
lb2.Height = 500
For i = 1 To 25
lb1.AddItem ("Potato " & i)
lb2.AddItem ("Frog " & i)
Next i
lb1.Top = 0
lb2.Top = lb1.Height + 10
Frame1.ScrollHeight = lb1.Height + lb2.Height + 10
End Sub
then scroll to the point where you can see the end of Listbox1 and the start of Listbox2, and try clicking elements in Listbox2.

Related

Giving buttons different names through code and referring to them later on

My code creates a 5x5 grid of buttons. I am wanting to give each of these buttons different names "BtnColour1", "BtnColour2", etc. How do I give them all different names and how do I refer to each button later in the program?
Dim bytCounter As Byte
For bytCounter = 1 To 25
Dim btnColour As New Button
Me.Controls.Add(btnColour)
btnColour.Height = 50
btnColour.Width = 50
btnColour.Name = "btnColour" & bytCounter
btnColour.Enabled = False
btnColour.Left = ((bytCounter - 1) Mod 5) * 51
btnColour.Top = ((bytCounter - 1) \ 5) * 51
AddHandler btnColour.Click, AddressOf BtnClick
Your code (I guess you forgot the ending Next) does create 25 Buttons, with names btnColour1... btnColour25.
In the BtnClick event, to get the name of the clicked button, you should write something like:
Private Sub BtnClick(sender As Object, e As EventArgs)
Dim buttonName as string=CType(sender, Button).Name
'buttonName now has the clicked button name
End Sub
Of course, since you set the enabled property to False, your button click event will not fire.
In a general sense (and in addition to Spyros' answer, which is a good way to do it in an event handler - the sender is always the thing that raised the event), when you give a control a name and add it to a control's Controls collection, you can then retrieve it by that name later:
'Here you added the button to the form controls:
Me.Controls.Add(btnColour)
'later in the code you can ask for it back by name, for example:
Dim controls = Me.Controls.Find("btnColour1")
What you get back is an array of Controls. You get an array because Find can search all children (panels inside panels inside groupboxes inside forms etc) and it is thus conceivable that multiple controls in different panels will both have the same name. In your case if you know you only have one control called "btnColour1" it's safe to get it by array index:
Dim control = controls(0) 'controls variable is from the above Find
Lastly, remember that it comes back as a Control, the parent class for all controls. Because you know it's a button, it's safe to cast without check:
Dim button = DirectCast(control, Button)
Remember that if your property is available on the base Control class you don't even need a cast:
'here's a 1 line way to get the text of the button named btnColour1
'Find all controls named btnColour1, take the first, get the text
Dim t = Me.Controls.Find("btnColour1")(0).Text
If you want to refer to the buttons later in the program to change a setting without clicking the button, you can add each button to an array and call each of them with an index number:
Dim buttons(24) As Button
Then as the buttons are created, you can add each button to the array:
Dim bytCounter As Byte
For bytCounter = 1 To 25
Dim btnColour As New Button
Me.Controls.Add(btnColour)
buttons(bytCounter) = btnColour
you can then reference each button and their properties using the index number of the button in the array. You may also want to add a specific tag to each button to make each button more unique using:
btnColour.Tag = bytCounter

Sub to add controls works when called from one sub but the other

I am getting an error that I just can't figure out. I wrote a sub that opens a form in Design view, deletes all of the dynamic controls, then adds the requested number.
The sub gets called in two different ways. The user opening the form (via a Main Menu form) to fill out a new form (so the dynamic controls are deleted then recreated) OR after the form is created, the user can click a button on the form to add more rows of controls. Both the Main Menu and button form call the same sub, BUT when the button is clicked the code gets stuck and error 29054 'Microsoft Access can't add, rename, or delete the control(s) you requested.' is thrown. The button to debug is deactivated so I can't see what line is actually getting stuck, but when I step through the code this is the last line before the error pops up (the last indent block in the context below):
With Application.CreateControl("BOM5", acComboBox, acDetail, frm.Controls("Tabs").Pages(PageNum).Name, , ChangeTypeLeft, LastControlTop, ChangeTypeWidth, ControlHeight)
The rest of the code is as follows.
DoCmd.OpenForm "BOM5", acDesign
Set frm = Application.Forms("BOM5")
' Cycle through controls and set LastControlTop based on the last dynamic control, if any
For i = 0 To frm.Controls.Count - 1
Set ctl = frm.Controls(i)
Debug.Print ctl.Name
If ctl.Tag Like DYNAMIC_TAG & "*" Then
If ctl.ControlType = acComboBox Or ctl.ControlType = acTextBox Then
LastControlTop = ctl.Top + ControlHeight + ControlPadding
FormRowCount = FormRowCount + 1
End If
Else
FormRowCount = (FormRowCount / 6) + 1 ' Convert number of fields to number of rows then add one to start new batch of controls
Exit For
End If
Next
PageNum = frm.Controls("Tabs").Pages.Count - 1 ' .Pages has an index of 0. Getting PageNum to follow suit with the -1
' Add controls for inputting parts
For FormRowCount = FormRowCount To NewControlCount
With Application.CreateControl("BOM5", acComboBox, acDetail, frm.Controls("Tabs").Pages(PageNum).Name, , ChangeTypeLeft, LastControlTop, ChangeTypeWidth, ControlHeight)
.Name = "ChangeType" & FormRowCount
.Tag = DYNAMIC_TAG
.RowSourceType = "Table/Query"
.RowSource = "ChangeType"
End With
The last for loop has 5 other With Application.CreatControl statements, but I just showed the first one. The other 5 are similar except text boxes instead of combo.
I've had this error before, but I think I resolved it by moving the DoCmd.OpenForm statement to a different part of the code (like out of an if statement or for loop or somewhere that wasn't letting it get called) but I don't think that will resolve it. Besides, the first for loop iterates correctly since I see it grabbing the control height of the last dynamic control.
You can't do this. A form or report can only hold a certain amount of controls, deleted or not, so - eventually - it will not accept more controls. At that point, you must recreate the form from scratch.
So, don't delete the controls. Create those you may need and, at any time, hide those not needed and rename the rest if necessary.

Userform MultiPage Navigation

I have a Userform I built in Excel that has roughly ten tabs utilizing the MultiPage setup. Problem is I am running out of space for additional tabs and would really like to replace the tabs with a friendlier Navigation Menu or Toolbar. I've looked into the Microsoft ToolBar Control, which is close to what I'm looking for but I can't change the height and the dropdown has to have the arrow clicked and nto the text, which is not that intuitive in my opinion.
So my question is, are any of you familiar with a better multipage navigation setup, perhaps using a form of ActiveX?
I would reccommend seperating your User Form into seperate forms or Child forms based on category.
You can have one form launch another one if they are both Modeless, and both can be on screen at the same time. This way, you can work with both consecutively.
To launch a form as modeless, when you do Form1.Show change it to Form1.Show vbModeless
What I ended up doing was making a Listbox with all my different pages that is apart of my Multipage then placed this listbox on the left hand side of my Userform then used this code
Populate the listbox
i = 0
With Main_Window.form_navigation_list
.AddItem "Domestic"
.List(i, 0) = "Page 1"
.List(i, 1) = 0
i = i + 1
.AddItem
.List(i, 0) = "Page 2"
.List(i, 1) = 1
i = i + 1
End With
Then I have this for the on click
Private Sub form_navigation_list_Click()
Dim i As Integer
Dim SelectedRow As Integer
For SelectedRow = 0 To Main_Window.form_navigation_list.ListCount - 1
If Main_Window.form_navigation_list.Selected(SelectedRow) Then
nav_page = Main_Window.form_navigation_list.List(SelectedRow, 1)
With Main_Window
.MultiPage1.Value = nav_page
.Show
End With
End If
Next
End Sub
I started off using a flexgrid but kept getting a memory leak issue and program would crash, so I swapped to the listbox and it's working fine now. Now I can customize this listbox to look a bit more like the navigation I want.

Reversi VB.net logic behind it

I am trying to make the reversi game in VB.Net. I have some difficulties translating the game`s logic into vb.net
If a button is black and the button next to it is white,than the button next to the white one will be black wen pressed.
newButton.tag = colum of button + (row of button * amount of columns)
-> I made 64 buttons via a function loop and added a tag
Dim knop As Button = sender
Dim value As String = knop.Tag
If value = "...(?)" Then
knop.BackColor = Color.Black
If ....(?)
End If
End If
I already made a scheme with the label of the buttons, but I find it hard to implement the logic. Can someone help me out with thid one?
EDIT: http://i.stack.imgur.com/3gdrJ.png
If you use Dim ButtonList As List(Of List(Of Button)) and add the buttons to the form in runtime you can add each the button for each row to a list then add that list to ButtonList. Now you can access each button by the indexes in the 2 dimensional list.
Since you're changing the backcolor just use that instead of using the tag.

Opening new form after right click on selected record in continuous form

In my access database before I was using a list box in the form for having list of items and I had below code for opening new form after right click on each selected item in list box.
Private Sub ItemList_MouseUp(Button As Integer, Shift As Integer, X As Single, Y As Single)
Const RIGHTBUTTON = 2
If Button = RIGHTBUTTON Then
DoCmd.OpenForm "frmShortcut_GenerateTask"
DoCmd.MoveSize udtPos.X * mp.TwipsPerPixelX, udtPos.Y * mp.TwipsPerPixelY
End If
End Sub
Now I am using a continuous form instead of list box and I have defined a [isselected) field for selecting each record in continuous form after clicking on that. Now my problem is how I have to write code for right clicking and opening new form.
I used the same code I had used for list box, but it does not work and nothing happened.
Private Sub Form_MouseUp(Button As Integer, Shift As Integer, X As Single, Y As Single)
Const RIGHTBUTTON = 2
If Button = RIGHTBUTTON Then
DoCmd.OpenForm "frmShortcut_GenerateTask"
DoCmd.MoveSize udtPos.X * mp.TwipsPerPixelX, udtPos.Y * mp.TwipsPerPixelY
End If
End Sub
Private Sub P_Click()
On Error Resume Next
Me.IsSelected = Not Me.IsSelected
' Save the status
Me.Dirty = False
' Force conditional highlighting
P_ForceHighLight
' Update display in SF_Selected
Me.Parent("SF_Selected").Requery
ActiveControl.SelLength = 0
On Error GoTo 0
End Sub
I recommend either using a DoubleClick event in all of your textboxes and combos, or else putting a small button at one edge of your continuous form that allows the user to open records. Right-click is going to give you problems (so will labels and image controls) because this event doesn't cause (or ensure) that the Form's internal Recordset Bookmark property is actually moved to the record they right-clicked on. In my experience, only buttons, textboxes, and comboboxes will move the Bookmark to the record the user is trying to select.
To Test this, try putting code at the top of your different routines that shows you what record is selected:
MsgBox "RecordID = " & Me!RecordIDField
If you are consistently getting the correct ID, then your problem has to do with how your opening your new form, passing parameters or arguments, etc.