Comparing worksheets in Excel - my range is not matching my array - vba

I want to compare three worksheets (which should be identical) in a workbook and highlight any non-matching cells. I've based the following code on Using VBA to compare two Excel workbooks:
Sub CompareWorksheets()
Dim varSheetA As Worksheet
Dim varSheetB As Worksheet
Dim varSheetC As Worksheet
Dim varSheetAr As Variant
Dim varSheetBr As Variant
Dim varSheetCr As Variant
Dim strRangeToCheck As String
Dim iRow As Long
Dim iCol As Long
Set varSheetA = Worksheets("DS")
Set varSheetB = Worksheets("HT")
Set varSheetC = Worksheets("NM")
strRangeToCheck = ("A1:L30")
' If you know the data will only be in a smaller range, reduce the size of the ranges above.
varSheetAr = varSheetA.Range(strRangeToCheck).Value
varSheetBr = varSheetB.Range(strRangeToCheck).Value
varSheetCr = varSheetC.Range(strRangeToCheck).Value ' or whatever your other sheet is.
For iRow = LBound(varSheetAr, 1) To UBound(varSheetAr, 1)
For iCol = LBound(varSheetAr, 2) To UBound(varSheetAr, 2)
Debug.Print iRow, iCol
If varSheetAr(iRow, iCol) = varSheetBr(iRow, iCol) And varSheetAr(iRow, iCol) = varSheetCr(iRow, iCol) Then
varSheetA.Cells(iRow, iCol).Interior.ColorIndex = xlNone
varSheetB.Cells(iRow, iCol).Interior.ColorIndex = xlNone
varSheetC.Cells(iRow, iCol).Interior.ColorIndex = xlNone
Else
varSheetA.Cells(iRow, iCol).Interior.ColorIndex = 22
varSheetB.Cells(iRow, iCol).Interior.ColorIndex = 22
varSheetC.Cells(iRow, iCol).Interior.ColorIndex = 22
End If
Next
Next
End Sub
The problem is, when "strRangeToCheck" starts at A1, everything works as it should, but as soon as I change the range to something like ("B4:C6"), it looks like the correct comparisons are still being made, but the cells that get highlighted always get shifted back up to cell A1 as the starting point (as opposed to B4, which is what I want). In other words, the highlighting "pattern" is correct, but shifted up and over a few cells.

I expanded on #Vityata example.
CompareWorksheets compares the same range on up to up to 60 Worksheets, whereas CompareRanges will compare ranges of the same size and shape.
Sub Test_Comparisons()
CompareWorksheets "A1:L30", Worksheets("DS"), Worksheets("HT"), Worksheets("NM")
CompareRanges Worksheets("DS").Range("A1:L30"), Worksheets("HT").Range("K11:V40"), Worksheets("NM").Range("A101:L130")
End Sub
Sub CompareWorksheets(CompareAddress As String, ParamArray arrWorkSheets() As Variant)
Application.ScreenUpdating = False
Dim cell As Range
Dim x As Long
Dim bFlag As Boolean
'Reset all the colors
For x = 0 To UBound(arrWorkSheets)
arrWorkSheets(x).Range(CompareAddress).Interior.ColorIndex = xlNone
Next
For Each cell In arrWorkSheets(0).Range(CompareAddress)
bFlag = False
For x = 1 To UBound(arrWorkSheets)
If arrWorkSheets(x).Range(cell.ADDRESS).Value <> cell.Value Then
bFlag = True
Exit For
End If
Next
If bFlag Then
For x = 0 To UBound(arrWorkSheets)
arrWorkSheets(x).Range(cell.ADDRESS).Interior.ColorIndex = 22
Next
End If
Next
Application.ScreenUpdating = True
End Sub
Sub CompareRanges(ParamArray arrRanges() As Variant)
Application.ScreenUpdating = False
Dim cell As Range
Dim x As Long, y As Long, z As Long
Dim bFlag As Boolean
'Reset all the colors
For z = 0 To UBound(arrRanges)
arrRanges(z).Interior.ColorIndex = xlNone
Next
For x = 1 To arrRanges(0).Rows.Count
For y = 1 To arrRanges(0).Rows.Count
For z = 1 To UBound(arrWorkSheets)
If arrWorkSheets(1).Cells(x, y).Value <> arrWorkSheets(z).Cells(x, y).Value Then
bFlag = True
Exit For
End If
Next
If bFlag Then
For z = 0 To UBound(arrWorkSheets)
arrWorkSheets(z).Cells(x, y).Interior.ColorIndex = 22
Next
End If
Next
Next
Application.ScreenUpdating = True
End Sub

What I have understood from the first reading, is that you have 3 worksheets which you want to compare. This code works, if you want to compare a selected range in the first three worksheets in a workbook. It colors the different values in red, in each workbook:
Option Explicit
Sub compareWorksheets()
Dim rngCell As Range
Dim counter As Long
For Each rngCell In Selection
If Worksheets(1).Range(rngCell.Address) <> Worksheets(2).Range(rngCell.Address) _
Or Worksheets(1).Range(rngCell.Address) <> Worksheets(3).Range(rngCell.Address) Then
For counter = 1 To 3
Worksheets(counter).Range(rngCell.Address).Interior.Color = vbRed
Next counter
End If
Next rngCell
End Sub
If you want to compare a range A1:Z10 in the three worksheets, change the words Selection with Worksheets(1).Range("A1:Z10") or simply select the range in a one workbook.

Related

Excel VBA: Locking range of cells

Im trying to lock a few ranges of cells to prevent them from being altered outside of the button press.
I have the following code so far:
Private Sub DateRangePayer()
Dim unionRange As Range, uRng As Range, EssentialWrite As Range, chCell As Range, chRng As Range
Dim d As Long, k As Long, x As Long
ActiveSheet.Unprotect
Set EssentialWrite = Sheets("Essential Info").Range("E2:E6")
Set unionRange = ActiveSheet.Range("Q8:R12, T8:T12, Q16:R20, T16:T20")
Set chRng = ActiveSheet.Range("Q8:R12, T8:T12, Q16:R20, T16:T20")
x = Sheets("Essential Info").Range("G19").Value
ReDim OArr(1 To 5, 1 To 1) As Variant
For d = DateSerial(Year(x), Month(x), 1) To DateSerial(Year(x), Month(x) + 1, 0) - 1
If Weekday(d, vbSunday) = 7 Then
k = k + 1
OArr(k, 1) = d
End If
Next d
If k = 4 Then OArr(k + 1, 1) = "-"
For Each uRng In unionRange.Areas
uRng.Value = OArr
uRng.NumberFormat = "dd-mmmm"
Next uRng
For Each chCell In chRng.Cells
chCell.MergeArea.Locked = (chCell.Value <> "")
Next chCell
EssentialWrite.Value = OArr
EssentialWrite.NumberFormat = "dd-mmmm"
ActiveSheet.Protect
End Sub
The main parts of the code are the
ActiveSheet.Unprotect
For Each chCell In chRng.Cells
chCell.MergeArea.Locked = (chCell.Value <> "") Next chCell
ActiveSheet.Protect
Currently the code executes with zero errors. However the range of cells is not locked at all and is actually editable in its entirety.
Im doing this to prevent unexpected user entries in the specified cells
Any advice on what may work.
Im sorry if the code is a little messy. Im kinda just hacking together at this point and relatively new to this
This code locks only the code that say LOCKED in the image below.
Sub lockCells()
Dim ws As Worksheet
Set ws = Sheets("Sheet1")
ws.Cells.Locked = False
Dim rng As Range
Set rng = ws.Range("A1:A10")
Dim cell As Range
For Each cell In rng
cell.Locked = cell.Value <> ""
Next cell
ws.Protect 1234
End Sub
I'm not sure this is 'best practice' but I'd use:
chCell.Cells(1, 1).Locked = (chCell.Value <> "")

Using VBA to copy a (fixed range) iterated column in Sheet1 to a range of columns in Sheet2

I have a range of numbers in Sheet1 (AG6:AG25) that contain the RAND() function, and I'm looking to iterate the outcomes for each via pasting transposed values into Sheet2 on a new row for each iteration.
Solved as follows:
Sub MonteCarlo()
Dim transposedVariant As Variant
Dim sourceRowRange As Range
Dim sourceRowRangeVariant As Variant
Dim rangeFilledWithTransposedData As Range
Dim i As Long
Application.ScreenUpdating = False
For i = 1 To 5000
Set sourceRowRange = Sheets("Sheet1").Range("AG6:AG25")
sourceRowRangeVariant = sourceRowRange.Value
transposedVariant = Application.Transpose(sourceRowRangeVariant)
Set rangeFilledWithTransposedData = Sheets("Sheet2").Range("A" & i & ":T" & i)
rangeFilledWithTransposedData.Value = transposedVariant
Next i
Application.ScreenUpdating = True
End Sub
Please find the code. I hope it will help you.
Sub simulation()
Dim i As Long
Dim j As Long
For i = 1 To 1000
For j = 1 To 20
'Calculate
Sheet1.Cells(j, 1) = Application.WorksheetFunction.RandBetween(100, 150) * 1
Next j
Next i
End Sub

Copy corresponding row VBA

I'm using a VBA to copy all the unique values from one sheet to another sheet. My VBA looks like this:
Sub UniqueListSample()
Application.ScreenUpdating = False
Dim lastrow As Long
Dim i As Long
Dim dictionary As Object
Set dictionary = CreateObject("scripting.dictionary")
Set shee = ThisWorkbook.Sheets("Sheet1")
lastrow = shee.Cells(Rows.Count, "B").End(xlUp).Row
On Error Resume Next
For i = 1 To lastrow
If Len(Sheet1.Cells(i, "B")) <> 0 Then
dictionary.Add shee.Cells(i, "B").Value, 1
End If
Next
Sheet3.Range("A3").Resize(dictionary.Count).Value = _
Application.Transpose(dictionary.keys)
Application.ScreenUpdating = True
End Sub
This takes all the unique values from Sheet 1 column B and moves them to sheet 3 column A. What I'm now trying to add is a function that takes the same rows from column C in sheet 1 and paste them into sheet 3 column B.
Is there an easy way to add this to the existing VBA?
please check this:
Option Explicit
Sub UniqueListSample()
Application.ScreenUpdating = False
Dim lastrow As Long
Dim i As Long
Dim dictionary As Object
Dim shee As Worksheet
Set dictionary = CreateObject("scripting.dictionary")
Set shee = ThisWorkbook.Sheets("Sheet1")
lastrow = shee.Cells(Rows.Count, "B").End(xlUp).Row
On Error Resume Next
For i = 1 To lastrow
If Len(Sheet1.Cells(i, "B")) <> 0 Then
dictionary.Add shee.Cells(i, "B").Value, shee.Cells(i, "c").Value
End If
Next
With Sheet3
.Range("A3").Resize(dictionary.Count).Value = _
Application.Transpose(dictionary.keys)
For i = 1 To dictionary.Count
.Cells(i + 2, 2) = dictionary(Sheet3.Cells(i + 2, 1).Value)
Next
End With
Application.ScreenUpdating = True
End Sub
If you just want one column you can utilise the Item. I prefer to avoid the "On Error" statement - the method below will not error if the same key is used (it will just overwrite).
Sub UniqueListSample()
Application.ScreenUpdating = False
Dim lastrow As Long
Dim i As Long
Dim dictionary As Object
Dim shee As Worksheet
Set dictionary = CreateObject("scripting.dictionary")
Set shee = ThisWorkbook.Sheets("Sheet1")
lastrow = shee.Cells(Rows.Count, "B").End(xlUp).Row
With dictionary
For i = 1 To lastrow
If Len(Sheet1.Cells(i, "B")) <> 0 Then
If Not (.Exists(shee.Cells(i, "B").Value)) Then
.Item(shee.Cells(i, "B").Value) = shee.Cells(i, "C").Value
End If
End If
Next
Sheet3.Range("A3").Resize(.Count).Value = Application.Transpose(.keys)
Sheet3.Range("B3").Resize(.Count).Value = Application.Transpose(.items)
End With
Application.ScreenUpdating = True
End Sub

Assign column to an array VBA

I have this code. DataSet is set as a variant.
DataSet = Selection.Value
Works fine but is there a way I can change it to just column A, specifically cells A2 to A502? Ive tried setting that as the range but it doesn't work. It also needs to ignore blank spaces because not all of the cells will have content. I am trying to eliminate the need to highlight the cells as the entries will only be in that specific range.
Try these 2 versions:
Option Explicit
Public Sub getNonemptyCol_ForLoop()
Dim dataSet As Variant, fullCol As Variant, i As Long, j As Long
Dim lrFull As Long, lrData As Long, colRng As Range
Set colRng = ThisWorkbook.Worksheets(1).Range("A2:A502")
fullCol = colRng
lrFull = UBound(fullCol)
lrData = lrFull - colRng.SpecialCells(xlCellTypeBlanks).Count
ReDim dataSet(1 To lrData, 1 To 1)
j = 1
For i = 1 To lrFull
If Len(fullCol(i, 1)) > 0 Then
dataSet(j, 1) = fullCol(i, 1)
j = j + 1
End If
Next
End Sub
Public Sub getNonemptyCol_CopyPaste() 'without using a For loop
Dim dataSet As Variant, ws As Worksheet
Application.ScreenUpdating = False
Set ws = ThisWorkbook.Worksheets(1)
With ws.UsedRange
ws.Activate
.Range("A2:A502").SpecialCells(xlCellTypeConstants).Copy
.Cells(1, (.Columns.Count + 1)).Activate
ActiveSheet.Paste
dataSet = ws.Columns(.Columns.Count + 1).SpecialCells(xlCellTypeConstants)
'dataSet now contains all non-blank values
ws.Columns(.Columns.Count + 1).EntireColumn.Delete
.Cells(1, 1).Activate
End With
Application.ScreenUpdating = True
End Sub
Assign with dynamic column.
Sub SetActiveColunmInArray()
Dim w As Worksheet
Dim vArray As Variant
Dim uCol As Long
Dim address As String
Set w = Plan1 'or Sheets("Plan1") or Sheets("your plan name")
w.Select
uCol = w.UsedRange.Columns.Count
address = w.Range(Cells(1, 1), Cells(1, uCol)).Cells.address
vArray = Range(address).Value2
End Sub

How to create multiple embedded line charts with dynamic range sizes using VBA

Here is my problem. I managed to create a macro that looks like this:
Sub Macro1()
Range("G17:G36").Select
ActiveSheet.Shapes.AddChart.Select
ActiveChart.SetSourceData Source:=Range("'Sheet1'!$G$17:$G$36")
ActiveChart.ChartType = xlLine
End Sub
I know this was pretty basic to record but my problem is how to change it and make the range dynamic and conditional. For example when I get to the row 17 I have a value in the cell D17 that is greater than lets say 200 and a value in E17 greater than 100. This should trigger the beginning of my range. So if D17>200 AND E17>100 I need to get G17 as the beginning of the range. As for G36 (the end of the range) the logic is very similar but this time I would test for a condition like this: IF F36<64 THEN get G36 as the end of the range.
The should repeat till the end. For example the last row could be at 28000 so I expect a good few of these charts to be created along the way.
Thanks is advance for your help,
Schroedinger.
This is how it looks now and gives me a run-time error explained in my correspondence with EngJon.
Sub GenerateCharts()
Application.ScreenUpdating = False
'Get the last row
Dim LastRow As Long
LastRow = ActiveSheet.UsedRange.Rows.Count
Dim endOfRange As Long
Dim wholeRange As Range
Dim i As Long
For i = 1 To LastRow
If Cells(i, 4) > 0.000001 And Cells(i, 5) > 0.00000002 Then
'Determine the end of the range
endOfRange = DetermineRange(i)
Set wholeRange = Range(Cells(i, 7), Cells(endOfRange, 7))
NewChart (wholeRange)
i = endOfRange
End If
Next i
Application.ScreenUpdating = True
End Sub
Function DetermineRange(row As Long) As Long
Dim LastRow As Long
LastRow = ActiveSheet.UsedRange.Rows.Count
Dim j As Long
For j = row To LastRow
If Cells(j, 6) < -0.0000000018 Then
DetermineRange = j
Exit Function
End If
Next j
DetermineRange = j
End Function
Function NewChart(rng As Range)
ActiveSheet.Shapes.AddChart.Select
ActiveChart.SetSourceData Source:=rng
ActiveChart.ChartType = xlLine
End Function
This is a final solution for me. I hope it helps someone. Big tnx to EngJon and Paagua Grant.
Sub GenerateCharts()
Application.ScreenUpdating = False
Dim StartCell As Long
Dim EndCell As Long
Dim ChartRange As Range
Dim DataEnd As Long
Dim i As Integer
Dim j As Integer
Dim HasStart As Boolean
Dim HasEnd As Boolean
'Sets end of data based on the row you are charting
DataEnd = Cells(Rows.Count, 7).End(xlUp).Row
'Begin loop to find start and end ranges, create charts based on those ranges
For i = 1 To DataEnd
If HasStart Then
If Cells(i, 4).Value < 0 Then
EndCell = i
HasEnd = True
End If
Else 'If there isn't a starting cell yet
If Cells(i, 4).Value > 0.000001 And Cells(i, 5).Value > 0.00000002 Then
StartCell = i
HasStart = True
End If
End If
If HasStart And HasEnd Then
Set ChartRange = ActiveSheet.Range(Cells(StartCell, 7), Cells(EndCell, 7))
ActiveSheet.Shapes.AddChart(xlLine, _
Left:=ActiveSheet.Range(Cells(StartCell, 10), Cells(StartCell, 10)).Left, _
Top:=ActiveSheet.Range(Cells(StartCell, 10), Cells(StartCell, 10)).Top, _
Width:=ActiveSheet.Range(Cells(StartCell, 10), Cells(StartCell, 20)).Width, _
Height:=ActiveSheet.Range(Cells(StartCell, 10), Cells(StartCell + 25, 10)).Height _
).Select
ActiveChart.SetSourceData Source:=ChartRange
HasStart = False
HasEnd = False
End If
Next
Application.ScreenUpdating = True
End Sub
You can use your recorded Macro1 as a Function and call it when you need to create a new Chart:
Function NewChart(rng As Range)
ActiveSheet.Shapes.AddChart.Select
ActiveChart.SetSourceData Source:=rng
ActiveChart.ChartType = xlLine
End Function
You will also need the following function:
Function DetermineRange(row As Long) As Long
Dim LastRow As Long
LastRow = ActiveSheet.UsedRange.Rows.Count
Dim j As Long
For j = row To LastRow
If Cells(j, 6) < 64 Then
DetermineRange = j
Exit Function
End If
Next j
DetermineRange = j
End Function
You will call it in a Sub that iterates over all rows:
Sub GenerateCharts()
Application.ScreenUpdating = False
'Get the last row
Dim LastRow As Long
LastRow = ActiveSheet.UsedRange.Rows.Count
Dim endOfRange As Long
Dim wholeRange As Range
Dim i As Long
For i = 1 To LastRow
If Cells(i, 4) > 200 And Cells(i, 5) > 100 Then
'Determine the end of the range
endOfRange = DetermineRange(i)
Set wholeRange = Range(Cells(i, 7), Cells(endOfRange, 7))
NewChart wholeRange
i = endOfRange
End If
Next i
Application.ScreenUpdating = True
End Sub
Copy those three in a module and execute the Sub. Please comment if this did what you needed.
Here's a slightly different option that performs all of the tasks in a single function.
Option Explicit
Private Sub CommandButton1_Click()
Application.ScreenUpdating = False
Dim StartCell As Long, EndCell As Long, ChartRange As Range, DataEnd As Long, i As Integer, j As Integer, HasStart As Boolean, HasEnd As Boolean, _
ChartTop As Long, ChartHeight As Long
DataEnd = Cells(Rows.Count, 7).End(xlUp).Row 'Sets end of data based on the row you are charting.
ChartTop = 50
ChartHeight = 100
'Begin loop to find start and end ranges, create charts based on those ranges.
For i = 1 To DataEnd
If HasStart Then
If Cells(i, 6).Value < 64 Then
EndCell = i
HasEnd = True
End If
Else 'If there isn't a starting cell yet.
If Cells(i, 7).Value > 200 And Cells(i, 5).Value > 100 Then
StartCell = i
HasStart = True
End If
End If
If HasStart And HasEnd Then
Set ChartRange = ActiveSheet.Range(Cells(StartCell, 7), Cells(EndCell, 7))
ActiveSheet.Shapes.AddChart(Top:=ChartTop, Height:=ChartHeight).Select
With ActiveChart
.SetSourceData Source:=ChartRange
.ChartType = xlLine
End With
ChartTop = ChartTop + ChartHeight + 15
HasStart = False
HasEnd = False
End If
Next
Application.ScreenUpdating = True
End Sub
This also makes sure that each chart created by the tool does not overlap the previous chart.
For the sake of space and clarity, I am putting my response to your followup questions here.
Assuming standard row heights and column widths, you can set
ChartTop =(StartCell-1)*15
to set the top of the chart to begin at the top of the same row as your data, and within the
ActiveSheet.Shapes.AddChart(Top:=ChartTop, Height:=ChartHeight).Select
you can add
Left:=(X * 48)
where X is one less than the column number that you want the chart to be left-aligned to, e.g. if you want the chart to start at the left edge of Column I, X would be equal to 8. However, as far as I can tell, there is no easy way to adjust these values if your row height/column widths is non-standard, e.g. if you have auto-fit your columns to your data.