How to get the height of a table row in Word - vba

I've looked all over the place and tried various things. It's been assumed that it can't be done. So I'm going to try here and see if anybody else has had any luck.
Is there any way to get the height of a table row in Word when the row's HeightRule is set to wdRowHeightAuto?
Alternatively, if there's a way to get the cell's height instead, I'll accept that as a solution since you can calculate the row's height by finding the row's biggest cell.

It's possible to find the row height with Range.Information(). The following snippet doesn't work for the last row in a table or the last row on a page
Dim Tbl as Table
Dim RowNo as Integer
Dim RowHeight as Double
' set Tbl and RowNo to the table and row number you want to measure
RowHeight=Tbl.Rows(RowNo+1).Range.Information(wdVerticalPositionRelativeToPage) _
- Tbl.Rows(RowNo).Range.Information(wdVerticalPositionRelativeToPage)
This returns the height of the row in points by calculating the difference in position between the selected row and the following one.
I have a routine which works in all cases and returns the height in points of the second and subsequent lines in a cell, i.e. a single-line cell returns 0. (I use this in an application which reduces the font size in certain cells to fit the text on one line.)
Dim Doc As Document
Dim Tbl As Table
Dim Pos As Long
Dim RowNo As Integer
Dim ColNo As Integer
Dim CellHeight As Single
' set Doc, Tbl, RowNo and Colno to the document,table and row number you want to
' measure or provide a cell's range if you prefer
Pos = Tbl.Cell(RowNo, ColNo).Range.End - 1 ' last character in cell
CellHeight = Doc.Range(Pos, Pos).Information(wdVerticalPositionRelativeToTextBoundary)

How about cheating?
Dim tbl As Word.Table
Dim r As Row
Dim c As Cell
Set tbl = ActiveDocument.Tables(1)
For Each r In tbl.Rows
iHeight = r.HeightRule
r.HeightRule = 1
Debug.Print r.Height
r.HeightRule = iHeight
Next

I tried the above and found that changing the HeightRule changes the height of the row, which given I am trying to "freeze" the height on what appears in my table beforehand, makes nonsense of the above.
For rows which are empty or contain a single paragraph of unwrapped text in a consistent paragraph format adding up the font size, para before and after can work as follows:
Set r = c.Row
With r
If .HeightRule <> wdRowHeightExactly Then
.HeightRule = wdRowHeightExactly
Set p = c.Range.ParagraphFormat
.Height = c.BottomPadding + c.TopPadding + p.SpaceBefore + p.SpaceAfter + p.LineSpacing
End If
End With

Related

Using MATCH Function with Table Headers in VBA

I'm trying to use the MATCH function to determine a row. I have several holes and some of the holes go through numerous parts (ex: hole 1 could go through two parts, but Hole 2, next to it, could go through four parts). The diameters could be different in each layer, and can vary by quite a bit. I need to pull the correct diameter for the layer. My code works up to the line where I try to get j equal to the row number that the next hole is on. I think my problem is with the range. I'd like to use the table header, since the input table size can vary from project to project.
I updated the code to reflect the suggestions in the comment and the research I was able to do to use the table header in the MATCH function. The issue now is that it pulls the row below what I would have anticipated. Using the code on the example table, j , the row that MATCH returns, is 5, which is a row for Hole 3, unless it counts the header row, but I thought that using dataBodyRange would exclude that row. Note that my table begins at cell A5. I think I still have a reference issue.
Private Sub UpdatePitchDia_Click()
'Want to use diameters from the same part
Dim i
Dim j
Dim LastRowIn
Dim Hole_Num
Dim Incoming_Dia
Dim Hole_L
Dim Hole_R
Dim Hole_U
Dim Hole_D
Dim Stack_Up
Dim Stack_Up_L
Dim L_Dia
Dim tbl_In As ListObject
Dim Hole As Range
Set tbl_In = ThisWorkbook.Sheets("Input").ListObjects("tbl_Input")
Set Hole = tbl_In.ListColumns(1).Range
LastRowIn = tbl_In.DataBodyRange.Rows.Count
i = 1
j = 0
For i = 1 To LastRowIn Step 1
Hole_L = [tbl_Input].Cells(i, 4)
If Not IsEmpty(Hole_L) Then 'Not all holes have adjacent holes
'Need to make sure that the part layers are the same
j = WorksheetFunction.Match(Hole_L, Hole, 0)
'MsgBox "Row of Hole Left (" & Hole_L & ") is " & j
Stack_Up = [tbl_Input].Cells(i, 2)
Stack_Up_L = [tbl_Input].Cells(j, 2)
If Stack_Up <> Stack_Up_L Then
j = j + 1
Stack_Up_L = [tbl_Input].Cells(j, 2)
Else: L_Dia = [tbl_Input].Cells(j, 3)
End If
End If
Next
End Sub

Word Vba: Get position of the first table borderline

I am writing some code in VBA in word and I would like to get the position of first borderline of the table as shown in this image
As seen in the above image, i would like to equalize the first table margin and second table margin (not sure if "margin" is the right word). The intention is to make the first table and second table column widths equal and combine them, merging them into one table. The word document has about 20 separate table and hence i would like to combine all of them into one single table. Please note that the empty tables that you see above has data removed to preserve confidential information.
What I have tried so far:
Public Sub CorrectTables()
Dim mainTable As Table
Dim secondTable As Table
Dim firstColWidth As Integer
Dim secondColumnWidth As Integer
Dim thirdColumnWidth As Integer
Dim fourthColumnWidth As Integer
Dim fiftColumnWidth As Integer
'Get current table
Set mainTable = ActiveDocument.Tables(1)
**firstColMarginPos = mainTable.LeftPadding** This is where i need some help to set the position
firstColWidth = mainTable.Columns(1).Width
secondColumnWidth = mainTable.Columns(2).Width
thirdColumnWidth = mainTable.Columns(3).Width
fourthColumnWidth = mainTable.Columns(4).Width
fiftColumnWidth = mainTable.Columns(5).Width
Dim currentTableCount As Integer
While ActiveDocument.Tables.Count <> 1
currentTableCount = ActiveDocument.Tables.Count
Set secondTable = ActiveDocument.Tables(2)
secondTable.LeftPadding = firstColMarginPos
secondTable.Columns(1).Width = firstColWidth
secondTable.Columns(2).Width = secondColumnWidth
secondTable.Columns(3).Width = thirdColumnWidth
secondTable.Columns(4).Width = fourthColumnWidth
secondTable.Columns(5).Width = fiftColumnWidth
Selection.Tables(1).Select
Selection.Collapse WdCollapseDirection.wdCollapseEnd
While (ActiveDocument.Tables.Count - currentTableCount = 0)
Selection.Delete Unit:=wdCharacter, Count:=1
Wend
Wend
End Sub
I figured out the answer. For the first table, i am able to get the indentation:
firstColMarginPos = mainTable.Rows.LeftIndent
Then i am able to use it to set the value for the second table:
secondTable.Rows.SetLeftIndent LeftIndent:=firstColMarginPos, RulerStyle:=wdAdjustFirstColumn

VBA - check for duplicates while filling cells through a loop

I am writing a VBA code that goes through a defined matrix size and filling cells randomly within its limits.
I got the code here from a user on stackoverflow, but after testing it I realized that it does not fit for avoiding duplicate filling, and for instance when filling 5 cells, I could only see 4 cells filled, meaning that the random filling worked on a previously filled cell.
This is the code I'm working with:
Dim lRandom As Long
Dim sCells As String
Dim sRandom As String
Dim rMolecules As Range
Dim i As Integer, j As Integer
Dim lArea As Long
lArea = 400 '20x20
'Populate string of cells that make up the container so they can be chosen at random
For i = 1 To 20
For j = 1 To 20
sCells = sCells & "|" & Cells(i, j).Address
Next j
Next i
sCells = sCells & "|"
'Color the molecules at random
For i = 1 To WorksheetFunction.Min(5, lArea)
Randomize
lRandom = Int(Rnd() * 400) + 1
sRandom = Split(sCells, "|")(lRandom)
Select Case (i = 1)
Case True: Set rMolecules = Range(sRandom)
Case Else: Set rMolecules = Union(rMolecules, Range(Split(sCells, "|")(lRandom)))
End Select
sCells = Replace(sCells, "|" & sRandom & "|", "|")
lArea = lArea - 1
Next i
rMolecules.Interior.ColorIndex = 5
Using this same exact code which works perfectly, WHAT can I insert and WHERE do I do that so that the code would check if a cell is previously already filled with a string or a color?
I feel as though this code I'm looking for should be right before
rMolecules.Interior.ColorIndex = 5
But I'm not sure what to type.
EDIT
From the comments I realized that I should be more specific.
I am trying to randomly fill cells with the blue color (.ColorIndex = 5), but what I need to check first is if the randomizing hadn't marked a cell twice, so that for instance in this case, if I want to mark 5 different cells, it marks only 4 of them because of a duplicate and thus fills only 4 cells with the blue color. I need to avoid that and make it choose another cell to mark/fill.
I'd appreciate your help.
Keep the cells you use in a Collection and remove them as you fill the random cells:
Sub FillRandomCells(targetRange As Range, numberOfCells As Long)
' populate collection of unique cells
Dim c As Range
Dim targetCells As New Collection
' make sure arguments make sense
If numberOfCells > targetRange.Cells.Count Then
Err.Raise vbObjectError, "FillRandomCells()", _
"Number of cells to be changed can not exceed number of cells in range"
End If
For Each c In targetRange.Cells
targetCells.Add c
Next
' now pick random 5
Dim i As Long, randomIndex As Long
Dim upperbound As Long
Dim lowerbound As Long
For i = 1 To numberOfCells
lowerbound = 1 ' collections start with 1
upperbound = targetCells.Count ' changes as we are removing cells we used
randomIndex = Int((upperbound - lowerbound + 1) * Rnd + lowerbound)
Set c = targetCells(randomIndex)
targetCells.Remove randomIndex ' remove so we don't use it again!
c.Interior.Color = 5 ' do what you need to do here
Next
End Sub
Sub testFillRandomCells()
FillRandomCells ActiveSheet.[a1:t20], 5
FillRandomCells ActiveSheet.[b25:f30], 3
End Sub
EDIT: Changed to make the target range and number of changed cells configurable as arguments to a function. Also added error checking (always do that!).
Why not build a list of random numbers and place in a Scripting.Dictionary, one can use the Dictionary's Exist method to detect duplicates, loop through until you have enough then you can enter your colouring code confident that you have a unique list.

Compares two column based on the value of a third column's value

What I want to do is create a macro to look at a column (AF) and based on that value, compare column (BI), (BJ), and/or (BK) together and if its false, highlight the compared cells in yellow. I know that's a little hard to follow but this example should help clarify:
My Sheet has the following columns:
Column AF Column BI Column BJ Column BK
PRODUCT Height Length Width
I need a macro to look at the product type and compare the dimensions for that product as follows:
- If product = A, then Length = Width, if not then highlight Length and Width Cells
- If product = B then Length > Width, if not then highlight Length and Width Cells
- If product = C then Width > Height < Length, if not highlight Length, Width, and Height cells
- If product - D then Width = Length < Height, if not highlight Width, Length, and/or Height
My Data starts on row 3 and ends at row 5002.
I have tried researching this and was only able to find solutions that compare two cells then write a third column. I could combine an IF formula and conditional formatting to achieve this but I don't want to have this run all the time as the sheet will be sorted and color coded. I plan to place this macro into a command button.
Suggest to combine Statements such as Select Case, If...Then...Else, together with Operators And, Or. See the following pages:
https://msdn.microsoft.com/en-us/library/office/gg251599.aspx
https://msdn.microsoft.com/en-us/library/office/gg278665.aspx
https://msdn.microsoft.com/EN-US/library/office/gg251356.aspx
After which you should be able to write something that resembles this:
(Code below is just a sample, it will not work)
Select Case Product
Case A
If Length <> Width Then
Rem Highlight Length And Width Cells
End If
Case B
If Length <= Width Then
Rem Insert here the code to highlight Length And Width Cells
End If
Case C
If Width <= Height And Height >= Length Then
Rem Insert here the code to highlight Length, Width, and Height cells
End If
Case D
If Width <> Length And Length >= Height Then
Rem Insert here the code to highlight Width, Length, and/or Height
End If
End Sub
In case you don’t know to highlight the Width, Length and Height Cells; I suggest to do it manually while recording a macro, this shall give a good starting point.
I suggest to work with objects, defining variables for the Data range, each row being validated, the position of the fields to validate, etc. see below code with comments
Sub Highlight_Cells_based_Comparison()
Dim rData As Range
Dim rRow As Range
Dim rCllsUnion As Range
Rem Set variables to hold Fields position within the DATA range
Dim bPosProd As Byte, bPosHght As Byte, bPosLeng As Byte, bPosWdth As Byte
Rem Set variables to hold Fields values
Rem (data type Variant as don't know type of values these fields are holding, change as appropriated)
Rem see https://msdn.microsoft.com/en-us/library/office/gg251528.aspx)
Dim sProd As String, vHght As Variant, vLeng As Variant, vWdth As Variant
Dim lRow As Long
Rem Set Range (assuming it goes from column C to BK - change as needed)
Rem Not starting from column A on porpuse
Set rData = ActiveSheet.Range("C3:BK5002")
Rem Get Fields position from Header row
Rem Suggest to use this method instead of hard coding columns
On Error Resume Next
With rData
bPosProd = WorksheetFunction.Match("PRODUCT", .Rows(1), 0)
bPosHght = WorksheetFunction.Match("Height", .Rows(1), 0)
bPosLeng = WorksheetFunction.Match("Length", .Rows(1), 0)
bPosWdth = WorksheetFunction.Match("Width", .Rows(1), 0)
End With
If Err.Number <> 0 Then Exit Sub
On Error GoTo 0
Rem Loop thru each row excluding header
For lRow = 2 To rData.Rows.Count
Set rRow = rData.Rows(lRow)
With rRow
Rem Get Row Field values
sProd = .Cells(bPosProd).Value2
vHght = .Cells(bPosHght).Value2
vLeng = .Cells(bPosLeng).Value2
vWdth = .Cells(bPosWdth).Value2
Select Case sProd
Case A 'Change value of A as required
Rem If product = A, then Length = Width, if not then highlight Length and Width Cells
Rem If Length <> Width Then Highlight Length And Width 'Cells
If vLeng <> vWdth Then
Set rCllsUnion = Union(.Cells(bPosLeng), .Cells(bPosWdth))
Rem Suggest to use a subroutine for this piece as it's a repetitive task
Rem see https://msdn.microsoft.com/en-us/library/office/gg251648.aspx
GoSub CllsUnion_Highlight
End If
Case B
Rem repeat as in Case A with required changes
Case C
'...
Case D
'...
End Select: End With: Next
Exit Sub
Rem Subroutine to highlight cells
CllsUnion_Highlight:
With rCllsUnion.Interior
.Color = 65535
.TintAndShade = 0
.Pattern = xlSolid
.PatternColorIndex = xlAutomatic
.PatternTintAndShade = 0
End With
Return
End Sub

Excel VBA only show labels for each series' max value

I am trying to create a macro which loops through series in a chart and only shows the maximum / minimum label dependent on what the max / min value is.
Some of the series will only have negative values and in these cases I wish to only show the minimum datapoint label, and vice versa for series with 0 or greater values.
The code I have so far is:
Sheets("Curve").ChartObjects("Chart 14").Activate
For Each serie In ActiveChart.SeriesCollection
Dim pointCount As Integer
Dim pointValues As Variant
pointCount = serie.Points.Count
pointValues = serie.Values
For pointIndex = 1 To pointCount
If pointValues(pointIndex) < 1000 Then
serie.Points(pointIndex).HasDataLabel = True
End If
Next pointIndex
Next serie
End Sub
Which works fine when I manually enter the threshold, but I want to replace the '1000' with Max(series) value instead, so that each series in the chart has only one label visible.
The following modified routine includes MaxPoint, MaxPointIndex, MinPoint, and MinPointIndex variables which are calculated in the For loop on each serie's points. It then sets the label for the maximum point if the series has only positive value and minimum point otherwise.
Option Explicit
Sub chart()
Dim serie As Variant
Dim Pointindex As Long
Dim MaxPoint As Long
Dim MaxPointIndex As Long
Dim MinPoint As Long
Dim MinPointIndex As Long
Sheets("Curve").ChartObjects("Chart 14").Activate
For Each serie In ActiveChart.SeriesCollection
Dim pointCount As Integer
Dim pointValues As Variant
pointCount = serie.Points.Count
pointValues = serie.Values
MinPoint = 10000 'set to value greater than any point in any serie
MaxPoint = 0
For Pointindex = 1 To pointCount
If pointValues(Pointindex) > MaxPoint Then
MaxPoint = pointValues(Pointindex)
MaxPointIndex = Pointindex
ElseIf pointValues(Pointindex) < MinPoint Then
MinPoint = pointValues(Pointindex)
MinPointIndex = Pointindex
End If
Next Pointindex
If MinPoint >= 0 Then
serie.Points(MaxPointIndex).HasDataLabel = True
Else
serie.Points(MinPointIndex).HasDataLabel = True
End If
Next serie
End Sub
I have an alternative approach, which does not require VBA. It adds an extra series with an invisible data point at the max and this point has a data label. It also changes dynamically if the data changes and a different point is maximum, without having to rerun the VBA procedure.
For each series in the chart you'll need to use a range the same size as the Y values of the series.
Assume the original Y values for a given series is in D2:D10, and we'll use G2:G10 for our extra data. In G2 enter
=IF($D2=MAX($D$2:$D$10),$D2,NA())
and fill this down to G10. Modify this formula for the case where you might instead look for the minimum value if everything is negative.
Copy G2:G10 and hold CTRL while you select the X values for this series. Copy. Select the chart, use Paste Special, and select Add data as new Series, in Columns, Categories in First Column.
Select the added series, which is one point (unless there are two points at the maximum), format to have no lines and no markers. Add your data labels to this series.
Repeat for the other series in the chart.