VBA Excel - Word Wrap - excel

I'm creating a small piece of VBA code with a specific formula, however it has a couple of if statements, one of which originates a double-line string (with vbNewLine)
The issue is that I can't see the text.
So I wanted to word wrap it, but each time I set the ActiveCell.WrapText = True, nothing happens.
I checked with a message box. I set the WrapText to True, I return the property value with the MessageBox to confirm, and it's still False.
I've been told to use ActiveCell.Rows.AutoFit as well, but AutoFit does nothing if the text isn't wrapped.
Any idea what I might be doing wrong here?

try:
Sub WrapandFit()
ActiveCell.WrapText = True
ActiveCell.EntireRow.AutoFit
End Sub
It worked for me. Make sure that your screenupdating is also set to true.

For me, the code below worked. (only set to change header row, (change range))
ActiveSheet.Range("A1:R1").Select
With Selection
.WrapText = True
End With

UDFs (procedures that use the keyword Function) only return values. They cannot change other parts of the Excel object model, like cell formatting. Only Subroutines (procedures that use the keyword Sub) can do that.
You need to have your cells formatted properly before you enter your UDF. Or you could use a worksheet change event sub to format them after the fact.

Turn off/On word wrap for whole sheet row can be done by VB code shown below:
If the first row is set true, excel inherits that property for whole sheet, unless you specifically turned it off using another code.
MyWorkSheet.Rows.WrapText = True
To turn off wrapping property of a specific row:
MyWorkSheet.Rows(8).WrapText = False

I suspect that you are trying to wrap text in merged cells. If yes, you cannot simply call:
MyWorkSheet.Rows.WrapText = True
Instead, you have to simulate the wrapping operations. I found the code from http://blog.contextures.com/archives/2012/06/07/autofit-merged-cell-row-height/ helped me last year.
Private Sub Worksheet_Change(ByVal Target As Range)
Dim MergeWidth As Single
Dim cM As Range
Dim AutoFitRng As Range
Dim CWidth As Double
Dim NewRowHt As Double
Dim str01 As String
str01 = "OrderNote"
If Not Intersect(Target, Range(str01)) Is Nothing Then
Application.ScreenUpdating = False
On Error Resume Next
Set AutoFitRng = Range(Range(str01).MergeArea.Address)
With AutoFitRng
.MergeCells = False
CWidth = .Cells(1).ColumnWidth
MergeWidth = 0
For Each cM In AutoFitRng
cM.WrapText = True
MergeWidth = cM.ColumnWidth + MergeWidth
Next
'small adjustment to temporary width
MergeWidth = MergeWidth + AutoFitRng.Cells.Count * 0.66
.Cells(1).ColumnWidth = MergeWidth
.EntireRow.AutoFit
NewRowHt = .RowHeight
.Cells(1).ColumnWidth = CWidth
.MergeCells = True
.RowHeight = NewRowHt
End With
Application.ScreenUpdating = True
End If
End Sub

This may not be exactly what the OP had in mind but I figured I'd share my VBA Word Wrap function since I couldn't find anything on the web to do what I wanted.
This function insert CR+LF's into the string in order to wrap it, so the word wrap is maintained if the text is copied to another application, text-based or otherwise.
Function wrapText(strIn As String, Optional maxLen As Long = 110) As String
Dim p As Long: wrapText = strIn
Do
p = InStrRev(wrapText, " ", p + maxLen) - 1
wrapText = Left(wrapText,p) & vbCrLf & Right(wrapText, Len(wrapText)-p-1)
Debug.Print Mid(Replace(wrapText, vbCrLf, "||"), p - 20)
'Stop
Loop While p + maxLen < Len(wrapText)
End Function
It defaults to maximum width of 115 characters but can optionally be changed to anything. It only breaks on spaces (the last one that appears on/before position #115), and it only inserts CR + LF's (with the constant vbCrLf), but they can be adapted as required.
As an example of application, I was building complex SQL queries in Excel and wanted to copy the SQL over to the server app neat & tidy, instead of one giant line.

Related

How can I make removal of Strikethrough text in Excel cells using XML parsing more robust?

I have a complex spreadsheet with many cells of text containing random mixtures of normal text and text with strikethrough. Before I scan a cell for useful information, I have to remove the struck through text. I intially achieved this (with VBA) using the Characters object, but it was so slow as to be totally impractical, for business purposes. I was then kindly supplied with some code (on this site) that parses the XML encoding. This was 1000's of times faster, but it occassionally causes the following error:
"The parameter node is not a child of this node".
So far, it only happens in heavily loaded cells (1000's of characters), otherwise it works fine. I cannot see anything wrong in the code or the XML structure of the problem cells, although I am a total newbie to XML. Using the VBA debugger, I know the error is occurring when RemoveChild() is called, typically when it has already worked without error on a few struck through sections of a cell's text.
Is there a way I could make the following code more robust?
Public Sub ParseCellForItems(TargetCell As Excel.Range, ItemsInCell() As String)
Dim XMLDocObj As MSXML2.DOMDocument60
Dim x As MSXML2.IXMLDOMNode
Dim s As MSXML2.IXMLDOMNode
Dim CleanedCellText As String
On Error GoTo ErrorHandler
Call UnstrikeLineBreakCharsInCell(TargetCell)
Set XMLDocObj = New MSXML2.DOMDocument60
'Add some namespaces.
XMLDocObj.SetProperty "SelectionNamespaces", "xmlns:ss='urn:schemas-microsoft-com:office:spreadsheet' " & _
"xmlns:ht='http://www.w3.org/TR/REC-html40'"
'Load the cell data as XML into XMLDOcObj.
If XMLDocObj.LoadXML(TargetCell.Value(xlRangeValueXMLSpreadsheet)) Then
Set x = XMLDocObj.SelectSingleNode("//ss:Data") 'Cell content.
If Not x Is Nothing Then
Set s = x.SelectSingleNode("//ht:S") 'Struck through cell content.
Do While Not s Is Nothing
x.RemoveChild s
Set s = x.SelectSingleNode("//ht:S")
Loop
CleanedCellText = XMLDocObj.Text
'Parse CleanedCellText for useful information.'
'...
End If
End If
Set XMLDocObj = Nothing
'Presumably don't have to 'destroy' x and s as well, as they were pointing to elements of XMLObj.
Exit Sub
ErrorHandler:
Call RaiseError(Err.Number, Err.Source, "ParseCellForItems()", Err.Description, Erl)
End Sub
Public Sub UnstrikeLineBreakCharsInCell(TargetCell As Excel.Range)
Dim mc As MatchCollection
Dim RegExObj1 As RegExp
Dim Match As Variant
On Error GoTo ErrorHandler
Set RegExObj1 = New RegExp
RegExObj1.Global = True
RegExObj1.IgnoreCase = True
RegExObj1.Pattern = "\n" 'New line. Equivalent to vbNewLine.
Set mc = RegExObj1.Execute(TargetCell.Value)
For Each Match In mc
TargetCell.Characters(Match.FirstIndex + 1, 1).Font.Strikethrough = False
Next Match
Set mc = Nothing
Set RegExObj1 = Nothing
Exit Sub
ErrorHandler:
Call RaiseError(Err.Number, Err.Source, "UnstrikeLineBreakCharsInCell()", Err.Description, Erl)
End Sub
Yep, as per Tim Williams' comment, making sure you're calling RemoveChild() from its immediate parent fixes the problem:
Set s = x.SelectSingleNode("//ht:S")
Do While Not s Is Nothing
s.ParentNode.RemoveChild s
Set s = x.SelectSingleNode("//ht:S")
Loop

VBA excel module works not always

Hello stackoverflow users,
I am facing the following problem, I receive a very big Excel table every day and would like to simplify it. So I decided to automatize this task, I wrote a VBA script and saved it as a module.
I open and execute it, sometimes it works. I am searching for hours already for any hint.
Function HideRows()
ActiveSheet.Rows("2:2").EntireRow.Hidden = True
ActiveSheet.Rows("5:5").EntireRow.Hidden = True
ActiveSheet.Rows("8:8").EntireRow.Hidden = True
ActiveSheet.Rows("10:10").EntireRow.Hidden = True
ActiveSheet.Rows("11:11").EntireRow.Hidden = True
ActiveSheet.Rows("24:24").EntireRow.Hidden = True
ActiveSheet.Rows("29:29").EntireRow.Hidden = True
ActiveSheet.Rows("30:30").EntireRow.Hidden = True
ActiveSheet.Rows("31:31").EntireRow.Hidden = True
ActiveSheet.Rows("37:37").EntireRow.Hidden = True
End Function
Function HideColumns()
Dim rng As Range
For Each rng In Range("C:J").Columns
rng.EntireColumn.Hidden = True
Next rng
For Each rng In Range("L:M").Columns
rng.EntireColumn.Hidden = True
Next rng
End Function
Function FilterByAttributes()
beginRow = 1
EndRow = Cells(Rows.Count, 1).End(xlUp).row
ActiveSheet.Range("K" & EndRow).AutoFilter Field:=11, Criteria1:=RGB(255, 0, 0), Operator:=xlFilterCellColor
End Function
Private Sub CommandButton1_Click()
Call HideColumns
Call HideRows
Call FilterByAttributes
End Sub
Is there any better possibility to format the table with less amount of clicks according to the conditions in my script?
UPDATE: the algorithm of my actions:
Download excel table from my email
Open this excel table
Open "Developer tools tab"->Visual Basic-> File-> Import->Select module->Execute Module. This step has to be somehow simplified, have no ideas how
Continue working with the resultant table
I would like to make as less clicks as possible for the "special filter"
Thanks in advance
Some thoughts:
1) Consider adding the macro to a personal workbook instead of importing it every day to a new excel file.
2) You don't need a loop to hide columns: ActiveSheet.Columns("C:J").Hidden = True, and similarly for .Columns("L:M").
3) The Call keyword can be dropped.
4) Add Option Explicit to the top of the module and declare all variables, specifically beginRow and EndRow.

Concatenating reference with formula gives runtime error

I'm trying to use VBA to concatenate a string inside a formula. If I only use the code below i'm not getting any errors but when i add the IFERROR together with the code I get a runtime error.
Is there any way to work around it?
text1 = "='C:\Users\JOHLA\\Desktop\Yield ark\Nyt-yield-ark\[Yield-Uge-"
text2 = ".xlsm]Scrap'!H7"
The code including string with IFERROR that gives runtime error is given below.
Private Sub CommandButton1_Click()
Dim ws As Worksheet
Dim i As Integer
Dim preRange As Range
Dim path, filename1 As String
Application.ScreenUpdating = False
Application.DisplayAlerts = False
Set ws = ActiveWorkbook.Sheets("Sheet1")
Set preRange = ws.Range("E9:I17")
i = ws.Range("C1").Value
text1 = "=IFERROR('C:\Users\JOHLA\Desktop\Yield ark\Nyt-yield-ark\[Yield-Uge-"
text2 = ".xlsm]Scrap'!H7;0)"
With ws
For i = .Range("C1").Value To .Range("C1").Value + 4
debug.print text1 & i & text2
preRange = text1 & i & text2
Set preRange = preRange.Offset(0, 5)
Next i
End With
End Sub
Judging by your use of semicolon in your formula, it would suggest that you're using local settings which are not compatible with VBA.Formula
in this case, you either need to change the formula to use a comma or set the formula using FormulaLocal:
preRange.FormulaLocal = Replace(text1 & i & text2, "'", Chr(34))
As you can see, I've also added a Replace that changes ' into " - as I think you need this also.
Lastly, don't forget to enable ScreenUpdating and DisplayAlerts at the end of your routine.
Any time you use
Application.ScreenUpdating = False
Application.DisplayAlerts = False
you need to make sure that you are using error handling to manage if your code breaks.
On Error GoTo ExitErr
Application.ScreenUpdating = False
Application.DisplayAlerts = False
<your code here>
ExitErr:
Application.ScreenUpdating = True
Application.DisplayAlerts = True
This makes sure that if your code breaks (in your case the most obvious being someone renaming "Sheet1"), Excel isn't left with ScreenUpdating and DisplayAlerts left turned off. I can't tell you how many times I've had to fix other people's code because they turned off these functions and then couldn't figure out why Excel was acting up.

Why do I get run-time error -2147417848 (80010108) in excel 2013 most of the time I run UserForm?

Task:
I work in Excel2013. I tried to write in VBA a userform to add parameters into dynamic named ranges. All named ranges are held in one sheet and were created using insert>table. I select the range, show existing values and get the new value. All went well untill I actually got to adding value to the range.
Problem:
Excel shuts down most of the time when I try to run the UserForm. Saying:
"Run-time error '-2147417848 (80010108)' Method X of object 'Range' failed"
with different methods ('_Default' last time I checked) at different stages of me breaking code down.
Symtoms:
After this line as I found I get the error:
Cells(y, x) = v
where y and x are integers and v a string I get from the userform. During the debug I checked all values are defined and have values. Moreover, Immediate window with the same numbers input manually (not as variables), works!
It mostly doesn't work, though it did follow through doing the job.
If somone could tell the reason why it breaks it would be greatly appreciated!
Some of the captions and potential values are in Unicode in case it matters, though I tried putting it all in English as well.
Private Sub UserForm_Initialize()
' Preparing all controls of UserForm
Sheet2.Activate
Me.LB_parameter.SetFocus
Me.LB_parameter.value = ""
Me.LB_elements.RowSource = ""
Me.L_element.Enabled = False
Me.TB_element.Enabled = False
Me.TB_element.Locked = True
Me.Btn_Add.Enabled = False
Me.Btn_Add.Locked = True
End Sub
Private Sub LB_parameter_DblClick(ByVal Cancel As MSForms.ReturnBoolean)
' Filling the existing list of values for the selected parametr
If Me.LB_parameter.value <> "" Then
Me.LB_elements.RowSource = "D_" & Me.LB_parameter.value & "s"
Me.L_element.Enabled = True
Me.TB_element.Enabled = True
Me.TB_element.Locked = False
Me.TB_element.SetFocus
End If
End Sub
Private Sub TB_element_DblClick(ByVal Cancel As MSForms.ReturnBoolean)
' Catching the event of filling out the potential new value
Me.Btn_Add.Enabled = True
Me.Btn_Add.Locked = False
Me.L_element.Enabled = False
Me.TB_element.Enabled = False
Me.TB_element.Locked = True
End Sub
Private Sub Btn_Add_Click()
If Me.TB_element.Text = "" Then
' Check if Empty
MsgBox ("Âû íå âïèñàëè çíà÷åíèå!")
' Reset the UserForm
Me.Btn_Add.Enabled = False
Me.Btn_Add.Locked = True
Me.L_element.Enabled = True
Me.TB_element.Enabled = True
Me.TB_element.Locked = False
Me.TB_element.SetFocus
Else
' check if exists
Dim str
For Each str In range("D_" & Me.LB_parameter.value & "s")
If Me.TB_element.Text = str Then
MsgBox ("Ââåäåííîå çíà÷åíèå óæå ñóùåñòâóåò!")
' reset the UserForm
Me.Btn_Add.Enabled = False
Me.Btn_Add.Locked = True
Me.L_element.Enabled = True
Me.TB_element.Enabled = True
Me.TB_element.Locked = False
Me.TB_element.SetFocus
Me.TB_element.value = ""
Exit Sub
End If
Next str
' add to the range here
Dim x As Integer, y As Integer, v As String
y = range("D_" & Me.LB_parameter.value & "s").Rows.Count + 2
x = Me.LB_parameter.ListIndex + 1
v = Me.TB_element.value
' Next line causes break down
Cells(y, x) = v
MsgBox ("Âû äîáàâèëè ýëåìåíò:'" & v & "' äëÿ ïàðàìåòðà '" & Me.LB_parameter.value & "'.")
' Reset the Userform
Me.LB_parameter.SetFocus
Me.LB_parameter.value = ""
Me.LB_elements.RowSource = ""
Me.L_element.Enabled = False
Me.TB_element.Enabled = False
Me.TB_element.Locked = True
Me.Btn_Add.Enabled = False
Me.Btn_Add.Locked = True
End If
End Sub
Sheet I add values to the parametrs and namedranges window:
The UserForm layout:
Cells(y, x) = v
This call is shorthand for this:
ActiveSheet.Cells(y, x).Value = v
I'm not sure why it's crashing on you, but the _Default property of a Range object being its Value, what I'd try here is being more explicit about what I'm trying to achieve, namely:
Exactly which Worksheet is supposed to get modified?
Exactly which Range is being referred to?
I very very very seldom work with ActiveSheet - most of the time I know exactly what object I'm working with. Try using an object. You can create a new one:
Dim target As Worksheet
Set target = ThisWorkbook.Worksheets("pl")
...Or you can give the sheet a code name in the properties toolwindow (F4):
That (Name) property defines an identifier that you can use in VBA code to access a global-scope object that represents that specific worksheet. Assuming that's Sheet1, you could do this:
Sheet1.Cells(x, y) = v
If that still fails, then you can be even more specific about the Range object you're accessing and the property you're setting:
Dim target As Range
Set target = Sheet1.Cells(x, y)
target.Value = v
Normally that wouldn't make a difference though. But I see you're making Range calls, which are also implicitly calling into the ActiveSheet.
I'd start by eliminating these, and working off an explicit object reference.
Then I'd work on getting the spreadsheet logic out of the form; that button click handler is doing way too many things - but I digress into Code Review territory - feel free to post your code there when you get it to work as intended!
Looks like the problem lies in my version of Excel. Not sure if the problem is in my copy or in the 2013 in general. In Excel 2007 on the same machine the UserForm with given suggestions worked continuously without any errors at all! Will update in comments later as I try it in different versions.

Excel UDF detecting page breaks?

I'm trying to write a UDF that returns whether the cell is at a page break.
So far I have this:
Function pbreak() As Boolean
' Application.Volatile
pbreak = False
Dim ra As Range
Set ra = Application.Caller
With ra
For i = 1 To .Worksheet.HPageBreaks.Count
If .Worksheet.HPageBreaks(i).Location.Row = .Row Then
pbreak = True
End If
Next
End With
End Function
This returns a #VALUE error. I've tried debugging it, HPageBreaks.Count returns 3 (and there are 3 page breaks), but HPageBreaks(i) yields an "index out of range"-error for all pagebreaks that are below the current cell .
Is this a bug (ie .Count is wrong), or is there some special behavior with page breaks that I am missing?
Is there a way to fix this (preferably without resorting to on error resume next)?
Thanks
Martin
Option Explicit
Function pbreak() As Boolean
' Application.Volatile
Dim i As Integer 'the missing line
pbreak = False
Dim ra As Range
Set ra = Application.Caller
With ra
For i = 1 To .Worksheet.HPageBreaks.Count
If .Worksheet.HPageBreaks(i).Location.Row <= .Row Then
If .Worksheet.HPageBreaks(i).Location.Row = .Row Then
pbreak = True
'exit the function once a page break is found.
Exit Function
End If
Else
Exit Function
End If
Next
End With
End Function
EDIT: Always use Option Explicit & compile the code before using it.
Use of Exit Function inside the loop is to prevent the code from running it further, once the result is known.

Resources