Preference Votes Data to Groups - excel

I have a problem creating a formula or VBA macro that sorts 'preference voting' data into appropriate groups for students selecting summer camp electives. Historically, we've done the voting and sorting on paper, and i'd like to move to something a little less time consuming for the many, many rounds of electives we do at camp.
Ive created a form they fill out, which gives me a spreadsheet with their elective preferences. it looks like this
Kids A B C
1001 2 3 1
1002 3 1 2
1003 3 1 2
1004 3 1 2
1005 3 1 2
1006 3 1 2
1007 3 2 1
1008 3 2 1
1009 2 1 3
1010 3 1 2
1011 2 1 3
what id like to be able to do is run a macro or (even better) a dynamic function that sorts the voters into categories - like this
A B C
1001 1002 1007
1010 1003 1008
1011 1004 1009
1005
1006
basically - elective A has no first choice votes so its initial count = 0. Elective B has 8 first choice votes, so its initial count is 8, elective c has 3 first choice votes so its initial count is 3. I need these to be at least close to balanced (plus i actually have over 100 students), so we have 2nd choices also (3rd is a strike). so the minimum count for each group needs to be 1/4 + 1 total voting population.
Obviously no solution is perfect, because theres an inherently subjective choice about who gets moved from their first choice to their second, but any help would be appreciated.
If theres something in stat math that would point me in the right direction that would help too. ive tried googling this, but all references to voting systems i can find assume i want to anonymise the data, which is the opposite of what i need.
ive tried vlookups and indexing, but the formulas quickly get unwieldy, and dont seem to do what i need anyway. SORT functions seem to be the way to go, but i cant wrap my head around the syntax of them (using just visual sort is how ive rendered the above sorting.) RANK doesnt seem to offer what im looking for.

I have simulated the voting process and created somehow equal groups of kids based on their preferred choices.
If anything is unclear please leave a comment and I will do my best to better explain the content.
Note(disclaimer hehe): I would have done this using only Types, Collections and arrays, however the ability to demonstrate visual representation of my solution required me to use spreadsheet. The code used in this example can easily be modified not to work with spreadsheets but Collections.
Here's what I have done in steps:
1 - Setup spreadsheet (spreadsheet name: "Sheet1", module name: Formatting)
2 - Randomized Voting Process (module name: RandomVotes)
3 - Calculations Step 1 (module name: Step1)
4 - Calculations Step 2 (module name: Step2)
Step 1
Note: you can skip this step and step2 if you already have the results of voting in the following format:
Kids is column A
A is column B
B is column C
C is column D
Your initial spreadsheet should look like the below screenshot
You can manually make it look like this although I have recorded a macro that formats your spreadsheet to the standards required for the macro to work properly. Copy-paste the below code to a new module and rename it(rename the module) to Formatting execute the below code(press F5 to execute)
Sub FormatSpreadsheet()
Application.ScreenUpdating = False
Cells.Select
With Selection.Font
.Name = "Consolas"
.Size = 10
.Strikethrough = False
.Superscript = False
.Subscript = False
.OutlineFont = False
.Shadow = False
.Underline = xlUnderlineStyleNone
.ThemeColor = xlThemeColorLight1
.TintAndShade = 0
.ThemeFont = xlThemeFontNone
End With
With Selection.Font
.Name = "Consolas"
.Size = 10
.Strikethrough = False
.Superscript = False
.Subscript = False
.OutlineFont = False
.Shadow = False
.Underline = xlUnderlineStyleNone
.ThemeColor = xlThemeColorLight1
.TintAndShade = 0
.ThemeFont = xlThemeFontNone
End With
Range("A1").Select
ActiveCell.FormulaR1C1 = "Kids"
Range("B1").Select
ActiveCell.FormulaR1C1 = "A"
Range("C1").Select
ActiveCell.FormulaR1C1 = "B"
Range("D1").Select
ActiveCell.FormulaR1C1 = "C"
Range("A2").Select
ActiveCell.FormulaR1C1 = "1"
Cells.Select
Selection.NumberFormat = "#"
Range("A2").Select
ActiveCell.FormulaR1C1 = "0001"
Range("A3").Select
ActiveCell.FormulaR1C1 = "0002"
Range("A4").Select
ActiveCell.FormulaR1C1 = "0003"
Range("A2:A4").Select
Selection.AutoFill Destination:=Range("A2:A47"), Type:=xlFillDefault
Range("A2:A47").Select
Range("B1:D1").Select
With Selection.Interior
.Pattern = xlSolid
.PatternColorIndex = xlAutomatic
.Color = 65535
.TintAndShade = 0
.PatternTintAndShade = 0
End With
With Selection.Interior
.Pattern = xlSolid
.PatternColorIndex = xlAutomatic
.ThemeColor = xlThemeColorAccent1
.TintAndShade = 0.399975585192419
.PatternTintAndShade = 0
End With
Columns("A:P").Select
With Selection
.HorizontalAlignment = xlGeneral
.VerticalAlignment = xlCenter
.WrapText = False
.Orientation = 0
.AddIndent = False
.IndentLevel = 0
.ShrinkToFit = False
.ReadingOrder = xlContext
.MergeCells = False
End With
With Selection
.HorizontalAlignment = xlCenter
.VerticalAlignment = xlCenter
.WrapText = False
.Orientation = 0
.AddIndent = False
.IndentLevel = 0
.ShrinkToFit = False
.ReadingOrder = xlContext
.MergeCells = False
End With
Range("B1:D1").Select
Selection.Copy
Range("F1").Select
ActiveSheet.Paste
Range("J1").Select
ActiveSheet.Paste
Range("N1").Select
ActiveSheet.Paste
Range("H7").Select
Application.CutCopyMode = False
Range("B:D,F:F,G:G,H:H,J:J,K:K,L:L,N:N,O:O,P:P").Select
Range("P1").Activate
Selection.Borders(xlDiagonalDown).LineStyle = xlNone
Selection.Borders(xlDiagonalUp).LineStyle = xlNone
With Selection.Borders(xlEdgeLeft)
.LineStyle = xlContinuous
.ColorIndex = xlAutomatic
.TintAndShade = 0
.Weight = xlThin
End With
With Selection.Borders(xlEdgeTop)
.LineStyle = xlContinuous
.ColorIndex = xlAutomatic
.TintAndShade = 0
.Weight = xlThin
End With
With Selection.Borders(xlEdgeBottom)
.LineStyle = xlContinuous
.ThemeColor = 1
.TintAndShade = -0.14996795556505
.Weight = xlThin
End With
With Selection.Borders(xlEdgeRight)
.LineStyle = xlContinuous
.ColorIndex = xlAutomatic
.TintAndShade = 0
.Weight = xlThin
End With
With Selection.Borders(xlInsideVertical)
.LineStyle = xlContinuous
.ColorIndex = xlAutomatic
.TintAndShade = 0
.Weight = xlThin
End With
With Selection.Borders(xlInsideHorizontal)
.LineStyle = xlContinuous
.ThemeColor = 1
.TintAndShade = -0.14996795556505
.Weight = xlThin
End With
Range("B1:D1,F1:H1,J1:L1,N1:P1").Select
Range("N1").Activate
Selection.Borders(xlDiagonalDown).LineStyle = xlNone
Selection.Borders(xlDiagonalUp).LineStyle = xlNone
With Selection.Borders(xlEdgeLeft)
.LineStyle = xlContinuous
.ColorIndex = 0
.TintAndShade = 0
.Weight = xlThin
End With
With Selection.Borders(xlEdgeTop)
.LineStyle = xlContinuous
.ColorIndex = 0
.TintAndShade = 0
.Weight = xlThin
End With
With Selection.Borders(xlEdgeBottom)
.LineStyle = xlContinuous
.ColorIndex = 0
.TintAndShade = 0
.Weight = xlThin
End With
With Selection.Borders(xlEdgeRight)
.LineStyle = xlContinuous
.ColorIndex = 0
.TintAndShade = 0
.Weight = xlThin
End With
With Selection.Borders(xlInsideVertical)
.LineStyle = xlContinuous
.ColorIndex = 0
.TintAndShade = 0
.Weight = xlThin
End With
With Selection.Borders(xlInsideHorizontal)
.LineStyle = xlContinuous
.ColorIndex = 0
.TintAndShade = 0
.Weight = xlThin
End With
Range("A1").Select
Selection.Borders(xlDiagonalDown).LineStyle = xlNone
Selection.Borders(xlDiagonalUp).LineStyle = xlNone
Selection.Borders(xlEdgeLeft).LineStyle = xlNone
Selection.Borders(xlEdgeTop).LineStyle = xlNone
With Selection.Borders(xlEdgeBottom)
.LineStyle = xlContinuous
.ColorIndex = 0
.TintAndShade = 0
.Weight = xlThin
End With
With Selection.Borders(xlEdgeRight)
.LineStyle = xlContinuous
.ColorIndex = 0
.TintAndShade = 0
.Weight = xlThin
End With
Selection.Borders(xlInsideVertical).LineStyle = xlNone
Selection.Borders(xlInsideHorizontal).LineStyle = xlNone
Range("E1").Select
ActiveCell.FormulaR1C1 = "1st choice"
Range("I1").Select
ActiveCell.FormulaR1C1 = "2nd choice"
Range("M1").Select
ActiveCell.FormulaR1C1 = "3rd choice"
Range("E:E,I:I,M:M").Select
Range("M1").Activate
Selection.ColumnWidth = 12.13
Range("E1:H1").Select
With Selection.Interior
.PatternColorIndex = xlAutomatic
.ThemeColor = xlThemeColorAccent3
.TintAndShade = -0.249977111117893
.PatternTintAndShade = 0
End With
Range("E1:H1").Select
With Selection.Interior
.Pattern = xlSolid
.PatternColorIndex = xlAutomatic
.ThemeColor = xlThemeColorAccent5
.TintAndShade = 0.399975585192419
.PatternTintAndShade = 0
End With
Range("I1:L1").Select
With Selection.Interior
.PatternColorIndex = xlAutomatic
.Color = 15773696
.TintAndShade = 0
.PatternTintAndShade = 0
End With
Range("E1:H1").Select
With Selection.Interior
.Pattern = xlSolid
.PatternColorIndex = xlAutomatic
.ThemeColor = xlThemeColorAccent4
.TintAndShade = 0.599993896298105
.PatternTintAndShade = 0
End With
Range("M1:P1").Select
With Selection.Interior
.PatternColorIndex = xlAutomatic
.Color = 13434879
.TintAndShade = 0
.PatternTintAndShade = 0
End With
Range("E1,I1,M1").Select
Range("M1").Activate
Selection.Borders(xlDiagonalDown).LineStyle = xlNone
Selection.Borders(xlDiagonalUp).LineStyle = xlNone
With Selection.Borders(xlEdgeLeft)
.LineStyle = xlContinuous
.ColorIndex = 0
.TintAndShade = 0
.Weight = xlThin
End With
Selection.Borders(xlEdgeTop).LineStyle = xlNone
With Selection.Borders(xlEdgeBottom)
.LineStyle = xlContinuous
.ColorIndex = 0
.TintAndShade = 0
.Weight = xlThin
End With
With Selection.Borders(xlEdgeRight)
.LineStyle = xlContinuous
.ColorIndex = 0
.TintAndShade = 0
.Weight = xlThin
End With
Selection.Borders(xlInsideVertical).LineStyle = xlNone
Selection.Borders(xlInsideHorizontal).LineStyle = xlNone
Range("A1").Select
Application.ScreenUpdating = True
End Sub
You spreadsheet now should like like the below screenshot
Note: column A goes down to number 0046 (row 47) so, if you have more kids then add more numbers before continuing.
Step 2
Add a new Module and name it RandomVotes
Copy-Paste and then execute (F5) the code to get results.
The code will simulate a voting process and print results in columns B to D:
Sub RandomizeVotes()
Application.ScreenUpdating = False
Dim i As Long, j As Long
Dim r As Range, nxtRnd As Long
Dim rowComplete As Boolean
For i = 2 To Range("A" & Rows.Count).End(xlUp).Row
Set r = Range("B" & i)
r = GetRandom
Do Until rowComplete
r.Offset(0, 1) = GetRandom
r.Offset(0, 2) = GetRandom
If r <> r.Offset(0, 1) And r <> r.Offset(0, 2) And r.Offset(0, 1) <> r.Offset(0, 2) Then rowComplete = True
Loop
Set r = Nothing
rowComplete = False
Next i
Application.ScreenUpdating = True
End Sub
Function GetRandom() As Long
Randomize
Dim x As Double
x = Rnd
If x < 0.3 Then
GetRandom = 1
ElseIf x >= 0.3 And x < 0.6 Then
GetRandom = 2
ElseIf x >= 0.6 Then
GetRandom = 3
End If
End Function
At this point, go back to your spreadsheet it should give you the following results:
Note: I said you can skip this step if you already have your voting results in the format specified above. I would recommend following all steps just to see how things work.
Step3
Add a new Module, name it Step1.
Copy-Paste the below code and again: execute it.
This code will populate columns F:P based on kids choices
Option Explicit
' Choices columns
Sub Step_1()
Dim i As Long
Dim r As Range
For i = 2 To Range("A" & Rows.Count).End(xlUp).Row
Set r = Range("B" & i)
' first choices
If r = 1 Then
r.Offset(0, 4) = r.Offset(0, -1).Text
ElseIf r.Offset(0, 1) = 1 Then
r.Offset(0, 5) = r.Offset(0, -1).Text
ElseIf r.Offset(0, 2) = 1 Then
r.Offset(0, 6) = r.Offset(0, -1).Text
End If
' second choices
If r = 2 Then
r.Offset(0, 8) = r.Offset(0, -1).Text
ElseIf r.Offset(0, 1) = 2 Then
r.Offset(0, 9) = r.Offset(0, -1).Text
ElseIf r.Offset(0, 2) = 2 Then
r.Offset(0, 10) = r.Offset(0, -1).Text
End If
' third choices
If r = 3 Then
r.Offset(0, 12) = r.Offset(0, -1).Text
ElseIf r.Offset(0, 1) = 3 Then
r.Offset(0, 13) = r.Offset(0, -1).Text
ElseIf r.Offset(0, 2) = 3 Then
r.Offset(0, 14) = r.Offset(0, -1).Text
End If
Set r = Nothing
Next i
deleteEmpties
End Sub
Private Sub deleteEmpties()
Application.ScreenUpdating = False
Dim i As Long, j As Long
For i = Range("A" & Rows.Count).End(xlUp).Row To 2 Step -1
For j = 16 To 6 Step -1
If IsEmpty(Cells(i, j)) Then Cells(i, j).Delete Shift:=xlUp
Next j
Next i
Application.ScreenUpdating = False
End Sub
The result should look similar to the below screenshot (if you have randomized choices than it will look different)
Step 4
Add a new Module, name it Step2.
Copy-Paste the below code and again: execute it.
This code will re-populate columns F:H. This pretty much (and hopefully ;) ) achieves what you were looking for.
At this point, your column F:H are sorted by kids numbers. To add more although intentional randomness to the process you can re-sort the numbers. For example instead of
0002
0005
0010
0013
0017
0021
0022
0025
0026
0038
0043
you can do
0038
0005
0026
0013
0017
0022
0021
0002
0010
0025
0043
You will see what I mean when we get to the algorithm that will even out the groups.
My solution to even out the groups of kids:
find out roughly how many kids per group ( total / 3 )
find group with the highest preferred count
get the first in the list [starting from the end of the list] (thats why randomizing columns order may be a good idea)
find kid's second choice and move him to that column
for example:
Since the group B is the highest preferred group we need to move some people off of it in order to even out the other ones.
Each time we have to check the size of groups. Once they come close to each other we stop moving kids around.
Take the first kid 0001 and check whether his 2nd choice is the lowest group. If it's a false then we move to the next one, and keep moving until we find one kid who's second choice is the lowest group (A in my example ).
'0011' and '0012' match our criteria so we can move them to the lowest group.
Checking for the length of the size of the most preferred group again.
and so on results in this Step2 Module code:
Option Explicit
Type Group
Name As String
Column As String
Size As Long
End Type
Type Number
Total As Long
Average As Long
HiBound As Long
LoBound As Long
End Type
Type Child
Id As String
Choice1 As String
Choice2 As String
Choice3 As String
End Type
Public A As Group
Public B As Group
Public C As Group
' moving based on the second preference
Sub Step_2()
Dim T As Number
A.Name = "A"
A.Column = "F"
A.Size = Range("F" & Rows.Count).End(xlUp).Row
B.Name = "B"
B.Column = "G"
B.Size = Range("G" & Rows.Count).End(xlUp).Row
C.Name = "C"
C.Column = "H"
C.Size = Range("H" & Rows.Count).End(xlUp).Row
T.Total = Range("A" & Rows.Count).End(xlUp).Row
T.Average = T.Total / 3
T.HiBound = T.Average + 1
T.LoBound = T.Average - 1
Dim i As Long, j As Long, k As Long
Dim kidChoice As Range, kidId As Range
For i = Range("" & getBiggest.Column & "" & Rows.Count).End(xlUp).Row To 2 Step -1
A.Size = Range("F" & Rows.Count).End(xlUp).Row
B.Size = Range("G" & Rows.Count).End(xlUp).Row
C.Size = Range("H" & Rows.Count).End(xlUp).Row
If Range("" & getBiggest.Column & "" & Rows.Count).End(xlUp).Row = T.Average Or _
Range("" & getSmallest.Column & "" & Rows.Count).End(xlUp).Row = T.Average _
Then
Exit For
Else
For k = Range("A" & Rows.Count).End(xlUp).Row To 2 Step -1
Set kidChoice = Range("" & getBiggest.Column & "" & i)
Set kidId = Range("A" & k)
Dim kid As Child
kid.Id = kidId.Text
kid.Choice1 = getBiggest.Name
If StrComp(kidChoice.Text, kidId.Text, 1) = 0 Then
For j = 1 To 3
If kidId.Offset(0, j) = 2 Then
kid.Choice2 = Cells(1, j + 1).Text
End If
If kidId.Offset(0, j) = 3 Then
kid.Choice3 = Cells(1, j + 1).Text
End If
Next j
If kid.Choice2 = getSmallest.Name Then
' transfer groups
Dim nxtSmall As Long
nxtSmall = Range("" & getSmallest.Column & "" & Rows.Count).End(xlUp).Row + 1
Range("" & getSmallest.Column & "" & nxtSmall).Value = kid.Id
kidChoice.Delete Shift:=xlUp
End If
End If
Set kidId = Nothing
Next k
Set kidChoice = Nothing
End If
Next i
End Sub
Private Function getBiggest() As Group
If A.Size > B.Size And A.Size > C.Size Then
getBiggest = A
ElseIf B.Size > A.Size And B.Size > C.Size Then
getBiggest = B
ElseIf C.Size > A.Size And C.Size > B.Size Then
getBiggest = C
ElseIf A.Size = B.Size Or A.Size = C.Size Then
getBiggest = A
ElseIf B.Size = A.Size Or B.Size = C.Size Then
getBiggest = B
ElseIf C.Size = A.Size Or C.Size = B.Size Then
getBiggest = C
End If
End Function
Private Function getSmallest() As Group
If A.Size < B.Size And A.Size < C.Size Then
getSmallest = A
ElseIf B.Size < A.Size And B.Size < C.Size Then
getSmallest = B
ElseIf C.Size < A.Size And C.Size < B.Size Then
getSmallest = C
ElseIf A.Size = B.Size Or A.Size = C.Size Then
getSmallest = A
ElseIf B.Size = A.Size Or B.Size = C.Size Then
getSmallest = B
ElseIf C.Size = A.Size Or C.Size = B.Size Then
getSmallest = C
End If
End Function
Final result
And the final result of the equating the groups of kids preferred choices:
I really hope this helps!
Summary
If your sheet already looks like
then run Step_1 and then Step_2
I have ran this a few times for testing purposes, here are some sample results
Your sample
Random Votes + primary split into columns . Obviously, it isn't printing exactly the same results as you provided in your sample. You have already said there is no perfect solution. its ran on only 11 kids and you have said you have 100+. I think it does the job though and functions as expected
executed Step_1
Result
Sample 1
Random Votes + primary split into columns
executed Step_1
Result
Sample 2
Random Votes + primary split into columns
executed Step_1
Result
Sample 3
Random Votes + primary split into columns
executed Step_1
Result

Related

Doing a loop from 1 to LastRow

I've written the code below to do a loop witch I have used in the past, I now however want to do switch the loop.
If a cell in column Q contains a 1 then it adds a row with a certain layout. The code now goes from Q3276 to Q8, how do I reverse the process Preferably I want the loop to go rom Q8 to Q LastRow. Also if anyone has a more lean way of writing the code please let me know.
Dim rngc As Range, rc As Long
Set rngc = Range("Q8:Q3276")
For rc = rngc.Count To 1 Step -1
If rngc(rc).Value = 1 Then
rngc(rc + 1).EntireRow.Insert
rngc(rc + 1).EntireRow.Select
With Selection.Interior
.Pattern = xlSolid
.PatternColorIndex = xlAutomatic
.ThemeColor = xlThemeColorAccent1
.TintAndShade = 0.599993896298105
.PatternTintAndShade = 0
End With
Selection.Borders(xlDiagonalDown).LineStyle = xlNone
Selection.Borders(xlDiagonalUp).LineStyle = xlNone
With Selection.Borders(xlEdgeLeft)
.LineStyle = xlContinuous
.ColorIndex = 0
.TintAndShade = 0
.Weight = xlThin
End With
With Selection.Borders(xlEdgeTop)
.LineStyle = xlContinuous
.ColorIndex = 0
.TintAndShade = 0
.Weight = xlThin
End With
With Selection.Borders(xlEdgeBottom)
.LineStyle = xlContinuous
.ColorIndex = 0
.TintAndShade = 0
.Weight = xlThin
End With
With Selection.Borders(xlEdgeRight)
.LineStyle = xlContinuous
.ColorIndex = 0
.TintAndShade = 0
.Weight = xlThin
End With
Selection.Borders(xlInsideVertical).LineStyle = xlNone
Selection.Borders(xlInsideHorizontal).LineStyle = xlNone
Range("A35").Select
End If
Next rc
Preferably I want the loop to go rom Q8 to Q LastRow.
To reverse a loop, you can use For rc = 1 to rngc.Count. Note that this will complicate what you are trying to do.
Also if anyone has a more lean way of writing the code please let me know.
Avoid using Select/Selection etc
Use Autofilter. This way no loops will be required and you can work with filtered rows in ONE GO
The border constants range form 5 to 12. What I mean is that the value of xlDiagonalDown is 5 and so on till xlInsideHorizontal which has a value of 12. In such a case we can use a Loop/Select Case to format the borders/cells as shown below
I have commented the code so you should not have a problem understanding it.
Option Explicit
Sub Sample()
Dim ws As Worksheet
Dim lRow As Long
Dim rng As Range
Dim filteredRange As Range
Dim i As Long
'~~> Change this to the relevant sheet
Set ws = Sheet1
With ws
'~~> Remove any filters
.AutoFilterMode = False
'~~> Find last row in Col Q
lRow = .Range("Q" & .Rows.Count).End(xlUp).Row
'~~> Set your range
Set rng = .Range("Q8:Q" & lRow)
'~~> Filter the range and set your filtered range
With rng
.AutoFilter Field:=1, Criteria1:="=1"
Set filteredRange = .Offset(1, 0).SpecialCells(xlCellTypeVisible).EntireRow
End With
'~~> Check if we have any filtered rows
If Not filteredRange Is Nothing Then
With filteredRange
'~~> Change interior color
With .Interior
.Pattern = xlSolid
.PatternColorIndex = xlAutomatic
.ThemeColor = xlThemeColorAccent1
.TintAndShade = 0.599993896298105
.PatternTintAndShade = 0
End With
'~~> Format the borders
For i = 5 To 12
Select Case i
'~~> Left, Top, Bottom, Right
Case 7 To 10
With .Borders(i)
.LineStyle = xlContinuous
.ColorIndex = 0
.TintAndShade = 0
.Weight = xlThin
End With
'~~> DiagUp,DiagDown,InsideVert,InsideHorz
Case 5, 6, 11, 12
.Borders(i).LineStyle = xlNone
End Select
Next i
End With
End If
'~~> Remove filters
.AutoFilterMode = False
End With
End Sub

Selecting and Formatting Borders in VBA

I'm trying to add thick borders on the left and right side every five columns, going all the way to the last row of the table (there are no blanks in any rows).
My code only adds the borders on the first and second rows.
I use following line twice:
Range(Selection, Selection.End(xlDown)).Select
Here's what a portion of the spreadsheet looks like. Note that the first row is a merged cell, and in the second row are table headers.
Application.DisplayAlerts = False
Dim lastCol As Integer
lastCol = ws.Cells(2, ws.Columns.Count).End(xlToLeft).Column
For i = 2 To lastCol Step 5
Range(Cells(1, i), Cells(1, i + 4)).Select
With Selection
.HorizontalAlignment = xlCenter
.VerticalAlignment = xlCenter
.WrapText = False
.Orientation = 0
.AddIndent = False
.IndentLevel = 0
.ShrinkToFit = False
.ReadingOrder = xlContext
.MergeCells = False
End With
Selection.Merge
With Selection.Interior
.Pattern = xlSolid
.PatternColorIndex = xlAutomatic
.ThemeColor = xlThemeColorAccent1
.TintAndShade = 0
.PatternTintAndShade = 0
End With
With Selection.Font
.ThemeColor = xlThemeColorDark1
.TintAndShade = 0
End With
'Add thick borders
Range(Selection, Selection.End(xlDown)).Select
Range(Selection, Selection.End(xlDown)).Select 'here's where I'm struggling
Selection.Borders(xlDiagonalDown).LineStyle = xlNone
Selection.Borders(xlDiagonalUp).LineStyle = xlNone
With Selection.Borders(xlEdgeLeft)
.LineStyle = xlContinuous
.ColorIndex = xlAutomatic
.TintAndShade = 0
.Weight = xlThick
End With
Selection.Borders(xlEdgeTop).LineStyle = xlNone
Selection.Borders(xlEdgeBottom).LineStyle = xlNone
With Selection.Borders(xlEdgeRight)
.LineStyle = xlContinuous
.ColorIndex = xlAutomatic
.TintAndShade = 0
.Weight = xlThick
End With
Selection.Borders(xlInsideVertical).LineStyle = xlNone
Selection.Borders(xlInsideHorizontal).LineStyle = xlNone
Next i
Application.DisplayAlerts = True
The picture appears to be a list object (structured table in Excel). If so, you can work directly with the table object in VBA and avoid a ton of code writing and logic building.
Dim t as ListObject
Set t = ws.ListObjects("myTable")
Dim i as Long
For i = 1 to t.ListColumns.Count Step 5
With t.ListColumns(i).Range.Resize(t.ListRows.Count,5).Borders(xlEdgeLeft)
.LineStyle = xlContinuous
.ColorIndex = xlAutomatic
.TintAndShade = 0
.Weight = xlThick
End With
'same for right border
Next

EXCEL VBA macro for putting a style to a range and then ucase

i'm newbie, sorry in advance for myy long question
So, i have two macros (one recorded and pasted in personal macro) and other i found in google
The first one, with my selection fills the color to orange and adds bolds borders
The second one with the selection, upercases all the range.
However, when i run this two macro together with another sub (calling the subs) the text does not shows up, i need to change of cell then select again and run the macro again in order to function.
Sub text ()
Dim rng As Range
Dim sAddr As String
Set rng = Selection
Selection.Merge
ActiveCell.FormulaR1C1 = _
"=""additional due for "" & TEXT(TODAY(),""MMMM "") & ""end of month"""
sAddr = rng.Address
rng = Evaluate("index(upper(" & sAddr & "),)")
Selection.NumberFormat = "General"
End Sub
Then the filling up sub (which is a little long)
Sub ORANGE()
Selection.Font.Bold = True
With Selection.Interior
.Pattern = xlSolid
.PatternColorIndex = xlAutomatic
.Color = 49407
.TintAndShade = 0
.PatternTintAndShade = 0
End With
Selection.Borders(xlDiagonalDown).LineStyle = xlNone
Selection.Borders(xlDiagonalUp).LineStyle = xlNone
With Selection.Borders(xlEdgeLeft)
.LineStyle = xlContinuous
.ColorIndex = 0
.TintAndShade = 0
.Weight = xlMedium
End With
With Selection.Borders(xlEdgeTop)
.LineStyle = xlContinuous
.ColorIndex = 0
.TintAndShade = 0
.Weight = xlMedium
End With
With Selection.Borders(xlEdgeBottom)
.LineStyle = xlContinuous
.ColorIndex = 0
.TintAndShade = 0
.Weight = xlMedium
End With
With Selection.Borders(xlEdgeRight)
.LineStyle = xlContinuous
.ColorIndex = 0
.TintAndShade = 0
.Weight = xlMedium
End With
Selection.Borders(xlInsideVertical).LineStyle = xlNone
Selection.Borders(xlInsideHorizontal).LineStyle = xlNone
With Selection
.HorizontalAlignment = xlCenter
.VerticalAlignment = xlCenter
'CAMBIO 2
.WrapText = False
.Orientation = 0
.AddIndent = False
.IndentLevel = 0
.ShrinkToFit = False
.ReadingOrder = xlContext
.MergeCells = False
End With
Selection.Borders(xlDiagonalDown).LineStyle = xlNone
Selection.Borders(xlDiagonalUp).LineStyle = xlNone
With Selection.Borders(xlEdgeLeft)
.LineStyle = xlContinuous
.ColorIndex = 0
.TintAndShade = 0
.Weight = xlMedium
End With
With Selection.Borders(xlEdgeTop)
.LineStyle = xlContinuous
.ColorIndex = 0
.TintAndShade = 0
.Weight = xlMedium
End With
With Selection.Borders(xlEdgeBottom)
.LineStyle = xlContinuous
.ColorIndex = 0
.TintAndShade = 0
.Weight = xlMedium
End With
With Selection.Borders(xlEdgeRight)
.LineStyle = xlContinuous
.ColorIndex = 0
.TintAndShade = 0
.Weight = xlMedium
End With
Selection.Borders(xlInsideVertical).LineStyle = xlNone
Selection.Borders(xlInsideHorizontal).LineStyle = xlNone
Selection.NumberFormat = "General"
End Sub
The way i use both macros is simply calling first ORANGE and then TEXT, beacuse the other way does not works, when i try them in VBA run macro option it works fine.
When i use the button in the ribbon i need to change of cell, select it again and it will work. i use this macro a lot but it simply makes me repeat it each time.
Does anybody knows who to perform both task at once without the result being an empty orange cell?
Thanks!
Try this. Read comments inside the code:
Public Sub AddTextAndFormat()
Dim selectedRange As Range
Set selectedRange = Selection
' Merges the selection
selectedRange.Merge
' Adds the formula to the first selection's cell
selectedRange.Formula = "=""additional due for "" & TEXT(TODAY(),""MMMM "") & ""end of month"""
' Uppercase that first cell
selectedRange.Cells(1, 1).Value = UCase$(selectedRange.Cells(1, 1).Value)
' Apply formats
With selectedRange
.Font.Bold = True
' Borders:
.BorderAround LineStyle:=xlContinuous, ColorIndex:=0, Weight:=xlMedium
.Borders(xlDiagonalDown).LineStyle = xlNone
.Borders(xlDiagonalUp).LineStyle = xlNone
.Borders(xlInsideVertical).LineStyle = xlNone
.Borders(xlInsideHorizontal).LineStyle = xlNone
' Other format:
.HorizontalAlignment = xlCenter
.VerticalAlignment = xlCenter
'CAMBIO 2
.WrapText = False
.Orientation = 0
.AddIndent = False
.IndentLevel = 0
.ShrinkToFit = False
.ReadingOrder = xlContext
'.MergeCells = False -> This line unmerges the first cells merge
.NumberFormat = "General"
End With
With selectedRange.Interior
.Pattern = xlSolid
.PatternColorIndex = xlAutomatic
.Color = 49407
.TintAndShade = 0
.PatternTintAndShade = 0
End With
End Sub

How to create a button which adds new 'cards' on my spreadsheet?

I am creating a card based database system and I want to use a button to basically be able to new cards, as seen here.
I have already created a button and assigned a macro to it, which when clicked adds a new row of these 'cards'. However, I need my macro to be dynamic whereby the new cards are always added 3 rows down from the previous row of cards. How can this be done?
Here is my code for the macro:
Range("B66:F75").Select
With Selection.Interior
.Pattern = xlSolid
.PatternColorIndex = xlAutomatic
.ThemeColor = xlThemeColorAccent5
.TintAndShade = 0.799981688894314
.PatternTintAndShade = 0
End With
Range("B66:F75").Select
Range("F75").Activate
Selection.Borders(xlDiagonalDown).LineStyle = xlNone
Selection.Borders(xlDiagonalUp).LineStyle = xlNone
With Selection.Borders(xlEdgeLeft)
.LineStyle = xlContinuous
.ColorIndex = 0
.TintAndShade = 0
.Weight = xlThin
End With
With Selection.Borders(xlEdgeTop)
.LineStyle = xlContinuous
.ColorIndex = 0
.TintAndShade = 0
.Weight = xlThin
End With
With Selection.Borders(xlEdgeBottom)
.LineStyle = xlContinuous
.ColorIndex = 0
.TintAndShade = 0
.Weight = xlThin
End With
With Selection.Borders(xlEdgeRight)
.LineStyle = xlContinuous
.ColorIndex = 0
.TintAndShade = 0
.Weight = xlThin
End With
Selection.Borders(xlInsideVertical).LineStyle = xlNone
Selection.Borders(xlInsideHorizontal).LineStyle = xlNone
Range("B66").Select
Selection.Font.Bold = True
ActiveCell.FormulaR1C1 = "Name:"
Range("B67").Select
Selection.Font.Bold = True
ActiveCell.FormulaR1C1 = "Email:"
Range("B68").Select
Selection.Font.Bold = True
ActiveCell.FormulaR1C1 = "Institution:"
Range("B70").Select
Selection.Font.Bold = True
ActiveCell.FormulaR1C1 = "Research Focus:"
Range("B73").Select
Selection.Font.Bold = True
ActiveCell.FormulaR1C1 = "Expertise:"
Range("B75").Select
Selection.Font.Bold = True
ActiveCell.FormulaR1C1 = "Relevant Links:"
Range("B66:F75").Select
Selection.Copy
Range("H66").Select
ActiveSheet.Paste
Range("N66").Select
ActiveSheet.Paste
Range("W68").Select
I presume what needs to change is the range, to make it dynamic.
OP mentioned in comments that it can start from a blank sheet. So here is my solution.
I assume the entire spreadsheet if filled with the medium blue color so the code does not add that.
Option Explicit
Sub CreatingCards()
'Basic idea is that we will create a base row and then copy paste it "x" times.
Dim TotalRows As Long 'How many rows of cards to generate
Dim lRow As Long 'Used to keep track of the last row of text
Dim p As Long 'Used for looping
TotalRows = 4
With ActiveSheet.Range("B6:F15")
.Interior.ThemeColor = xlThemeColorAccent5
.Interior.TintAndShade = 0.799981688894314
.BorderAround Weight:=xlThin
End With
'Add Words
ActiveSheet.Range("B6").Value = "Name:"
ActiveSheet.Range("B7").Value = "Email:"
ActiveSheet.Range("B8").Value = "Institution:"
ActiveSheet.Range("B10").Value = "Research Focus:"
ActiveSheet.Range("B13").Value = "Expertise:"
ActiveSheet.Range("B15").Value = "Releveant Links:"
'Bold Headers
ActiveSheet.Range("B6").Font.Bold = True
ActiveSheet.Range("B7").Font.Bold = True
ActiveSheet.Range("B8").Font.Bold = True
ActiveSheet.Range("B10").Font.Bold = True
ActiveSheet.Range("B13").Font.Bold = True
ActiveSheet.Range("B15").Font.Bold = True
'Generate the other two cards in the row
ActiveSheet.Range("B6:F15").Copy
ActiveSheet.Range("H6").PasteSpecial xlPasteAll
ActiveSheet.Range("N6").PasteSpecial xlPasteAll
For p = 1 To TotalRows - 1 'Because we generated the first row of cards already
lRow = ActiveSheet.Cells.Find("*", searchorder:=xlByRows, searchdirection:=xlPrevious).Row
'Defines lRow as the last row with text in it.
Range("B6:R15").Copy
Range("B" & lRow + 3).PasteSpecial xlPasteAll 'Putting +3 allows for two blank rows between each card.
Next p
End Sub

Using Excel VB to change imported data from Access colour coded accordingly

What I'm trying to do is change specific range cells to specific colours.
It works that When there is an increase and when it is more than -2.00% that it should be red. However, when it is decreasing from the previous time it should be green, and once it is below -2.00% it should go black again.
So basically The cell with data in starts at C2 and ends at H54.
It works in a row format where like C2 is the main then D2 is continue data, etc. C3 is a new main data and D3 is the continue of that data, etc.
My code that I have been testing but not getting right is as follows:
Range("C2").Select
If Range("C2").Value >= "-2.00%" Then
With Selection.Font
.Color = -16776961
.TintAndShade = 0
End With
ElseIf Range("C2").Value < "-2.00%" Then
With Selection.Font
.ThemeColor = xlThemeColorLight1
.TintAndShade = 0
End With
End If
Range("D2").Select
If Range("D2").Value <= "-2.00%" & Range("C2").Value Then
With Selection.Font
.Color = -11489280
.TintAndShade = 0
End With
ElseIf Range("D2").Value > "-2.00%" & Range("C2").Value Then
With Selection.Font
.Color = -16776961
.TintAndShade = 0
End With
ElseIf Range("D2").Value < "-2.00%" Then
With Selection.Font
.ThemeColor = xlThemeColorLight1
.TintAndShade = 0
End With
End If
Range("E2").Select
If Range("E2").Value <= "-2.00%" & Range("D2").Value Then
With Selection.Font
.Color = -11489280
.TintAndShade = 0
End With
ElseIf Range("E2").Value > "-2.00%" & Range("D2").Value Then
With Selection.Font
.Color = -16776961
.TintAndShade = 0
End With
ElseIf Range("E2").Value < "-2.00%" Then
With Selection.Font
.ThemeColor = xlThemeColorLight1
.TintAndShade = 0
End With
End If
However, when it goes to below 2.00% it still is green, and same error stays even when it increase again...
I will appreciate any assistance in getting this done asap... If you know of a shorter method please put it down for me to test it out.
Thank you very much for taking the time to review this.
Here is a sample picture of the result and what it actually should be:
This seems to follow your business logic as I perceive it from the code and sample image(s).
Sub ject()
Dim r As Long, c As Long, vRTRNs As Variant, thrshld As Double
thrshld = 0.02
With Worksheets("Sheet2")
With .Cells(1, 1).CurrentRegion
With .Resize(.Rows.Count - 1, .Columns.Count - 2).Offset(1, 2)
.Cells.Font.ColorIndex = xlColorIndexAutomatic
vRTRNs = .Value2
For r = LBound(vRTRNs, 1) To UBound(vRTRNs, 1)
'deal with the first value
If vRTRNs(r, LBound(vRTRNs, 2)) >= thrshld Then
.Cells(r, 1).Font.Color = vbRed
End If
'the remainder of the columns in the row
For c = LBound(vRTRNs, 2) + 1 To UBound(vRTRNs, 2)
Select Case vRTRNs(r, c)
Case Is >= thrshld
.Cells(r, c).Font.Color = _
IIf(vRTRNs(r, c) >= vRTRNs(r, c - 1), vbRed, vbGreen)
Case Is < thrshld
.Cells(r, c).Font.ColorIndex = xlColorIndexAutomatic
End Select
Next c
Next r
End With
End With
End With
End Sub
Results:
    

Resources