I'm making a macro that sets a print area to user selected areas of document. Basically there is a box next to a bunch of cells and if user ticks the box then the bunch of cells is included to the print area.
Here is my code so far:
Sub TestCellA1()
Dim t As Integer, d As Integer
t = 0
d = 20
Dim rng_per As Range
Set rng_per = Range("A3:E328") 'prints whole document
Dim rng1 As Range
If Not IsEmpty(Range("F19")) = True Then
ActiveSheet.PageSetup.PrintArea = Range(rng_per)
Else
Do While t < 10
If IsEmpty(Range("F" & d).Value) = True Then
'MsgBox "Do not print"
Else
'MsgBox "Do print"
ActiveSheet.PageSetup.PrintArea = rng1
End If
t = t + 1
d = d + 25
Loop
End If
End Sub
So far this works to the point where the actual work is supposed to be done. I planned that everytime when loop finds box ticked it adds that part of document to the print area. As a newbie with vba I have no idea how to add those areas to print area. Any ideas how to do it? Thanks in advance& have a good day.
If you create and load a range into rng_to_add, the following will take the existing PrintArea and Union (append to) the rng_to_add:
' Going into this, you need to have declared a variable called rng_to_add
Dim rng_to_add As Range
' and loaded the range area you want to add to the PrintArea. This will
' be different for your particular situation.
Set rng_to_add = Sheets("Sheet1").Range("A1:C3")
' Referring to the current PageSetup of the Activesheet..
With ActiveSheet.PageSetup
' Check if the PrintArea of above PageSetup is empty
If .PrintArea = "" Then
' If so, set the PrintArea to the address of the Range: rng_to_add
.PrintArea = rng_to_add.Address
Else
' If not, set it to the address of a union (append) of the existing
' PrintArea's range and the address of the Range: rng_to_add
.PrintArea = Union(Range(.PrintArea), rng_to_add).Address
End If
' End the reference to the current PageSetup of the Activesheet
End With
So, for portability and/or integrating into your existing routines, you could create subroutines that manage the PrintArea like so:
Sub Clear_PrintArea()
' Set PrintArea to nothing
ActiveSheet.PageSetup.PrintArea = ""
End Sub
Sub Add_range_to_PrintArea(rng_to_add As Range)
' Referring to the current PageSetup of the Activesheet..
With ActiveSheet.PageSetup
' Check if the PrintArea of above PageSetup is empty
If .PrintArea = "" Then
' If so, set the PrintArea to the address of the Range: rng_to_add
.PrintArea = rng_to_add.Address
Else
' If not, set it to the address of a union (append) of the existing
' PrintArea's range and the address of the Range: rng_to_add
.PrintArea = Union(Range(.PrintArea), rng_to_add).Address
End If
' End the reference to the current PageSetup of the Activesheet
End With
End Sub
You could then call it like so:
Clear_PrintArea
Add_range_to_PrintArea Range("A1:C3")
Add_range_to_PrintArea Range("A7:C10")
Add_range_to_PrintArea Range("A13:C16")
Related
I am using this code
Sub print_area()
Dim ws As Worksheet
For Each ws In ActiveWorkbook.Worksheets
With ws.PageSetup
.PrintTitleColumns = "$A:$E"
.PrintArea = ws.Range("A128").Value
.CenterHorizontally = True
End With
Next
End Sub
Range("A128").Value has the value as this: $F$1:$AF$125,$AG$1:$BE$125,$BF$1:$CD$125,$CE$1:$DA$125,$DB$1:$DX$125
print areas are not set properly (the areas are close to set range but not as desired), what other parameters do I have to set to make it work right?
Red arrow indicates where the print area should be
As a rule, Excel automatically sets page breaks for printing based on paper size, scale, specified number of sheets in width and height, page orientation, and other parameters. You can use VPageBreak object and HPageBreak object in combination with a number of .PageSetup properties to manually fit page breaks. Note that you cannot set page breaks with the .PrintArea property (see below in the code for why). In the following code I set page breaks after the cells "AF1", "BE1", "CD1", "DA1", "DX1":
Sub print_area()
Dim ws As Worksheet
For Each ws In ActiveWorkbook.Worksheets
With ws.PageSetup
.PrintTitleColumns = "$A:$E"
.PrintArea = "$F$1:$AF$125,$AG$1:$BE$125,$BF$1:$CD$125,$CE$1:$DA$125,$DB$1:$DX$125" ' there will be one area between the upper left and lower right cells
Debug.Print .PrintArea ' check the final .PrintArea; prints $F$1:$DX$125
.Zoom = False
.Orientation = xlLandscape
.FitToPagesWide = False 'it's Auto
.FitToPagesTall = 1
.CenterHorizontally = True
ws.ResetAllPageBreaks
breaks = Array("AF1", "BE1", "CD1", "DA1", "DX1") 'zero-based array
For i = 1 To UBound(breaks)
ws.VPageBreaks.Add Before:=ws.Range(breaks(i - 1)).Offset(, 1)
Next
End With
Next
End Sub
Please note that in response to your pagination actions, Excel can
(and usually does) change the pagination itself
So I'm trying to use three Comboboxes to have a selection list for data input. I'm needing to make a selection in this order: Region -> Site -> Maintenance Plant. When a selection is made in the Region Combobox, then the Site Combobox list should filter to the options that pertain to the corresponding Region selection. Im thinking either a pivot table or vLookup needs to be used but I'm at a loss and have no clue how to get this done. Please help and thank you very much in advance.
Private Sub UserForm_Initialize()
Dim CreateBy As Range
Dim Region As Range
Dim Site As Range
Dim MaintPlant As Range
Dim Dept As Range
Dim Act As Range
Dim ImpActTyp As Range
Dim ValCat As Range
Dim ws As Worksheet
Set ws = Worksheets("LookupLists")
For Each CreateBy In ws.Range("RosterList")
With Me.CboCreateBy
.AddItem CreateBy.Value
End With
Next CreateBy
For Each Region In ws.Range("RegionList")
With Me.CboRegion
.AddItem Region.Value
End With
Next Region
For Each Site In ws.Range("SiteList")
With Me.CboSite
.AddItem Site.Value
End With
Next Site
For Each MaintPlant In ws.Range("MaintPlantList")
With Me.CboMntPlant
.AddItem MaintPlant.Value
End With
Next MaintPlant
For Each Dept In ws.Range("DeptList")
With Me.CboDept
.AddItem Dept.Value
End With
Next Dept
For Each Act In ws.Range("ActList")
With Me.CboAct
.AddItem Act.Value
End With
Next Act
For Each ImpActTyp In ws.Range("ImpActTypList")
With Me.CboImpActTyp
.AddItem ImpActTyp.Value
End With
Next ImpActTyp
For Each ValCat In ws.Range("ValCatList")
With Me.CboValCat
.AddItem ValCat.Value
End With
Next ValCat
Me.DateTextBox.Value = Format(Date, "Medium Date")
Me.PLife.Value = 0
Me.CSE.Value = 0
Me.CboRegion.SetFocus
End Sub
Get ready, because I'm about to reimagine your entire code here. I strongly recommend you create a backup of your original code module or workbook just due to the vast differences and if our ideas didn't align properly.
This will perform real-time filtering on your table, so keep this in mind using this method.
I did perform some testing on the following code, but I am human and threw this together in 20 mins or so. I wouldn't implement this in a real setting until you have fully tested the code and are comfortable with it.
And I just wanted to thank you for your use of Named Ranges. This made coding this easier.
You must enable the Microsoft Scripting Runtime library. This is used to grab the unique values from your tables. (Tools > References)
So to get things started, here is the entire code for your userform's code module:
Option Explicit
Private ws As Worksheet
Private tblLO As ListObject
Private Sub combo_region_Change()
Application.EnableEvents = False
Me.combo_maintPlant.Clear
Me.combo_site.Clear
'This is the first filter, so no worries about clearing entire AutoFilter
tblLO.AutoFilter.ShowAllData
Select Case Me.combo_region.Value
Case ""
Me.combo_site.Value = ""
Me.combo_maintPlant.Value = ""
Me.combo_site.Enabled = False
Me.combo_maintPlant.Enabled = False
Case Else
'If data is entered into first combobox, filter the table
tblLO.Range.AutoFilter 1, Me.combo_region.Value
'Populate the site combo box with new data
populateSiteCombo
'Enable the Site Combobox for user input
Me.combo_site.Enabled = True
End Select
Application.EnableEvents = True
End Sub
Private Sub combo_site_Change()
Application.EnableEvents = False
Me.combo_maintPlant.Clear
'Clear the filtering, then readd the Region's filter
tblLO.AutoFilter.ShowAllData
tblLO.Range.AutoFilter 1, Me.combo_region
Select Case Me.combo_site.Value
Case ""
Me.combo_maintPlant.Value = ""
Me.combo_maintPlant.Enabled = False
Case Else
'If data is entered into first combobox, filter the table
tblLO.Range.AutoFilter 2, Me.combo_site.Value
'Populate the Plant combo box with new data
populatePlantCombo
'Enable the Plant Combobox for user input
Me.combo_maintPlant.Enabled = True
End Select
Application.EnableEvents = True
End Sub
Private Sub populatePlantCombo()
'Grab unique values from Region column using Dictionary
Dim i As Long, regionDict As New Scripting.Dictionary
Dim arrReg() As Variant
'If it filters only 1 item, then it's just a single cell and not an arr
With ws.Range("MaintPlantList").SpecialCells(xlCellTypeVisible)
If .Count = 1 Then
Me.combo_maintPlant.AddItem .Value
Exit Sub
Else
arrReg = .Value
End If
End With
With New Scripting.Dictionary
For i = 1 To UBound(arrReg)
If Not .Exists(arrReg(i, 1)) Then
.Add arrReg(i, 1), "" 'We only add to dictionary for tracking
Me.combo_maintPlant.AddItem arrReg(i, 1)
End If
Next
End With
End Sub
Private Sub populateSiteCombo()
'Grab unique values from Region column using Dictionary
Dim i As Long, regionDict As New Scripting.Dictionary
Dim arrReg() As Variant
'If it filters only 1 item, then it's just a single cell and not an arr
With ws.Range("SiteList").SpecialCells(xlCellTypeVisible)
If .Count = 1 Then
Me.combo_site.AddItem .Value
Exit Sub
Else
arrReg = .Value
End If
End With
With New Scripting.Dictionary
For i = 1 To UBound(arrReg)
If Not .Exists(arrReg(i, 1)) Then
.Add arrReg(i, 1), "" 'We only add to dictionary for tracking
Me.combo_site.AddItem arrReg(i, 1)
End If
Next
End With
End Sub
Private Sub populateRegionCombo()
'Grab unique values from Region column using Dictionary
Dim i As Long, regionDict As New Scripting.Dictionary
Dim arrReg() As Variant
arrReg = ws.Range("RegionList").Value
With New Scripting.Dictionary
For i = 1 To UBound(arrReg)
If Not .Exists(arrReg(i, 1)) Then
.Add arrReg(i, 1), "" 'We only add to dictionary for tracking
Me.combo_region.AddItem arrReg(i, 1)
End If
Next
End With
End Sub
Private Sub UserForm_Initialize()
Set ws = ThisWorkbook.Worksheets("LookupLists") 'Module-defined var
Set tblLO = ws.ListObjects("Table1") 'Module-defined var
tblLO.AutoFilter.ShowAllData
Me.combo_maintPlant.Enabled = False
Me.combo_site.Enabled = False
'We only populate this one during init because the others
'will populate once a value is used in this box
populateRegionCombo
End Sub
If you decided to scroll down to understand what's going on here, then great.
Let's start with the initialization:
Private Sub UserForm_Initialize()
Set ws = ThisWorkbook.Worksheets("LookupLists") 'Module-defined var
Set tblLO = ws.ListObjects("Table1") 'Module-defined var
tblLO.AutoFilter.ShowAllData
Me.combo_maintPlant.Enabled = False
Me.combo_site.Enabled = False
'We only populate this one during init because the others
'will populate once a value is used in this box
populateRegionCombo
End Sub
We defined the module variables ws and tblLO. I'm not a huge fan of module-scoped variables, but we can usually get along when they are private vars to a userform module. Now the other functions in the code module can access these.
We reset autofiltering and disabled the two combo boxes that shouldn't be used until a selection is made for the region. Only after the region is selected will the next box be available for selection. We will handle these using Change Events for the comboboxes.
The userform is mostly controlled by the combo_region_change and combo_site_change events. Everytime region_change is fired, it will clear all the other combo boxes to redetermine it's new value. Then it will refilter as appropriately. The combo_site does the same, but it only clears the maintaince box. These event handlers also establish which of the other combox boxes are enabled depending on their values. So if you where to completely clear the site box for example, it will disable access to the Plant box again.
Finally you just have the "populate subs". Their jobs are simply to (re)populate the next combo box once the appropriate event handler is triggered.
Tip: If you feel the need to reset the filtering once you close your userform, you can just place the code to reset it in a UserForm_Terminate() event. It makes no difference to the above code if autofilter is enabled or not prior to it running, so that is preference only.
I create listbox in excel with VBA userform. Its values are obtained from the Sheet in Excel.
How can I delete the values in the sheet "database" while deleting the box list item?
please help me.
Private Sub UserForm_Initialize()
Dim ws As Worksheet
Dim rng As Range
Dim MyArray
Set ws = Sheets("Database")
Set rng = ws.Range("K2:L" & ws.Range("K" & ws.Rows.Count).End(xlUp).Row)
With Me.ListBox1
.Clear
.ColumnHeads = False
.ColumnCount = rng.Columns.Count
MyArray = rng
.List = MyArray
.ColumnWidths = "90;90"
.TopIndex = 0
End With
End Sub
Private Sub CommandButton2_Click()
For lItem = Me.ListBox1.ListCount - 1 To 0 Step -1
If ListBox1.Selected(lItem) Then
ListBox1.RemoveItem lItem
If Me.ListBox1.MultiSelect = fmMultiSelectSingle Then
Exit For
End If
End If
Next
End Sub
How do I delete the values in the sheet "database"?
As you assign database items via the array method (not using ControlSource), you want to know how to synchronize listbox items with your data base after manual deletion.
Approach A) - Write the entire Listbox1.List
If you want a mirror image of the listbox items after the For- Next loop, you could simply write these items back to a given range (of course you should clear 'surplus rows', too) via the following one liner
rng.Resize(Me.ListBox1.ListCount, 2) = Me.ListBox1.List
Instead of reduplicating the data range declaration in CommandButton2_Click, I'd suggest to declare it ONCE in the declaration head of the Userform code module (and omit it in Userform_Initialize):
Thus the complete code would be as follows:
â–ºAdditional notes due to comment
Insert these two code lines on top of your UserForm code module (and before any procedures).
Option Explicit is strictly recommended in any code to force the declaration of variable types (but you can't use this statement within a Sub as you did). The declaration Dim rng As Range OUTSIDE the other procedures (i.e. on top) allows that any procedure within this code module knows the rng variable.
Option Explicit ' declaration head of the UserForm module
Dim rng as Range ' ONE database declaration only!
' << OUTSIDE of following procedures
' << Start of regular procedures
Private Sub UserForm_Initialize()
Dim ws As Worksheet
' Dim rng As Range ' << not needed here, see top declaration
Dim MyArray
Set ws = Sheets("Database")
Set rng = ws.Range("K2:L" & ws.Range("K" & ws.Rows.Count).End(xlUp).Row)
With Me.ListBox1
.Clear
.ColumnHeads = False
.ColumnCount = rng.Columns.Count
MyArray = rng
.List = MyArray
.ColumnWidths = "90;90"
.TopIndex = 0
End With
End Sub
Private Sub CommandButton3_Click()
Dim lItem&
For lItem = Me.ListBox1.ListCount - 1 To 0 Step -1
If ListBox1.Selected(lItem) Then
ListBox1.RemoveItem lItem ' remove item from listbox
If Me.ListBox1.MultiSelect = fmMultiSelectSingle Then
Exit For
End If
End If
Next
rng.Offset(Me.ListBox1.ListCount, 0).Resize(rng.Rows.Count, 2) = "" ' clear rows
rng.Resize(Me.ListBox1.ListCount, 2) = Me.ListBox1.List ' write list back
End Sub
Note that no rows are deleted physically, the resulting listbox items in the two target columns K:L are shifted up only (approach B allows to delete entire rows as well).
Approach B) - Help procedure within main loop
Using the same data range declaration in the declaration head of the UserForm â–º as shown above (i.e. OUTSIDE the procedures as Subs or Functions), you could use a help procedure DelData allowing to distinguish between two principal cases:
[1] Shift up deleted cells in your database
[2] Delete the entire row
Event procedure CommandButton2_Click
Private Sub CommandButton2_Click()
' Purpose: delete items both from database and listbox
Dim lItem&
For lItem = Me.ListBox1.ListCount - 1 To 0 Step -1
If ListBox1.Selected(lItem) Then
DelData lItem, True ' [1] True=delete items and shift up
'DelData lItem, False ' [2] False=delete entire row
ListBox1.RemoveItem lItem ' remove item from listbox
If Me.ListBox1.MultiSelect = fmMultiSelectSingle Then
Exit For ' do it once in single select case
End If
End If
Next
End Sub
Help procedure DelData
Sub DelData(ByVal indx&, Optional ByVal bShiftUp As Boolean = True)
' Purpose: delete indicated row items in database
' Note: data set in OP includes header
If bShiftUp Then ' [1] bShiftUp = True: delete row items and shift up
rng.Offset(indx).Resize(1, rng.Columns.Count).Delete xlShiftUp
Else ' [2] bShiftUp = False: delete entire row of indicated items
rng.Offset(indx).Resize(1, rng.Columns.Count).EntireRow.Delete
End If
End Sub
Side note
It's recommended to fully qualify range references to avoid getting data from wrong workbooks, so I'd suggest the following statement in your UserForm_Initialize procedure:
Set ws = ThisWorkbook.Worksheets("Database")
Enjoy it :-)
Before removing the item from the ListBox you need to use the located value at the ListBox.Selected to find and remove the item from your "database".
Something like this:
Private Sub CommandButton2_Click()
For lItem = Me.ListBox1.ListCount - 1 To 0 Step -1
If ListBox1.Selected(lItem) Then
DeleteItemFromDatabase ListBox1.Selected(lItem).Value
ListBox1.RemoveItem lItem
If Me.ListBox1.MultiSelect = fmMultiSelectSingle Then
Exit For
End If
End If
Next
End Sub
Then your Sub DeleteItemFromDatabase(ByVal itemToDelete As [type]) would find itemToDelete in your "database" and remove it.
As an additional note, you may want to consider using Access as your database since it's actually designed to be one. I realize this isn't always possible, but thought I'd throw it out there as a thought for you.
In the code below, I check if the value of Sheet2 cell A1 is contained in combobox1 list and, if found, put it in the 'selection mode'. But it does not work. Which part of the code should be corrected?
Private Sub UserForm_Initialize()
Set xRg = Worksheets("Sheet1").Range("A1:B5")
Me.ComboBox1.List = xRg.Columns(1).Value
End Sub
Private Sub CommandButton1_Click()
Dim foundRng As Range
Set findrange = Sheets("Sheet1").Range("A1:B5")
Set foundRng = findrange.Find(Sheets("Sheet2").Range("A1"))
If foundRng Is Nothing Then
MsgBox "Nothing found"
Else
MsgBox "I Found"
Me.ComboBox1.ListIndex = foundRng.Value
End If
End Sub
Declare variables and provide for correct data types
I didn't change your code too much, but would like to give you some hints:
Set Option Explicit to compel yourself to declare variables (objects).
Provide for input cases in your Sheet2!A1 cell where a type mismatch could occur if you compare a string or an empty string (and not a number) against ListIndex numbers.
It's recommended to fully qualify your range references (fqrr).
Prefer to use the term Worksheets if you are referring to worksheets only.
Check Stack Overflow's Help Tour
regarding How do I ask a good question?, and,
How to create a Minimal, Complete, and Verifiable example
Try to learn something about error handling and Debugging VBA in order to be in the position to give more precise information about occurring errors. "It doesn't work" is like a red rag for a bull to more experienced programmers at this site, be more precise here :-;
Some minor changes ...
Option Explicit ' declaration head of your UserForm code module
Dim xrg As Range ' possibly declared here to be known in all UserForm procedures
Private Sub UserForm_Initialize()
Set xrg = ThisWorkbook.Worksheets("Sheet1").Range("A1:B5") ' << fully qualified range reference (fqrr)
Me.ComboBox1.List = xrg.Columns(1).Value
End Sub
Private Sub CommandButton1_Click()
Dim foundRng As Range, findrange As Range
Set findrange = ThisWorkbook.Worksheets("Sheet1").Range("A1:B5") ' fqrr
Set foundRng = findrange.Find(Thisworkbook.Worksheets("Sheet2").Range("A1")) ' fqrr
If foundRng Is Nothing Then
MsgBox "Nothing found"
Me.ComboBox1.ListIndex = -1
ElseIf foundRng.Value = vbNullString Then
MsgBox "Empty search item"
Me.ComboBox1.ListIndex = -1
Else
MsgBox "1 item found"
If IsNumeric(foundRng.Value) Then
Me.ComboBox1.ListIndex = CLng(foundRng.Value) + 1
Else
Me.ComboBox1.ListIndex = foundRng.Row - 1
End If
End If
End Sub
Recommended link
You can find a helpful guide about Debugging VBA at Chip Pearson's site.
Addendum due to comment
In order to define a dynamic range without following empty rows you could rewrite the Initialize procedure as follows:
Private Sub UserForm_Initialize()
Dim n& ' ... As Long
With ThisWorkbook.Worksheets("Sheet1")
n = .Range("A" & .Rows.Count).End(xlUp).Row
Set xrg = .Range("A1:B" & n) ' << fully qualified range reference
End With
Me.ComboBox1.List = xrg.Columns(1).Value
End Sub
Good luck for future learning steps :-)
The AddComment syntax works on first selected sheet in workbook, but for the next one gives me this error: Error 1004 "Application-defined or Object-defined error". I do not know why crashes if multiple sheets were selected and works only for the first selected one. Does anyone have some idea?
If selectedSheet.Cells(7, columnIndex).value <> 100 Then
selectedSheet.Cells(7, columnIndex).Interior.ColorIndex = 3
If standardReportFilePath <> "" Then 'not using the Standard Report Evalution algorithm
If VerifyStandardReportFile(selectedSheet.Name, selectedSheet.Cells(1, columnIndex).value, wbk, amplitude, missingCrashes) = True Then
selectedSheet.Cells(1, columnIndex).Interior.ColorIndex = 36 ' color the crash cell with yellow
Set rng = selectedSheet.Cells(1, columnIndex)
If rng.Comment Is Nothing Then
**rng.AddComment "In Standard Report this crash starts to deploy from " & CStr(amplitude) & " amplitude"**
Else
rng.Comment.Text "In Standard Report this crash starts to deploy from " & CStr(amplitude) & " amplitude"
End If
End If
End If
End If
End If
An alternate set of code that shows the problem. (Run this with three blank worksheets in a new workbook.):
Sub test()
Dim ws As Worksheet
Dim Rng As Range
'Running code with a single sheet selected
Worksheets("Sheet1").Select
'Code that shows issue - this will work
Set ws = Worksheets("Sheet2")
Set Rng = ws.Cells(1, 1)
If Rng.Comment Is Nothing Then
Rng.AddComment "xxx"
End If
'Get rid of comment again
Rng.Comment.Delete
'Running code with multiple sheets selected
Worksheets(Array("Sheet1", "Sheet2", "Sheet3")).Select
'Code that shows issue - will crash on the "AddComment"
Set ws = Worksheets("Sheet2")
Set Rng = ws.Cells(1, 1)
If Rng.Comment Is Nothing Then
Rng.AddComment "xxx"
End If
End Sub
I found a workaround, but still don't know why this problem even happens. For some reason error occurs when you have more then one worksheet selected. The solution is... To select one sheet before adding comments with someSheet.Select. At the end of macro you can try to select all previously selected sheets again if needed.
What I do understand - thanks to Yoweks comment - is:
You are looping through all the selected sheets, check something, set comments (giving you the problems, because it does'nt work with more than one selected sheet) and want the previosly selected sheets to be selected afterwards.
You can save the previosly selected sheet in a variable, select one of them, run your code and then select all previosly selected sheets again. PLease try the following code:
Sub Comments()
Dim WsArr As Sheets, WS As Worksheet, ColIdx As Long
ColIdx = 7
Set WsArr = ActiveWorkbook.Windows(1).SelectedSheets
WsArr(1).Select
For Each WS In WsArr
'*** your logic
Set Rng = WS.Cells(1, ColIdx)
If Rng.Comment Is Nothing Then
Rng.AddComment "In Standard Report this crash starts to deploy from " & CStr(amplitude) & " amplitude"
Else
Rng.Comment.Text "Changed T"
End If
Next WS
WsArr.Select
End Sub
From the Excel documentation
You can add notes to individual cells by using comments
You can see in the 'Review' tab within Excel that, when you select multiple sheets, you cannot create a comment. I assume this is to do with Excel's internals determining which cell should have a comment assigned to it.
Here is a function which you can call to assign a comment to a given cell, even if you have multiple sheets selected.
This sub also removes the need to test if a comment already exists, simply pass a new comment to a cell which already has one.
Sub UpdateComment(Rng As Range, Cmnt As String)
Application.ScreenUpdating = False
' Get currently selected sheets
Dim mySheets As Sheets: Set mySheets = ThisWorkbook.Windows(1).SelectedSheets
' Set current selection to just one sheet: this is where error is avoided
ThisWorkbook.Sheets(1).Select
' Set Comment, new if doesn't exist or changed if it does
If Rng.Comment Is Nothing Then
Rng.AddComment Cmnt
Else
Rng.Comment.Text Cmnt
End If
' Tidy up: re-select sheets & enable screen updating
mySheets.Select
Application.ScreenUpdating = True
End Sub
Use it like so in your code:
' ... your previous code
Set rng = selectedSheet.Cells(1, columnIndex)
UpdateComment rng, "In standard report this crash starts to deploy from ..."
To loop over all selected sheets
Dim sh As Worksheet
For Each sh In ThisWorkbook.Windows(1).SelectedSheets
Set rng = sh.Cells(1, columnIndex)
UpdateComment rng, "In standard report this crash starts to deploy from ..."
Next sh
I had the same problem while trying to get a comments function to work so instead of trying to figure it out per scenario, I decided to do a general one; call as needed.
Sub General_Functions_Comments(InCell As Range, TxtComment As String, Optional IsMergedAnalyzed As Boolean)
Dim IsComment As Comment
Dim RangeFixedMerged As Range
If InCell.MergeCells = False Or IsMergedAnalyzed = True Then ' 3. If InCell.MergeCells = False
With InCell
Set IsComment = .Comment
If IsComment Is Nothing Then ' 1. If Iscomment Is Nothing
.AddComment.Text Text:=TxtComment
.Comment.Shape.TextFrame.AutoSize = True
.Comment.Visible = False
Else ' 1. If Iscomment Is Nothing
If InStr(.Comment.Text, TxtComment) Then ' 2. If InStr(.Comment.Text, TxtComment)
Else ' 2. If InStr(.Comment.Text, TxtComment)
.Comment.Text .Comment.Text & Chr(10) & TxtComment
.Comment.Shape.TextFrame.AutoSize = True
.Comment.Visible = False
End If ' 2. If InStr(.Comment.Text, TxtComment)
End If ' 1. If Iscomment Is Nothing
End With
Else ' 3. If InCell.MergeCells = False
Set RangeFixedMerged = InCell.Cells(1, 1)
Call General_Functions_Comments(RangeFixedMerged, TxtComment, True)
Set RangeFixedMerged = Nothing
End If ' 3. If InCell.MergeCells = False
End Sub
In your code
If standardReportFilePath <> "" Then 'not using the Standard Report Evalution algorithm
If VerifyStandardReportFile(selectedSheet.Name, selectedSheet.Cells(1, columnIndex).Value, wbk, amplitude, missingCrashes) = True Then
selectedSheet.Cells(1, columnIndex).Interior.ColorIndex = 36 ' color the crash cell with yellow
Set Rng = selectedSheet.Cells(1, columnIndex)
If Rng.Comment Is Nothing Then
Call General_Functions_Comments(Rng, "In Standard Report this crash starts to deploy from " & CStr(amplitude) & " amplitude", True)
Else: Call General_Functions_Comments(Rng, "In Standard Report this crash starts to deploy from " & CStr(amplitude) & " amplitude", True)
End If
End If
End If
End If
End If
*Aside question, why to set an if, else statement if both will do the same?
I remember generally similar case (I could not do something from code), trying hard to solve it and finally I found that...
Note that if you have multiple sheets selected, "New comment" button on the ribbon is inactive, so you just can't do it from code if you cannot do it manually.
Why? - don't ask me. I see a nice workaround above, which seems to be the only way to achieve what you need.