VBA Formating on Characters greater than 255 using Characters.Font - vba

I posted a question recently on how to format the first part of text from a input box and append it to existing text in a cell.
So for example if I have a cell with... this
23/08/2013: Hi there how are you today
24/08/2013: Customer is feeling good today
and I double click the cell, I get an input box to enter a comment. I take the comment.. add todays date to it in the VBA code and then append and format it to the existing code using this
If Target.Column = NOTES_COL Then 'Add a note
lngPos = Len(Target.Text)
strNote = InputBox(Prompt:="Enter Note", _
Title:="Notes", Default:="")
If (Len(Trim(strNote)) > 0) Then
If Target.Value = "" Then
Target.Font.Bold = False
newVal = Date & ": " & strNote
Else
newVal = Chr(10) & Date & ": " & strNote
End If
Target.Characters(Start:=lngPos + 1).Text = newVal
Target.Characters(Start:=lngPos + 1, Length:=11).Font.Bold = True
End If
End If
So basically this takes a comment... adds a date to the comment, and a new line and then appends it to the existing characters and formats the date Bold.
This all works fine until I go over 255 Characters, which the poster that helped me in the last solution warned about, but I thought it was trying to insert one comment > 255 and not the entire cell lenght.
How do I get around this... as I can add mulitple comments into a cell
regards Mick

A similar issue was discussed on SO here. According to that thread, the issue is that the InputBox method only allows a max of 255 characters. I've verified this myself.
That thread also suggested using a userform. That is the best option, especially considering entering over 255 characters in a one-line inputbox is probably setting up for frustration. How will you review what you've already typed, for example?
There are a few less polished options I can think of:
Have users enter comments directly in another cell - for example, select (and I hate using select!) a different cell, and let the user put whatever notes they desire there. Then run code to append that cell's value to your target cell.
Something even more crazy would be to call an outside program, such as notepad (or the Mac counterpart), save, and import that information in to append to the target cell.
All of these options are just work-arounds for the InputBox limit. Maybe there are some better alternatives someone more experienced can think of?
Update
Also, there seems to be an issue with using .Characters to add to anything over 255 characters long. I suspect this is because the .Characters method (not property) is set up to work on a textframe, not a cell value. A textframe is actually an object that works with shapes. It's interesting you're using it here on a range to edit its value - cool trick, courtesy of another question you've asked.
A solution would be to replace the cell value. You can then use .Characters as an object, to format portions of the cell value, as I think you've already tried to do in your other question.

Related

Issue using Instr with Cell Formats

I have a puzzle that i've trying to solve for some time. I have a spreadsheet that imports data from a .csv file, which worls well apart from the intial location setting.
I currently run a search to find the last cell that contains a value and use that as the starting point. Now for reason above and beyond me this sometimes fails and places the starting point halfway down the spreadsheet. SO to get around this I decided to write a check code or a more sophisticated location finder.
The new Location finder is as follows
For a = 1 To 400
Dim SearchString As Variant
Dim SearchSymbol As String
SearchString = Cells(NewLastRowNumber, 10)
SearchSymbol = "€"
If InStr(1, SearchString, SearchSymbol, 1) = 0 Then
NewLastRowNumber = NewLastRowNumber - 1
Else
NewLastRowNumber = NewLastRowNumber + 1
Exit For
End If
Next a
This works, apart from the what it searches within the cell. The ideal behind is it search a column of data containing cost, i.e (Row 1 -> €100, Row 2 -> €235 etc..) and find the last cell containng € currently I can only ever get it to find the column header and not the cell.
Each cell in the column is formatted as a Custom (€0.00), not sure if this makes any difference or not.
Ive embeded some images to further demonstrate my issue.
At this point NewLastRowNumber = 13
At this point the loop should break and record NewLastRowNumber = 13 but instead it continues until it finds the column header.
Hope this all makes sense & thanks.
The cell is formatted as Currency or Custom, thus you cannot find the Euro sign there. When you check InStr(), it checks the cell .Value, not the .Format.
To find the Eur in the Cell, check the format like this:
If Cells(NewLastRowNumber, 10).NumberFormat = "€0.00" Then
To see the exact number format in VBA, select the cell with the wanted format and run the following:
Sub Test()
Debug.Print Selection.NumberFormat
End Sub

Excel VBA: Reformatting date to very short date with two-digit-years does not work

I have a list of dates exported from another application. Initially they are formatted as Standard, without any special or automatic conversion, in the format YYYY.MM.DD.
Previously I just replaced the dots with slashes and Excel recognized the dates as dates, automatically formatting them to the appropriate locale DD.MM.YYYY, which worked fine.
Now, I just wanted to remove the first two digits of the year (new format DD.MM.YY), but it does not work in any way I've tried. What I have done so far:
currentCell.Value = CDate(currentCell.Value): This switches the format to date, but the standard long formatting still applies.
currentCell.NumberFormat = "dd.mm.yy;#" As seen from recording a macro and changing the value manually. Does not seem to make any difference in output format.
currentCell.Value = FormatDateTime(currentCell.Value, vbShortDate): Does not work, as Short Date is DD.MM.YYYY, not DD.MM.YY in the current locale.
format(date, "dd.mm.yy") as suggested by Tom Preston and msaint: Does not change the output compared to the NumberFormat version.
Another strange thing I've observed: After applying the date change per VBA, manual change of date format via the GUI is not possible anymore/seems to not have any effect.
What could be the cause of this and how could I achieve the really short date in VBA?
Try
format(date, "dd.mm.yy")
This will give you answer in string format if that is acceptable?
To give this some context:
Sub Datings()
Dim d As Date
d = Now
Debug.Print d, Format(d, "dd/mm/yy")
End Sub
Convert the text date to something recognised by Excel as a date (replace the . with / as you did originally, then just update the numberformat in the cell.
Sheet1.Range("A1") = Replace(Sheet1.Range("A1"), ".", "/")
Sheet1.Range("A1").NumberFormat = "dd.mm.yy"
After the suggestion to add more surrounding code by tom preston, I've tried Darren Bartrup-Cook's example code on the first cell as a very basic test. It did not work, so I used breakpoints to skip through all code before and after this piece of code.
This revealed that all the time, it was working correctly, but later other code reverted the formatting by calling Cells(1,1).Value = "" & Cells(1,1).Value on the correctly formatted cells. I did not notice it because in the GUI it was still shown as Format: Date, although internally it was overwritten.
The complete solution was therefore as simple as using
Range("A1:A" & lastRow).Replace What:=".", replacement:="/", LookAt:=xlPart
Sheet1.Range("A1:A" & lastRow).NumberFormat = "dd.mm.yy"
and not touching column A later on.
So, lesson learned: use many breakpoints to narrow down the problem and check that you do not shoot yourself in the foot later on.
Try this:
Option Explicit
Sub ReformatDate()
Dim C As Range
Dim V As Variant
For Each C In Selection
With C.Offset(0, 1)
.NumberFormat = "dd.mm.yy"
If IsDate(C) Then
.Value = C
Else
V = Split(C.Text, ".")
.Value = DateSerial(V(0), V(1), V(2))
End If
End With
Next C
End Sub
If your system recognizes the value as a date, then just reformat
If your system does NOT recognize the value as a date, then convert it
Split it using the dot as a delimiter
Convert the parts to a real date
write it back and format accordingly
You'll need to make appropriate modifications if you want it to overwrite, instead of putting the value in the next column; and if you want to use a more automatic method of selecting the range to convert.

Making a formula a VBA Macro

I got the following code
=LEFT(A2, MIN(ROW(INDIRECT("1:"&LEN(A2)))+(((CODE(MID(UPPER(A2),
ROW(INDIRECT("1:"&LEN(A2))), 1))>64)*(CODE(MID(UPPER(A2),
ROW(INDIRECT("1:"&LEN(A2))), 1))<91))+
((CODE(MID(A2, ROW(INDIRECT("1:"&LEN(A2))), 1))>47)*
(CODE(MID(A2, ROW(INDIRECT("1:"&LEN(A2))), 1))<58)))*1E+99)-1)
I have this code and a few others, but how can I make it into a macro applicable to my entire workbook? I know its probably the same as a macro in terms of time, but I eventually want to loop it throughout a directory and would help automate a process. Is there a way to make this a macro for my workbook?
The crudest quickest way would be something like this:
Range("J2:J5000").Formula = "=LEFT(A2, MIN(ROW(INDIRECT(""1:""&LEN(A2)))+(((CODE(MID(UPPER(A2), ROW(INDIRECT(""1:""&LEN(A2))), 1))>64)*(CODE(MID(UPPER(A2), ROW(INDIRECT(""1:""&LEN(A2))), 1))<91))+((CODE(MID(A2, ROW(INDIRECT(""1:""&LEN(A2))), 1))>47)*(CODE(MID(A2, ROW(INDIRECT(""1:""&LEN(A2))), 1))<58)))*1E+99)-1)"
Which will put your exact formula in the range (and update itself according to the row reference). Obviously the reference to column J can be changed and the 5000 can be made dynamic using rows.count).end(xlup).row but without knowing which columns to play with I just had to take a stab at a crude solution.
However depending on what your "symbols" could be a solution using the split command would most likely be the better choice. Can you post more direction on this? Then I can edit this answer and add a code solution in for that for you.
Also include some sample data and expected results, maybe 10 rows worth to give a good set for testing
For an example of how the split command works select one of the cells with data in it that you need to split on the underscore and go to the debug window in the VBE (CTRL-G) and enter this (including the question mark) then press enter.
?split(Activecell.text,"_")(0)
Now update the 0 to 1 and press enter. This will show you how this command works, it splits a string to an array based on the delimiter you give it.
EDIT:
This code will do what you want, Notice how Split is being used.
Function GetFirstPart(SplitString As String)
Dim PosibleSplits As Variant, X As Long
PossibleSplits = Array("_", "+", "-")
For X = LBound(PossibleSplits) To UBound(PossibleSplits)
If Len(SplitString) <> Len(Split(SplitString, PossibleSplits(X))(0)) Then
GetFirstPart = Split(SplitString, PossibleSplits(X))(0)
Exit For
End If
Next
End Function
Use it by pasting the code into a module then in your sheet use it the same as any other formula =GetFirstPart(A1) where A1 has the string to split, drag down as far as your data goes.
You can add other delimiters in this line PossibleSplits = Array("_", "+", "-")

Excel Macro, inserting internationally valid formula during run-time

I've got an Excel spreadsheet, with a Macro, that inserts a conditional formatting, like this:
Selection.FormatConditions.Add Type:=xlExpression, Formula1:="=UND($A3=""" & lastName & """; $B3=""" & firstName & """)"
As you can see, I've used the German formula for "AND" (i.e. "UND"), and obviously, this code doesn't work as soon as I use it on a French or English version of Excel.
Usually formulas are localized automatically, but how can I insert a formula during run-time that will work on ALL versions?
Ok, thanks for helping me with this, you've helped me crack this one.
It is indeed not possible to just use English. One can use English when operating on a formula, eg. by setting coding Range("A1").formula="AND(TRUE)", but this does not work with FormatConditions.
My solution is a function that writes a formula temporarily to a cell, reads it through the FormulaLocal property, and returns the localized formula, like so:
Function GetLocalizedFormula(formula As String)
' returns the English formula from the parameter in the local format
Dim temporary As String
temporary = Range("A1").formula
Range("A1").formula = formula
Dim result As String
result = Range("A1").FormulaLocal
Range("A1").formula = temporary
GetLocalizedFormula = result
End Function
The returned formula can be used on FormatConditions, which will be re-localized or un-localized when the document is later opened on a different-language version of Excel.
I just found a very elegant solution to the problem in a German Excel forum. This doesn't write to a dummy cell but rather uses a temporary named range. I used the original idea (credit to bst) to write a translating function for both directions.
Convert localized formula to English formula:
Public Function TranslateFormula_LocalToGeneric(ByVal iFormula As String) As String
Names.Add "temporaryFormula", RefersToLocal:=iFormula
TranslateFormula_LocalToGeneric = Names("temporaryFormula").RefersTo
Names("temporaryFormula").Delete
End Function
Convert English formula to localized formula:
Public Function TranslateFormula_GenericToLocal(ByVal iFormula As String) As String
Names.Add "temporaryFormula", RefersTo:=iFormula
TranslateFormula_GenericToLocal = Names("temporaryFormula").RefersToLocal
Names("temporaryFormula").Delete
End Function
This is very handy if you need to deal with formulas in conditional formatting, since these formulas are always stored as localized formulas (but you could need their generic version, e.g. to use Application.Evaluate(genericFormula)).
Store (a trivial version of) the formula in a (hidden) cell in your workbook.
Then when you open the workbook that formula will be translated automatically by excel for the user.
Now you just have to dissect this formula in your script (find the opening bracket "(" and take the past left of that:
Use something like:
strLocalizedFormula = Mid(strYourFormula, 2, InStr(1, strYourFormula, "(") - 2)
where strYourFormula will be a copy from the formula from your worksheet.
I hope this works as I only use an English environment.
Also from reading this:
http://vantedbits.blogspot.nl/2010/10/excel-vba-tip-translate-formulas.html
I am thinking you should (only) be able to use the english version of a cell formula from VBA.
Maybe try this (untested as I only have English version insatlled)
Write your international version of the formula to an out of the way cell using Range.Formula . Then read it back from Range.FormulaLocal, and write that string to the FormatConditions
I know this thread is ages old, and someone may have found an elegant solution, but I just had the same problem where I needed to apply conditional formatting without modifying the sheet, creating temporary cell contents or named ranges. All users use English language versions of Excel, so the functions used in the formulas are the same, but the regional settings vary by location, and therefore also the parameter separater; In Norwegian, it's ";" instead of ",", much like the rest of Europe, I guess.
For example, I needed to automatically create conditional formatting, using Excel formula for the following criterion:
.FormatConditions.Add xlExpression, Formula1:="=AND(ISNUMBER(B" & I & "),B" & I & ">=" & Ul1 & ")"
Where "Ul1" is a value defined in a previous step, and it's not important for the solution.
However, I needed to be able to run this on computers with both Norwegian and English settings
I and found a very short and simple solution from Andrew Pulsom here: https://www.mrexcel.com/board/threads/french-vba-vs-english-vba.729570/. He just made the parameter separator into a variable:
If Application.International(xlDecimalSeparator) = "," Then
Sep = ";"
Else
Sep = ","
End If
Cl1 = "=AND(ISNUMBER(B" & I & ")" & Sep & "B" & I & "<" & Ul1 & ")"
Worked like a charm for me :)
I know that this only solves part of the problem, but I assume that this could apply to many international companies which use English Office installations with local regional settings.
Thanks everyone! I found the post very useful.
My solution is a combination of others, I add it in case somebody finds it useful.
Dim tempform As String
Dim strlocalform1 As String
Dim strlocalform2 As String
' Get formula stored in WorksheetA Cell O1 =IFERROR(a,b)
tempform = Worksheets("Sheet").Range("O1").Formula
' Extract from the formula IFERROR statement in local language.
strlocalform1 = Mid(tempform, 2, InStr(1, tempform, "(") - 1)
' Extract from the formula separator , (comma) in local settings.
strlocalform2 = Mid(tempform, InStr(1, tempform, "a") + 1, 1)
' Add formula in local language to desired field.
pvt.CalculatedFields.Add Name:="NewField", Formula:="=" & strlocalform1 & "FORMULA" & strlocalform2 & ")"
Hope this helps!
Please refer to the link for more explanation: https://bettersolutions.com/csharp/excel-interop/locale-culture.htm
CultureInfo baseCulture = System.Threading.Thread.CurrentThread.CurrentCulture;
Thread.CurrentThread.CurrentCulture = new CultureInfo(xlapp.LanguageSettings.LanguageID(Office.MsoAppLanguageID.msoLanguageIDUI));
// do something
System.Threading.Thread.CurrentThread.CurrentCulture = baseCulture;

Why is my conditional format offset when added by VBA?

I was trying to add conditional formats like this:
If expression =($G5<>"") then make set interior green, use this for $A$5:$H$25.
Tried this, worked fine, as expected, then tried to adapt this as VBA-Code with following code, which is working, but not as expected:
With ActiveSheet.UsedRange.Offset(1)
.FormatConditions.Delete
'set used row range to green interior color, if "Erledigt Datum" is not empty
With .FormatConditions.Add(Type:=xlExpression, _
Formula1:="=($" & cstrDefaultProgressColumn & _
.row & "<>"""")")
.Interior.ColorIndex = 4
End With
End With
The Problem is, .row is providing the right row while in debug, however my added conditional-formula seems to be one or more rows off - depending on my solution for setting the row. So I am ending up with a conditional formatting, which has an offset to the row, which should have been formatted.
In the dialog it is then =($G6<>"") or G3 or G100310 or something like this. But not my desired G5.
Setting the row has to be dynamicall, because this is used to setup conditional formats on different worksheets, which can have their data starting at different rows.
I was suspecting my With arrangement, but it did not fix this problem.
edit: To be more specific, this is NOT a UsedRange problem, having the same trouble with this:
Dim rngData As Range
Set rngData = ActiveSheet.Range("A:H") 'ActiveSheet.UsedRange.Offset(1)
rngData.FormatConditions.Delete
With rngData.FormatConditions.Add(Type:=xlExpression, _
Formula1:="=($" & cstrDefaultProgressColumn & _
1 & "<>"""")")
.Interior.ColorIndex = 4
End With
My Data looks like this:
1 -> empty cells
2 -> empty cells
3 -> empty cells
4 -> TitleCols -> A;B;C;...;H
5 -> Data to TitleCols
. .
. .
. .
25
When I execute this edited code on Excel 2007 and lookup the formula in the conditional dialog it is =($G1048571<>"") - it should be =($G1<>""), then everything works fine.
Whats even more strange - this is an edited version of a fine working code, which used to add conditional formats for each row. But then I realized, that it's possible to write an expression, which formats a whole row or parts of it - thought this would be adapted in a minute, and now this ^^
edit: Additional task informations
I use conditional formatting here, because this functions shall setup a table to react on user input. So, if properly setup and a user edits some cell in my conditionalized column of this tabel, the corresponding row will turn green for the used range of rows.
Now, because there might be rows before the main header-row and there might be a various number of data-columns, and also the targeted column may change, I do of course use some specific informations.
To keep them minimal, I do use NamedRanges to determine the correct offset and to determine the correct DefaultProgessColumn.
GetTitleRow is used to determine the header-row by NamedRange or header-contents.
With ActiveSheet.UsedRange.Offset(GetTitleRow(ActiveSheet.UsedRange) - _
ActiveSheet.UsedRange.Rows(1).row + 1)
Corrected my Formula1, because I found the construct before not well formed.
Formula1:="=(" & Cells(.row, _
Range(strMatchCol1).Column).Address(RowAbsolute:=False) & _
"<>"""")"
strMatchCol1 - is the name of a range.
Got it, lol. Set the ActiveCell before doing the grunt work...
ActiveSheet.Range("A1").Activate
Excel is pulling its automagic range adjusting which is throwing off the formula when the FromatCondition is added.
The reason that Conditional Formatting and Data Validation exhibit this strange behavior is because the formulas they use are outside the normal calculation chain. They have to be so that you can refer to the active cell in the formula. If you're in G1, you can't type =G1="" because you'll create a circular reference. But in CF or DV, you can type that formula. Those formulas are disassociated with the current cell unlike real formulas.
When you enter a CF formula, it's always relative to the active cell. If, in CF, you make a formula
=ISBLANK($G2)
and you're in A5, Excel converts it to
=ISBLANK(R[-3]C7)
and when that gets put into the CF, it ends up being relative to the cell it's applied to. So in row 2, the formula comes out to
=ISBLANK($G655536)
(for Excel 2003). It offsets -3 rows and that wraps to the bottom of the spreadsheet.
You can use Application.ConvertFormula to make the formula relative to some other cell. If I'm in row 5 and the start of my range is in row 2, I make the formula relative to row 8. That way the R[-3] will put the formula in A5 as $G5 (three rows up from A8).
Sub test()
Dim cstrDefaultProgressColumn As String
Dim sFormula As String
cstrDefaultProgressColumn = "$G"
With ActiveSheet.UsedRange.Offset(1)
.FormatConditions.Delete
'set used row range to green interior color, if "Erledigt Datum" is not empty
'Build formula
sFormula = "=ISBLANK(" & cstrDefaultProgressColumn & .Row & ")"
'convert to r1c1
sFormula = Application.ConvertFormula(sFormula, xlA1, xlR1C1)
'convert to a1 and make relative
sFormula = Application.ConvertFormula(sFormula, xlR1C1, xlA1, , ActiveCell.Offset(ActiveCell.Row - .Cells(1).Row))
With .FormatConditions.Add(Type:=xlExpression, _
Formula1:=sFormula)
.Interior.ColorIndex = 4
End With
End With
End Sub
I only offset .Cells(1) row-wise because the column is absolute in this example. If both row and column are relative in your CF formula, you need more offsetting. Also, this only works if the active cell is below the first cell in your range. To make it more general purpose, you would have to determine where the activecell is relative to the range and offset appropriately. If the offset put you above row 1, you would need to code it so that it referred to a cell nearer the bottom of the total number of rows for your version of Excel.
If you thought selecting was a bit of a kludge, I'm sure you'll agree that this is worse. Even though I abhor unnecessary Selecting and Activating, Conditional Formatting and Data Validation are two places where it's a necessary evil.
A brief example:
Sub Format_Range()
Dim oRange As Range
Dim iRange_Rows As Integer
Dim iCnt As Integer
'First, create a named range manually in Excel (eg. "FORMAT_RANGE")
'In your case that would be range "$A$5:$H$25".
'You only need to do this once,
'through VBA you can afterwards dynamically adapt size + location at any time.
'If you don't feel comfortable with that, you can create headers
'and look for the headers dynamically in the sheet to retrieve
'their position dynamically too.
'Setting this range makes it independent
'from which sheet in the workbook is active
'No unnecessary .Activate is needed and certainly no hard coded "A1" cell.
'(which makes it more potentially subject to bugs later on)
Set oRange = ThisWorkbook.Names("FORMAT_RANGE").RefersToRange
iRange_Rows = oRange.Rows.Count
For iCnt = 1 To iRange_Rows
If oRange(iCnt, 1) <> oRange(iCnt, 2) Then
oRange(iCnt, 2).Interior.ColorIndex = 4
End If
Next iCnt
End Sub
Regarding my comments given on the other reply:
If you have to do this for many rows, it is definitely faster to load the the entire range into memory (an array) and check the conditions within the array, after which you do the writing on those cells that need to be written (formatted).
I could agree that this technique is not "necessary" in this case - however it is good practise because it is flexible for many (any type of) customizations afterwards and easier to debug (using the immediate / locals / watches window).
I'm not a fan of Offset although I don't state it doesn't work as it should and in some limited scenarios I could say that the chance for problems "could" be small: I experienced that some business users tend to use it constantly (here offset +3, there offset -3, then again -2, etc...); although it is easy to write, I can tell you it is hell to revise. It is also very often subject to bugs when changes are made by end users.
I am very much "for" the use of headers (although I'm also a fan of reducing database capabilities for Excel, because for many it results in avoiding Access), because it will allow you very much flexibility. Even when I used columns 1 and 2; better is it to retrieve the column nr dynamically based on the location of the named range of the header. If then another column is inserted, no bugs will appear.
Last but not least, it may sound exaggerated, but the last time, I used a class module with properties and functions to perform all retrievals of potential data within each sheet dynamically, perform checks on all bugs I could think of and some additional functions to execute specific tasks.
So if you need many types of data from a specific sheet, you can instantiate that class and have all the data at your disposal, accessible through defined functions. I haven't noticed anyone doing it so far, but it gives you few trouble despite a little bit more work (you can use the same principles again over and over).
Now I don't think that this is what you need; but there may come a day that you need to make large tools for end users who don't know how it works but will complain a lot about things because of something they might have done themselves (even when it's not your "fault"); it's good to keep this in mind.