Finding Column Header Height and Row Header Width - excel

This is kind of a follow-up to my previous question here, but different enough I felt that asking a new question would be best. I have used a series of window handles to lock a userform to the Excel spreadsheet, which causes the 0,0 position to be the top left of the column and row headers (or the "select all" button). The tl;dr of this is that I'm trying to find how to determine the height of the column headers, and the width of the row headers, so that I can position a userform correctly on a page regardless of the user's default excel font settings.
I don't think the code that I have so far for my userform will be helpful, but I'm happy to post it if anyone would like to see. I can remove the headings altogether by setting the DisplayHeadings property to false, but this doesn't really work for my end goal.
It does seem like the height of the column header would be equal to the default height of a cell with the same font type and size. I haven't tested this method since it would only give me half of what I need, but I would still like to confirm if this is accurate.
I also know that the width of the row header will change the further you go down on the spreadsheet (eg. first increasing at 1,000, and then 10,000, 100,000, and 1,000,000). I only need to find the smallest width (everything less than 1,000), but I would like to know how to find a larger width if it's not too complicated.
To locate the header size, I have tried comparing a cell's .left and .top properties after removing the display headings through the following code:
Sub TestHeadings()
Dim fl, ft, tl, tt As Integer
tl = Application.ActiveSheet.Range("A1").Left
tt = Application.ActiveSheet.Range("A1").Top
Application.ActiveWindow.DisplayHeadings = False
fl = Application.ActiveSheet.Range("A1").Left
ft = Application.ActiveSheet.Range("A1").Top
Debug.Print "True: " & tl & ", " & tt
'Returns True: 0, 0
Debug.Print "False: " & fl & ", " & ft
'Returns False: 0, 0
End Sub
I have also tried comparing a userform's (called Working_Menu, the .StartUpPosition property is set to 0-Manul) .left and .top properties after disabling the display headings, through the following code:
Sub TestHeadings()
Dim fl, ft, tl, tt As Integer
Application.ActiveWindow.DisplayHeadings = False
With Working_Menu
.Left = 5 'Also tried 0
.Top = 5 'Also tried 0
.Show
End With
fl = Working_Menu.Left
ft = Working_Menu.Top
Application.ActiveWindow.DisplayHeadings = True
tl = Working_Menu.Left
tt = Working_Menu.Top
Debug.Print "True: " & tl & ", " & tt
'Returns True: 5, 145, or 0, 140
Debug.Print "False: " & fl & ", " & ft
'Returns False: 5, 144.75, or 0, 139.5 (Adjusted for screen resolution)
End Sub
My results are commented in the code, but neither approach returned any differences indicating a header size. Does anyone have any idea how I can determine the height of the column header, or the width of the row header?
Thanks!

This Sub will return its parameters HeightPoints (column header height in points) and WidthPoints (row header width in points):
Sub HeadingsSize(ByRef HeightPoints As Single, ByRef WidthPoints As Single)
Dim rC As Range, bSU As Boolean
Dim x1 As Long, x2 As Long, y1 As Long, y2 As Long
Const PxToPt As Single = 72 / 96
bSU = Application.ScreenUpdating
If bSU Then Application.ScreenUpdating = False
With ActiveWindow
Set rC = .VisibleRange.Cells(1)
y1 = .PointsToScreenPixelsY(rC.Top)
x1 = .PointsToScreenPixelsX(rC.Left)
.DisplayHeadings = Not .DisplayHeadings
y2 = .PointsToScreenPixelsY(rC.Top)
x2 = .PointsToScreenPixelsX(rC.Left)
.DisplayHeadings = Not .DisplayHeadings
End With
HeightPoints = Abs(y2 - y1) * PxToPt
WidthPoints = Abs(x2 - x1) * PxToPt
Application.ScreenUpdating = bSU
End Sub

Related

Running a calculation function for n number of rows

I want to automate a calculation for a measurement data Excel file.
While I made the function work, I could only figure out how to do that for one row at a time. The calculation is specific to the data on every row.
I thought I could change the Range("J3") value to Range("J3:J52") for a capacity of 50 calculations.
How do I make the function calculate for every row, separately, using the data of said specific row?
It doesn't matter if it runs for all 50 rows or if I have to figure out some loop function to find how many rows to calculate for, as long as one button press in the end will make the magic happen.
I have included a screenshot of the sheet for reference, the main calculation is done in Excel, but what this is doing is choosing the correct option out of a few different correction calculations and explaining to the user why.
I think there are some unnecessary Dim lines at the start but if it runs, I wasn't going to remove them.
'The main function, activated by a simple button Sub'
Function ISO16032()
'DeltaL Range'
Dim DeltaL As Range
Set DeltaL = Range("F3")
'Result is the corrected value in G column'
Dim Result As Long
'Note is the calc note in H column'
Dim Note As String
'X is the DeltaL between noise and background noise'
Dim x As Long
x = Range("F3").Value
Select Case Range("F3").Value
'No correction when X = > 10'
Case 10.6 To 200
Result = Range("J3")
Range("G3").Value = Result
Note = "No correction"
Range("H3").Value = Note
'Correction according to ISO16032 when X = between 4 and 10'
Case 3.6 To 10.5
Result = Range("K3")
Range("G3").Value = Result
Note = "Correction per ISO16032"
Range("H3").Value = Note
'Maximal correction value set to 2,2 dB if X < 4'
Case 0.1 To 3.5
Result = Range("L3")
Range("G3").Value = Result
Note = "Correction limit set to 2,2 dB"
Range("H3").Value = Note
'If x = < 0, the measurement is invalid'
Case Else
Note = "Repeat measurement!"
Range("H3").Value = Note
End Select
End Function
Hi and welcome to stackoverflow, I think that a simple loop, with the addition of an argument to your ISO function allows you to solve your problem like this
Sub Looping()
For i = 3 To 52
' Convert i to String because we need to concatenate with the letter F, G, H...
Call ISO16032(CStr(i))
Next
End Sub
Function ISO16032(Cell_X)
'DeltaL Range'
Dim DeltaL As Range
Set DeltaL = Range("F" + Cell_X)
'Result is the corrected value in G column'
Dim Result As Long
'Note is the calc note in H column'
Dim Note As String
'X is the DeltaL between noise and background noise'
Dim x As Long
x = Range("F" + Cell_X).Value
Select Case Range("F" + Cell_X).Value
'No correction when X = > 10'
Case 10.6 To 200
Result = Range("J" + Cell_X)
Range("G" + Cell_X).Value = Result
Note = "No correction"
Range("H" + Cell_X).Value = Note
'Correction according to ISO16032 when X = between 4 and 10'
Case 3.6 To 10.5
Result = Range("K" + Cell_X)
Range("G" + Cell_X).Value = Result
Note = "Correction per ISO16032"
Range("H" + Cell_X).Value = Note
'Maximal correction value set to 2,2 dB if X < 4'
Case 0.1 To 3.5
Result = Range("L" + Cell_X)
Range("G" + Cell_X).Value = Result
Note = "Correction limit set to 2,2 dB"
Range("H" + Cell_X).Value = Note
'If x = < 0, the measurement is invalid'
Case Else
Note = "Repeat measurement!"
Range("H" + Cell_X).Value = Note
End Select
End Function
EDIT: think indented your code like I did to make it more readable too (or like #Darren Bartrup-Cook did when editing your question)
This is more of an example of how to calculate each row.
Just getting your code to work across multiple rows.
Cells
With...End With Statement
'No need for Sub to call function that doesn't return anything.... just write a sub.
Public Sub ISO16032()
Dim LastRow As Long
With ThisWorkbook.Worksheets("Sheet1")
LastRow = .Cells(.Rows.Count, 10).End(xlUp).Row
Dim RowCounter As Long
Dim Result As Long
Dim Note As String
'Only calculate if there is data in rows 3 onwards.
If LastRow >= 3 Then
'Reset results on each pass.
'Assuming -1 is an impossible answer so code knows
'not to put anything on Case Else.
Result = -1
Note = ""
'Cycle through each row and calculate.
For RowCounter = 3 To LastRow
Select Case .Cells(RowCounter, 6) 'Look at column F(column 6) on each row.
Case 10.6 To 200
Result = .Cells(RowCounter, 10)
Note = "No corrections."
Case 3.6 To 10.5
Result = .Cells(RowCounter, 11)
Note = "Correction per ISO16032"
Case 0.1 To 3.5
Result = .Cells(RowCounter, 12)
Note = "Correction limit set to 2,2 dB"
Case Else
Note = "Repeat measurement!"
End Select
'Place results on sheet.
.Cells(RowCounter, 7) = IIf(Result >= 0, Result, "")
.Cells(RowCounter, 8) = Note
Next RowCounter
End If
End With
End Sub

Set cell size equal to picture size

I'm trying to import a picture to excel cell and I'm facing issues with re-sizing.
Steps:
Copy/Paste the picture to the cell
Re-size the picture manually
And also resize the cell to fix on the picture.
Is there any other way to do it instead of manually?
I'm not sure what exactly you meant with re size the picture manually, but might this be working for you?
Sub ResizeCells()
Dim X As Double, Y As Double, Z As Double
Dim s As Shape
For Each s In ActiveSheet.Shapes
If s.Type = msoPicture Then
For X = s.TopLeftCell.Column To s.BottomRightCell.Column
Y = Y + ActiveSheet.Cells(1, X).ColumnWidth
Next X
For X = s.TopLeftCell.Row To s.BottomRightCell.Row
Z = Z + ActiveSheet.Cells(1, X).RowHeight
Next X
s.TopLeftCell.ColumnWidth = Y
s.TopLeftCell.RowHeight = Z
End If
Next s
End Sub
Note:
Max RowHeight is 409
Max ColumnWidth is 255
This goes the other way.
We will insert a Shape from the Internet.
We will move it to cell B1.
We will resize the Shape (both height and width) to fit in B1First place this link in cell A1:
http://www.dogbreedinfo.com/images26/PugPurebredDogFawnBlackMax8YearsOld1.jpg
Then run:
Sub MAIN()
Call InstallPicture
Call PlaceAndSizeShape
End Sub
Sub InstallPicture()
Dim v As String
v = Cells(1, 1).Value
With ActiveSheet.Pictures
.Insert (v)
End With
End Sub
Sub PlaceAndSizeShape()
Dim s As Shape, B1 As Range, w As Double, h As Double
Set s = ActiveSheet.Shapes(1)
s.Select
Selection.ShapeRange.LockAspectRatio = msoFalse
Set B1 = Range("B1")
s.Top = B1.Top
s.Left = B1.Left
s.Height = B1.Height
s.Width = B1.Width
End Sub
This post is old, but nobody mentioned resizing the picture to match the cell.
Excel is very unreliable when I tired to scale the width using #Andrew's code. Luckily, rCell.Left is in the correct units. You can get the actual column width using:
rCell.Offset(0, 1).Left - rCell.Left
This Code will Resize the Cell to Your Picture
Sub ResizePictureCells()
For Each Picture In ActiveSheet.DrawingObjects
PictureTop = Picture.Top
PictureLeft = Picture.Left
PictureHeight = Picture.Height
PictureWidth = Picture.Width
For N = 2 To 256
If Columns(N).Left > PictureLeft Then
PictureColumn = N - 1
Exit For
End If
Next N
For N = 2 To 65536
If Rows(N).Top > PictureTop Then
PictureRow = N - 1
Exit For
End If
Next N
Rows(PictureRow).RowHeight = PictureHeight
Columns(PictureColumn).ColumnWidth = PictureWidth * (54.29 / 288)
Picture.Top = Cells(PictureRow, PictureColumn).Top
Picture.Left = Cells(PictureRow, PictureColumn).Left
Next Picture
End Sub

How to set an automatically generated radio button to true in VBA?

I am creating an Excel sheet in which the radio buttons are automatically generated based on the value of specific parameter. Please refer this for clear understanding:
A group of radio buttons are copied n number of times. where n is the number of rows that refers to a parameter.
Each radio buttons in this auto-generated matrix should be checked against a condition and one of the twelve radio button should be set to True in one group that matches the condition. The main complication here is that, each group of radio buttons are copied to required rows based on the requirement and so, the radio buttons are generated n*12 times and i don't know how to program each radio button that is generated automatically.
I need to know, which Function can I use to fulfill my requirement.
I have created the matrix with the following code:
Dim n, m, i, j, x, k, a As Integer
n = (Sheets("ALLO").Range("E4").Value) * 2
x = Sheets("ALLO").Range("E3").Value
m = (Sheets("ALLO").Range("E5").Value) + 1
a = m
For i = 2 To n Step 2
Sheets("Dummy_Result").Range("A2:M2").Copy Destination:=Sheets("Results").Range("A" & i)
Next i
For j = 3 To n Step 2
Sheets("Dummy_Result").Range("A3:M3").Copy Destination:=Sheets("Results").Range("A" & j)
Next j
For k = n + 1 To m Step 1
Sheets("Dummy_Result").Range("A3:M3").Copy Destination:=Sheets("Results").Range("A" & k)
Next k
End Sub
My updated Program, for generating the buttons automatically for the range dependent on the value of 'm'. The number of buttons generated should be directly proportional to the value of 'm'. This Program doesn't work when I use the dynamic range instead of Fixed range (As suggested by Mr.JosephC)
Sub Test()
Dim n, m, i, j, x, k, a As Integer
n = (Sheets("ALLO").Range("E4").Value) * 2 'No of Tack stations
x = Sheets("ALLO").Range("E3").Value
m = (Sheets("ALLO").Range("E5").Value) + 1
a = m
For i = 2 To n Step 2 'Correct
Sheets("Dummy_Result").Range("A2").Copy Destination:=Sheets("Results_1").Range("A" & i)
Call AddOptionButtons(Sheets("Results_1").Range("B & m: M & m"))
Next i
For j = 3 To n Step 2
Sheets("Dummy_Result").Range("A3").Copy Destination:=Sheets("Results_1").Range("A" & j)
Call AddOptionButtons(Sheets("Results_1").Range("B & m: M & m"))
Next j
For k = n + 1 To m Step 1
Sheets("Dummy_Result").Range("A3").Copy Destination:=Sheets("Results_1").Range("A" & k)
Call AddOptionButtons(Sheets("Results_1").Range("B & m: M & m"))
Next k
End Sub
Private Sub AddOptionButtons(ByRef TargetRange As Range)
Dim oCell As Range
For Each oCell In TargetRange
oCell.RowHeight = 20
oCell.ColumnWidth = 6
Dim oOptionButton As OLEObject
Set oOptionButton = TargetRange.Worksheet.OLEObjects.Add(ClassType:="Forms.OptionButton.1", Left:=oCell.Left + 1, Top:=oCell.Top + 1, Width:=15, Height:=18)
oOptionButton.Name = "ob" & oCell.row & "_" & oCell.Column
'oOptionButton.Object.Caption = "Button" oOptionButton.Object.GroupName = "grp" & oCell.Top
Next
End Sub**strong text**
Please refer the Dummy result here
This is just something to get you started. Imagine you have 6 Form Controls Option buttons like this:
Then, if you pass them in an array buttons1 you may loop through them by their index and assign their value to True, based on another array with conditions condition1:
Public Sub TestMe()
Dim condition1 As Variant
condition1 = Array(False, True, False, False, False, False)
Dim buttons1 As Variant
buttons1 = Array("Option Button 2", "Option Button 3", "Option Button 4", _
"Option Button 5", "Option Button 6", "Option Button 7")
Dim cnt As Long
For cnt = LBound(buttons1) To UBound(buttons1)
With Worksheets(1).Shapes(buttons1(cnt)).OLEFormat
If condition1(cnt) Then .Object.Value = True
End With
Next cnt
End Sub
Thus, after running the code, as far as the second unit in the condition1 array is True, the Option Button 3 is selected.
This will add option buttons to each cell in the target range. It will resize the cells a bit to try and make enough space for them (you can fiddle with placement of the option buttons and size of the cells as you see fit). It will name the option buttons with their "index" values based on the row and column numbers they are set in ie. ob2_4 is option button in row 2, column 4 (D). It will also set the group name to be the same for all option buttons on the same row.
Sub Test()
Call AddOptionButtons(Sheet1.Range("B5:D7"))
End Sub
Private Sub AddOptionButtons(ByRef TargetRange As Range)
Dim oCell As Range
For Each oCell In TargetRange
oCell.RowHeight = 20
oCell.ColumnWidth = 6
Dim oOptionButton As OLEObject
Set oOptionButton = TargetRange.Worksheet.OLEObjects.Add(ClassType:="Forms.OptionButton.1", Left:=oCell.Left + 1, Top:=oCell.Top + 1, Width:=15, Height:=18)
oOptionButton.Name = "ob" & oCell.Row & "_" & oCell.Column 'Name them to make it easier if you need to access them later
'oOptionButton.Object.Caption = "Caption" ' If you want to add text to the buttons
oOptionButton.Object.GroupName = "grp" & oCell.Top
Next
End Sub
Personal note:
As an aside, please use meaningful names for your variables. :) The only time you should use single character variables is if you have hardware requirements on the footprint of your code.

Manipulating variables with excel

Language: Excel
Hello & thank you for reading my post.
I am trying to create a formula to... multiply cells (or in this case letters) based upon variables. The variables being whether it is divisible by (1000 + 250x), and according to the answer, multiply it by the according letter percentage.
Visual representation:
A B C
1% 2% 3%
250 500 1000
1 1,000
2 1,250
3 1,500
4 1,750
5 2,000
For instance, since #1 is divisible by 1000, I would multiply it by 3%
Second instance, since #2 is divisible by 250, and 1000, I would multiply the 250 by 1% and the 1000 by 3%, and then add them together.
My current attempt:
=IF(MOD(A2,F14)<=1,A2*F15,"")
A2 = the starting amount
F14 = what A2 is being divided by
F15 = the percentage
This kind of works, but it does not allow me to find the best possible solution.
Would greatly appreciate your help in my dilemma.
I can't think of any good solution as of Excel formulas as the result you want is too complex: like you marked your question you need a loop, no matter what, which formulas cannot do for you I'm afraid.
But, as you added VBA as one of your tags, I assume a VBA solution would work for you, so here's the script I wrote:Option Explicit 'variables MUST BE declared, otherwise error. very handy rule
Option Base 0 'won't be needed this time, but in general, this rule is also a great ally '(it says: arrays' 1st item will always be the "0th" one)
Dim divLARGE, divMED, divSMALL 'you can use variable types in Excel
Dim percLARGE, percMED, percSMALL 'but sadly, not in VBScript which I have ATM'test input values and their results, won't be needed in your Excel
Dim testA, testB, testC, testD, testE, testF 'so add types if you like
Dim resA, resB, resC, resD, resE, resF '(should make execution a little faster)'Init our variables declared above. in VBScript you can't do this at declaration,'i.e. can't say "Dim whatever As Boolean = true" which would be the right way to do this
Call Initialize()'Call the "main routine" to execute codeCall Main()'you can add access modifiers here. "private" would be the best'i.e. "private Sub Main()"
Sub Main()
resA = CalcMaster(testA, divLARGE)
resB = CalcMaster(testB, divLARGE)
resC = CalcMaster(testC, divLARGE)
resD = CalcMaster(testD, divLARGE)
resE = CalcMaster(testE, divLARGE)
resF = CalcMaster(testF, divLARGE)
MsgBox (CStr(testA) + " --> " + CStr(resA) + vbCrLf + _
CStr(testB) + " --> " + CStr(resB) + vbCrLf + _
CStr(testC) + " --> " + CStr(resC) + vbCrLf + _
CStr(testD) + " --> " + CStr(resD) + vbCrLf + _
CStr(testE) + " --> " + CStr(resE) + vbCrLf + _
CStr(testF) + " --> " + CStr(resF) + vbCrLf)
End Sub
Sub Initialize()
divLARGE = 1000 'the large number for which we look after remnants
divMED = 500 'medium/middle sized number to divide by
divSMALL = 250 'the small value
percLARGE = 3 'percentage we want if no remnants on LARGE number
percMED = 2 'same but for medium/mid size numbers
percSMALL = 1 'and the percentage we want for the small remnants
testA=1000 'result should be exactly 30.0
testB=1250 'res == 32.5
testC=1500 'res == 40.0
testD=1750 'res == 42.5
testE=2000 'res == 60.0
testF=-198 'res == #ERROR/INVALID VALUE
End Sub
Function CalcMaster(inVar, byDiv) 'A silly function name popped in my mind, sorry :)
Dim remnant, percDiv 'sometimes happens, looks cheaper calc.wise to handle like this; if initial input 'can be 0 and that's a problem/error case, handle this scenario some other way
If (inVar = 0) Then Exit Function
remnant = inVar Mod byDiv
'if you'll implement more options, do a Select...Case instead (faster)
If (byDiv = divLARGE) Then
percDiv = percLARGE
ElseIf (byDiv = divMED) Then
percDiv = percMED
Else
percDiv = percSMALL
End If
If (remnant = 0) Then
CalcMaster = inVar * (percDiv / 100)
Exit Function
End If
'had remnant; for more than 3 options I would use an array of options
'and call back self with the next array ID
If (byDiv = divLARGE) Then
CalcMaster = CalcMaster(inVar - remnant, divLARGE) + CalcMaster(remnant, divMED)
ElseIf (byDiv = divMED) Then
CalcMaster = CalcMaster(inVar - remnant, divMED) + CalcMaster(remnant, divSMALL)
Else 'or return 0, or raise error and handle somewhere else, etc
'MsgBox ("wrong input number: " + CStr(inVar))
CalcMaster = -1
End If
End Function
It's not perfect, I assume there could be better solutions out there but I think it's good enough for the cause. I hope you agree :)Cheers
after Sparrow explanation I got that the "best possible solution" is the one that maximizes the "total prize" obtained by multiplying all possible whole divisors (i.e. 250, 500, 1000) by their correspondent "prize"(1%, 2%, 3%)
here's a consequent solution
Option Explicit
Sub main()
Dim dataRng As Range, cell As Range, percRng As Range, divRng As Range
Dim i As Long, value As Long, nDivisors As Long
Dim prize As Double, totalPrize As Double
Set dataRng = ActiveSheet.Range("B1:B10") '<== here set the range cointaining the numbers to be processed
Set percRng = ActiveSheet.Range("F15:H15") '<== here set the range of % "prizes": they MUST be in ascending order (from lowest to highest)
Set divRng = ActiveSheet.Range("F14:H14") '<== here set the range of the possible divisors. this range MUST be of the same size as thre "prizes" range
For Each cell In dataRng.SpecialCells(xlCellTypeConstants, xlNumbers)
value = cell.value
nDivisors = 0
prize = 0
totalPrize = 0
Do
i = FindMaxDivisor(value, percRng, divRng)
If i > 0 Then
value = value - divRng(i) ' update value to the remainder
prize = percRng(i) * divRng(i) ' get current "prize"
totalPrize = totalPrize + prize 'update totalprize
nDivisors = nDivisors + 1 'update divisors number
cell.Offset(, nDivisors) = divRng(i) 'write divisor in next blank adjacent cell in the number row
End If
Loop While value > 0 And i >= 0
If i >= 0 Then ' the number has been correctly divided by given divisors
With cell.Offset(, nDivisors + 1)
.value = totalPrize
.Font.Color = vbRed
End With
Else
MsgBox "Not possible to break " & cell.value & " into given divisors"
End If
Next cell
End Sub
Function FindMaxDivisor(value As Long, percRng As Range, divRng As Range) As Long
Dim i As Long
FindMaxDivisor = -1 'default value should not be found any whole divisor
i = divRng.Columns.Count
Do While value Mod divRng(i) <> 0 And i > 1
i = i - 1
Loop
If value Mod divRng(i) = 0 Then FindMaxDivisor = i
End Function
each number "best" divisors will be written in columns next to the number and in the last one there'll be written the "total prize" in red

VBA - Excel TextBox1 assign object to varaible

I get error when I try to assign TextBox1 to variable X.
Sub TB1()
Dim X As TextBox
Dim Y As String
X = TextBox1
If Len(X) < 4 Then
Y = X
Do
Y = "0" & Y
Loop Until Len(Y) >= 4
X = Y
End If
End Sub
You've got a few issues. See comments for details in the code
Sub TB1()
Dim X As TextBox
Dim Y As String
Set X = Me.TextBoxes("Textbox 1")
'You need to have some sort of reference to get to the textbox.
'Me in this case is a worksheet object I tested in the Sheet1 module.
'It has a collection of textboxes which you can refer to by name. Click on your textbox in excel to see the name in the upper left corner.
'The `Set` syntax is necessary for objects
If Len(X.Text) < 4 Then
Y = X.Text
'Have to explicitly refer to the text in the textbox since it has other properties you can change like height and width
Do
Y = "0" & Y
Loop Until Len(Y) >= 4
X.Text = Y 'Explicitly refer to the textbox text again to reassign
End If
End Sub

Resources