I'm trying to build a small nesting Function inside of my current working system. What I am having trouble with is that when I add the third shape to the collection and try to position it based on the previous shape added to the collection, it still positions it based on the very first shape. What I end up with is the original in the original position and the copies stacked on top of one another.
Function ArrangeImages(ByRef scol1 As Collection, ByRef sA, sB As Shape)
Dim i, ii As Long
i = scol1.Count
ii = i - 1
If i = 1 Then
Set sB = scol1.Item(i)
End If
If scol1.Count > 1 Then
Set sA = scol1.Item(ii)
Set sB = scol1.Item(i)
sB.SetPosition sA.PositionX, sA.PositionY + (sA.SizeHeight / 2) +
(sB.SizeHeight / 2) + 0.15
End If
End Function
You are setting the objects equal to each other and they reference each other in memory. In fact, you are doubling the object and using up twice as much memory.
Set sets an object equal to the reference of the object you want it to be. Here is an example.
Public Sub test()
Dim s As Worksheet
Dim s2 As Worksheet
Set s = Application.Workbooks.Add().Worksheets.Add
s.Name = "one"
Set s2 = s 's and s2 Name = "one"
s.Name = "two" 's and s2 Name = "two"
End Sub
Let sets an object equal to the value of the object you want it to be.
Try
Set sA = new Shape
Let sA = sB
Instead of
Set sA = sB
If that doesn't work, you may have to create a property or variable for each of the shapes you are wanting to work with.
Similar Question
Related
Why does the messagebox show "False"?
Dim images(4, 4) As Image
For rows = 0 To 4
For columns = 0 To 4
images(rows, columns) = My.Resources.kaboom
Next
Next
MessageBox.Show(images(3, 3).Equals(My.Resources.kaboom))
If you look at the code behind the kaboom property, you will see it creates a new object every time.
'''<summary>
''' Looks up a localized resource of type System.Drawing.Bitmap.
'''</summary>
Friend ReadOnly Property kaboom() As System.Drawing.Bitmap
Get
Dim obj As Object = ResourceManager.GetObject("kaboom", resourceCulture)
Return CType(obj,System.Drawing.Bitmap)
End Get
End Property
If you keep a reference to one object, it will be equal to true. It might also be faster since it doesn't need to create a new object.
Dim kaboom As Image = My.Resources.kaboom
Dim images(4, 4) As Image
For rows = 0 To 4
For columns = 0 To 4
images(rows, columns) = kaboom
Next
Next
MessageBox.Show(images(3, 3).Equals(kaboom))
Maybe you are already planning on doing this but here is a suggestion. If you are creating some sort of game, separate the display from the game logic. This mean, save the type of tile instead of the image and compare that. Later, you can add a bunch of different properties to a tile.
Const TYPE_KABOOM As Integer = 1
Dim tileType(4, 4) As Integer
For rows = 0 To 4
For columns = 0 To 4
tileType(rows, columns) = TYPE_KABOOM
Next
Next
MessageBox.Show(tileType(3, 3).Equals(TYPE_KABOOM))
I have a Collection Class (or rather a dictionary class, in this case) that is used to store a variable amount of edge objects. When I try to populate the Dictionary that holds all the information via loop, the data is continuously overwritten and I cannot seem to figure out why. The code for the class in question follows:
Option Explicit
Private pEdges As New Scripting.Dictionary
Property Get Count() As Long
Count = pEdges.Count
End Property
Property Get EdgeByName(ByVal iName As Variant) As cEdge
Set EdgeByName = pEdges(iName)
End Property
'Would it be better to pass all of the data to this add sub, and create
'the class objects here, rather than creating a temporary class object and
'just passing it along?
Sub Add(ByVal iEdge As cEdge)
Dim Edge As New cEdge
Set Edge = iEdge
pEdges.Add Edge.Name, Edge
End Sub
Sub Remove(ByVal iName As Variant)
pEdges.Remove (iName)
End Sub
Sub RemoveAll()
pEdges.RemoveAll
End Sub
Sub PrintNames()
Dim Key As Variant
For Each Key In pEdges
Debug.Print Key & " - " & pEdges(Key).Name & vbCrLf;
Next
Debug.Print vbdrlf;
End Sub
Sub that generates the Edges object follows:
Sub CalculateEdges(cCavities() As cCavity, dEdges As cEdges)
Dim i As Integer
For i = 1 To UBound(cCavities)
Dim TempEdge As cEdge
Set TempEdge = New cEdge
Dim AdjSize As Integer
AdjSize = cCavities(i).AdjacencySize
If AdjSize> MaxEdges Then MaxEdges = AdjSize
Dim j As Integer
For j = 1 To AdjSize
With TempEdge
'Edge Names are a combination of two node names
.Name = cCavities(i).Name & cCavities(i).Adjacency(j)
'Sets the start node (Object) for the edge
.SetNode cCavities(i), 0
'Sets the end node (Object) for the edge
.SetNode BackGround.NodeByName(cCavities, cCavities(i).Adjacency(j)), 1
'Used later in program
.Value = 0
End With
dEdges.Add TempEdge
dEdges.PrintNames
Next j
Next i
End Sub
The output of the dEdges.PrintNames sub is what I have been using for debugging this (since the Watches window doesn't show the item data of a dictionary).
As the loops go on it prints the Key and the Name Value of the edge object that the key corresponds to. If working correctly, these two strings should be identical. As it is though, every time I add a new edge object to the dictionary, it overwrites the objects for all the previously entered keys. I have the suspicion that this is related to the fact that I create a TempEdge Variable to pass to the Collection Class, but I am not sure.
Example of output:
C1C2 - C1C2
C1C2 - C1C3
C1C3 - C1C3
C1C2 - C1C4
C1C3 - C1C4
C1C4 - C1C4
ETC
This is just one single data point being tested, but let me assure you that all the variables inside the cEdge object are overwritten, not just the name string. It is simply the easiest to check since it is just a string.
As a side note, if there is a way to see the Object stored in the dictionary, similar to the "Watches" window, I would very much like to know how to do it. The entire reason I am even using the temp edge at this point is so I can keep track of what data is going into the dictionary at any given point in the loop.
Second side note, If I can get this working I will most likely switch the cCavities array to a similar collection class. It is not currently one because I cant seem to get them working right.
Moving the Set "TempEdge = New cEdge" into the loop will create a new instance and a new pointer location with every loop while maintaining your collections references to previous pointers.
Sub CalculateEdges(cCavities() As cCavity, dEdges As cEdges)
Dim i As Integer
For i = 1 To UBound(cCavities)
Dim TempEdge As cEdge
Dim AdjSize As Integer
AdjSize = cCavities(i).AdjacencySize
If AdjSize> MaxEdges Then MaxEdges = AdjSize
Dim j As Integer
For j = 1 To AdjSize
Set TempEdge = New cEdge
With TempEdge
'Edge Names are a combination of two node names
.Name = cCavities(i).Name & cCavities(i).Adjacency(j)
'Sets the start node (Object) for the edge
.SetNode cCavities(i), 0
'Sets the end node (Object) for the edge
.SetNode BackGround.NodeByName(cCavities, cCavities(i).Adjacency(j)), 1
'Used later in program
.Value = 0
End With
dEdges.Add TempEdge
dEdges.PrintNames
Next j
Next i
End Sub
I went ahead with the idea to pass along all the data to the add routine, and it seems to have solved the issue. I would still like to know why the method I was using did not work, though, so please feel free to comment or answer with regards to that.
The solution was to change the cEdges.Add Sub to accept all the individual parameters that were once passed to the temporary edge variable:
Sub Add(ByVal iName As String, iNode1 As cCavity, iNode2 As cCavity, iValue As Integer)
Dim Edge As New cEdge
With Edge
.Name = iName
.SetNode iNode1, 0
.SetNode iNode2, 1
.Value = iValue
End With
pEdges.Add Edge.Name, Edge
End Sub
This changes the populating loop to look like:
Sub CalculateEdges(cCavities() As cCavity, dEdges As cEdges)
Dim i As Integer
For i = 1 To UBound(cCavities)
Dim AdjSize As Integer
AdjSize = cCavities(i).AdjacencySize
If AdjSize > MaxEdges Then MaxEdges = AdjSize
Dim j As Integer
For j = 1 To AdjSize
dEdges.Add cCavities(i).Name & cCavities(i).Adjacency(j), cCavities(i), BackGround.NodeByName(cCavities, cCavities(i).Adjacency(j)), 0
dEdges.PrintNames
Next j
Next i
End Sub
This code, especially the .Add line, could be cleaned up. I will most likely do that, but this is fine for now.
EDIT: Upon further research and a bit more trial and error, I have discovered the reason for the data being overwritten. The Set keyword only creates a pointer to the original value, effectively making my above code have one object, the TempEdge variable, and a whole bunch of different heads that pointed to it. That is why when the Temp edge was edited, all the subsequent heads changes.
I know similar questions have been asked before like this and this
but I was having issues with initializing the checkbox array object (My VBA is quite rusty).
I have the following code:
Dim chkAdQ(4) As Checkbox
Set chkAdQ(0) = chkAdQ1
Set chkAdQ(1) = chkAdQ2
Set chkAdQ(2) = chkAdQ3
Set chkAdQ(3) = chkAdQ4
where chkAdQ1, chkAdQ2 etc. are ActiveX checkboxes present on the form. On debugging I can see that chkAdQ(4) prompts 'nothing' on the declaration itself and hence the assignment gives a Type mismatch exception.
I also tried by declaring chkAdQ(4) as an Object but to no avail. Any thoughts?
You can add all checkboxes on the worksheet quite nicely with a simple loop
Sub AddCheckBoxesToArray()
Dim chkAdQ As Variant
Dim cb
i = 0
ReDim chkAdQ(i)
For Each cb In Sheet2.OLEObjects
If TypeName(cb.Object) = "CheckBox" Then
If i > 0 Then ReDim Preserve chkAdQ(0 To i)
Set chkAdQ(i) = cb
i = i + 1
End If
Next cb
For Each cb In chkAdQ
Debug.Print cb.Name
Next cb
End Sub
Remove the second loop when using. This is just to prove that they have all been added by printing their names to the Immediate window
Try this
Dim chkAdQ(0 To 3) As Variant
Set chkAdQ(0) = chkAdQ1
Set chkAdQ(1) = chkAdQ2
Set chkAdQ(2) = chkAdQ3
Set chkAdQ(3) = chkAdQ4
Im trying to consolodate my code a bit by making the code a little more polymorphic. I have a userform in which I have 5 tabs. Each tab has 3 columns in which to place controls dynamically at run time.
I am looking to consolidate all of my Top and Left Positions for each control as i start building this form.
Right now I have variables like this.
Tab0StartFromTop
Tab1StartFromTop
Tab2StartFromTop
Tab3StartFromTop
Tab4StartFromTop
Tab0Col1Left
Tab0Col2Left
Tab0Col3Left
''you get the picture
What I'd like to do is something like this
Dim TabAttributes(0 To 4) As Collection
Dim ColumnAttributes As clsColumn ''clsColumn is a class object with properties for both .Top and .Left
Dim ColumnAttributeCollection As Collection
''Load Up my initial data. Distance from top will be incremented as I add controls
Set ColumnAttributes = New clsColumn
ColumnAttributes.Top = StartFromTop
ColumnAttributes.Left = StartFromLeft
''load all 3 objects into the (0) zero index for tab0
ColumnAttributeCollection(1).Add ColumnAttributes
ColumnAttributeCollection(2).Add ColumnAttributes
ColumnAttributeCollection(3).Add ColumnAttributes
''Now stick it inside my TabAttributes
TabAttributes(0).add ColumnAttributeCollection
so now I could access it like this, or in some way.
For each blah..
TabAttributes(MyControl.Tabindex).Column(MyControl.ColumnIndex).Top
TabAttributes(MyControl.Tabindex).Column(MyControl.ColumnIndex).Left
Next
also as I loop through each control I will need to adjust the top integer as more controls are added to that specific column on that specific tab.
TabAttributes(MyControl.Tabindex).Column(MyControl.ColumnIndex).Top = _
TabAttributes(MyControl.Tabindex).Column(MyControl.ColumnIndex).Top + 40
I hope that makes sense. Thanks in advance for the suggestions.
Personally I prefer using arrays over collections. Perhaps something like this might work for you:
Const lTop As Long = 0 'Use named variable to always work with Top attribute
Const lLeft As Long = 1 'Use named variable to always work with Left attribute
Dim lTabIndex As Long
Dim aTabColumns() As Variant
ReDim aTabColumns(0 To 4, 0 To 1)
'The first dimension (0 to 4) is for your tab numbers
'The second dimension (0 to 1) shows if you're working with the Top or Left attribute for that tab's column
'Example use: aTabColumns(1, lTop) = aTabColumns(1, lTop) + 40
'Example showing how to loop through the array and set initial values
For lTabIndex = LBound(aTabColumns, 1) To UBound(aTabColumns, 1)
aTabColumns(lTabIndex, lTop) = StartFromTop
aTabColumns(lTabIndex, lLeft) = StartFromLeft
Next lTabIndex
Finally got it to work properly using a dictionary
Dim TabAttributes As Dictionary
Dim ColumnAttributes As Dictionary
Dim Column1Info As clsColumn
Dim Column2Info As clsColumn
Dim Column3Info As clsColumn
Dim TabColumnIndex As Integer
Set Column1Info = New clsColumn
Set Column2Info = New clsColumn
Set Column3Info = New clsColumn
Set TabAttributes = New Dictionary
Set ColumnAttributes = New Dictionary
'set column offsets
Column1Info.Left = StartFromLeft
Column1Info.Top = StartFromTop
Column2Info.Left = StartFromLeft + Column1Width + ColumnGap
Column2Info.Top = StartFromTop
Column3Info.Left = StartFromLeft + Column1Width + ColumnGap * 2 + Column2Width
Column3Info.Top = StartFromTop
'add column offsets to class object
ColumnAttributes.Add 1, Column1Info
ColumnAttributes.Add 2, Column2Info
ColumnAttributes.Add 3, Column3Info
'add class object to each tab index
For TabColumnIndex = 0 To 4
TabAttributes.Add TabColumnIndex, ColumnAttributes
Next
So now I can get my data like this...
TabAttributes(MyControl.Tabindex)(MyControl.ColumnIndex).Top = 125
thanks for the help guys
I have a macro that so far, adds 4 new table columns to an existing table ("Table1"). Now, I would like the macro to format the 3rd and 4th row as percentage. I would like to include this in the loop already listed in my code. I have tried several different ways to do this. I don't think I quite understand how the UBound function works, but hopefully you can understand what I am trying to do.
I also am unsure if I am allowed to continue to utilize the WITH statement in my nested For loop in regards to me 'lst' variable.
#Jeeped - I'm looking at you for this one again...thanks for basically walking me through this whole project lol
Sub attStatPivInsertTableColumns_2()
Dim lst As ListObject
Dim currentSht As Worksheet
Dim colNames As Variant, r1c1s As Variant
Dim h As Integer, i As Integer
Set currentSht = ActiveWorkbook.Sheets("Sheet1")
Set lst = ActiveSheet.ListObjects("Table1")
colNames = Array("AHT", "Target AHT", "Transfers", "Target Transfers")
r1c1s = Array("=([#[Inbound Talk Time (Seconds)]]+[#[Inbound Hold Time (Seconds)]]+[#[Inbound Wrap Time (Seconds)]])/[#[Calls Handled]]", "=350", "=[#[Call Transfers and/or Conferences]]/[#[Calls Handled]]", "=0.15")
With lst
For h = LBound(colNames) To UBound(r1c1s)
.ListColumns.Add
.ListColumns(.ListColumns.Count).Name = colNames(h)
.ListColumns(.ListColumns.Count).DataBodyRange.FormulaR1C1 = r1c1s(h)
If UBound(colNames(h)) = 2 or UBound(colNames(h)) = 3 Then
For i = UBound(colNames(h), 2) To UBound(colNames(h), 3)
.ListColumns(.ListColumns.Count).NumberFormat = "0%"
End if
Next i
Next h
End With
End Sub
You don't need to nest a second for loop. If you want to set the 3rd and 4th columns to a percentage, you only need to set that when the iteration of the loop (h) is 2 or 3 (remembering that arrays index from 0). You also shouldn't cross arrays for the main loop, and since LBound is in most cases 0 you might as well just use that anyway. Try this:
With lst
For h = 0 To UBound(r1c1s)
.ListColumns.Add
.ListColumns(.ListColumns.Count).Name = colNames(h)
.ListColumns(.ListColumns.Count).DataBodyRange.FormulaR1C1 = r1c1s(h)
If h = 2 or h = 3 Then
.ListColumns(.ListColumns.Count).NumberFormat = "0%"
End if
Next h
End With
To answer the other point in your question, UBound(array) just gives the index of the largest element (the Upper BOUNDary) in the given array. So where you have 50 elements in such an array, UBound(array) will return 49 (zero based as mentioned before). LBound just gives the other end of the array (the Lower BOUNDary), which is generally zero.