VBA conditional formatting of rows based on cell text value - vba

The order of my data from my query comes out as desired - Column A asc, Column B asc.
Code Completion Date Receipt
P81800A1 09/03/2018 167,000.00
P81800A1 14/03/2018 178,000.00
P82080A 12/03/2018 352,500.00
P83103C1 02/03/2018 570,000.00
P83103C1 02/03/2018 358,000.00
P83103C1 02/03/2018 357,500.00
P83103C1 12/03/2018 340,000.00
P83103C1 12/03/2018 457,000.00
P83103C1 13/03/2018 415,000.00
P83180C1 06/03/2018 645,000.00
P83180C1 06/03/2018 520,000.00
This means if I get a completion for P81800A1 on 15/03/18 when I refresh the data, it will go in between lines 2 and 3 of above.
I have tried to summarise my goal in the attached image.
I want to VBA Conditional format each row based on the cell value of A in that row. Ie P81800A1 rows have one colour. All distinct codes have the same colour. The actual colour does not matter.
I want to do it in VBA so it is robust. I do not want to be creating any additional columns and basing it on formula in standard conditional formatting.

So this is not perfect but a reasonable start. It uses a dictionary to collect the unique codes and randbetween with dictionary item count to generate an associated colour. Conditional formatting rules are applied using the distinct codes.
Notes:
You might want to improve the random colour generation part (at present range is limited and you may occasionally get very dark formats - though you could run the macro again)
Make range selection more robust as start position is hard coded at present and later parts of code use this start position as well
Required, for early binding, reference to Microsoft scripting runtime to be added via VBE>Tools>References. I have included one example of how to use late binding (commented out). If using late binding you would need to specify Object instead of Dictionary for parameters and function return types (where dictionary returned).
Assumes data starts in A2 at present (sheet 9)
Option Explicit
Public Sub FormatMatchingCodes()
Dim wb As Workbook
Dim wsTarget As Worksheet
Set wb = ThisWorkbook
Set wsTarget = wb.Worksheets("Sheet9") 'change as appropriate
Dim lastRow As Long
Application.ScreenUpdating = False
lastRow = GetLastRow(wsTarget)
Dim formatRange As Range
If Not lastRow <= 2 Then
Set formatRange = wsTarget.Range("A2:C" & lastRow) 'Excludes header row
Else
MsgBox "End row is before start row"
Exit Sub
End If
Dim codeColoursDictionary As Dictionary
Set codeColoursDictionary = GetDistinctCodeCount(formatRange.Value2)
wsTarget.Cells.FormatConditions.Delete
AddFormatting formatRange, codeColoursDictionary
Application.ScreenUpdating = True
End Sub
Public Function GetDistinctCodeCount(ByVal sourceData As Variant) As Dictionary 'as object if latebound
''LATE binding
' Dim distinctDict As Object
' Set distinctDict = CreateObject("Scripting.Dictionary")
''Early binding add reference to VBE > tools > references > Microsoft scripting runtime
Dim distinctDict As Scripting.Dictionary
Set distinctDict = New Scripting.Dictionary
Dim currentCode As Long
For currentCode = LBound(sourceData, 1) To UBound(sourceData, 1)
If Not distinctDict.exists(sourceData(currentCode, 1)) Then
distinctDict.Add sourceData(currentCode, 1), Application.WorksheetFunction.RandBetween(13434828, 17777777) + distinctDict.Count
End If
Next currentCode
Set GetDistinctCodeCount = distinctDict
End Function
Public Function GetLastRow(ByVal wsTarget As Worksheet) As Long
With wsTarget
GetLastRow = .Cells(.Rows.Count, "A").End(xlUp).row 'change to column containing last row up to which you want to format
End With
End Function
Public Sub AddFormatting(ByVal formatRange As Range, ByVal codeColoursDictionary As Dictionary) 'note pass as object if late binding
Dim key As Variant
Dim counter As Long
For Each key In codeColoursDictionary.Keys
counter = counter + 1
With formatRange
.FormatConditions.Add Type:=xlExpression, Formula1:="=$A2=""" & key & """"
.FormatConditions(counter).StopIfTrue = False
With .FormatConditions(counter).Interior
.PatternColorIndex = xlAutomatic
.Color = codeColoursDictionary(key)
' .TintAndShade = 0
End With
End With
Next key
End Sub
Data in sheet after run:
Version 2 for OP
Option Explicit
Public Sub FormatMatchingCodes2()
Dim wb As Workbook
Dim wsTarget As Worksheet
Set wb = ThisWorkbook
Set wsTarget = wb.Worksheets("Sheet9") 'change as appropriate
Dim lastRow As Long
Application.ScreenUpdating = False
lastRow = GetLastRow(wsTarget)
Dim formatRange As Range
If Not lastRow <= 2 Then
Set formatRange = wsTarget.Range("A2:G" & lastRow) 'Excludes header row
Else
MsgBox "End row is before start row"
Exit Sub
End If
Dim codeColoursDictionary As Dictionary
Set codeColoursDictionary = GetDistinctCodeCount(formatRange.Value2)
wsTarget.Cells.FormatConditions.Delete
AddFormatting formatRange, codeColoursDictionary
Application.ScreenUpdating = True
End Sub
Public Function GetDistinctCodeCount(ByVal sourceData As Variant) As Dictionary 'as object if latebound
''LATE binding
' Dim distinctDict As Object
' Set distinctDict = CreateObject("Scripting.Dictionary")
''Early binding add reference to VBE > tools > references > Microsoft scripting runtime
Dim distinctDict As Scripting.Dictionary
Set distinctDict = New Scripting.Dictionary
Dim currentCode As Long
For currentCode = LBound(sourceData, 1) To UBound(sourceData, 1)
If Not distinctDict.exists(sourceData(currentCode, 5)) Then
distinctDict.Add sourceData(currentCode, 5), Application.WorksheetFunction.RandBetween(13434828, 17777777) + distinctDict.Count
End If
Next currentCode
Set GetDistinctCodeCount = distinctDict
End Function
Public Function GetLastRow(ByVal wsTarget As Worksheet) As Long
With wsTarget
GetLastRow = .Cells(.Rows.Count, "E").End(xlUp).row 'change to column containing last row up to which you want to format
End With
End Function
Public Sub AddFormatting(ByVal formatRange As Range, ByVal codeColoursDictionary As Dictionary) 'note pass as object if late binding
Dim key As Variant
Dim counter As Long
For Each key In codeColoursDictionary.Keys
counter = counter + 1
With formatRange
.FormatConditions.Add Type:=xlExpression, Formula1:="=$E2=""" & key & """"
.FormatConditions(counter).StopIfTrue = False
With .FormatConditions(counter).Interior
.PatternColorIndex = xlAutomatic
.Color = codeColoursDictionary(key)
' .TintAndShade = 0
End With
End With
Next key
End Sub

Related

Using VBA Match and Index function

Trying to use match & Index with specified ranges. Does not recognise RefreshDrNumbers in the code.
I am using the Case Function to specify ranges.
Can't seem to make the Case, Match & Index function connect or talk to each other?
The other Forum I've asked is
https://www.mrexcel.com/board/threads/add-ranges-to-match-and-index-functions.1162701/
Private Sub Jobcard_Demands_Click()
If Jobcard_Demands = ("Drawing No`s Update") Then
Dim matchRange As Range
Dim ODict As Object
Dim PartsListLastRow As Long, DestLastRow As Long
Dim LookupRange As Range
Dim i As Integer
Dim wsSource As Worksheet, wsDest As Worksheet
Set wsSource = ThisWorkbook.Worksheets("Parts List")
Set wsDest = ThisWorkbook.Worksheets("Job Card Master")
PartsListLastRow = wsSource.Range("A" & Rows.Count).End(xlUp).Row
DestLastRow = wsDest.Range("A" & Rows.Count).End(xlUp).Row
'This holds the lookup range (including both the lookup key
'column and the value column)
Set matchRange = wsSource.Range("E1:F" & PartsListLastRow)
'Get a dictionary of all the lookup values. The function, as
'defined below, takes the range as well as the relative column
'of the keys and values. In our case, the first column of our
'range has the keys, and the second has the values
Set ODict = GetDictionary(matchRange, 5, 6)
'Below, define the lookup range. In your specific code, this
'varies based on the combobox value, but I think you'll be able
'to figure out how to define it (I'm just hardcoding mine
Set LookupRange = wsDest.Range("A1:A" & DestLastRow)
'Loop over the lookup range
For i = 1 To DestLastRow
'Since the GetPartInfo function handles cases where there isn't a match
' (it returns a blank string), you don't have to use an if/else statement
wsDest.Range("B" & i).Value = GetPartInfo(ODict, wsDest.Range("E" & i).Value)
Next i
End If
End Sub
Private Function GetDictionary(rng As Range, keyCol As Long, valCol As Long) As Object
Dim sht As Worksheet
Dim rCell As Range
Dim ODict As Object
Set sht = rng.Parent
Set ODict = CreateObject("Scripting.Dictionary")
For Each rCell In rng.Columns(keyCol).Cells
If Not ODict.Exists(rCell.Offset(, keyCol - 1).Value) Then
ODict.Add rCell.Offset(, keyCol - 1).Value, rCell.Offset(, valCol - 1).Value
End If
Next rCell
Set GetDictionary = ODict
End Function
'This is just a helper function to de-clutter the main subroutine. Returns an
' empty string in cases where the part doesn't exist in the dictionary
Private Function GetPartInfo(ByRef ODict As Object, sKey As String)
Dim Output As String
Output = ""
If ODict.Exists(sKey) Then
Output = ODict(sKey)
End If
GetPartInfo = Output
End Function
Whenever I'm working with code that performs many lookups over the same range, I tend to package that lookup range into a dictionary. Lookups in a dictionary are highly efficient, so you don't have to worry about the "cost" of the lookup. There is an overhead to populate the dictionary, but this is often recovered as the number of lookups grows.
I took that approach in the below solution. I use helper functions to create the dictionary and to lookup dictionary values. This helps to declutter the main routine. See if you can work with the code below, and adapt it to your solution. I commented it where I felt it would add value, and I think you should be able to adapt to your needs. Write back with any issues.
Sub RefreshStuff()
Dim matchRange As Range
Dim oDict As Object
Dim lastRow As Long
Dim lookupRange As Range
Dim wsDest As Worksheet
'This holds the lookup range (including both the lookup key
'column and the value column)
Set matchRange = Sheets("Parts List").Range("E1:F6")
'Get a dictionary of all the lookup values. The function, as
'defined below, takes the range as well as the relative column
'of the keys and values. In our case, the first column of our
'range has the keys, and the second has the values
Set oDict = GetDictionary(matchRange, 1, 2)
'Below, define the lookup range. In your specific code, this
'varies based on the combobox value, but I think you'll be able
'to figure out how to define it (I'm just hardcoding mine
lastRow = 10
Set wsDest = Sheets("Job Card Master")
Set lookupRange = wsDest.Range("A1:A" & lastRow)
'Loop over the lookup range
For i = 1 To lastRow
'Since the GetPartInfo function handles cases where there isn't a match
' (it returns a blank string), you don't have to use an if/else statement
wsDest.Range("B" & i).Value = GetPartInfo(oDict, wsDest.Range("A" & i).Value)
Next i
End Sub
Private Function GetDictionary(rng As Range, keyCol As Long, valCol As Long) As Object
Dim sht As Worksheet
Dim rCell As Range
Dim oDict As Object
Set sht = rng.Parent
Set oDict = CreateObject("Scripting.Dictionary")
For Each rCell In rng.Columns(keyCol).Cells
If Not oDict.exists(rCell.Offset(, keyCol - 1).Value) Then
oDict.Add rCell.Offset(, keyCol - 1).Value, rCell.Offset(, valCol - 1).Value
End If
Next rCell
Set GetDictionary = oDict
End Function
'This is just a helper function to de-clutter the main subroutine. Returns an
' empty string in cases where the part doesn't exist in the dictionary
Private Function GetPartInfo(ByRef oDict As Object, sKey As String)
Dim output As String
output = ""
If oDict.exists(sKey) Then
output = oDict(sKey)
End If
GetPartInfo = output
End Function

Copy data from one sheet to another in reverse order using vba

I have two sheets in my excel PullData and AllStocks. I would like to copy data from PullData column A and paste the values reverse order into other sheet AllStocks.
Currently, I am using OFFSET function to perform it. But I see a performance issue while running large data set using this method. Is there any better way I can perform this task ?
My CUrrent Code :
Sub GetData()
Dim Main As Worksheet
Dim PullData As Worksheet
Dim AllStocks As Worksheet
Dim i,m As Integer
Set RawImport = Workbooks("vwap.xlsm").Sheets("RawImport")
Set PullData = Workbooks("vwap.xlsm").Sheets("PullData")
m = PullData.Cells(Rows.Count, "A").End(xlUp).Row
For i = 3 To m
AllStocks.Range("A2:A" & i).Formula = "=OFFSET(PullData!$A$" & m & ",-(ROW(PullData!A1)-1),0)"
Next i
End Sub
no loop code:
Option Explicit
Sub GetData()
Dim pullDataVals As Variant
With Workbooks("vwap.xlsm")
With .Sheets("PullData")
pullDataVals = Split(StrReverse(Join(Application.Transpose(.Range("A3", .Cells(.Rows.Count, "A").End(xlUp)).Value), ",")), ",")
End With
.Sheets("RawImport").Range("A2").Resize(UBound(pullDataVals) + 1).Value = Application.Transpose(pullDataVals)
End With
End Sub
just check your sheets names: in your question you're speaking about "PullData and AllStocks" but in your code some RawImport sheet is featuring...
or, in a super compressed style:
Sub GetData()
With Workbooks("vwap.xlsm").Sheets("PullData")
With .Range("A3", .Cells(.Rows.Count, "A").End(xlUp))
.Parent.Parent.Sheets("RawImport").Range("A2").Resize(.Rows.Count).Value = Application.Transpose(Split(StrReverse(Join(Application.Transpose(.Value), ",")), ","))
End With
End With
End Sub
should your data in PullData be a more than one character string or more than one digit number, to prevent what Gary's Student remarked, you could use ArrayList object and its Reverse method:
Sub GetData()
Dim arr As Object
Dim cell As Range
Set arr = CreateObject("System.Collections.Arraylist")
With Workbooks("vwap.xlsm")
With .Sheets("PullData")
For Each cell In .Range("A3", .Cells(.Rows.Count, "A").End(xlUp))
arr.Add cell.Value
Next
End With
arr.Reverse
.Sheets("RawImport").Range("A2").Resize(arr.Count) = Application.Transpose(arr.toarray)
End With
End Sub
This solution applies the INDEX formula to a temporary Name.
Sub Range_ReverseOrder()
Const kFml As String = "=INDEX(_Src,#RowsSrc+#RowTrg-ROW(),1)"
Dim nmSrc As Name, rgTrg As Range
Dim lRows As Long, sFml As String
Rem Set Objects
With Workbooks("vwap.xlsm")
lRows = .Worksheets("PullData").Cells(Rows.Count, 1).End(xlUp).Row
Set nmSrc = .Names.Add(Name:="_Src", _
RefersTo:=.Worksheets("PullData").Cells(2, 1).Resize(-1 + lRows, 1))
.Names("_Src").Comment = "Range_ReverseOrder"
Set rgTrg = .Worksheets("RawImport").Cells(2, 1).Resize(-1 + lRows, 1)
End With
Rem Set Formula
sFml = kFml
sFml = Replace(sFml, "#RowsSrc", nmSrc.RefersToRange.Rows.Count)
sFml = Replace(sFml, "#RowTrg", rgTrg.Row)
Rem Apply Formula
With rgTrg
.Offset(-1).Resize(1).Value = "Reverse.Order"
.Formula = sFml
.Value2 = .Value2
End With
Rem Delete Temporary Name
nmSrc.Delete
End Sub

Check whether value exists in collection or array, and if not, add it [duplicate]

This question already has answers here:
Does VBA have Dictionary Structure?
(11 answers)
Closed 4 years ago.
I want to add a list of items to a collection and avoid adding duplicates.
Here's my list in Column A
Apple
Orange
Pear
Orange
Orange
Apple
Carrot
I only want to add
Apple
Orange
Pear
Carrot
Here's what I came up with, and it works, but it's not pretty.
dim coll as New Collection
ln = Cells(Rows.Count, 1).End(xlUp).Row
coll.Add (Cells(1, 1).Value) 'Add first item manually to get it started
For i = 1 To ln
addItem = True 'Assume it's going to be added until proven otherwise
For j = 1 To coll.Count 'Loop through the collection
'If we ever find the item in the collection
If InStr(1, Cells(i, 1), coll(j), vbTextCompare) > 0 Then
addItem = False 'set this bool false
End If
Next j
If addItem = True Then 'It never got set to false, so add it
coll.Add (Cells(i, "A").Value)
End If
Next i
Is there a less convoluted way to do it? Preferably something like
If Not coll.Contains(someValue) Then
coll.Add (someValue)
End If
I would strongly recommend using dictionaries, as they have a lot of features that collections do not, including Exists function.
With that said, it would be quite easy to create a function that first checks to see if a value exists within a collection, and then another function that will only add a value if it doesn't already exist.
Check if value exists
To see if it already exists, just use a simple for loop. If the value exists, return true and exit the function.
' Check to see if a value is in a collection.
' Functional approcah to mimic dicitonary `exists` method.
Public Function CollectionValueExists(ByRef target As Collection, value As Variant) As Boolean
Dim index As Long
For index = 1 To target.Count
If target(index) = value Then
CollectionValueExists = True
Exit For
End If
Next index
End Function
Add unique values
Using the new function CollectionValueExists it is as simple as a if conditional statement to see if it should be added or not.
To make this even more dynamic, you could also use a ParamArray to allow multiple values to be added with one call. Simply loop each value and see if it needs to be added. This does not apply to your example, but is flexible for other uses.
' Adds unique values to a collection.
' #note this mutates the origianal collection.
Public Function CollectionAddUnique(ByRef target As Collection, ParamArray values() As Variant) As Boolean
Dim index As Long
For index = LBound(values) To UBound(values)
If Not CollectionValueExists(target, values(index)) Then
CollectionAddUnique = True
target.Add values(index)
End If
Next index
End Function
Demo
Putting it all together, you can simply loop your range and call the new function.
Private Sub demoAddingUniqueValuesToCollection()
Dim fruits As Collection
Set fruits = New Collection
Dim cell As Range
For Each cell In Range("A1", Range("A" & Rows.Count).End(xlUp))
CollectionAddUnique fruits, cell.value
Next cell
End Sub
this will fill a collection of only unique:
Dim coll As New Collection
Dim ln As Long
ln = Cells(Rows.count, 1).End(xlUp).Row
Dim i As Long
For i = 1 To ln
On Error Resume Next
coll.Add Cells(i, 1).Value, Cells(i, 1).Value
On Error GoTo 0
Next i
Dim ech
For Each ech In coll
Debug.Print ech
Next ech
Here is mine
Option Explicit
Sub Test()
Dim Ln
Ln = Cells(Rows.Count, 1).End(xlUp).Row
Dim rngInput As Excel.Range
Set rngInput = Range(Cells(1, 1), Cells(Ln, 1)) '* really should qualify with a sheet otherwise you're at the mercy of activesheet
Dim dicUnique As Scripting.Dictionary '* requires Tools->Reference : Microsoft Scripting Runtime
Set dicUnique = UniqueCellContents(rngInput)
Dim vOutput As Variant
vOutput = dicUnique.Keys
Dim rngOutput As Excel.Range
Set rngOutput = Range(Cells(1, 3), Cells(dicUnique.Count, 3)) '* really should qualify with a sheet otherwise you're at the mercy of activesheet
rngOutput.Value = Application.Transpose(vOutput)
'
' Dim coll As New Collection
'
' Ln = Cells(Rows.Count, 1).End(xlUp).Row
'
' coll.Add (Cells(1, 1).Value) 'Add first item manually to get it started
' For i = 1 To Ln
'
' AddItem = True 'Assume it's going to be added until proven otherwise
'
' For j = 1 To coll.Count 'Loop through the collection
'
' 'If we ever find the item in the collection
' If InStr(1, Cells(i, 1), coll(j), vbTextCompare) > 0 Then
'
' AddItem = False 'set this bool false
'
' End If
'
' Next j
'
' If AddItem = True Then 'It never got set to false, so add it
'
' coll.Add (Cells(i, "A").Value)
'
' End If
'
' Next i
End Sub
Function UniqueCellContents(ByVal rngInput As Excel.Range) As Scripting.Dictionary
Dim dic As Scripting.Dictionary '* requires Tools->Reference : Microsoft Scripting Runtime
Set dic = New Scripting.Dictionary
Dim vValues As Variant
vValues = (rngInput)
If Not IsArray(vValues) Then
dic.Add vValues, 0
Else
Dim vLoop As Variant
For Each vLoop In vValues
If Not dic.Exists(vLoop) Then
dic.Add vLoop, 0
End If
Next vLoop
End If
Set UniqueCellContents = dic
End Function
Another method is to use a Scripting Dictionary. This does have an Exists method - the code below actually bypasses this and will overwrite an existing item if the key already exists.
Sub x()
Dim oDic As Object, r As Range
Set oDic = CreateObject("Scripting.Dictionary")
For Each r In Range("A1:A7")
oDic(r.Value) = r.Row
' if not odic.exists(r.value) then ...
Next r
MsgBox Join(oDic.keys, ",")
End Sub
If you want to check for the existence of an item in a collection (as they don't have the exist functionality of dictionaries) then I use the following snippet
Public Function InCollection(Col As Collection, key As String) As Boolean
Dim var As Variant
Dim errNumber As Long
InCollection = False
Set var = Nothing
Err.clear
On Error Resume Next
var = Col.Item(key)
errNumber = CLng(Err.Number)
On Error GoTo 0
'5 is not in, 0 and 438 represent incollection
If errNumber = 5 Then ' it is 5 if not in collection
InCollection = False
Else
InCollection = True
End If
End Function
Used such as:
If InCollection(CollectionName,IDKey) Then
Else
End If
Another way
Dim coll As New Collection
Dim i As Long
For i = 1 To Cells(Rows.count, 1).End(xlUp).Row
If Worksheetfunction.CountIf(Cells(1,1).Resize(i), Cells(i, 1).Value) = 1 Then coll.Add Cells(i, 1).Value, Cells(i, 1).Value
Next
Or
Dim coll As New Collection
Dim oldValues As Variant
Dim cell As Range
With Range(Cells(1, 1), Cells(Rows.count, 1).End(xlUp))
oldValues = .Value
.RemoveDuplicates Columns:=1, Header:=xlNo
For Each cell In .SpecialCells(xlCellTypeConstants)
coll.Add cell.Value, cell.Value
Next
.Value = oldValues
End With

vba userform , if any of the checkboxes in the frame is true then macro should not be applied on the sheetname mentioned in the checkbox

I have created a userform (to change the column and row width of active sheet or all sheets )which has three frames.
In the first frame I have given two option box. Firsts option box : - To change the row and column width from Column B onwards and other option box to change the row column width from column c onwards.
User will select anyone of them and then move to second frame: which has again two options one to make the changes in active sheet and second option box to make the changes in all the sheets.
So if the user in the first form will select first option (change row and column width from B onwards and in the second frame will select active sheet then the column and row width will change from Column B onwards in the active sheet and so on...
Now I want to create third fram which has 3 checkboxes which has name of 3 sheets (Sheet1, Sheet2 and Sheet3.) I want that when the user has selected his options in frame one and two if the user in the third fram select any of the checkboxes or all of the checkboxes then the changes should not apply in the sheetname mentioned in any of the 3 checkboxes which he has selected.
I have successfully executed frame one and frame 2 however struggling to create a code for frame 3 which will have 3 checkboxes (which contains name of 3 sheets) which is to excluded to make any row and column width changes.
Please find below my codes which are in the module:
Sub rowcolactivesheetb()
Dim exworkb As Workbook
Dim xlwksht As Worksheet
Dim lastrow1 As Long
Dim lastcolumn1 As Long
Dim firstrowDB As Long
With ActiveSheet
lastrow1 = .Cells(Rows.Count, "A").End(xlUp).Row
lastcolumn1 = .Cells(1, Columns.Count).End(xlToLeft).Column
.Range(.Cells(1, 2), .Cells(lastrow1, lastcolumn1)).Select
Selection.Cells.RowHeight = 9.14
Selection.Cells.ColumnWidth = 7.14
End With
End Sub
Sub rowcolallsheetb()
Dim exworkb As Workbook
Dim xlwksht As Worksheet
Dim lastrow1 As Long
Dim lastcolumn1 As Long
Dim firstrowDB As Long
Dim Z As Integer
Dim ShtNames() As String
ReDim ShtNames(1 To ActiveWorkbook.Sheets.Count)
For Z = 1 To Sheets.Count
ShtNames(Z) = Sheets(Z).Name
Sheets(Z).Select
lastrow1 = Sheets(Z).Cells(Rows.Count, "A").End(xlUp).Row
lastcolumn1 = Sheets(Z).Cells(1, Columns.Count).End(xlToLeft).Column
ActiveWorkbook.Sheets(Z).Range(Sheets(Z).Cells(1, 2), Sheets(Z).Cells(lastrow1, lastcolumn1)).Select
Selection.Cells.RowHeight = 9.14
Selection.Cells.ColumnWidth = 7.14
Next Z
End Sub
Sub rowcolactivesheetc()
Dim exworkb As Workbook
Dim xlwksht As Worksheet
Dim lastrow1 As Long
Dim lastcolumn1 As Long
Dim firstrowDB As Long
With ActiveSheet
lastrow1 = .Cells(Rows.Count, "A").End(xlUp).Row
lastcolumn1 = .Cells(1, Columns.Count).End(xlToLeft).Column
.Range(.Cells(1, 3), .Cells(lastrow1, lastcolumn1)).Select
Selection.Cells.RowHeight = 9.14
Selection.Cells.ColumnWidth = 7.14
End With
End Sub
Sub rowcolallsheetc()
Dim exworkb As Workbook
Dim xlwksht As Worksheet
Dim lastrow1 As Long
Dim lastcolumn1 As Long
Dim firstrowDB As Long
Dim Z As Integer
Dim ShtNames() As String
ReDim ShtNames(1 To ActiveWorkbook.Sheets.Count)
For Z = 1 To Sheets.Count
ShtNames(Z) = Sheets(Z).Name
Sheets(Z).Select
lastrow1 = Sheets(Z).Cells(Rows.Count, "A").End(xlUp).Row
lastcolumn1 = Sheets(Z).Cells(1, Columns.Count).End(xlToLeft).Column
ActiveWorkbook.Sheets(Z).Range(Sheets(Z).Cells(1, 3), Sheets(Z).Cells(lastrow1, lastcolumn1)).Select
Selection.Cells.RowHeight = 9.14
Selection.Cells.ColumnWidth = 7.14
Next Z
End Sub
Userform code:
Private Sub CommandButton1_Click()
If Me.OptionButton5.Value = True Then
If Me.OptionButton7.Value = True Then
Call rowcolactivesheetb
ElseIf Me.OptionButton8.Value = True Then
rowcolallsheetb
End If
End If
If Me.OptionButton6.Value = True Then
If Me.OptionButton7.Value = True Then
Call rowcolactivesheetc
ElseIf Me.OptionButton8.Value = True Then
rowcolallsheetc
End If
End If
End Sub
First of all, I don't think I'd use OptionButtons. From your description it seems as if ListBoxes would suit you far better.
Secondly, it might be more elegant to pass the values into a single routine that actually sets the columns and rows rather than creating separate but almost identical routines.
I've stuck with your OptionButton structure and made the assumption that the three additional OptionButtons you allude to will be called OptionButton9, 10 & 11.
So the module code could be something like this:
Public Sub SizeRowsAndCols(fromB As Boolean, _
fromC As Boolean, _
targetActive As Boolean, _
targetAll As Boolean, _
excSheets As Variant)
Dim fromCol As Long
Dim sh As Worksheet
Dim nameString As Variant
'Define the column value
Select Case True
Case fromB: fromCol = 2
Case fromC: fromCol = 3
Case Else: MsgBox "Column selection error"
End Select
'Run routine on single or multiple sheets
Select Case True
Case targetActive
SetValuesOnSheet ThisWorkbook.ActiveSheet, fromCol
Case targetAll
For Each sh In ThisWorkbook.Worksheets
If IsEmpty(excSheets) Then
'If no sheets are to be excluded
SetValuesOnSheet sh, fromCol
Else
'Exclude the sheets in the list
For Each nameString In excSheets
If sh.Name <> nameString Then
SetValuesOnSheet sh, fromCol
End If
Next
End If
Next
Case Else
MsgBox "Sheet selection error"
End Select
End Sub
Private Sub SetValuesOnSheet(sh As Worksheet, fromCol As Long)
Dim lastR As Long, lastC As Long
Dim rng As Range
With sh
lastR = .Cells(.Rows.Count, "A").End(xlUp).Row
lastC = .Cells(1, .Columns.Count).End(xlToLeft).Column
Set rng = .Range(.Cells(1, fromCol), .Cells(lastR, lastC))
rng.RowHeight = 9.14
rng.ColumnWidth = 7.14
End With
End Sub
And the UserForm code might be:
Private Sub CommandButton1_Click()
Dim c As Long
Dim sheetNames As String
Dim list As Variant
'Build the list of excluded sheets
If OptionButton9.Value Then sheetNames = "Sheet1"
If OptionButton10.Value Then sheetNames = IIf(sheetNames <> "", "|", "") & "Sheet2"
If OptionButton11.Value Then sheetNames = IIf(sheetNames <> "", "|", "") & "Sheet3"
list = IIf(sheetNames <> "", Split(sheetNames, "|"), Empty)
'Call the generic routine
SizeRowsAndCols OptionButton5.Value, _
OptionButton6.Value, _
OptionButton7.Value, _
OptionButton8.Value, _
list
End Sub

Create Dynamically-named Workseet and Move entire rows based on cell value

Long-time user of this forum, first request for VBA help. Still consider myself a very beginner in VBA.
I need to make a daily batch file more meaningful by breaking up the rows in a single worksheet- "Main" (between 13,000 - 1,000,000 rows) into new worksheets. As this file gets processed daily, my requirement is that we can move rows based on the "Record Type" cell in column A.
The "Record Type" e.g. "25" or "41" or "ZA" could each have 3 populated columns, whilst Record Type "26" could have 30 populated... hence important to have entire row moved.
I am limited in my abilities and knowledge here, and have researched many examples on how to move rows (or a range of cells within a row) but these are limited to static options such as YES/NO, PAID/NOT PAID.
So in summary I need to:
1. Create a new worksheet for each distinct record in column A ("Record Type" in "Main")
2. Move entire row from "Main" to subsequently created worksheet in row 2.
Here is my attempt that somewhat creates the new worksheets (though I have to disable the error-handling and can't run as a script- have to step-through)
Sub breakout1()
Workbooks(1).Activate
Dim lastCol As Integer
Dim LastRow As Long
Dim x As Long
Dim rng As Range
Dim Rng1 As Range
Dim Rng2 As Range
Dim Rng3 As Range
Dim SheetNameArray
Dim fn As WorksheetFunction
Dim CalcSetting As Integer
Dim newsht As Worksheet
Set fn = Application.WorksheetFunction
With Application
CalcSetting = .Calculation
.Calculation = xlCalculationManual
.ScreenUpdating = False
End With
With ActiveSheet
Set rng = .UsedRange
Set Rng1 = Intersect(rng, .Range("A:A"))
lastCol = rng.Column + rng.Columns.Count - 1
.Range("A:A").AdvancedFilter Action:=xlFilterCopy, _
CopyToRange:=.Cells(1, lastCol + 2), Unique:=True
Set Rng2 = Intersect(.Columns(lastCol + 2).CurrentRegion, _
.Rows("2:" & Rows.Count))
ReDim SheetNameArray(1 To Rng2.Cells.Count)
SheetNameArray = fn.Transpose(Rng2)
.Columns(lastCol + 2).Clear
For x = LBound(SheetNameArray) To UBound(SheetNameArray)
On Error Resume Next
Set newsht = ThisWorkbook.Sheets(CStr(SheetNameArray(x)))
If Err <> 0 Then
Worksheets.Add
ActiveSheet.Name = CStr(SheetNameArray(x))
Err.Clear
End If
'On Error GoTo 0
'rng.AutoFilter Field:=1, Criteria1:=SheetNameArray(x)
'Set Rng3 = Intersect(rng, .Cells.SpecialCells(xlCellTypeVisible))
'Rng3.Copy Workbooks(1).Sheets(CStr(SheetNameArray(x))).Range("A1")
'rng.AutoFilter
Next x
End With
Range("A1").Select
Application.Calculation = CalcSetting
End Sub
I didn't focus on your true goal which I couldn't grasp out of your description
but here's a refactoring of your code that works for creating and/or populating sheets named after what found in unique values in "base" sheet (se code to set it properly) column "A
Option Explicit
Sub breakout2()
Dim x As Long
Dim rng As Range
Dim SheetNameArray As Variant
Dim CalcSetting As Integer
Dim newsht As Worksheet, BaseSht As Worksheet
With Application
CalcSetting = .Calculation
.Calculation = xlCalculationManual
.ScreenUpdating = False
End With
Set BaseSht = ThisWorkbook.Sheets("breakout") '<== choose "base" sheet
'Set BaseSht= Workbooks(1).ActiveSheet '<== this would activate the first workbook opend in current excel session. is it the one you actually want?
With BaseSht
Set rng = .UsedRange
SheetNameArray = GetSheetNames(rng, 1, 2)
For x = LBound(SheetNameArray) To UBound(SheetNameArray)
Set newsht = SetSheet(CStr(SheetNameArray(x)))
rng.AutoFilter Field:=1, Criteria1:=SheetNameArray(x)
Intersect(rng, .Cells.SpecialCells(xlCellTypeVisible)).Copy Parent.Sheets(CStr(SheetNameArray(x))).Range("A1")
rng.AutoFilter
Next x
End With
Range("A1").Select '<=== what for? Selection is rarely a good programming habit. set and use 'range' type variables instead
With Application
.Calculation = CalcSetting
.ScreenUpdating = True
End With
End Sub
Function SetSheet(shtName As String) As Worksheet
On Error Resume Next
ThisWorkbook.Sheets(shtName).Activate
If Err <> 0 Then
On Error GoTo 0
ThisWorkbook.Worksheets.Add
ActiveSheet.Name = shtName
End If
Set SetSheet = ActiveSheet
End Function
Function GetSheetNames(usedRng As Range, colWithSheetNames As Long, colShift As Long) As Variant
Dim sht As Worksheet
Dim rangeToScan As Range, rangeWithNames As Range, rngToCopyTo As Range
With usedRng
Set sht = .Parent
Set rngToCopyTo = sht.Columns(.Columns(.Columns.Count).column + 2)
End With
With sht
Set rangeToScan = Intersect(usedRng, .Columns(colWithSheetNames))
rangeToScan.AdvancedFilter Action:=xlFilterCopy, CopyToRange:=rngToCopyTo, Unique:=True
Set rangeWithNames = .Range(rngToCopyTo.Cells(1, 1).Offset(1), .Cells(.Rows.Count, rngToCopyTo.column).End(xlUp))
End With
GetSheetNames = Application.WorksheetFunction.Transpose(rangeWithNames)
rngToCopyTo.Clear
End Function