Referring to a header range even after columns are inserted - vba

I have a spreadsheet as illustrated below:
.
The button with the green text allows me to expand and collapse columns A:F, according to the macro below.
The problem is that the macro refers specifically to columns A:F. If I were to insert an additional column under my main column header ("Läkemedelsinformation"), I would still only be able to collapse/expand columns A:F, unless I edit the code manually.
How do I go about making the macro detect the relevant columns dynamically based on the main column header?
Public Sub LKMinfo()
Dim SH As Worksheet
Dim Rng As Range
Dim obj As Variant
Dim BTN As Button
Dim iLen As Long
Const myColumns As String = "A:F" '<<===== Change
Const släkemedelsinformation As String = "Läkemedelsinformation" '<<===== Change
Const sHidden As String = " Hidden"
Const sVisible As String = " Visible"
Set SH = ActiveSheet
Set BTN = SH.Buttons(Application.Caller)
Set Rng = SH.Columns(myColumns)
With Rng.EntireColumn
.Hidden = Not .Hidden
If .Hidden Then
iLen = Len(sHidden) + Len(släkemedelsinformation)
BTN.Characters.Text = släkemedelsinformation & " Hidden"
With BTN.Characters(Start:=1, Length:=iLen).Font
.Name = "Arial"
.FontStyle = "Bold"
.Size = 10
.ColorIndex = 3 '\\ RED
End With
Else
iLen = Len(sVisible) + Len(släkemedelsinformation)
BTN.Characters.Text = släkemedelsinformation & " Visible"
With BTN.Characters(Start:=1, Length:=iLen).Font
.Name = "Arial"
.FontStyle = "Bold"
.Size = 10
.ColorIndex = 4 '\\ GREEN
End With
End If
End With
End Sub

You can find the columns using the .Find method, passing in the column header name.
I would advise to set the .Find to a range variable.
Dim col_header1 As Range, col_header2 As Range
Set col_header1 = Columns("A:Z").Find("header_name1")
Set col_header2 = Columns("A:Z").Find("header_name2")
You can then assess if you were successful in finding all of the headers. If all of the headers were found, you can assign the column number values and use them in your code.
If Not col_header1 Is Nothing And _
Not col_header2 Is Nothing Then
col_number1 = col_header1.Column
col_number2 = col_header2.Column
Else
MsgBox ("One or more of the columns were not found!")
Exit Sub
End If

Just name your range.
In the example below, I have three headers, and named that range "Barn" using the Name Box (that little field to the left of the function bar). For more help with this, see e.g. this video or any of great many found using a simple web search.
If I then insert a column after column B, the named range "Barn" just expands to include it. This is illustrated when I select the "Barn" range using the Name Box drop-down:
You can refer to this range in VBA as follows:
Dim r As Range
Set r = Range("Barn")
With r.EntireColumn
'... do stuff
So you don't need to specify which columns Range r should refer to anymore (e.g. A to F — don't need to specify that). You just say that r refers to the range named "Barn".

Related

VBA code to only show rows that contain similar text to an input field?

I'm new to VBA and am trying to cobble together some code to allow a user to input a word (or several words) into a cell and then show a list of matching row entries.
I have tried the following code but am getting an "instring = type mismatch" error.
Note that "B3" is the field dedicated for the "search word" and column F is the column containing the text I want to search within. If the word is contained, I want to show that row and hide all rows that don't contain that word.
Sub Find_Possible_Task()
ROW_NUMBER = 0
SEARCH_STRING = Sheets("codeset").Range("B3")
ROW_NUMBER = ROW_NUMBER + 1
ITEM_IN_REVIEW = Sheets("codeset").Range("F:F")
If InStr(ITEM_IN_REVIEW, SEARCH_STRING) Then
Do
Cells(c.Row).EntireRow.Hidden = False
Loop Until ITEM_IN_REVIEW = ""
End If
End Sub
TIA!
Few bad coding conventions or even possibly downright errors:
It's a good practice to explicity declare the scope Public/Private of your Sub procedure
Unless you're passing the variables from some place else, they need to be declared with Dim keyword
Using Option Explicit will help you prevent aforementioned error(s)
(Subjective) variables in all caps are ugly and in most programming languages it is convention to reserve all caps variables names for constants (Const)
Option Explicit
Private Sub keep_matches()
Dim what As Range
Dim where As Range
Dim res As Range ' result
Dim lr As Long ' last active row
Dim ws As Worksheet: Set ws = Sheets("codeset")
lr = ws.Cells(ws.Rows.Count, "F").End(xlUp).Row
Set what = ws.Range("B3")
Set where = ws.Range("F1:F" & lr)
' we'll create an extra column for a loop in our .Find method
where.Copy
ws.Range("F1").EntireColumn.Insert
ws.Range("F1").PasteSpecial xlPasteValues
where.EntireRow.Hidden = True ' preemptively hide them all
Set where = ws.Range("F1:F" & lr)
Set res = where.Find(what, lookIn:=xlValues) ' ilook for matches, 1st attempt
If Not res Is Nothing Then ' if found
Do Until res Is Nothing ' repeat for all results
res.EntireRow.Hidden = False
res = "Checked"
Set res = where.FindNext(res)
Loop
Else
MsgBox("No matches were found")
where.EntireRow.Hidden = False ' we don't wanna hide anything
End If
ws.Range("F1").EntireColumn.Delete ' remove the extra help column for Find method
End Sub
Should work as expected.
If there are any question, let me know.
instead of instr(), consider range.find().
Sub Find_Possible_Task()
Dim SEARCH_STRING As String
Dim ITEM_IN_REVIEW As Range
Dim found As Range
Dim i As Integer
SEARCH_STRING = Sheets("Sheet1").Range("B3").Value
i = 1
Do
Set ITEM_IN_REVIEW = Sheets("Sheet1").Cells(i, 6)
Set found = ITEM_IN_REVIEW.Find(What:=SEARCH_STRING)
If found Is Nothing Then
ITEM_IN_REVIEW.EntireRow.Hidden = True
End If
i = i + 1
Loop Until ITEM_IN_REVIEW = ""
End Sub
alternatively, consider using filter table:
1. check if your table has filter on ==> if yes, pass. if no, turn on filter.
2. filter column F for keyword to contain value in cell B3.

VBA word add caption

I am trying to add captions to a word document, using VBA. I am using the following code. The data starts off as tables in an Excel spreadsheet, with one per sheet. We are trying to generate a list of tables in the word document.
The following code loads starts editing a word template:
Set objWord = CreateObject("Word.Application")
objWord.Visible = True
Set objDoc = objWord.Documents.Add("Template path")
' Moving to end of word document
objWord.Selection.EndKey END_OF_STORY, MOVE_SELECTION
' Insert title
objWord.Selection.Font.Size = "16"
objWord.Selection.Font.Bold = True
objWord.Selection.TypeText ("Document name")
objWord.Selection.ParagraphFormat.SpaceAfter = 12
objWord.Selection.InsertParagraphAfter
The following code loops through the sheets in the worksheet and adds the tables and headers.
' Declaring variables
Dim Wbk As Workbook
Dim Ws As Worksheet
Dim END_OF_STORY As Integer: END_OF_STORY = 6
Dim MOVE_SELECTION As Integer: MOVE_SELECTION = 0
Dim LastRow As Integer
Dim LastColumn As Integer
Dim TableCount As Integer
Dim sectionTitle As String: sectionTitle = " "
' Loading workbook
Set Wbk = Workbooks.Open(inputFileName)
' Moving to end of word document
objWord.Selection.EndKey END_OF_STORY, MOVE_SELECTION
' Looping through all spreadsheets in workbook
For Each Ws In Wbk.Worksheets
' Empty Clipboard
Application.CutCopyMode = False
objWord.Selection.insertcaption Label:="Table", title:=": " & Ws.Range("B2").Text
In the cell B2, I have the following text: "Table 1: Summary". I am hoping for the word document to have a header which reflects this text. The problem is the table number is repeated twice, and I get output: "Table 1: Table 1: Summary". I tried the following alterations, both of which resulted in errors:
objWord.Selection.insertcaption Label:="", title:="" & Ws.Range("B2").Text
objWord.Selection.insertcaption Label:= Ws.Range("B2").Text
What am I doing wrong, and more generally how does the insertcaption method work?
I have tried reading this, but am confused by the syntax.
https://msdn.microsoft.com/en-us/vba/word-vba/articles/selection-insertcaption-method-word
One of the built-in features of using the Caption style in MS Word is the automatic numbering it applies and dynamically adjust in your document. You are explicitly trying to manage the table numbering yourself - which is fine - but you'll then have to un-do some of Word's automatic helpful numbering in your code.
Working from Excel, I've tested the code below to set up a test document with Captions and then a quick routine to remove the automatic part of the label. This example code works as a stand-alone test to illustrate how I worked it, leaving it to you to adapt to your own code.
The initial test sub simply establishes the Word.Application and Document objects, then creates three tables with following paragraphs. Each of the tables has it's own caption (which shows the doubled up label, due to the automatic labeling from Word). The code throws up a MsgBox to pause so you can take a look at the document before it's modified.
Then the code goes back and searches the entire document for any Caption styles and examines the text within the style to find the double label. I made the assumption that a double label is present if there are two colons ":" detected in the caption text. The first label (up to and past the first colon) is removed and the text replaced. With that, the resulting document looks like this:
The code:
Option Explicit
Sub test()
Dim objWord As Object
Dim objDoc As Object
Set objWord = CreateObject("Word.Application")
objWord.Visible = True
Set objDoc = objWord.documents.Add
Dim newTable As Object
Set newTable = objDoc.Tables.Add(Range:=objDoc.Range, NumRows:=3, NumColumns:=1)
newTable.Borders.Enable = True
newTable.Range.InsertCaption Label:="Table", Title:=": Table 1: summary xx"
objDoc.Range.InsertParagraphAfter
objDoc.Range.InsertAfter "Lorem ipsum"
objDoc.Characters.Last.Select
objWord.Selection.Collapse
Set newTable = objDoc.Tables.Add(Range:=objWord.Selection.Range, NumRows:=3, NumColumns:=2)
newTable.Range.InsertCaption Label:="Table", Title:=": Table 2: summary yy"
newTable.Borders.Enable = True
objDoc.Range.InsertParagraphAfter
objDoc.Range.InsertAfter "Lorem ipsum"
objDoc.Characters.Last.Select
objWord.Selection.Collapse
Set newTable = objDoc.Tables.Add(Range:=objWord.Selection.Range, NumRows:=3, NumColumns:=3)
newTable.Range.InsertCaption Label:="Table", Title:=": Table 3: summary zz"
newTable.Borders.Enable = True
objDoc.Range.InsertParagraphAfter
objDoc.Range.InsertAfter "Lorem ipsum"
MsgBox "document created. hit OK to continue"
RemoveAutoCaptionLabel objWord
Debug.Print "-----------------"
End Sub
Sub RemoveAutoCaptionLabel(ByRef objWord As Object)
objWord.Selection.HomeKey 6 'wdStory=6
With objWord.Selection.Find
.ClearFormatting
.Replacement.ClearFormatting
.Style = "Caption"
.Text = ""
.Forward = True
.Wrap = 1 'wdFindContinue=1
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
Do While .Execute()
RemoveDoubleLable objWord.Selection.Range
objWord.Selection.Collapse 0 'wdCollapseEnd=0
Loop
End With
End Sub
Sub RemoveDoubleLable(ByRef capRange As Object)
Dim temp As String
Dim pos1 As Long
Dim pos2 As Long
temp = capRange.Text
pos1 = InStr(1, temp, ":", vbTextCompare)
pos2 = InStr(pos1 + 1, temp, ":", vbTextCompare)
If (pos1 > 0) And (pos2 > 0) Then
temp = Trim$(Right$(temp, Len(temp) - pos1 - 1))
capRange.Text = temp
End If
End Sub

someExcel VBA: Cannot create a range object successfully

This is my first question here, so bear with me. I'm a security consultant working on a huge firewall migration, for which I got my VBA skill from under a thick layer of dust. So far I have managed to get all my issues resolved by searching, but this issue: I get errors when doing exactly how I find it everywhere.
What I want to do:
I have an array that contains (among other things), strings formatted like this: "A3:P59", representing a cell range.
Now, this are ranges within a table. When I get the address of a certain cell in the table, I want to test if it's in that range.
I wrote a test function:
Function TestCellRange() As Boolean
Dim tbl As ListObject
Dim cell, rng, test As range
Dim range As range
Dim bRow, eRow As Integer
Set tbl = shRulebase.ListObjects("tblBFFirewallRules")
shRulebase.Activate
With shRulebase
cell = tbl.DataBodyRange(5, 1).Address(False, False) 'it's this command that gives me issues
Set range = .range(.Cells(bRow, 1), .Cells(eRow, 16))
Debug.Print cell
'Set rng = shRulebase.range(range)
Debug.Print rng
Set test = Application.Intersect(cell, range(range(A3), range(P59)))
If test Is Nothing Then
MsgBox ("oops")
TestCellRange = False
Else
MsgBox ("yup yup")
TestCellRange = True
End If
End With
End Function
Now whatever I try, I keep getting blocked on the set range:
set range = .Range("A3:P59") -> will return "object required", on the "set test" line (if i use intersect (cell, range))
Set range = range("A3:P59") -> will return object variable or with block variale not set on the same line
Set range = .range(.Cells(bRow, 1), .Cells(eRow, 16)) -> will step through, but debug.print returns a type mismatch and "Set test = Application.Intersect(cell, range)" returns a "object required"
Any help would be really appreciated...I'm all to familiar with networks ip's and the bits and bytes of it, but here I am a bit out of my comfort zone and I need to finish this by tomorrow :(
Greetings,
Kraganov
EDIT Some More tries:
rng and cell as variant:
cell = tbl.DataBodyRange(5, 1).Address(False, False)
rng = .range("A3:P59").Address(False, False)
Set test = Application.Intersect(cell, rng)
==>I would get objects required
just using rng as range and trying to set it without "set"
rng = .range("A3:P59")
EDIT 2 : I found a way around using the range.
So what I was trying to do, was the following:
I had a table that contains information about firewall rules. However, not every line describes a rule. There are also lines that described the context in which the rules below that line were to be placed.
Outside of the table, aside of those lines there would be a cell with the range of cells for that context. I wanted to use that to describe the context for those rules, if I pulled them.
I ended up looping through the table rows and identifying those specific rows and setting a "context" variable when, a row like that was met.
Try setting the cell as well as following:
set cell = tbl.DataBodyRange(5, 1).Address(False, False)
What is cell? A Range?
You do not need to add 'set' to the range value assignment.
Try just
range = .Range("A3:P59")
Function TestCellRange() As Boolean
Dim tbl As ListObject
Dim cellToTest As Range
Dim testResult As Range
Set tbl = shRulebase.ListObjects("tblBFFirewallRules")
Set cellToTest = tbl.DataBodyRange.Cells(5, 1)
'or with one more level of indirection
'Set cellToTest = shRulebase.range(tbl.DataBodyRange.Cells(5, 1).Value)
Set testResult = Application.Intersect(cellToTest, [A3:P59])
If testResult Is Nothing Then
MsgBox ("oops")
TestCellRange = False
Else
MsgBox ("yup yup")
TestCellRange = True
End If
End Function
Thanks to the post of VincentG I found the working solution. Thanks for that.
Function TestCellRange() As Boolean
Dim tbl As ListObject
Dim cellToTest As range
Dim testResult As range
Set tbl = shRulebase.ListObjects("tblBFFirewallRules")
shRulebase.Activate
Set cellToTest = tbl.DataBodyRange.Cells(5, 1)
'or with one more level of indirection
'Set cellToTest = shRulebase.range(tbl.DataBodyRange.Cells(5, 1).Value)
Set testResult = Application.Intersect(cellToTest, range("A3:P59"))
If testResult Is Nothing Then
MsgBox ("oops")
TestCellRange = False
Else
MsgBox ("yup yup")
TestCellRange = True
End If
End Function

In a specific row of a table replace a "*" with a checked checkbox, and "" with a checkbox that is not checked

I have a couple of tables and want to replace column 2 or column 5 (if it exists) with check boxes.
If there is an asterisk in the cell, I want the check box checked = True.
If there's no asterisk, the cell will only be a unchecked check box. These check boxes are from the developer tab, under controls, legacy forms.
I researched but failed:
replacing an asterisk with a check box (checked)
limiting it to a specific column (see image)
replacing a blank cell with a check box (unchecked)
limiting the action to a specific column (2 and 5 (if it exists))
Dim oCell As Cell
Dim oRow As Row
For Each oRow In Selection.Tables(1).Rows
For Each oCell In oRow.Cells 'this won't work specifically with my example, needs to be a little more specific
If oCell.Range.Text = "*" Then
MsgBox oCell.RowIndex & ", " & oCell.ColumnIndex & " check it!"
'I don't how to put in a check box here
End If
Next oCell
Next oRow
'I want to combine the top code and code below...right?
'do for each cell in column 2
With ActiveDocument.FormFields.Add(Range:=ActiveDocument.Selection, Type:=wdFieldFormCheckBox)
If cellvalue = "" Then 'just verbal logic here
.CheckBox.Value = False
End If
If cellvalue = "*" Then 'just verbal logic here
.checkbox.Value = True
End If
End With
Here's how I would do this:
Dim objDoc As Document
Dim oCell As Cell
Dim oCol As Column
Dim objTable As Table
Dim bFlag As Boolean
Set objDoc = ActiveDocument
Set objTable = Selection.Tables(1)
'This may or may not be necessary, but I think it's a good idea.
'Tables with spans can not be accessed via the spanned object.
'Helper function below.
If IsColumnAccessible(objTable, 2) Then
For Each oCell In objTable.Columns(2).Cells
'This is the easiest way to check for an asterisk,
'but it assumes you have decent control over your
'content. This checks for an asterisk anywhere in the
'cell. If you need to be more specific, keep in mind
'that the cell will contain a paragraph return as well,
'at a minimum.
bFlag = (InStr(oCell.Range.Text, "*") > 0)
'Delete the content of the cell; again, this assumes
'the only options are blank or asterisk.
oCell.Range.Delete
objDoc.FormFields.Add Range:=oCell.Range, Type:=wdFieldFormCheckBox
'Set the value. I found some weird results doing this
'any other way (such as setting the form field to a variable).
'This worked, though.
If bFlag Then
oCell.Range.FormFields(1).CheckBox.Value = True
End If
Next oCell
End If
'Then do the same for column 5.
Public Function IsColumnAccessible(ByRef objTable As Table, iColumn As Integer) As Boolean
Dim objCol As Column
'This is a little helper function that returns false if
'the column can't be accessed. If you know you won't have
'any spans, you can probably skip this.
On Error GoTo IsNotAccessible
IsColumnAccessible = True
Set objCol = objTable.Columns(iColumn)
Exit Function
IsNotAccessible:
IsColumnAccessible = False
End Function

how to use variable in range using vba

Hello I have written code for generating graph using vba. everything working correctly ,but problem is i want to use variable for selecting particular column
the code is :
Set x = Range("$CF$2", Range("$CF$2").End(xlDown))
Set y = Range("$CG$2", Range("$CG$2").End(xlDown))
Dim c As Chart
Set c = ActiveWorkbook.Charts.Add
Set c = c.Location(Where:=xlLocationAsObject, Name:=assume)
With c
.ChartType = xlXYScatterLines
' set other chart properties
With .Parent
.Top = Range("cl1").Top
.Left = Range("cl12").Left
.Name = "c"
End With
End With
Dim s As Series
Set s = c.SeriesCollection(1)
With s
.Values = y
.XValues = x
' set other series properties
End With
I want to use variable COLs in first to line they are
Set x = Range("$CF$2", Range("$CF$2").End(xlDown))
Set y = Range("$CG$2", Range("$CG$2").End(xlDown))
COLs is variable of string
I'm not sure I understand, but if you want a Range object based on a string, why not try this:
Option Explicit
Sub TestRange()
'***** Declare variables
Dim oX As Range
Dim sCOLs As String
'***** Select column
sCOLs = "A"
'***** Set Range based on column from sCOLs
Set oX = Range(sCOLs & "2", Range(sCOLs & "2").End(xlDown))
'***** Do something with oX
Debug.Print TypeName(oX)
'***** Clean up
Set oX = Nothing
End Sub
You could also try and have the whole range as a string, maybe a bit cleaner code?
Dim sRange as String
sRange = "A2"
Set oX = Range(sRange, Range(sRange).End(xlDown))
You could also use Inputbox to have the user click on a certain cell. This then creates a variable "UserRange" which contains the cell reference you can use.
Sub test()
Dim UserRange As Range
Set UserRange = Application.InputBox(Prompt:="Please Select Range", Title:="Range Select", Type:=8)
UserRange.Value = "Test"
End Sub