Drawing a circle with radial lines - vb6

for i = 0 to 23
'' ...
'' create 'line' control
'' ...
line.x1 = (inner_radius*cos(15 * i)) + centerx
line.y1 = (inner_radius*sin(15 * i)) + centery
line.x2 = (outer_radius*cos(15 * i)) + centerx
line.y2 = (outer_radius*sin(15 * i)) + centery
next
I'm using this algorithm to render many line controls to make something like the following:
The result is rather bizarre:
I think this happens due to the rounding of the cos() and sin() functions, so my question is, is there some algorithm I can apply to fix the rounding? Or is there a better way to render such controls, perhaps?
EDIT:
The problem, as pointed by Hrqls was that I was using degrees instead of radians... this is the function that I ended up using:
Sub ProgressAnim(ByVal centerx, _
ByVal centery, _
ByVal outer_radius, _
ByVal inner_radius, _
ByVal step_count, _
ByVal line_width)
Dim pi
Dim degstep
Dim scan
Dim newcontrol As Line
Dim controlid
pi = 4 * Atn(1)
degstep = pi / (step_count / 2)
For scan = 0 To step_count - 1
controlid = "line" & (scan + 1)
Set newcontrol = Me.Controls.Add("vb.line", controlid)
newcontrol.X1 = centerx + (inner_radius * Cos(degstep * scan))
newcontrol.Y1 = centery + (inner_radius * Sin(degstep * scan))
newcontrol.X2 = centerx + (outer_radius * Cos(degstep * scan))
newcontrol.Y2 = centery + (outer_radius * Sin(degstep * scan))
newcontrol.BorderStyle = 1
newcontrol.BorderWidth = line_width
newcontrol.Visible = True
Next
End Sub
Calling it like this
ProgressAnim 150, 250, 16, 9, 18, 1
produces this:
which is much closer to what I expected... sadly, I still don't know how to achieve anti-aliasing, but this will do. (For the moment, at least) :)

Your problem is that you calculate the angles in degrees while VB uses radians for its angles
have a look at the following project :
Option Explicit
Private Sub Form_Click()
DrawWheel
End Sub
Private Sub DrawWheel()
Dim intI As Integer
Dim sngRadius As Single
Dim sngRadiusY As Single
Dim sngCenterX As Single, sngCenterY As Single
Dim sngX1 As Single, sngY1 As Single
Dim sngX2 As Single, sngY2 As Single
Dim sngStep As Single
Dim sngAngle As Single
Dim sngCos As Single, sngSin As Single
'calculate form sizes
sngRadius = (ScaleWidth - 240) / 2
sngRadiusY = (ScaleHeight - 240) / 2
sngCenterX = 120 + sngRadius
sngCenterY = 120 + sngRadiusY
If sngRadiusY < sngRadius Then sngRadius = sngRadiusY
'draw circle
Circle (sngCenterX, sngCenterY), sngRadius
'calculate step between lines
sngStep = Atn(1) / 3
'draw lines
For intI = 0 To 23
'calculate angle for each line
sngAngle = sngStep * intI
'calculate coordinates for each line
sngCos = Cos(sngAngle)
sngSin = Sin(sngAngle)
sngX1 = sngCenterX + sngCos * sngRadius / 10
sngY1 = sngCenterY + sngSin * sngRadius / 10
sngX2 = sngCenterX + sngCos * sngRadius
sngY2 = sngCenterY + sngSin * sngRadius
'draw each lines
Line (sngX1, sngY1)-(sngX2, sngY2)
'print sequence number
Print CStr(intI)
Next intI
End Sub
Click on the form to draw the wheel
Atn(1) is PI/4 ... For 24 lines you need to divide 2*PI by 24 .. thus you need to divide PI by 12 ... which makes you divide Atn(1) by 3

change for i = 0 to 23 to for i = 0 to 21
and (15 * i) with (0.3 * i)
Try that code in form1 with a timer1:
Dim c As Integer, centerx As Integer, centery As Integer, inner_radius As Integer, outer_radius As Integer
Dim x1 As Single, y1 As Single, x2 As Single, y2 As Single
Private Sub Form_Load()
c = 0
centerx = Form1.Width / 2
centery = Form1.Height / 2
inner_radius = 1200
outer_radius = 1
Timer1.Interval = 200
End Sub
Private Sub Timer1_Timer()
x1 = (inner_radius * Cos(0.3 * c)) + centerx
y1 = (inner_radius * Sin(0.3 * c)) + centery
x2 = (outer_radius * Cos(0.3 * c)) + centerx
y2 = (outer_radius * Sin(0.3 * c)) + centery
Line (x1, y1)-(x2, y2), RGB(0, 0, 0)
c = c + 1
If c = 21 Then Timer1.Enabled = False
End Sub
check your numbers in this example to see the drawing behavior.

I would ensure that you keep the greatest accuracy by using proper fractions of 2PI.
Fiddle with the constants until you get roughly what you want:
Option Explicit
Private Sub Form_Load()
Timer.Interval = 50
End Sub
Private Sub Timer_Timer()
DrawRadialLines
End Sub
Private Sub DrawRadialLines()
Const ksngPI As Single = 3.14159!
Const ksngCircle As Single = 2! * ksngPI
Const ksngInnerRadius As Single = 130!
Const ksngOuterRadius As Single = 260!
Const ksngCenterX As Single = 1200!
Const ksngCenterY As Single = 1200!
Const klSegmentCount As Long = 12
Const klLineWidth As Long = 3
Static s_lActiveSegment As Integer ' The "selected" segment.
Dim lSegment As Long
Dim sngRadians As Single
Dim sngX1 As Single
Dim sngY1 As Single
Dim sngX2 As Single
Dim sngY2 As Single
Dim cLineColour As OLE_COLOR
Me.DrawWidth = klLineWidth
' Overdraw previous graphic.
Me.Line (ksngCenterX - ksngOuterRadius - Screen.TwipsPerPixelX * 2, ksngCenterY - ksngOuterRadius - Screen.TwipsPerPixelY * 2)-(ksngCenterX + ksngOuterRadius + Screen.TwipsPerPixelX * 2, ksngCenterY + ksngOuterRadius + Screen.TwipsPerPixelY * 2), Me.BackColor, BF
For lSegment = 0 To klSegmentCount - 1
'
' Work out the coordinates for the line to be draw from the outside circle to the inside circle.
'
sngRadians = (ksngCircle * CSng(lSegment)) / klSegmentCount
sngX1 = (ksngOuterRadius * Cos(sngRadians)) + ksngCenterX
sngY1 = (ksngOuterRadius * Sin(sngRadians)) + ksngCenterY
sngX2 = (ksngInnerRadius * Cos(sngRadians)) + ksngCenterX
sngY2 = (ksngInnerRadius * Sin(sngRadians)) + ksngCenterY
' Work out how many segments away from the "current segment" we are.
' The current segment should be the darkest, and the further away from this segment we are, the lighter the colour should be.
Select Case Abs(Abs(s_lActiveSegment - lSegment) - klSegmentCount \ 2)
Case 0!
cLineColour = RGB(0, 0, 255)
Case 1!
cLineColour = RGB(63, 63, 255)
Case 2!
cLineColour = RGB(117, 117, 255)
Case Else
cLineColour = RGB(181, 181, 255)
End Select
Me.Line (sngX1, sngY1)-(sngX2, sngY2), cLineColour
Next lSegment
' Move the current segment on by one.
s_lActiveSegment = (s_lActiveSegment + 1) Mod klSegmentCount
End Sub

Related

Automatic alignment of rectangle depend of size of paper in vb.net

I have rectangle that's add another rectangle depend on how many stamps/receipt to be print.
I add another rectangle using for loop but if I manage to add more than 3 rectangles the other rectangles will not diplay because it's keeping align straight or if I use if else if e.pagebounds.widht < (the last width of my rectangle) it goes down and it's fine but how about the next... next ... next for the row 3 and up how can I get that this is my code so far
Dim rec As Rectangle
Dim x1, y1 As Integer
Dim nextline As Integer
x1 = 40
y1 = 40
Dim b As Integer = 0
Dim containerrectangle As Rectangle
containerrectangle = New Rectangle(e.PageBounds.X, e.PageBounds.Y, e.PageBounds.Width, e.PageBounds.Height)
For i = 0 To 6
If e.PageBounds.Width - 100 < x1 + b Then
nextline = 40
'reset the x-axis of the rectangle
y1 = 250
b = 40
rec = New Rectangle(b, y1 + 50, 250, 250)
e.Graphics.DrawRectangle(Pens.Sienna, rec)
y1 += 250
b += 10
Else
rec = New Rectangle(x1 + b, 40, 250, 250)
e.Graphics.DrawRectangle(Pens.Sienna, rec)
x1 += 250
b += 10
End If
Next
please see image https://imgur.com/a/ssv6YzX
Have a look into this code - I'd say it's more straight forward:
Dim RectTemplate As New Rectangle(0, 0, 250, 250)
Dim GapPx As Int32 = 12 ' set gap between rectangles
Dim StaPt As New Point(GapPx, GapPx * 2) ' set starting point
Dim N As Int32 = 15 ' set (or get) the totalnumber of rectangles
Dim nL As Int32 = Math.Floor((e.PageBounds.Width - GapPx) / (RectTemplate.Width + GapPx)) ' number of rectangles per row
Dim nR As Int32 = Math.Ceiling(N / nL) ' number of rows of rectangles
For ir = 0 To nR - 1 ' process all rows
For il = 0 To nL - 1 ' process all rectangles in a row
Dim rect As New Rectangle(StaPt.X + il * (RectTemplate.Width + GapPx), StaPt.Y + ir * (RectTemplate.Height + GapPx), RectTemplate.Width, RectTemplate.Height)
e.graphics.DrawRectangle(Pens.Sienna, rect)
Next
Next

Convert BMP image to GRF format C# / VB.NET (To use in ZPL printer)

I am using following code to convert BMP Image to GRF format.
Public Shared Function CreateGrf(filename As String, imagename As String) As String
Dim bmp As Bitmap = Nothing
Dim imgData As BitmapData = Nothing
Dim pixels As Byte()
Dim x As Integer, y As Integer, width As Integer
Dim sb As StringBuilder
Dim ptr As IntPtr
Try
bmp = New Bitmap(filename)
imgData = bmp.LockBits(New System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadWrite, bmp.PixelFormat)
width = (bmp.Width + 7) \ 8
pixels = New Byte(width - 1) {}
sb = New StringBuilder(width * bmp.Height * 2)
sb.Append(Environment.NewLine)
ptr = imgData.Scan0
For y = 0 To bmp.Height - 1
Marshal.Copy(ptr, pixels, 0, width)
For x = 0 To width - 1
sb.AppendFormat("{0:X2}", CByte(Not pixels(x)))
Next
sb.Append(Environment.NewLine)
ptr = ptr.ToInt64() + imgData.Stride
Next
Finally
If bmp IsNot Nothing Then
If imgData IsNot Nothing Then
bmp.UnlockBits(imgData)
End If
bmp.Dispose()
End If
End Try
Return [String].Format("~DG{0},{1},{2},", imagename, width * y, width) + sb.ToString()
End Function
However there is an extra vertical line drawn at the end of the converted GRF file even though there is no such line in the BMP file. Other than that the size and everything is Ok. It seems the last pixel (hex value) of each row is not correct in the GRF file.
Original BMP File.
Converted GRF FIle
Public Function ConvertBmp2Grf(fileName As String, imageName As String) As Boolean
Dim TI As String
Dim i As Short
Dim WID As Object
Dim high As Object
Dim TEM As Short, BMPL As Short, EFG As Short, n2 As String, LON As String
Dim header_name As String, a As String, j As Short, COUN As Short, BASE1 As Short
Dim L As String, TOT As String
Dim N As Object
Dim TOT1 As Integer
Dim LL As Byte
FileOpen(1, fileName, OpenMode.Binary, , , 1) ' OPEN BMP FILE TO READ
FileGet(1, LL, 1)
TI = Convert.ToString(Chr(LL))
FileGet(1, LL, 2)
TI += Convert.ToString(Chr(LL))
If TI <> "BM" Then
FileClose()
Return False
End If
i = 17
FileGet(1, LL, i + 1)
N = LL * 256
FileGet(1, LL, i)
N = (N + LL) * 256
FileGet(1, LL, i + 3)
N = (N + LL) * 256
FileGet(1, LL, i + 2)
N += LL
WID = N
i = 21
FileGet(1, LL, i + 1)
N = LL * 256
FileGet(1, LL, i)
N = (N + LL) * 256
FileGet(1, LL, i + 3)
N = (N + LL) * 256
FileGet(1, LL, i + 2)
N += LL
high = N
FileGet(1, LL, 27)
N = LL
FileGet(1, LL, 29)
If N <> 1 Or LL <> 1 Then
'BMP has too many colors, only support monochrome images
FileClose(1)
Return False
End If
TEM = Int(WID / 8)
If (WID Mod 8) <> 0 Then
TEM += 1
End If
BMPL = TEM
If (BMPL Mod 4) <> 0 Then
BMPL += (4 - (BMPL Mod 4))
EFG = 1
End If
n2 = fileName.Substring(0, fileName.LastIndexOf("\", StringComparison.Ordinal) + 1) + imageName + ".GRF"
FileOpen(2, n2, OpenMode.Output) 'OPEN GRF TO OUTPUT
TOT1 = TEM * high : TOT = Mid(Str(TOT1), 2)
If Len(TOT) < 5 Then
TOT = Strings.Left("00000", 5 - Len(TOT)) + TOT
End If
LON = Mid(Str(TEM), 2)
If Len(LON) < 3 Then
LON = Strings.Left("000", 3 - Len(LON)) + LON
End If
header_name = imageName
PrintLine(2, "~DG" & header_name & "," & TOT & "," & LON & ",")
For i = high To 1 Step -1
a = ""
For j = 1 To TEM
COUN = 62 + (i - 1) * BMPL + j
FileGet(1, LL, COUN)
L = LL
If j = TEM And (EFG = 1 Or (WID Mod 8) <> 0) Then
BASE1 = 2 ^ ((TEM * 8 - WID) Mod 8)
L = Int(L / BASE1) * BASE1 + BASE1 - 1
End If
L = Not L
a += Right(Hex(L), 2)
Next j
PrintLine(2, a)
Next i
FileClose()
Return True
End Function
Marshal.Copy(ptr, pixels, 0, width)
The Bitmap is not byte aligned. So in this case when you copy the data in it is filling in the left over bits as black.
the bitmap is 154 bytes wide which creates 19 full bytes and 2 left over pixels. So the remaining 6 pixels are black.
In the end you need to use bitmaps with widths that are divisible by eight or make sure the end of the data copy from the bitmap to pixels(x) accounts for the remaining bytes.
1) remove "7" in this part : width = (bmp.Width + 7) \ 8
2) detect if the bitmap's remaining value after Mod
if(bmp.Width % 8 > 0)
{
var remaining = bmp.Width % 8;
var newbmp = ResizeImage(bmp, bmp.Width + remaining, bmp.Height);
bmp.Dispose();
bmp = newbmp;
}
the logic for ResizeImage
public static Bitmap ResizeImage(Image image, int width, int height)
{
var destRect = new Rectangle(0, 0, width, height);
var oldRect = new Rectangle(0, 0, image.Width, image.Height);
var destImage = new Bitmap(width, height);
destImage.SetResolution(image.HorizontalResolution, image.VerticalResolution);
using (var graphics = Graphics.FromImage(destImage))
{
graphics.FillRectangle(Brushes.White, destRect);
graphics.CompositingMode = CompositingMode.SourceCopy;
graphics.CompositingQuality = CompositingQuality.HighQuality;
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.SmoothingMode = SmoothingMode.HighQuality;
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
using (var wrapMode = new ImageAttributes())
{
wrapMode.SetWrapMode(WrapMode.TileFlipXY);
graphics.DrawImage(image, oldRect, 0, 0, image.Width, image.Height,
GraphicsUnit.Pixel, wrapMode);
}
}
return destImage;
}

Mean and Covariance of folder of images in VB6

I have spent a lot of time downloading and trying to use various VB6 examples
to obtain the mean and covariance of YCbCr of jpg image files folder c:\test, however, I only get this code in Csharp only. How I can get it work in VB 6.0?
int num_samples = 0;
foreach (string f in Directory.GetFiles(txtTrainingFolder.Text, "*.jpg"))
{
using (Bitmap bmp = new Bitmap(f))
Add(bmp);
num_samples++;
}
public void Add(Bitmap bmp)
{
for (int y = 0; y < bmp.Height; y++)
for (int x = 0; x < bmp.Width; x++)
{
Color c = bmp.GetPixel(x, y);
// Skip black pixels in training images
if (c.R < 10 && c.G < 10 && c.B < 10)
continue;
double cb, cr;
CbCr(c.R, c.G, c.B, out cb, out cr);
sum_cr += cr;
sum_cb += cb;
sum_rr += cr * cr;
sum_rb += cr * cb;
sum_bb += cb * cb;
n++;
}
}
public void Finish()
{
// Mean
mean_cr = sum_cr / n;
mean_cb = sum_cb / n;
// Covariance
cov00 = sum_bb / n - mean_cb * mean_cb;
cov01 = sum_rb / n - mean_cr * mean_cb;
cov11 = sum_rr / n - mean_cr * mean_cr;
// Inverse covariance
double det = cov00 * cov11 - cov01 * cov01;
inv00 = cov00 / det;
inv01 = -cov01 / det;
inv11 = cov11 / det;
}
static void CbCr(byte r, byte g, byte b, out double cb, out double cr)
{
double d0 = r / 255.0, d1 = g / 255.0, d2 = b / 255.0;
cb = (-(0.148 * r) - (0.291 * g) + (0.439 * b) + 128);
cr = ((0.439 * r) - (0.368 * g) - (0.071 * b) + 128);
//cb = -37.797 * d0 - 74.203 * d1 + 112 * d2 + 128;
//cr = 112 * d0 - 93.786 * d1 - 18.214 * d2 + 128;
}
Try the project below:
when you click on the picturebox it then finds all jpg files in a directory and process them
'1 form with
' 1 picturebox: name=Picture1
Option Explicit
Private Sub Form_Resize()
Picture1.Move 0, 0, ScaleWidth, ScaleHeight
End Sub
Private Sub Picture1_Click()
ProcessFiles "c:\temp\", "*.jpg"
End Sub
Private Sub ProcessFiles(strPath As String, strMask As String)
Dim strFile As String
strFile = Dir$(strPath & strMask)
Do Until Len(strFile) = 0
'ShowFile strPath & strFile
LoadFile strPath & strFile
strFile = Dir$() 'find next file
Loop
End Sub
Private Sub ShowFile(strFile As String)
Caption = strFile
Picture1.Picture = LoadPicture(strFile)
End Sub
Private Sub LoadFile(strFile As String)
Dim intFile As Integer
Dim bytArray() As Byte
intFile = FreeFile
Open strFile For Binary As #intFile
bytArray = Input(LOF(intFile), #intFile)
Close #intFile
End Sub
ShowFile will show the file in the picturebox
LoadFile will load the file into an array of bytes
There are several online code C# to VB converters available. However, you will still need to do the VB.NET to VB6 conversion manually. The .NET and VB6 libraries are completely different and there are also many differences in the language features (e.g. the Using statement used in your code does not exist in VB6. It unloads the bitmap).
Converters:
developerFusion
DOTNET Spider
SharpDevelop code converter
Telerik code converter

Make control array in code

This code does not work I want to create a control array on my Form_Load in VB6 because I have to make 225 of them for a scrabble board and they have to be precise. My code is:
Private lblblocks(1 To 225) As Label
Private Sub Form_Load()
Dim i As Integer, j As Integer
For i = 1 To 15
For j = 1 To 15
Dim arrnum As Integer
arrnum = (i - 1) * 15 + j
Load lblblocks(arrnum)
With lblblocks(arrnum)
.Width = 1000
.Height = 1000
.Top = (i - 1) * 1000
.Left = (j - 1) * 1000
.Visible = True
.BackColor = Int(Rnd(1) * 255) + &H100 * Int(Rnd(1) * 255) + &H10000 * Int(Rnd(1) * 255)
End With
Next j
Next i
End Sub
I used the backcolor to see all my label boxes. This code does not work. I get an error "Object variable or With block variable not set". Any help? I don't know what is wrong. I would like to keep the label boxes in a control array I know how to do it without making it a control array.
Cody Gray had it correct in his comment. I don't believe you can create a control array on the fly only in code in VB6. You have to place one instance of the control on the form and give it an Index property value of zero. This creates a control array with only one element, at index zero. You can then modify your code to produce the desired result, like so:
Private Sub Form_Load()
Dim i As Integer
Dim j As Integer
For i = 0 To 14
For j = 0 To 14
Dim tileIdx As Integer
tileIdx = i * 15 + j
'If the tile index is zero, we already have that control,
'so there's no need to load new instance. Otherwise, use the
'Load method to create a new control in the array with the
'specified index.
If tileIdx > 0 Then
Load lblTile(tileIdx)
End If
With lblTile(tileIdx)
.Width = 1000
.Height = 1000
.Top = i * 1000
.Left = j * 1000
.Visible = True
.BackColor = Int(Rnd(1) * 255) + &H100 * Int(Rnd(1) * 255) + &H10000 * Int(Rnd(1) * 255)
End With
Next
Next
End Sub
As noted in the comment, you don't need to load another instance of the control at array index zero because you did that at design time. I also iterated my array starting from zero for slightly easier calculation of the indices.
we can add from scratch
Private Sub Command3_Click()
Dim rownum As Integer, ColNum As Integer
'Dim lblblocks(1 To 225) As Label
Dim lblblocks() As Label
Dim wwidth As Integer, hheight As Integer
wwidth = 400: hheight = 200
Dim i As Integer, j As Integer
rownum = 20: ColNum = 25
ReDim lblblocks(1 To rownum * ColNum)
For i = 1 To rownum
For j = 1 To ColNum
Dim arrnum As Integer
arrnum = (i - 1) * ColNum + j
Set lblblocks(arrnum) = Me.Controls.Add("VB.Label", "LB" & arrnum)
With lblblocks(arrnum)
'Set Bb(i) = formname.Controls.Add("VB.CommandButton", "Bb" & i)
.Width = wwidth
.Height = hheight
'.Top = (i - 1) * 100
'.Left = (j - 1) * 400
.Top = (i) * hheight
.Left = (j) * wwidth
.Caption = arrnum
.Visible = True
.BackColor = Int(Rnd(1) * 255) + &H100 * Int(Rnd(1) * 255) + &H10000 * Int(Rnd(1) * 255)
End With
Next j
Next i
End Sub

Memory and execution time reduction for algorithms

I have been asked to ask this question again and in a little different context. This is the previous post:
Filtering in VBA after finding combinations
I would like to make this code possible with 100 different variables without having excel run out of memory and reducing the execution time significantly.
The problem with the code below is that if I have 100 boxes, excel will run out of memory in the line "Result(0 To 2 ^ NumFields - 2)" ( The code works for < 10 boxes)
This is my input:
3 A B C D E ...
7.7 3 1 1 1 2 ...
5.5 2 1 2 3 3 ...
This is the code:
Function stackBox()
Dim ws As Worksheet
Dim width As Long
Dim height As Long
Dim numOfBox As Long
Dim optionsA() As Variant
Dim results() As Variant
Dim str As String
Dim outputArray As Variant
Dim i As Long, j As Long
Dim currentSymbol As String
'------------------------------------new part----------------------------------------------
Dim maxHeight As Double
Dim maxWeight As Double
Dim heightarray As Variant
Dim weightarray As Variant
Dim totalHeight As Double
Dim totalWeight As Double
'------------------------------------new part----------------------------------------------
Set ws = Worksheets("Sheet1")
With ws
'clear last time's output
height = .Cells(.Rows.Count, 1).End(xlUp).row
If height > 3 Then
.Range(.Cells(4, 1), .Cells(height, 1)).ClearContents
End If
numOfBox = .Cells(1, 1).Value
width = .Cells(1, .Columns.Count).End(xlToLeft).Column
If width < 2 Then
MsgBox "Error: There's no item, please fill your item in Cell B1,C1,..."
Exit Function
End If
'------------------------------------new part----------------------------------------------
maxHeight = .Cells(2, 1).Value
maxWeight = .Cells(3, 1).Value
ReDim heightarray(1 To 1, 1 To width - 1)
ReDim weightarray(1 To 1, 1 To width - 1)
heightarray = .Range(.Cells(2, 2), .Cells(2, width)).Value
weightarray = .Range(.Cells(3, 2), .Cells(3, width)).Value
'------------------------------------new part----------------------------------------------
ReDim optionsA(0 To width - 2)
For i = 0 To width - 2
optionsA(i) = .Cells(1, i + 2).Value
Next i
GenerateCombinations optionsA, results, numOfBox
' copy the result to sheet only once
ReDim outputArray(1 To UBound(results, 1) - LBound(results, 1) + 1, 1 To 1)
Count = 0
For i = LBound(results, 1) To UBound(results, 1)
If Not IsEmpty(results(i)) Then
'rowNum = rowNum + 1
str = ""
totalHeight = 0#
totalWeight = 0#
For j = LBound(results(i), 1) To UBound(results(i), 1)
currentSymbol = results(i)(j)
str = str & currentSymbol 'results(i)(j) is the SYMBOL e.g. A, B, C
'look up box's height and weight , increment the totalHeight/totalWeight
updateParam currentSymbol, optionsA, heightarray, weightarray, totalHeight, totalWeight
Next j
If totalHeight < maxHeight And totalWeight < maxWeight Then
Count = Count + 1
outputArray(Count, 1) = str
End If
'.Cells(rowNum, 1).Value = str
End If
Next i
.Range(.Cells(4, 1), .Cells(UBound(outputArray, 1) + 3, 1)).Value = outputArray
End With
End Function
Sub updateParam(ByRef targetSymbol As String, ByRef symbolArray As Variant, ByRef heightarray As Variant, ByRef weightarray As Variant, ByRef totalHeight As Double, ByRef totalWeight As Double)
Dim i As Long
Dim index As Long
index = -1
For i = LBound(symbolArray, 1) To UBound(symbolArray, 1)
If targetSymbol = symbolArray(i) Then
index = i
Exit For
End If
Next i
If index <> -1 Then
totalHeight = totalHeight + heightarray(1, index + 1)
totalWeight = totalWeight + weightarray(1, index + 1)
End If
End Sub
Sub GenerateCombinations(ByRef AllFields() As Variant, _
ByRef Result() As Variant, ByVal numOfBox As Long)
Dim InxResultCrnt As Integer
Dim InxField As Integer
Dim InxResult As Integer
Dim i As Integer
Dim NumFields As Integer
Dim Powers() As Integer
Dim ResultCrnt() As String
NumFields = UBound(AllFields) - LBound(AllFields) + 1
ReDim Result(0 To 2 ^ NumFields - 2) ' one entry per combination
ReDim Powers(0 To NumFields - 1) ' one entry per field name
' Generate powers used for extracting bits from InxResult
For InxField = 0 To NumFields - 1
Powers(InxField) = 2 ^ InxField
Next
For InxResult = 0 To 2 ^ NumFields - 2
' Size ResultCrnt to the max number of fields per combination
' Build this loop's combination in ResultCrnt
ReDim ResultCrnt(0 To NumFields - 1)
InxResultCrnt = -1
For InxField = 0 To NumFields - 1
If ((InxResult + 1) And Powers(InxField)) <> 0 Then
' This field required in this combination
InxResultCrnt = InxResultCrnt + 1
ResultCrnt(InxResultCrnt) = AllFields(InxField)
End If
Next
If InxResultCrnt = 0 Then
Debug.Print "testing"
End If
'additional logic here
If InxResultCrnt >= numOfBox Then
Result(InxResult) = Empty
Else
' Discard unused trailing entries
ReDim Preserve ResultCrnt(0 To InxResultCrnt)
' Store this loop's combination in return array
Result(InxResult) = ResultCrnt
End If
Next
End Sub
Here's a version that does all the heavy lifting in variant arrays
(Combinations logic based on this answer for This Answer by Joubarc)
This runs on a sample dataset of 100 boxes with > 40,000 returned, and in < 1 second
Notes:
Execution time rises quickly if the Max number of boxes increases (eg 4 from 100: approx 13s)
If the number of returned results exceeds 65535, the code to tranpose the array into the sheet fails (last line of the sub) If you need to handle this may results, you will need to change the way results are returned to the sheet
Sub Demo()
Dim rNames As Range
Dim rHeights As Range
Dim rWeights As Range
Dim aNames As Variant
Dim aHeights As Variant
Dim aWeights As Variant
Dim MaxNum As Long
Dim MaxHeight As Double
Dim MaxWeight As Double
' *** replace these six line with your data ranges
Set rNames = Range([F5], [F5].End(xlToRight))
Set rHeights = rNames.Offset(1, 0)
Set rWeights = rNames.Offset(2, 0)
MaxNum = [C5]
MaxHeight = [C6]
MaxWeight = [C7]
aNames = rNames
aHeights = rHeights
aWeights = rWeights
Dim Result() As Variant
Dim n As Long, m As Long
Dim i As Long, j As Long
Dim iRes As Long
Dim res As String
Dim TestCombin() As Long
Dim TestWeight As Double
Dim TestHeight As Double
Dim idx() As Long
' Number of boxes
ReDim TestCombin(0 To MaxNum - 1)
n = UBound(aNames, 2) - LBound(aNames, 2) + 1
' estimate size of result array = number of possible combinations
For m = 1 To MaxNum
i = i + Application.WorksheetFunction.Combin(n, m)
Next
ReDim Result(1 To 3, 1 To i)
' allow for from 1 to MaxNum of boxes
iRes = 1
For m = 1 To MaxNum
ReDim idx(0 To m - 1)
For i = 0 To m - 1
idx(i) = i
Next i
Do
'Test current combination
res = ""
TestWeight = 0#
TestHeight = 0#
For j = 0 To m - 1
'Debug.Print aNames(1, idx(j) + 1);
res = res & aNames(1, idx(j) + 1)
TestWeight = TestWeight + aWeights(1, idx(j) + 1)
TestHeight = TestHeight + aHeights(1, idx(j) + 1)
Next j
'Debug.Print
If TestWeight <= MaxWeight And TestHeight <= MaxHeight Then
Result(1, iRes) = res
' optional, include actual Height and Weight in result
Result(2, iRes) = TestHeight
Result(3, iRes) = TestWeight
iRes = iRes + 1
End If
' Locate last non-max index
i = m - 1
While (idx(i) = n - m + i)
i = i - 1
If i < 0 Then
'All indexes have reached their max, so we're done
Exit Do
End If
Wend
'Increase it and populate the following indexes accordingly
idx(i) = idx(i) + 1
For j = i To m - 1
idx(j) = idx(i) + j - i
Next j
Loop
Next
' Return Result to sheet
Dim rng As Range
ReDim Preserve Result(1 To 3, 1 To iRes)
' *** Adjust returnm range to suit
Set rng = [E10].Resize(UBound(Result, 2), UBound(Result, 1))
rng = Application.Transpose(Result)
End Sub

Resources