Need case-sensitive formatting (Excel) - vba

Sub test(sToken As String)
Cells.FormatConditions.Add Type:=xlCellValue, Operator:=xlEqual, Formula1:=sToken
Cells.FormatConditions(Cells.FormatConditions.Count).SetFirstPriority
With Cells.FormatConditions(1).Interior
.Pattern = xlPatternLightVertical
.PatternColorIndex = 4
.ColorIndex = 10
End With
Cells.FormatConditions(1).StopIfTrue = False
End Sub
The problem with the code above is, when I use Call test("a") (for example) I get formatted cells with
"a" and "A", but I want just an "a".
Any suggestions?
PS: not skilled in VBA and English, please don't kill =)
Ok, here the full macro for better understanding problem (with my crappy coding skills =P )
Sub FormatTokens()
Call FormatReset 'Clear formatting
Call SetFormatting("d", xlPatternNone, 1, 44)
Call SetFormatting("h", xlPatternCrissCross, 46, 44)
Call SetFormatting("t", xlPatternLightVertical, 4, 10) ' Here the 1st conflict token
Call SetFormatting("p", xlPatternNone, 1, 10)
Call SetFormatting("T", xlPatternNone, 4, 10) ' And here another
Call SetFormatting("v", xlPatternGray16, 49, 24)
' Blah, blah, blah in the same style...
End Sub
Private Sub SetFormatting(sToken As String, oPat As XlPattern, iPatCol As Integer, iCol As Integer)
Cells.FormatConditions.Add Type:=xlCellValue, Operator:=xlEqual, Formula1:=sToken
Cells.FormatConditions(Cells.FormatConditions.Count).SetFirstPriority
With Cells.FormatConditions(1).Interior
.Pattern = oPat
.PatternColorIndex = iPatCol
.ColorIndex = iCol
End With
Cells.FormatConditions(1).StopIfTrue = False
End Sub
Macro do the job, but not with "t" and "T" tokens

Explicitly specify Upper Case, Lower Case formatting.
Add the condition to check,
if UCase(range.value) = UCase(sToken) then
// do formatting
end if
EDIT
This works:
Cells.FormatConditions.Add Type:=xlCellValue, Operator:=xlEqual, _
Formula1:="=EXACT($B1,""a"")"
But this doesn't:
sToken = "=EXACT($A1, """"" & sToken & """"")"
Cells.FormatConditions.Add Type:=xlCellValue, Operator:=xlEqual, _
Formula1:=sToken

Use:
Formula1:= "=EXACT(A1;""" & sToken & """)"
Or:
Formula1:="=EXACT(" & Cells(1, 1).Address(False, False, xlA1) & ";""" & sToken & """)"
If you want to apply to a subrange you can simply change that part.

Well, after some deep readings of couple forums I found what I needed.
Here the solution, suitable in many different situations: set custom event handlers =)
Steps for set Worksheet events from VBA:
1. Create class module, which will be Your event handler (named clsWorksheetEventHandler in my case)
2. Code him:
Option Explicit
Public WithEvents WorksheetEvents As Worksheet 'As an object whose events should be handled
Private Sub WorksheetEvents_Change(ByVal Target As Range) 'Event to handle
'Some code You need to handle this event
End Sub
3. In Your working module add subroutines to initialize and terminate handling:
Option Explicit
Dim oWorksheetEventHandler As clsWorksheetEventHandler 'Ref for Your class
Dim colWorksheetEventHandlers As Collection 'For all referrals
Sub WorksheetEventHandlers_initialize()
'Create new Collection to store ours handlers
Set colWorksheetEventHandlers = New Collection
'Loop through worksheets
For Each Worksheet In Worksheets
'Create new instance of the event handler class
Set WorksheetEventHandler = New clsWorksheetEventHandler
'Set it to handle events in worksheet
Set WorksheetEventHandler.WorksheetEvents = Worksheet
'And add it to our collection
colWorksheetEventHandlers.Add WorksheetEventHandler
Next Worksheet
End Sub
Sub WorksheetEwentHandlers_terminate()
'Loop through our collection
For Each WorksheetEventHandler In colWorksheetEventHandlers
'Clear event handler
Set WorksheetEventHandler = Nothing
Next WorksheetEventHandler
'And finally clear memory
Set colWorksheetEventHandlers = Nothing
End Sub
4. ?????????????????????
5. PROFIT!!!!!!
I hope You enjoy =)
PS: Here are some links that have helped me greatly
How to create application-level event handlers in Excel
Controlling multiple textboxes on a userform

Related

Excel vba-- macro to add a new comment and set focus to that comment

I'd like to imitate the behavior of the default insert comment button with a macro. I want to store all of my macros in the Personal workbook, not the active workbook.
I'd like it to simply create a comment and then set the focus to that empty comment.
Below is what I have so far, using Terry's suggestion to make the comment .Visible and then .Shape.Select it:
Sub addNewComment()
Dim authorName As String
Dim authorNameLength As Integer
authorName = Application.UserName
authorNameLength = Len(authorName)
ActiveCell.AddComment _
authorName & ":" _
& Chr(10)
With ActiveCell.Comment
With .Shape
.AutoShapeType = msoShapeFoldedCorner
.Fill.ForeColor.RGB = RGB(215, 224, 239)
With .TextFrame
.AutoSize = True
.Characters.Font.Size = 11
.Characters.Font.Name = "Calibri"
.Characters(1, (authorNameLength + 1)).Font.Bold = True
.Characters((authorNameLength + 2), 1).Font.Bold = False
End With
End With
.Visible = True
.Shape.Select True
End With
End Sub
I'm not sure how to get the comment to go back to not being visible. Do I store the reference to the cell I just added the comment to, and then refer to that cell with the Worksheet_SelectionChange event? Or do I make that event just hide all comments on the sheet? Is it possible to use Worksheet_SelectionChange at all with the Personal workbook?
Also, my comment box does not resize as I type and add line breaks. It does resize after I exit, but actually too large by about four lines. Not sure why that is happening.
I'm sure there is a cleaner way to organize my With blocks as well.
I tried using the following to hide the comment again after selecting another cell:
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Target.Comment.Visible = False
End Sub
I received the following error:
error 91: Object variable or With block variable not set
You can select the comment once you make it visible using the following:
With range("a1")
.Comment.Visible = True
.Comment.Shape.Select True
End With
But I think you'll need to have another macro to hide the comment again once you deselect, as otherwise it will stay visible. You could try doing this on the SelectionChange event of the worksheet:
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Target.Comment.Visible = False
End Sub

Call Userform based on Userform Value in cell

I have a table with the following values:
Now, I would like to call the Userform in column H based on the value in column G, but I can't work out how to call the Userform based on the cell value. The error occurs in line
form.Name = wsControls.Cells(loop2, 8).Value
Here is my code:
Sub Check_Scenarios()
Dim wsAbsatz As Worksheet
Dim wsControls As Worksheet
Dim wsData As Worksheet
Dim loop1 As Long
Dim loop2 As Long
Dim lngKW As Long
Dim form As UserForm
Set wsAbsatz = ThisWorkbook.Worksheets("Production")
Set wsData = ThisWorkbook.Worksheets("Data")
Set wsControls = ThisWorkbook.Worksheets("Controls")
lngKW = wsControls.Cells(1, 2).Value + 2
If lngKW = 3 Then
Exit Sub
End If
For loop1 = wsControls.Cells(10, 2).Value To wsControls.Cells(19, 2).Value Step 10
If wsData.Cells(loop1 + 3, lngKW).Value <> "" Then
MsgBox (wsData.Cells(loop1 + 3, lngKW).Value)
For loop2 = 2 To 16
If wsData.Cells(loop1 + 3, lngKW).Value = wsControls.Cells(loop2, 7).Value Then
form.Name = wsControls.Cells(loop2, 8).Value 'error occurs here
form.Show
End If
Next loop2
End If
Next loop1
End Sub
Project:
Many thanks for your help!
You are trying to assign a Name to a blueprint. These are two errors.
You have to initialize your blueprint as something. Like this:
Dim form As New UserForm
Then, most probably your UserForm does not have a property called Name. It is called Caption. Thus it is like this:
Sub TestMe()
Dim uf As New UserForm1 'judging from your screenshot
uf.Caption = "Testing"
uf.Show
End Sub
Disclaimer:
There is a better way to work with UserForms, not abusing the blueprint, although almost every VBA book shows this UserForm.Show method (in fact every single one I have read so far).
If you have the time and the OOP knowledge implement the ideas from here - or from my interpretation of the ideas. There was also a documentation article about it in StackOverflow, but it was deleted with the whole documentation idea.
You don't "call" a userform. You instantiate it, and then you Show it.
UserForm is the "base class" from which all userforms are derived. See there is inheritance in VBA, only not with custom classes.
So you have a UserForm2 class, a UserForm3 class, a UserForm4 class, and so on.
These classes need to be instantiated before they can be used.
Dim theForm As UserForm
Set theForm = New UserForm2
theForm.Show
Set theForm = New UserForm3
theForm.Show
'...
So what you need is a way to parameterize this Set theForm = New ????? part.
And you can't. Because whatever you're going to do, the contents of a cell is going to be a string, and there's no way you can get an instance of a UserForm3 out of a String that says "UserForm3".
Make a factory function that does the translation:
Public Function CreateForm(ByVal formName As String) As UserForm
Select Case formName
Case "UserForm1"
Set CreateForm = New UserForm1
Case "UserForm2"
Set CreateForm = New UserForm2
Case "UserForm3"
Set CreateForm = New UserForm3
'...
End Select
End Function
And then call that function to get your form object:
Set form = CreateForm(wsControls.Cells(loop2, 8).Value)
If Not form Is Nothing Then form.Show

Wait until Excel RefreshAll (Ctrl+Alt+F5) finishes - VBA

I am running into a race condition issue where I have two QueryTables, each is hooked with its own AfterRefresh event. Each AfterRefresh event does some copy'n'pasting as well as doing some calculations.
Now, when the user click Refresh All (Ctrl+Alt+F5) in Excel, I would love to have each AfterRefresh handler to execute, but ONLY after all QueryTable refreshes are entirely completed.
I did a search on StackOverFlow, and someone suggested
Activeworkbook.RefreshAll
DoEvents
However, that's assuming that we are programmatically triggering the RereshAll. In my case, Refresh All is done by the built-in Refresh All (Ctrl+Alt+F5) button within Excel. Thus, I don't see where I can insert DoEvents in my case (unless I create my own Refresh All button, but I would like to avoid doing so).
I tried to search for "Excel VBA mutex", but I did not find anything in particular. So how do I make sure that all the refreshes are done, before each AfterRefresh handler takes place?
Thanks for reading!
Update: To help out with debugging.. here are my VBA codes.
I have a module named AutoOpen
Dim S As New DataCopy
Dim U As New DataCopy
Sub Auto_Open()
Set S.qt = ThisWorkbook.Sheets(1).QueryTables(2)
S.myWorkbookName = ThisWorkbook.Name
S.sWorksheetProcessName = "ProcessS"
S.sWorksheetDataColumnStart = 1
S.sWorksheetDataColumnEnd = 5
Set U.qt = ThisWorkbook.Sheets(1).QueryTables(1)
U.myWorkbookName = ThisWorkbook.Name
U.sWorksheetProcessName = "ProcessU"
U.sWorksheetDataColumnStart = 6
U.sWorksheetDataColumnEnd = 10
End Sub
I also have a Class module named DataCopy
Public WithEvents qt As QueryTable
Public myWorkbookName As String
Public sWorksheetProcessName As String
Public sWorksheetDataColumnStart As Integer
Public sWorksheetDataColumnEnd As Integer
Private Sub qt_AfterRefresh(ByVal Success As Boolean)
DataCopier
End Sub
Private Sub DataCopier()
'Debug.Print sWorksheetProcessName & "," & Application.CalculationState
Dim LastNRows As Integer
Dim sWorksheetDataName As String
' How many rows to copy
LastNRows = 297
sWorksheetDataName = "Data"
Application.ScreenUpdating = False
' Clear content in process tab
With Workbooks(myWorkbookName).Worksheets(sWorksheetProcessName)
.Range(.Cells(4, 1), .Cells(.Cells(Rows.Count, 1).End(xlUp).Row, 6)).ClearContents
End With
' Copy to process Tab
With Workbooks(myWorkbookName).Worksheets(sWorksheetDataName)
LastRow = .Cells(Rows.Count, 1).End(xlUp).Row
FirstRow = LastRow - LastNRows
If FirstRow < 2 Then
FirstRow = 2
End If
.Range(.Cells(FirstRow, sWorksheetDataColumnStart), .Cells(LastRow, sWorksheetDataColumnEnd)).Copy _
Destination:=Workbooks(myWorkbookName).Worksheets(sWorksheetProcessName).Range("A4")
End With
Debug.Print (sWorksheetProcessName & "," & sWorksheetDataColumnStart & "," & sWorksheetDataColumnEnd)
Application.ScreenUpdating = True
End Sub
Because of the race condition, only one AfterRefresh handler succeeds in copy'n'pasting.. the other one doesn't work until I click Refresh All button (Ctrl+Alt+F5) again.
If a DoEvents works after the explicit VBA trigger Activeworkbook.RefreshAll then a DoEvents before the code that you want to run in the event handlers should cover the case when the refresh is triggered by Ctrl+Alt+F5. Thus, begin each event handler with the line DoEvents.
change the queries to not allow background refresh, and they will not relinquish control until refreshed

running multiple macros when cells in a range are changed

bear with me, as I am a complete vba newbie and wrapping my head around what I already have has already taken me much longer than I care to admit.
I have a workbook with one master list "ITEMS" and several (up to 15) sub-tabs that grab information from the ITEMS sheet. I've been able to make this happen using buttons on each sub sheet which call this code:
Private Sub getNELL_Click()
Sheets("ITEMS").Range("A1:K400").AdvancedFilter Action:=xlFilterCopy, _
CriteriaRange:=Sheets("ITEMS").Range("O1:O2"), CopyToRange:=Range("A1:K1") _
, Unique:=False
End Sub
This code successfully grabs each relevant row into the sheet each time I click the button, where each getX has a different name/criteria range (getRILEY, getELLE etc.)
But what I'm looking to do next is to have these macros run automatically when any cell in the G column of the ITEMS sheet is changed. In plain text, what I need is:
When [Any Cell in Column G] in Sheet("ITEMS") is changed
Run getNELL, getRiley, getELLE (x15 different macros)
here's my file with all the sheet (sic) in it.
EDIT:
and it's done!
moving the macros to a module instead of in each individual sheet, making them public and removing the _Click, along with the following code worked the magic I needed.
Private Sub Worksheet_Change(ByVal Target As Range)
Dim KeyCells As Range
Set KeyCells = Range("G2:G400")
If Not Application.Intersect(KeyCells, Range(Target.Address)) _
Is Nothing Then
getNELL
getMIKA
getRILEY
getJANNA
getWOO
getELLE
getMK
getLAURA
getFLIPSE
getJENN
getCRIS
End If
End Sub
First off, use this link as a resource for triggering an event when cells change. That's usually just in the subroutine declaration.
For the code, change all of your private subs to public subs by replacing "private" with "public". Then in your subroutine list the subroutines to call:
>
Subx
Suby
Subz
end sub
Sorry the answer isn't super detailed as I am typing from my phone. Also, those sub examples should each be on their own line. I can't seem to change that on here.
you have already created filter criteria in ITEM sheet (grey highlighted)
so create one mapping for what sheet needs what criteria range in INDEX sheet
e.g.
SheetName Criteria Mapping
nell O1:O2
mika P1:P2
riley Q1:Q2
janna R1:R2
woo S1:S2
elle O3:O4
mk P3:P4
laura Q3:Q4
flipse R3:R4
jenn S3:S4
cris O5:O6
Add this code in a Module
Public Sub pGet_Data(ByVal SheetName As Worksheet, ByVal CriteriaRng As Range)
ThisWorkbook.Worksheets("ITEMS").Range("A1:K400").AdvancedFilter _
Action:=xlFilterCopy, _
CriteriaRange:=CriteriaRng, _
CopyToRange:=SheetName.Range("A1:K1"), _
Unique:=False
End Sub
And in Thisworkbook Module add given code:
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
Dim rngCriteriaRange As Range
Dim rngOneMap As Range
Dim wksSheet As Worksheet
If Sh.Name = "ITEMS" And Target.Column = 7 Then
Set rngCriteriaRange = Sh.Range("W6:X16") '<--you can make it dynamic
For Each rngOneMap In rngCriteriaRange.Rows
Set wksSheet = ThisWorkbook.Worksheets(rngOneMap.Cells(1, 1).Value)
Application.StatusBar = "Updating [" & wksSheet & "] Sheet"
Call pGet_Data(wksSheet, wksSheet.Range(rngOneMap.Cells(1, 2).Value))
Next rngOneMap
End If
MsgBox "Sheets has been updated.", vbOKOnly, "Be Happy..."
ClearMemory:
Set rngCriteriaRange = Nothing
Set rngOneMap = Nothing
Set wksSheet = Nothing
End Sub
I think this will resolve what you are looking for... :)

Adding data with multiple buttons in VBA

I'm trying to use buttons as the only input into a worksheet database.
Only buttons 1,2 & 3 will add data to my worksheet via the code below
'Name for Button 1 = B_1
Private Sub B_1_Click()
Dim iRow As Long
Dim ws As Worksheet
Set ws = Worksheets("Database")
'find first empty row in database
iRow = ws.Range("A:D").Find(What:="*", SearchOrder:=xlRows, _
SearchDirection:=xlPrevious, LookIn:=xlValues).Row + 1
'copy the data to the database
ws.Cells(iRow, 1).Value = Date
ws.Cells(iRow, 2).Value = Time
'This has to be bacon, tomato or cheese
ws.Cells(iRow, 3).Value = ???????
ws.Cells(iRow, 4).Value = 1
MsgBox "Data added", vbOKOnly + vbInformation, "Data Added"
Application.DisplayAlerts = False
ActiveWorkbook.Save
End Sub
IRow, 3 is where I have the problem. I want to store the info for bacon, tomato or cheese first and then when I press 2. The data in the worksheet should look something like this.
Date 12/01/2015
Time 14:20
Food BACON
Quantity 2
I've used a textbox previously as the input.
Can anybody please assist me with this code.
Thanks
If you want to work with only one food at a time, you should use Option Buttons instead of Commandbuttons. Option Buttons are mutually exclusive - if you select Cheese, Bacon is automatically deselected.
If you want to process multiple foods at once, you should use Toggle Buttons. Toggle Buttons are either up or down, but they're not mutually exclusive. They have the added benefit of looking like Command Buttons.
If you want the mutual exclusivity of Option Buttons, but you must have something that looks and works like a Commandbutton, then you have a couple of options, none of them optimal:
You could use a Tab Strip and hide everything except the tabs.
You could set a module-level variable that remembers the last button pushed. You would probably want to add code that would change the color of the button so the user knows which they pressed. And if you did that, you probably don't need the module-level variable, you could just read which button had the color.
You can make Toggle Buttons mutually exclusive through code. I'd personally go with this one so you get the visual effect of the button being pressed.
Here's some code to get you started
Private mbEventsDisabled As Boolean
Public Property Let EventsDisabled(ByVal bEventsDisabled As Boolean): mbEventsDisabled = bEventsDisabled: End Property
Public Property Get EventsDisabled() As Boolean: EventsDisabled = mbEventsDisabled: End Property
Private Sub tgBacon_Click()
If Not Me.EventsDisabled Then ClearToggles Me.tgBacon
End Sub
Private Sub tgCheese_Click()
If Not Me.EventsDisabled Then ClearToggles Me.tgCheese
End Sub
Private Sub tgTomato_Click()
If Not Me.EventsDisabled Then ClearToggles Me.tgTomato
End Sub
Public Sub ClearToggles(tg As ToggleButton)
Me.EventsDisabled = True
Me.tgBacon.Value = Me.tgBacon.Name = tg.Name
Me.tgCheese.Value = Me.tgCheese.Name = tg.Name
Me.tgTomato.Value = Me.tgTomato.Name = tg.Name
Me.EventsDisabled = False
End Sub
If you had more than three toggles, you'd want to refactor the ClearToggles sub to loop instead of calling them out individually.
Public Sub ClearToggles(tg As ToggleButton)
Dim ctl As Control
Me.EventsDisabled = True
For Each ctl In Me.Controls
If TypeName(ctl) = "ToggleButton" And ctl.Name <> tg.Name Then
ctl.Value = False
End If
Next ctl
Me.EventsDisabled = False
End Sub