Two lists combined into 1 dropdown box - sql

Dear all and thank you in advance.
Problem:
Instead of having two dropdown boxes for list A and list B. I would like to combine the two lists (they do relate) and display them to the user as 1 list. Then split it back into two so that I can store the relevant info.
List 1
Machines
1. Machine x
2. Machine y
List 2 Test Types
1. Test Type ab
2. Test Type ac
3. Text Type ad.
so Machine 1 can do test type ab, and ac. Machine 2 can do test type ac and ad.
It will be stored under 3 different tables (not really but just think that it will). 2 tables will contain the list and the third will contain the relationship between the two lists. I.e. which items from list 1 pair up with which items from list 2 etc.
To the user it would be displayed as follows
Machine X - ab
Machine x - ac
Machine y - ac
Machine y - ad
The user would then select 1 from the list and I would then decode the two items selected.
My thought so far is to use bits (and/or) as required.
There will be three functions
public function joinAB(a as long, b as long) as long
end function
Public function getA(ab as long) as long
end function
public function getB(ab as long)as long
end function
So just to clarify this is not to join text together, but to join/split ID's of the individual items in these two lists.
Anyone else have any other ideas. This will be done in a legacy system (VB6) which I have inherited. My VB6 coding skills are above average.
Thank you for any help/code snippets provided or general advice.
If you need more information please let me know.

Assuming a and b are numeric variables as your 3 functions suggest I would use the .ItemData() property of the items in the combined list just like Mark and use division and remained to obtain the separate parts:
Public Function joinAB(a As Long, b As Long) As Long
joinAB = a * 100 + b
End Function
Public Function getA(ab As Long) As Long
getA = ab \ 100
End Function
Public Function getB(ab As Long) As Long
getB = ab Mod 100
End Function
this assumes that b will never be higher than 100, and that neither a or b will be negative
If a and b are string variables then i would show the joined strings as the text int he combobox and split the selected text to get the seprate parts
Public Function joinAB(a As String, b As String) As String
joinAB = a & " - " & b
End Function
Public Function getA(ab As String) As String
getA = Left$(ab, InStr(ab, " - ") - 1)
End Function
Public Function getB(ab As String) As String
getB = Mid$(ab, InStr(ab, " - ") + 3)
End Function

make use of the 3rd table in which the relation is mapped , if the relation have a unique id then you can make use of that to join/split the list....
or provide us with the table structure and data...

There are probably a few solutions to this.
The simplest is that you create a unique 32-bit integer field on your join table. This can then be embedded in the ItemData property of the VB6 ListBox.
A couple of helper functions for this are:
Private Sub AddListBoxItem(ByRef lst as ListBox, ByVal in_nKey As Long, ByRef in_sDisplay As String)
With lst
.AddItem in_sDisplay
.ItemData(.NewIndex) = in_nKey
End With
End Sub
Private Function GetSelectedListBoxKey(ByRef in_lst As ListBox) As Long
With in_lst
GetSelectedListBoxKey = .ItemData(.ListIndex)
End With
End Function
As for implementing your functions, I would simply use two collection.
m_col_A_B_to_AB would be keyed by A & "_" & B to return AB. m_col_AB_to_A_B would be keyed to AB to return A and B.
Helper functions would be:
Private Sub AddRow(ByVal in_nA As Long, ByVal in_nB As Long, ByVal in_nAB As Long)
Dim an(0 To 1) As Long
an(0) = in_nA
an(1) = in_nB
m_col_A_B_to_AB.Add an(), CStr(in_nAB)
m_col_AB_to_A_B.Add in_nAB, CStr(in_nA) & "_" & CStr(in_nB)
End Sub
Private Sub Get_A_B(ByVal in_nAB As Long, ByRef out_nA As Long, ByRef out_nB As Long)
Dim vTmp As Variant
vTmp = m_col_A_B_to_AB.Item(CStr(in_nAB))
out_nA = vTmp(0)
out_nB = vTmp(1)
End Sub
Private Function GetA(ByVal in_nAB As Long) As Long
Get_A_B in_nAB, GetA, 0&
End Function
Private Function GetB(ByVal in_nAB As Long) As Long
Get_A_B in_nAB, 0&, GetB
End Function
Private Function JoinAB(ByVal in_nA As Long, ByVal in_nB As Long) As Long
JoinAB = m_col_AB_to_A_B.Item(CStr(in_nA) & "_" & CStr(in_nB))
End Function

Related

I need to pass a List using byval, however i know i cant do this as the list will still be changed but creating a "new" list slows down my code

this is my code for a recursive search for a chess program project. my problem is that when I recall the function "recursive search", the list is changed due to new chess moves being calculated. i previously used "list = New list" to create a new list however this makes the search take longer after hundreds of searches. so what I want to do is pass the list, and use the same list in the next search and when it finishes the sub and returns to the call point, I want the list to return to what it was before entering the sub. (eg, as if I passed a normal data structure byval) so that the "For each" can continue as if the data has not changed.
First post so hopefully this all makes sense, I know my code may not be the best but for now its working, I just need to stop making New lists as it slows down the program too much.
(Any other workarounds would also be appreciated)
Sub recurvive_search(ByVal the_board(,) As theboardclass, ByVal depth As Integer, ByRef depth_count() As Integer, ByVal whosgo__ As Integer, ByVal all_moves_list As List(Of A_Move))
If depth = 3 Then
depth_count(4) += 1
Else
all_moves_list = calculate_all_moves(the_board, whosgo__, all_moves_list)
For Each M In all_moves_list
If IsNothing(M.sym_of_moving_piece) = False Then
depth_count(depth) += 1
the_board = Me.change_board(the_board, the_board(M.From_x, M.From_Y).getsym, the_board(M.From_x, M.From_Y).getteam, M.New_x, M.New_Y, M.From_x, M.From_Y)
whosgo__ = switchgoes(whosgo__)
recurvive_search(the_board, depth + 1, depth_count, whosgo__, all_moves_list)
whosgo__ = switchgoes(whosgo__)
the_board = Me.undo_move(the_board, M)
End If
Next
End If
End Sub
I'm using Visual basic 2010 for School
Try this:
Sub recurvive_search(ByVal the_board(,) As theboardclass, ByVal depth As Integer, ByRef depth_count() As Integer, ByVal whosgo__ As Integer, ByRef all_moves_list As List(Of A_Move))
If depth = 3 Then
depth_count(4) += 1
Else
all_moves_list = calculate_all_moves(the_board, whosgo__, all_moves_list)
For Each M In all_moves_list
If IsNothing(M.sym_of_moving_piece) = False Then
depth_count(depth) += 1
the_board = Me.change_board(the_board, the_board(M.From_x, M.From_Y).getsym, the_board(M.From_x, M.From_Y).getteam, M.New_x, M.New_Y, M.From_x, M.From_Y)
whosgo__ = switchgoes(whosgo__)
recurvive_search(the_board, depth + 1, depth_count, whosgo__, all_moves_list)
whosgo__ = switchgoes(whosgo__)
the_board = Me.undo_move(the_board, M)
End If
Next
End If
End Sub
There are two easy ways to make a new list which has the same contents as an existing list.
One is to use the constructor which takes an IEnumerable(Of T) to populate the constructed list, e.g.
Dim newList = New List(Of A_Move)(oldList)
The other is to use the extension method IEnumerable(Of T).ToList() to create a new List(Of T) from an existing sequence:
Dim newList = oldList.ToList()

Is there a VBA equivalent (or way to replicate) passing parameters as 'Out' like C#?

I generally use VBA but have been reading up on programming techniques in The C# Programming Yellow Book which, obviously, is more specific to C#. Anyway, it mentions a technique of passing parameters using the Out keyword.
I already know that VBA supports byVal and byRef and am fairly certain there is no direct equivalent for Out. Passing parameters using Out is subtly different to passing parameters by Ref.
This Answer https://stackoverflow.com/a/388781/3451115 seems to give a good explanation of the difference between Out & Ref.
The Ref modifier means that:
The value is already set and
The method can read and modify it.
The Out modifier means that:
The Value isn't set and can't be read by the method until it is set.
The method must set it before returning.
In the code base that I've inherited there are several places where values are assigned to variables using methods that accept parameters byRef. It seems to me that while passing byRef does the job, passing by Out would be safer... So (and here is the question) is there a way of safely / reliably replicating Out in VBA?
In my first iteration (original question) I imagined that the code would have a pattern like:
Sub byOutExample(byRef foo As String)
' Check before running code:
' foo must = vbNullString
If foo <> vbNullString then Err.Raise(someError)
' Do Something to assign foo
foo = someString
' Check before exiting:
' foo must <> vbNullString
If foo = vbNullString then Err.Raise(someError)
End Sub
Other considerations: is it worth doing, is there a better way, what could go wrong?
Edit: I noticed in the comments for the above definition of Ref vs Out that the passed parameter need not be null, nothing, empty etc. it can be preassigned - the main criteria seems that it is re-assigned.
In light of #ThunderFrame's answer below and the comment that a parameter passed by Out can be pre-assigned (and used), perhaps the following is a better approach:
Sub byOutExample(ByRef foo As String)
Dim barTemp As String
barTemp = foo
' Do Something to assign a new value to foo (via barTemp)
barTemp = someString
' Must assign new variable
foo = barTemp
End Sub
In which case would it be true to say that, as long as foo only appears in the 2 locations shown above, the above code is an accurate way to replicate passing a parameter by Out in VBA?
The answer is unequivocally 'no' you cannot replicate the C# out parameter modifier in VBA. From https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/out-parameter-modifier:
Variables passed as out arguments do not have to be initialized before
being passed in a method call. However, the called method is required
to assign a value before the method returns.
These aspects simply don't exist in VBA. All variables in VBA are initialised with default values, ie the concept of an unitialised variable does not exist in VBA, so point 1 isn't possible; and the compiler cannot object if a specified parameter has not had a value assigned within the procedure, so point 2 isn't possible either.
Even the coding patterns in your example would rely on the Do Something to assign foo not to resolve to the relevant data type's default value (which is obviously not the same as being unitialised). The following, for example, would wrongly throw an error:
Public Sub Main()
Dim income As Long, costs As Long
Dim result As Long
income = 1000
costs = 500
ProcessSpend income, costs, result
End Sub
Private Sub ProcessSpend(income As Long, costs As Long, ByRef outValue As Long)
Const TAX_RATE As Long = 2
Dim netCosts As Long
Dim vbDefaultValue As Long
netCosts = costs * TAX_RATE
outValue = income - netCosts
If outValue = vbDefaultValue Then Err.Raise 5, , "Unassigned value"
End Sub
So we're really left with the question of is there a way of getting close to the characteristics of out in VBA?
Unitialised variables: the closest I can think of are a Variant or Object type which by default initialise to Empty and Nothing respectively.
Assign value within the procedure: the simplest way would be to test if the address of the assigning procedure matches your desired procedure address.
It's all leaning towards a helper class:
Option Explicit
Private mNumber As Long
Private mTargetProc As LongPtr
Private mAssignedInProc As Boolean
Public Sub SetTargetProc(targetProc As LongPtr)
mTargetProc = targetProc
End Sub
Public Sub SetNumber(currentProc As LongPtr, val As Long)
mAssignedInProc = (currentProc = mTargetProc)
mNumber = val
End Sub
Public Property Get Number() As Long
If mAssignedInProc Then
Number = mNumber
Else
Err.Raise 5, , "Unassigned value"
End If
End Property
And then the previous example would look like this:
Public Sub Main()
Dim income As Long, costs As Long
Dim result As clsOut
income = 1000
costs = 500
ProcessSpend income, costs, result
Debug.Print result.Number
End Sub
Private Sub ProcessSpend(income As Long, costs As Long, outValue As clsOut)
Const TAX_RATE As Long = 2
Dim netCosts As Long
If outValue Is Nothing Then
Set outValue = New clsOut
End If
outValue.SetTargetProc AddressOf ProcessSpend
netCosts = costs * TAX_RATE
outValue.SetNumber AddressOf ProcessSpend, income - netCosts
End Sub
But that's all getting very onerous... and it really feels as if we are trying to force another language's syntax onto VBA. Stepping back a little from the out characteristics and developing in a syntax for which VBA was designed, then a function which returns a Variant seems the most obvious way to go. You could test if you forgot to set the 'out' value by checking if the function returns an Empty variant (which suits point 1 and 2 of the out characteristics):
Public Sub Main()
Dim income As Long, costs As Long
Dim result As Variant
income = 1000
costs = 500
result = ProcessedSpend(income, costs)
If IsEmpty(result) Then Err.Raise 5, , "Unassigned value"
End Sub
Private Function ProcessedSpend(income As Long, costs As Long) As Variant
Const TAX_RATE As Long = 2
Dim netCosts As Long
netCosts = costs * TAX_RATE
'Comment out the line below to throw the unassigned error
ProcessedSpend = income - netCosts
End Function
And if you wanted the option of passing in a pre-assigned value, then could just define an optional argument as a parameter to the function.
You can pseudo enforce an out type parameter in VBA by passing it in ByRef, and then checking that it is Nothing (or the default value for a value type) before continuing, much as you have done with the String in your example.
I wouldn't impose the exit condition - sometimes an empty string is a perfectly valid return value, as is a Nothing reference.

Take augmented function inputs

I have the following declared:
Public Declare Function GetSystemMetrics Lib "user32.dll" (ByVal X0_Y1 As Long) As Long
It grabs the monitor resolution.
So that in the future I remember to type 0 for x resolution and 1 for y resolution I have named the argument variable to illustrate that (X0_Y1). (So user can use ctrl+a or ctrl+Shift+a when entering the function to display its arguments)
But what I really want is to type "x" to get the x res and "y" for y res (i.e. =GetSystemMetrics("x") gives the x resolution). Is there a way to do this within the function decleration? Like (ByVal iif(X0_Y1 ="x",0,1) As Long) to specify what to do with the input.
I'd rather not just do this:
Function GetRes(letter As String) As Long
Dim i As Long
i = IIf(letter = "x", 0, 1)
GetRes = GetSystemMetrics(i)
End Function
As it involves creating a whole new function which is more unweildy than just using the base one.
Perhaps there's some way to specify x/y as constants so that if the user enters them they are read as numbers not strings? Another nice option would be to get the input options displayed like the Cell function does. (Similar to this question, but not the same)
You can use an Enum Statement for this.
Declare an enum and your function like that
Public Enum MetricsType
xMetrics = 0
yMetrics = 1
End Enum
Public Declare Function GetSystemMetrics Lib "user32.dll" (ByVal xy As MetricsType) As Long
and you can use it like this
Dim x As Long, y As Long
x = GetSystemMetrics(xMetrics)
y = GetSystemMetrics(yMetrics)
This will also enable AutoComplete in the VBA editor.
To enhance the usability within the worksheet you can register/unregister your function as a UDF (user defined function). After registering you can select your function from the function menu and you see the comments within this dialog.
Sub RegisterUDF()
Dim s As String
s = "Some description here" & vbLf _
& "GetSystemMetrics(<Metrics>)"
Application.MacroOptions Macro:="GetSystemMetrics", Description:=s, Category:="My Category"
End Sub
Sub UnregisterUDF()
Application.MacroOptions Macro:="GetSystemMetrics", Description:=Empty, Category:=Empty
End Sub
To get the enum working within the worksheet there is only workaround possible. Therefore you add a named range referring to =0 or =1 like below:
Sub RegisterEnum()
ActiveWorkbook.Names.Add Name:="xMetrics", RefersToR1C1:="=0"
ActiveWorkbook.Names.Add Name:="yMetrics", RefersToR1C1:="=1"
'NOTE: don't use x or y as names here as these refer to the column names.
'That's why I used xMetrics instead.
End Sub
Then you are able to use the function in your worksheet like =GetSystemMetrics(xMetrics).
Creating the exactly same behavior like the built-in functions isn't possible as far as I know.

VBA and GetRawInputDeviceList

I am working in Access 2013 and try to get GetRawInputDeviceList, GetRawInputDeviceInfo, RegisterRawInputDevices and GetRawInputData equivalents for VBA with no success. I have also searched in vain for a procedure, function or module to get a list of connected HID devices to a computer to pick out a barcode scanner. This is the beginning of the third week so I am on my knees begging for assistance. Do any of you all have a module you're willing to share, a link to a website where this is dealt with? Any help is greatly appreciated.
Using the GetRawInputDeviceList API from VBA would be pretty tricky because of the pRawInputDeviceList parameter. Unless you're willing to jump through a ton of hoops to manage your own memory and manually handle the resulting array of RAWINPUTDEVICELIST in raw memory, you'll be better off coming at this from another direction.
Most barcode scanners I've dealt with present themselves to Windows as a keyboard. One possible solution would be to use a WMI query to enumerate attached Win32_Keyboard devices:
Private Sub ShowKeyboardInfo()
Dim WmiServer As Object
Dim ResultSet As Object
Dim Keyboard As Object
Dim Query As String
Query = "SELECT * From Win32_Keyboard"
Set WmiServer = GetObject("winmgmts:root/CIMV2")
Set ResultSet = WmiServer.ExecQuery(Query)
For Each Keyboard In ResultSet
Debug.Print Keyboard.Name & vbTab & _
Keyboard.Description & vbTab & _
Keyboard.DeviceID & vbTab & _
Keyboard.Status
Next Keyboard
End Sub
Note: If it doesn't turn up there, you can enumerate all of the USB devices by querying CIM_USBDevice: Query = "SELECT * From Win32_Keyboard"
EDIT: Per the comments, the above code won't return the handle needed to register to receive raw input events. This should get you started though - the RegisterRawInputDevices and GetRawInputData aspects are beyond the scope of what will easily go in an answer. Take a hack at it, and if you run into any problems post your code in another question.
Declarations:
Private Type RawInputDeviceList
hDevice As Long
dwType As Long
End Type
Private Type RidKeyboardInfo
cbSize As Long
dwType As Long
dwKeyboardMode As Long
dwNumberOfFunctionKeys As Long
dwNumberOfIndicators As Long
dwNumberOfKeysTotal As Long
End Type
Private Enum DeviceType
TypeMouse = 0
TypeKeyboard = 1
TypeHID = 2
End Enum
Private Enum DeviceCommand
DeviceName = &H20000007
DeviceInfo = &H2000000B
PreParseData = &H20000005
End Enum
Private Declare Function GetRawInputDeviceList Lib "user32" ( _
ByVal pRawInputDeviceList As Long, _
ByRef puiNumDevices As Long, _
ByVal cbSize As Long) As Long
Private Declare Function GetRawInputDeviceInfo Lib "user32" Alias "GetRawInputDeviceInfoW" ( _
ByVal hDevice As Long, _
ByVal uiCommand As Long, _
ByVal pData As Long, _
ByRef pcbSize As Long) As Long
Private Declare Function GetLastError Lib "kernel32" () As Long
Sample of retrieving device names with GetRawInputDeviceInfo:
Private Sub SampleCode()
Dim devices() As RawInputDeviceList
devices = GetRawInputDevices
Dim i As Long
For i = 0 To UBound(devices)
'Inspect the type - only looking for a keyboard.
If devices(i).dwType = TypeKeyboard Then
Dim buffer As String
Dim size As Long
'First call with a null pointer returns the string length in size.
If GetRawInputDeviceInfo(devices(i).hDevice, DeviceName, 0&, size) = -1 Then
Debug.Print "GetRawInputDeviceInfo error " & GetLastError()
Else
'Size the string buffer.
buffer = String(size, Chr$(0))
'The second call copies the name into the passed buffer.
If GetRawInputDeviceInfo(devices(i).hDevice, DeviceName, StrPtr(buffer), size) = -1 Then
Debug.Print "GetRawInputDeviceInfo error " & GetLastError()
Else
Debug.Print buffer
End If
End If
End If
Next i
End Sub
Private Function GetRawInputDevices() As RawInputDeviceList()
Dim devs As Long
Dim output() As RawInputDeviceList
'First call with a null pointer returns the number of devices in devs
If GetRawInputDeviceList(0&, devs, LenB(output(0))) = -1 Then
Debug.Print "GetRawInputDeviceList error " & GetLastError()
Else
'Size the output array.
ReDim output(devs - 1)
'Second call actually fills the array.
If GetRawInputDeviceList(VarPtr(output(0)), devs, LenB(output(0))) = -1 Then
Debug.Print "GetRawInputDeviceList error " & GetLastError()
Else
GetRawInputDevices = output
End If
End If
End Function
Sorry about the side scrolling.

vb.net function branching based on optional parameters performance

So I was coding a string search function and ended up with 4 since they needed to go forwards or backwards or be inclusive or exclusive. Then I needed even more functionality like ignoring certain specific things and blah blah.. I figured it would be easier to make a slightly bigger function with optional boolean parameters than to maintain the 8+ functions that would otherwise be required.
Since this is the main workhorse function though, performance is important so I devised a simple test to get a sense of how much I would lose from doing this. The code is as follows:
main window:
Private Sub testbutton_Click(sender As Object, e As RoutedEventArgs) Handles testbutton.Click
Dim rand As New Random
Dim ret As Integer
Dim count As Integer = 100000000
Dim t As Integer = Environment.TickCount
For i = 0 To count
ret = superfunction(rand.Next, False)
Next
t = Environment.TickCount - t
Dim t2 As Integer = Environment.TickCount
For i = 0 To count
ret = simplefunctionNeg(rand.Next)
Next
t2 = Environment.TickCount - t2
MsgBox(t & " " & t2)
End Sub
The functions:
Public Module testoptionality
Public Function superfunction(a As Integer, Optional b As Boolean = False) As Integer
If b Then
Return a
Else
Return -a
End If
End Function
Public Function simpleFunctionPos(a As Integer)
Return a
End Function
Public Function simplefunctionNeg(a As Integer)
Return -a
End Function
End Module
So pretty much as simple as it gets. The weird part is that the superfunction is consistently twice faster than either of the simple functions (my test results are "1076 2122"). This makes no sense.. I tried looking for what i might have done wrong but I cant see it. Can anybody explain this?
You didn't set a return type for simple function. So they return Object type.
So when you using simpleFunctionNeg function application convert Integer to Object type when returning value, and then back from Object to Integer when assigning returning value to your variable
After setting return value to Integer simpleFunctionNeg was little bid faster then superfunction