How to use WITH with an array of ranges - excel

I need to set the borders for a bunch of ranges.
This is how I do it:
For n = 1 to record_num
With ThisWorkbook.Sheets("Sheet1").Range("A" & (n-1)*3 + 1 & ":C" & (n-1)*3 + 2)
With .Borders(xlEdgeLeft)
.LineStyle = xlContinuous
.ThemeColor = 6
.TintAndShade = -0.499984740745262
.Weight = xlThick
End With
... some other border codes
End With
Next n
Because For ... Next is always slow, I'm thinking of putting the ranges in an array and set the borders all at once.
With ThisWorkbook.Sheets("Sheet1")
For n = 1 to record_num
Set cellArray(i) = .Range("A" & (n-1)*3 + 1 & ":C" & (n-1)*3 + 2)
Next i
End With
This loop works fine. I then tried to do a With cellArray(), With Range(cellArray()), With ThisWorkbook.Sheets("Sheet1").Range(cellArray()) and they all failed with the error message of "Method 'Range' of object '_Global' failed".
What is the proper syntax of doing it?

Format With Offset
Range.Union
Option Explicit
Sub formatWithOffset()
Dim record_num As Long
' e.g.;
record_num = 5
Dim rng As Range
Set rng = ThisWorkbook.Worksheets("Sheet1").Range("A1:C2")
Dim tRng As Range
Dim n As Long
For n = 1 To record_num
If Not tRng Is Nothing Then
Set tRng = Union(tRng, rng.Offset((n - 1) * 3))
Else
Set tRng = rng
End If
Next n
If Not tRng Is Nothing Then
With tRng
With .Borders(xlEdgeLeft)
.LineStyle = xlContinuous
.ThemeColor = 6
.TintAndShade = -0.499984740745262
.Weight = xlThick
End With
End With
End If
End Sub

If you want to use With with an array, you need to iterate through each element of the array individually, as the With operator only operates on a single object.
This assigns the ranges to the cellarray:
Dim cellarray(1 To 5) As Range
With ThisWorkbook.Sheets("Sheet1")
For n = 1 To 5
Set cellarray(n) = .Range("A" & (n - 1) * 3 + 1 & ":C" & (n - 1) * 3 + 2)
Next n
End With
and then run through the elements of cellarray, and use With to apply the styles.
For Each c In cellarray
With c
.Interior.ColorIndex = 12
End With
Next c

Related

Gradient color fill of specific cells

I am using working code, but this code has one drawback. The problem is that the gradient fill color is snapped to Select / Selection.
If dic.Exists(arr(1, n)) Then Cells(87, n + 1).Select
The macro highlights the required cells + the cell that was selected with the mouse.
What / how should I change to select only the necessary cells (by condition)?
Sub G()
Dim dic As New Dictionary
Dim x, arr, iNum&, n&
Dim z As Range
Set z = [Q10]
iNum = Range("A85").Value2
Set wb = ThisWorkbook:
s1 = wb.Sheets("1").Range("C87").Value2
Windows("2.xlsx").Activate
n = Cells(Rows.Count, 48).End(xlUp).Row
arr = Range("A2:CD" & n).Value2
If Not IsArray(arr) Then Err.Raise xlErrNA
For n = 1 To UBound(arr, 1)
If arr(n, 77) = 1 Then
If arr(n, 37) = iNum And arr(n, 3) = s1 Then x = dic(arr(n, 73))
End If
Next n
ThisWorkbook.Activate
n = Cells(20, Columns.Count).End(xlToLeft).Column
arr = Cells(20, 2).Resize(1, n).Value2
z.Activate
For n = 1 To UBound(arr, 2)
If dic.Exists(arr(1, n)) Then Cells(87, n + 1).Select
With Selection.Interior
.Pattern = xlPatternLinearGradient
.Gradient.Degree = 0
.Gradient.ColorStops.Clear
End With
With Selection.Interior.Gradient.ColorStops.Add(0)
.Color = 65535
.TintAndShade = 0
End With
With Selection.Interior.Gradient.ColorStops.Add(1)
.Color = 10498160
.TintAndShade = 0
End With
Next n
End Sub

Merge cells with same year in a row

I need to merge the cells one above the months.
Cells Should be merged from 01 to 12 showing year in cell.
Look for the picture for more clarification.
I have below code, but which show months after run in cell row1.
My idea is to convert above cells to years through vba and apply merge same year at the end.
which is shown in desired output.
Note.
ROW 4 and 5 are just my thinking, which will help year to merge.
Dim a(), i As Long, j As Long, m As Long, x As Range
With Range("b1:qaz1")
.MergeCells = False
.ClearContents
a() = .Offset(1).Value
m = Month(a(1, 1))
j = UBound(a, 2)
Set x = .Cells(1)
For i = 2 To j
If m <> Month(a(1, i)) Or i = j Then
With Range(x, .Cells(i - IIf(i = j, 0, 1)))
.MergeCells = True
.HorizontalAlignment = xlCenter
End With
x.Value = Format(DateSerial(2000, m, 1), "MMMM")
m = Month(a(1, i))
Set x = .Cells(i)
End If
Next
End With
End Sub
After running new program output look like
Since you have true dates in your caption row the month and year can be extracted from there. However, the code below converts dates that might have been created using formulas to hard dates before processing them.
Sub MergeCaptionsByYear()
' 031
Const CapsRow As Long = 1 ' change to suit
Const StartClm As Long = 2 ' change to suit
Dim Rng As Range ' working range
Dim Tmp As Variant ' current cell's value
Dim Cl As Long ' last used column
Dim Cstart As Long ' first column in Rng
Dim C As Long ' working column
Dim Yr As Integer ' year
Cl = Cells(CapsRow, Columns.Count).End(xlToLeft).Column
Range(Cells(CapsRow, StartClm), Cells(CapsRow, Cl)).Copy
Cells(CapsRow, StartClm).PasteSpecial xlValues
Application.CutCopyMode = False
C = StartClm - 1
Application.DisplayAlerts = False
Do
Tmp = Cells(CapsRow, C + 1).Value
If Not IsDate(Tmp) And (C <> Cl) Then
MsgBox "Cell " & Cells(CapsRow, C + 1).Address(0, 0) & _
" doesn't contain a date." & vbCr & _
"This macro will be terminated.", _
vbInformation, "Invalid cell content"
Exit Do
End If
If (Yr <> Year(CDate(Tmp))) Or (C = Cl) Then
If Yr Then
Set Rng = Range(Cells(CapsRow, Cstart), _
Cells(CapsRow, C))
With Rng
.Merge
.HorizontalAlignment = xlCenter
.NumberFormat = "yyyy"
End With
SetBorder Rng, xlEdgeLeft
SetBorder Rng, xlEdgeRight
End If
If C > (Cl - 1) Then Exit Do
Cstart = C + 1
Yr = Year(Tmp)
End If
C = C + 1
Loop
Application.DisplayAlerts = True
End Sub
Private Sub SetBorder(Rng As Range, _
Bord As XlBordersIndex)
' 031
With Rng.Borders(Bord)
.LineStyle = xlContinuous
.ColorIndex = xlAutomatic
.TintAndShade = 0
.Weight = xlMedium ' xlThin
End With
End Sub
Assuming the months range is "B5:AH5"
Sub test()
Dim monthsRng As Range
Set monthsRng = Range("B5:AH5")
monthsRng.Cells(1, 1).Offset(-1, 0).Select
For j = 1 To Int((monthsRng.Cells.Count / 12) + 2)
If ActiveCell.Offset(1, 0) <> 0 Then
For i = 1 To 12
ActiveCell.Value = Year(ActiveCell.Offset(1, 0))
If Year(ActiveCell.Offset(1, i)) = ActiveCell Then
Selection.Resize(1, i + 1).Select
Else
Exit For
End If
Next
With Selection
.HorizontalAlignment = xlCenter
.MergeCells = True
End With
Selection.Offset(0, 1).Select
Else
Exit For
End If
Next
End Sub
Replacing the inner for loop with below code will work irrespective of whether the dates in the Range("B5:AH5") in above procedure are formatted as dates or not.
For i = 1 To 12
ActiveCell.Value = Right(Format(ActiveCell.Offset(1, 0), "DD.MM.YYYY"), 4)
If Right(Format(ActiveCell.Offset(1, i), "DD.MM.YYYY"), 4) = Format(ActiveCell, Text) Then
Selection.Resize(1, i + 1).Select
Else
Exit For
End If
Next
However, in any case you need to format the output in excel as number (without 1000 separator and decimal places) and not date.

How to create a macro which will copy data from row to column, using conditions?

I am using currently v lookup to find and place values against the specific item. however, I am looking for help for a VB macro which will out the data in defined outcome.
please see 1st screen shot of raw data
second screen shot, should be the outcome.
Please note the "site" is not constant it can be any value, so I have put all site in column A .
currently V look is doing the job well. but makes the file crash sometime.
You can solve this with a Pivot Table using your original data source with NO changes in the table layout.
Drag the columns as shown below (you'll want to rename them from the default names): For Columns, drag the Date field there first. The Σ Values field will appear after you've dragged two Fields to the Values area, and should be below Date.
And with some formatting changes from the default, the result can look like:
Can you change your source data?
If you change your data to look like the table "Changed Source Data" below you can solve your issue with a pivot table.
Solution with a Pivot Table
Changed Source Data
There question can easily solved with pivot table. For practice i have create the below.
Let us assume that:
Data appears in Sheet "Data"
Results will be populated in sheet "Results"
Option Explicit
Sub Allocation()
Dim LastRow As Long, Row As Long, Column As Long, Invetory As Long, Sold As Long, Remaining As Long, LastRowRes As Long, LastColRes As Long, CurrentCol As Long, CurrentRow As Long, i As Long, y As Long
Dim iDate As Date
Dim Site As String
Dim wsData As Worksheet, wsResults As Worksheet
Dim ExcistSite As Boolean, ExcistDate As Boolean
Set wsData = ThisWorkbook.Worksheets("Data")
Set wsResults = ThisWorkbook.Worksheets("Results")
LastRow = wsData.Cells(wsData.Rows.Count, "A").End(xlUp).Row
wsResults.UsedRange.Clear
For Row = 2 To LastRow
iDate = wsData.Cells(Row, 1).Value
Site = wsData.Cells(Row, 2).Value
Invetory = wsData.Cells(Row, 3).Value
Sold = wsData.Cells(Row, 4).Value
Remaining = wsData.Cells(Row, 5).Value
If Row = 2 Then
With wsResults.Range("B1:D1")
.Merge
.Value = iDate
End With
wsResults.Range("A2").Value = "Site"
wsResults.Range("A2").Offset(1, 0).Value = Site
wsResults.Range("B2").Value = "Invetory"
wsResults.Range("B2").Offset(1, 0).Value = Invetory
wsResults.Range("C2").Value = "Sold"
wsResults.Range("C2").Offset(1, 0).Value = Sold
wsResults.Range("D2").Value = "Remaining"
wsResults.Range("D2").Offset(1, 0).Value = Remaining
Else
'Check if Site appears
LastRowRes = wsResults.Cells(wsResults.Rows.Count, "A").End(xlUp).Row
For i = 3 To LastRowRes
ExcistSite = False
If wsResults.Cells(i, 1).Value = Site Then
CurrentRow = i
ExcistSite = True
Exit For
Else
CurrentRow = i + 1
End If
Next i
If ExcistSite = False Then
wsResults.Cells(CurrentRow, 1).Value = Site
End If
'Check if date appears
LastColRes = wsResults.Cells(1, wsResults.Columns.Count).End(xlToLeft).Column
For y = 2 To LastColRes
ExcistDate = False
If wsResults.Cells(1, y).Value = iDate Then
CurrentCol = y
ExcistDate = True
Exit For
Else
CurrentCol = y + 1
End If
Next y
If ExcistDate = False Then
wsResults.Cells(2, CurrentCol + 2).Value = "Invetory"
wsResults.Cells(i, CurrentCol + 2).Value = Invetory
wsResults.Cells(2, CurrentCol + 3).Value = "Sold"
wsResults.Cells(i, CurrentCol + 3).Value = Sold
wsResults.Cells(2, CurrentCol + 4).Value = "Remaining"
wsResults.Cells(i, CurrentCol + 4).Value = Remaining
With wsResults.Range(Cells(1, LastColRes + 3), Cells(1, LastColRes + 5))
.Merge
.Value = iDate
End With
Else
wsResults.Cells(CurrentRow, CurrentCol).Value = Invetory
wsResults.Cells(CurrentRow, CurrentCol + 1).Value = Sold
wsResults.Cells(CurrentRow, CurrentCol + 2).Value = Remaining
End If
End If
Next Row
LastColRes = wsResults.Cells(1, wsResults.Columns.Count).End(xlToLeft).Column
LastRowRes = wsResults.Cells(wsResults.Rows.Count, "A").End(xlUp).Row
With wsResults.Range(Cells(1, 2), Cells(1, LastColRes))
With .Font
.Bold = True
.ThemeColor = xlThemeColorDark1
End With
With .Interior
.ThemeColor = xlThemeColorAccent1
End With
End With
With wsResults.Cells(2, 1)
With .Font
.Bold = True
.ThemeColor = xlThemeColorDark1
End With
With .Interior
.ThemeColor = xlThemeColorLight1
End With
End With
For i = 2 To LastColRes Step 3
With wsResults.Range(Cells(2, i), Cells(LastRowRes, i))
With .Interior
.ThemeColor = xlThemeColorAccent1
.TintAndShade = 0.399975585192419
End With
End With
Next i
For i = 3 To LastColRes + 3 Step 3
With wsResults.Range(Cells(2, i), Cells(LastRowRes, i + 1))
With .Font
.ThemeColor = xlThemeColorDark2
.TintAndShade = -0.249977111117893
End With
End With
Next i
With wsResults.UsedRange
.Borders.LineStyle = xlContinuous
.HorizontalAlignment = xlCenter
.VerticalAlignment = xlCenter
.EntireColumn.AutoFit
End With
End Sub

Multidimensional Matrix Transpose in Excel (Macro)

I have some "multidimensional" data in an Excel spreadsheet that currently look like this below:
I'd like to transform this into rows with multiple columns:
I have tried multiple macros but still can't handle all dimensions to transpose correctly to rows, would be extremely grateful for any help :)
P.
Here's the code which works well without 3rd dimension (sales type):
Sub test()
Dim inputRange As Range, inputRRay As Variant
Dim outputRange As Range, outputRRay() As Variant
Dim outRow As Long, inCol As Long, inRow As Long
Set inputRange = ThisWorkbook.Sheets("Sheet1").Range("A1:AA150")
Set outputRange = ThisWorkbook.Sheets("Sheet2").Range("A1")
inputRRay = inputRange.Value
ReDim outputRRay(1 To (UBound(inputRRay, 1) * UBound(inputRRay, 2)), 1 To 3)
outRow = 0
For inCol = 2 To UBound(inputRRay, 2)
For inRow = 2 To UBound(inputRRay, 1)
If inputRRay(inRow, inCol) <> vbNullString And inputRRay(inRow, inCol) <> 0 Then
outRow = outRow + 1
outputRRay(outRow, 1) = inputRRay(1, inCol)
outputRRay(outRow, 2) = inputRRay(inRow, 1)
outputRRay(outRow, 3) = inputRRay(inRow, inCol)
End If
Next inRow
Next inCol
With outputRange.Resize(1, 3)
.EntireColumn.Clear
.Value = Array("Store", "Product", "QTY")
.Font.FontStyle = "Bold"
End With
With outputRange.Offset(1, 0).Resize(UBound(outputRRay, 1), UBound(outputRRay, 2))
.Value = outputRRay
End With
With outputRange.Parent
With Range(outputRange.Range("a1"), .Cells(.Rows.Count, outputRange.Column).End(xlUp)).Resize(, 3)
.Borders(xlEdgeLeft).LineStyle = xlContinuous
.Borders(xlEdgeTop).LineStyle = xlContinuous
.Borders(xlEdgeBottom).LineStyle = xlContinuous
.Borders(xlEdgeRight).LineStyle = xlContinuous
.Borders(xlInsideVertical).LineStyle = xlContinuous
.Borders(xlInsideHorizontal).LineStyle = xlContinuous
.Columns.AutoFit
End With
End With
End Sub
If you're specifically after a VBA solution, then I think you might be over-complicating your code.
Your range definition looks odd. I don't quite see why you're selecting columns "A" to "AA" when the data is only in the first 7 columns. And the data transfer should simply be a case of looping the rows and then each column to transfer into the output array. The desired code would look something like the below. I've left all the formatting bits out as you can tailor that to however you want it.
It does seem as if this code has been lifted from somewhere else and you've tried to adjust it. That's fine, but it does require you to understand what the original code is doing, and it's nor obvious to me that you have that understanding. You might get more success if you write your code from scratch so that you know where the loops are taking you.
Dim data As Variant
Dim fmt As String
Dim output() As Variant
Dim r As Long, x As Long, i As Long
'Define your range
With Sheet1
data = .Range(.Range("A1"), _
.Range("A" & .Rows.Count).End(xlUp)) _
.Resize(, 7) _
.Value2
End With
'Redim output array based on range size.
'Note the + 1 for a header.
ReDim output(1 To UBound(data, 1) * 6 + 1, 1 To 4)
'Write the header.
output(1, 1) = "Product"
output(1, 2) = "Store"
output(1, 3) = "Sales Type"
output(1, 4) = "Qty"
'Transfer the data to output array.
i = 2 'index of ouput array
For r = 3 To UBound(data, 1)
For x = 0 To 5 'loops the 5 columns in each row
output(i + x, 1) = data(r, 1) 'product
output(i + x, 2) = data(1, IIf(x < 3, 2, 5)) 'store
output(i + x, 3) = data(2, x + 2) 'type
output(i + x, 4) = data(r, x + 2) 'qty
Next
i = i + 6 'increment output index by 6 rows
Next
'Write output to sheet.
Sheet2.Range("A1") _
.Resize(UBound(output, 1), _
UBound(output, 2)) _
.Value = output

Draw table according to user provided width and height

I am very new with VBA in Excel. What I want to accomplish is this. When a user enters a length of say 5, then 5 columns must be outlined red. Then also when a user enters a width of say 6, then 6 rows must be outlined red. Example:
I have this code thus far:
On worksheet change:
Private Sub Worksheet_Change(ByVal Target As Range)
If (Target.Address = "$A$2") Then
Call Draw2DTankl
ElseIf (Target.Address = "$B$2") Then
Call Draw2DTankw
End If
End Sub
Draw2DTankl:
Sub Draw2DTankl()
On Error Resume Next
Cells(2, 4).Value = ""
Dim x As Range
Set x = Worksheets("Sheet1").Cells
x.Borders.LineStyle = xNone
Range("A1") = "Length"
Dim Length As Integer
Length = CInt(Cells(2, 1).Value)
If (Length > 30) Then
MsgBox "A length of a maximum 30 is allowed"
Exit Sub
End If
If (Length < 0) Then
MsgBox "Invalid length value entered"
Exit Sub
End If
Dim Rws As Long, Rng As Range, r As Range
If (Length > 0) Then
Rws = 20
Set Rng = Range(Cells(20, "H"), Cells(Rws, 8 + Length - 1))
For Each r In Rng.Cells
With r.Borders
.LineStyle = xlContinuous
.Weight = xlMedium
.ColorIndex = 3
End With
Next r
End If
If (Err.Number <> 0) Then
MsgBox Err.Description
End If
End Sub
Draw2DTankw:
Sub Draw2DTankw()
On Error Resume Next
Cells(2, 4).Value = ""
Dim x As Range
Set x = Worksheets("Sheet1").Cells
x.Borders.LineStyle = xNone
Range("B1") = "Width"
Dim Width As Integer
Width = CInt(Cells(2, 2).Value)
If (Width > 30) Then
MsgBox "A width of a maximum 30 is allowed"
Exit Sub
End If
If (Width < 0) Then
MsgBox "Invalid Width value entered"
Exit Sub
End If
Dim Col As Long, Rng As Range, r As Range
If (Width > 0) Then
Col = 21
Set Rng = Range(Cells(21, "H"), Cells(Col, 8 + Length - 1))
For Each r In Rng.Cells
With r.Borders
.LineStyle = xlContinuous
.Weight = xlMedium
.ColorIndex = 3
End With
Next r
End If
If (Err.Number <> 0) Then
MsgBox Err.Description
End If
End Sub
Please help me. My code doesn't work. The length works, but that brakes when I change the width.
Entering my length draws:
Which is correct. But then if I enter the width of 6 this happens: (my length also dissapears)
I apologize for this long post!
It looks like in the Draw2DTankw you have Width declared above but in the rng you are using length
Dim Width As Integer Width = CInt(Cells(2, 2).Value)
Set Rng = Range(Cells(21, "H"), Cells(Col, 8 + Length - 1))
I've modified your code to draw both height and width by extending the range to include the width. This worked with I test it.
Private Sub Worksheet_Change(ByVal Target As Range)
If (Target.Address = "$A$2") Or (Target.Address = "$B$2") Then
DrawTable
End If
End Sub
Sub DrawTable()
On Error Resume Next
Cells(2, 4).Value = ""
Dim x As Range
Set x = ActiveSheet.Cells
x.Borders.LineStyle = xNone
Range("A1") = "Length"
Dim Length As Integer
Length = CInt(Cells(2, 1).Value)
'Combined Width sections
Dim Width As Integer
Width = CInt(Cells(2, 2).Value)
If (Length > 30) Then
MsgBox "A length of a maximum 30 is allowed"
Exit Sub
ElseIf (Width > 30) Then
MsgBox "A width of a maximum 30 is allowed"
Exit Sub
ElseIf (Length < 0) Then
MsgBox "Invalid length value entered"
Exit Sub
ElseIf (Width < 0) Then
MsgBox "Invalid Width value entered"
Exit Sub
End If
Dim Rws As Long, Rng As Range, r As Range
If (Length > 0) Then
Rws = 20
'Added width to cells(rws)
Set Rng = Range(Cells(20, "H"), Cells(Rws + Width - 1, 8 + Length - 1))
For Each r In Rng.Cells
With r.Borders
.LineStyle = xlContinuous
.Weight = xlMedium
.ColorIndex = 3
End With
Next r
End If
If (Err.Number <> 0) Then
MsgBox Err.Description
End If
End Sub

Resources