I wrote some code and have a question.
I sucessfully make macro which insert formular into cell.
Problem is It is not working automatically.
Function test(PCell As Range) As String
test = Chr(61) & Replace(PCell.Address, "$", "")
End Function
Cell shows =N3 (simple example). And I can execute Push "F2" - "Enter". It is working well.
Problem is .. There are more than 100 cells. If there is no solution, I have to push F2 - Enter 100 hundred times.
After select the cells, How can I execute the formular in cells? or by using VBA?
I tried to use Selection.Evaluate() But there is nothing happened. And "F9" key is also.
There is a faster and simpler way to achive this result.
'''vba
Sub ExecuteBulkFormula(SrcRng as Range,TgtRng as Range)
'What this procedure does is it loops through every area in the
'SrcRng and then loops through every cell in this area
'It then writes the formula in the array
'i: Loop Counter
'arr_index: Array Items Counter
'r_area: Every range contains atleast one Area as a Range Object
'The usage of Area is essential, as sometimes if we select multiple
'areas, For Example: A1:C2 & A5:C6, Then we can access these two "areas"
'The screenshot for the areas is added below (Figure 1).
'If however, we don't use Areas then we can only access the first Area
'i.e. A1:C2
Dim i as Long, arr_index as long
Dim r_area as Range
Dim arr as Variant
Redim arr(1 To 1000)
arr_index = 1
For Each r_area In SrcRng.Areas
For i = 1 to r_area.count
'Just Replace the below line to change the formula to fit your needs
arr(arr_index) = Chr(61) & Replace(r_area(i).Address, "$", "")
arr_index = arr_index+1
Next i
Next
Redim Preserve arr(1 To arr_index)
TgtRng.Formula = arr
End Sub
Figure 1
How to use this function?
In excel worksheet, press Alt+F11 or Developer Tab->Visual Basic
Right Click in any item in Project Explorer and then select Insert->Module
In this module, insert this above code.
To run this code you can use the immediate window (Ctrl+G) or write another procedure to run this code.
Suggestions
Instead of using Replace you can use Range.AddressLocal(False,False)
It will produce the same result.
This function ExecuteBulkFormula will run very fast
For 1000 rows: 0.016 sec (or 16 milliseconds)
For Reference my laptop is dual core i7-5500u which is low end.
to have function calculate at every sheet change, just add Application.Volatile (see:https://learn.microsoft.com/en-us/office/vba/api/excel.application.volatile)
Function test(PCell As Range) As String
Application.volatile
test = Chr(61) & Replace(PCell.Address, "$", "")
End Function
to place that cell in all cells of a given range:
Sub PlaceTest()
With Range("A1:A10") ' change the range address to to fit your needs
.Formula = "=test(RC[1])" ' this will feed the function with the cell 1 column to the right of where the function is placed: just play around with R1C1 notation (https://excelchamps.com/formulas/r1c1/)
End With
End Sub
Related
I am generating a display in vba for excel of a large and complex dataset. For this I would like to prepopulate an array with all the values/formulas as well as a set of range objects with format information, and then once all data has been generated I will apply all at once as it is significantly faster than updating each cell and format range individually.
For some formats like cell color, font and others - Union can be used to build the range which works excellently, however for things like surrounding borders I need to keep the range areas intact to avoid wrong formatting. I know I could use for instance a collection object storing each individual range and then cycle through them all, but I am surprised that I cannot find any way to create a range object with areas the way I want. Thus, this question is not especially around solving my problem, but more about if there are functions to control the range object than I yet haven't thought of. The problem is exemplified by the following code:
Option Explicit
Function MergeRanges_KeepingAreasIntact(rIn1 As Range, rIn2 As Range) As Range
'Some error checking controlling if ranges are empty or on different worksheets left out for readability
Set MergeRanges_KeepingAreasIntact = rIn1.Parent.Range(rIn1.Address(False, False) & "," & rIn2.Address(False, False))
End Function
Function MergeRanges_AreasNotIntact(rIn1 As Range, rIn2 As Range) As Range
'Some error checking controlling if ranges are empty or on different worksheets left out for readability
Set MergeRanges_AreasNotIntact = Union(rIn1, rIn2)
End Function
Sub Evaluate()
Dim i As Long, rMerge As Range
Debug.Print MergeRanges_KeepingAreasIntact(Sheet1.Range("A3:D7"), Sheet1.Range("A8:D12")).Address
Debug.Print MergeRanges_AreasNotIntact(Sheet1.Range("A3:D7"), Sheet1.Range("A8:D12")).Address
'########################################################
' try to build a range object with the 100 first diagonal
' cells to demonstrate range function limitations
'########################################################
On Error Resume Next
For i = 1 To 100
If i = 1 Then
Set rMerge = Sheet1.Cells(1, 1)
Else
Set rMerge = MergeRanges_KeepingAreasIntact(rMerge, Sheet1.Cells(i, i))
End If
If i <> rMerge.Cells.Count Then
Debug.Print "Areas count: ", i, "Address string length:", Len(rMerge.Address(False, False))
Exit For
End If
Next
On Error GoTo 0
'#############################################################
'The results from this sub will be:
'$A$3:$D$7,$A$8:$D$12
'$A$3:$D$12
'Areas count: 59 Address string length: 254
'#############################################################
End Sub
The function MergeRanges_AreasNotIntact is efficient but will fail when ranges are aligned side by side and share the same height, or aligned above-below and share the same width.
The other function "MergeRanges_KeepingAreasIntact" is both ugly, and most likely inefficient as it coverts ranges back and forth to address strings. Moreover it will fail when more than ~58 areas are needed as the string limit size for input to the range function is limited to 255 characters.
There is no Range.Areas.Add method, but is there any other way to build a range object with >58 areas, keeping aligned areas separate in the object?
Do you need to be able to reference the entire range as one range? If not, you could just create a collection of cells and when using them, cycle through them one at a time:
Sub test()
Dim RngColl As New Collection
Dim i As Long
Dim c As Range
'add 100 cell references to the collection
For i = 1 To 100
RngColl.Add Cells(i, i)
Next
'cycle through each item (cell reference) and do something with it
For Each c In RngColl
c.Value = 1
Next
End Sub
This works for 100 cells, or 10,000.
I m using excel to calculate some values then inserting the values in AutoCAD drawing block
by writing scripting lines which is more than 2500 lines
Then I need to copy all lines once and paste in AutoCAD once.
so I tried to combine the lines by concat or text join which exceed the limit for one cell
is there is away to exceed or to combine the values in more than one cell.
This sounds like an XY problem. There is probably a better way, using the scripting capabilities of Autocad directly, to do whatever it is that you are trying to do. Any solution which works by assembling text in one application to copy/paste it into another application is clunky at best.
Nevertheless, it is certainly possible to join multiple cells into a single string which is then copied to the clipboard. The following code does so.
Function Clipboard$(Optional s$)
'Code from Excel Hero
'https://stackoverflow.com/a/60896244/4996248
Dim v: v = s 'Cast to variant for 64-bit VBA support
With CreateObject("htmlfile")
With .parentWindow.clipboardData
Select Case True
Case Len(s): .setData "text", v
Case Else: Clipboard = .GetData("text")
End Select
End With
End With
End Function
Sub CopyRangeToClip(R As Range, Optional delimiter As String = "")
Dim i As Long, n As Long, A As Variant, cell As Range
n = R.Cells.Count
ReDim A(0 To n - 1)
For Each cell In R.Cells
A(i) = cell.Value
i = i + 1
Next cell
Clipboard Join(A, delimiter)
End Sub
You can create a simple sub (which is e.g. assigned to a keyboard shortcut) which applies this last sub to the current selection:
Sub CopyAndJoinSelection()
CopyRangeToClip Selection
End Sub
To show that this approach can copy large strings split into many cells, the following test sub copies a string of length 45,000 to the clipboad:
Sub Test()
Dim i As Long
For i = 1 To 9
Cells(i, 1).Value = String(5000, Trim(Str(i)))
Next i
CopyRangeToClip Range("A1:A9")
End Sub
Yo give credit where credit is due, note that the above code uses this excellent answer for the function which copies the text to the clipboard.
Worksheet1:
Excel sheet
New
Worksheet 1 has licences with 6 columns of information - two being the start and end date.
I need a method of extracting all the records that are within 90 days before the expiry date- the idea being I want a separate alert page
I have done a IF statement that is on the end of the columns that just prints 1 if date is hits the alert criteria or 0 if not...The idea now in Worksheet2 I need some sort of VLOOKUP and IF to extract those records automatically.
How would I do this?
=IF(IFERROR(DATEDIF(TODAY(),H5,"d"),91)<90,1,0)
While use of Pivot table or VBA macro is recommended in such cases, if you absolutely need to use the formula then you may use the below trick.
You already have the Binary column. Now, add another column say Cumulative Binary that will sum all the 1's till the current row using a SumIf formula as shown in the screenshot below (it is fine if some numbers are repeated because of 0's)
The formula in I3 in my workbook is
=SUMIF(H$3:H3,1,H$3:H3)
and you may adjust it as per your needs.
Now, it is easy since each row has a unique number, we could use Vlookup or like I have done here i.e. use Offset function which simply matches the value in the "Lookup Column" to the value in "Cumulative Binary" column and returns the rows that match.
=IFERROR(OFFSET($F$2,MATCH(M3,$I$3:$I$9,0),0,1,2),"")
Please note that it is an array formula as I need to return multiple columns (2 here). So, I selected two columns N,O as shown in the screenshot wrote the formula and used Ctrl+Shift+Enter (instead of Enter). Then I simply dragged the formula down. You may want to adjust it as per your needs by including more columns.
If you can use VBA, you may write some code like this:
Option Explicit
Public Sub CopyCloseToExpiration()
Dim rngSource As Range: Set rngSource = ThisWorkbook.Worksheets("Sheet1").Cells(2, 1).Resize(LastRow(ThisWorkbook.Worksheets("Sheet1")) - 1, 9)
Dim rngDestinationTopLeft As Range: Set rngDestinationTopLeft = ThisWorkbook.Worksheets("Sheet2").Cells(LastRow(ThisWorkbook.Worksheets("Sheet2")) + 1, 1)
Dim datLimit As Date: datLimit = DateAdd("d", 90, Date)
CopyBeforeDate rngSource, rngDestinationTopLeft, datLimit
End Sub
Public Sub CopyBeforeDate(rngSource As Range, rngDestinationTopLeft As Range, datLimit As Date)
Dim lngOffset As Long: lngOffset = 0
Dim rngRow As Range: For Each rngRow In rngSource.Rows
If rngRow.Cells(1, 8).Value < datLimit Then
rngDestinationTopLeft.offset(lngOffset, 0).Resize(rngRow.Rows.Count, rngRow.Columns.Count).Value = rngRow.Value
lngOffset = lngOffset + 1
End If
Next
End Sub
Public Function LastRow(ewsSheet) As Long
With ewsSheet
Dim lngResult As Long: lngResult = .Cells(.Rows.Count, 1).End(xlUp).Row
End With
LastRow = lngResult
End Function
You have to put the above into a new Module, customize it (e.g. replace "Sheet1" with the name of you worksheet's actual name), and run it (You can place the caret on the sub CopyCloseToExpiration and hit F5 or place a button somewhere and call this function from its event handler).
I have an issue with VBA Script.
I'm trying to use CountIf function with filtering
Sub test31()
Debug.Print "Sum Visible Cells only: " & Application.WorksheetFunction.Sum(Sheets("Sheet1").Range("A2:A645").SpecialCells(xlCellTypeVisible))
If Application.WorksheetFunction.CountIf(Sheets("Sheet1").Range("A2:A645").SpecialCells(xlCellTypeVisible), 1) > 0 Then
Debug.Print "Ok"
End If
End Sub
Example
Question:
When I mark in filter (for example, please see attached image) first, third, fourth, fifth, etc. (but if I unmark some of number between this scope I am getting error "unable to get countif property of the worksheetfunction class" (but when I mark everything or mark first 2 or 3, etc (without unmarking like in image) error does not appear)
The file
COUNTIF does not seem to like non-contiguous blocks which will happen when you get the list filters and rows disappear. But this I mean ticking 2 means range of visible is A1: A2 and A10:A645.
You need to consider using another function.
COUNTA will count non blanks. So we ask it to count the number of non blanks in the visible range. Note we make a range object as it makes the code easier to read and later parameterise.
NEW CODE
Sub test31()
Dim rng As Range
Set rng = ThisWorkbook.Sheets("Sheet1").Range("A2:A645")
Debug.Print "Sum Visible Cells only: " & Application.WorksheetFunction.Sum(rng.SpecialCells(xlCellTypeVisible))
' to see non contiguous uncomment this next line
' rng.SpecialCells(xlCellTypeVisible).Select
If Excel.WorksheetFunction.CountA(rng.SpecialCells(xlCellTypeVisible)) > 0 Then
Debug.Print "Ok"
Debug.Print "number visible: " & Excel.WorksheetFunction.CountA(rng.SpecialCells(xlCellTypeVisible))
End If
End Sub
OUTPUT
testing all in filter
Sum Visible Cells only: 3337
Ok
number visible: 643
testing with 2 unselected
Sum Visible Cells only: 3323
Ok
number visible: 636
I am not sure of your desired output but hope this helps.
Lure
Second attempt at answering your question.
First you are using a worksheet function countif in your vba in the form
worksheetfunction.countif( rng , 1 )
and rng is required to be a single range.
Next you are using the methods Sheets("Sheet1").Range("A2:A645").SpecialCells(xlCellTypeVisible) specifically SpecialCells, which is returning a range, but the range is not a single continguous block. Instead, if you debug through your code, and put a watch on this range, then you see that the property Areas actually has a count of 2.
Set rng = Sheets("Sheet1").Range("b2:b645").SpecialCells(xlCellTypeVisible)
Set areaCount = rng.Areas.Count
So the problem is not with your CountIf method, but rather that the Range you are passing into CountIf is made up of different ranges, ie it is of the wrong type.
CountIf can therefore not work for you in this scenario. What you need to use is a worksheet function that allows for working with filtered datasets.
So the function Subtotal which does take a list of ranges seems to be a more appropriate WorksheetFunction for your needs. Now you need to choose an appropriate Aggregate method for the first parameter, which for you is 2 or 3. Count numbers only : 2 or count none blank cells : 3.
Usefully the Aggregate function provides a list of aggregations you can use.
You can now also use the Aggregate function or Subtotal function to Sum your visible ranges, by using the Aggregate function Sum : 9.
Putting all of this together here is a suggested code snippet for you..
Sub test31()
Dim rngToUse As Range
Dim visibleSum As Long
Dim countOfVisible As Long
Set rngToUse = Sheets("Sheet1").Range("b2:b645")
visibleSum = WorksheetFunction.Subtotal(9, rngToUse)
countOfVisible = WorksheetFunction.Subtotal(3, rngToUse)
Debug.Print "Sum Visible Cells only: " & visibleSum
Debug.Print "Count of Visible Cells : " & countOfVisible
If countOfVisible > 0 Then
Debug.Print "Ok"
End If
End Sub
I hope that was a little more useful and informative.
Regards
Gareth
I have a single worksheet with sheets Sheet1 and Sheet2 and I am trying to reference a range of cells from Sheet2 to Sheet1
I know how to reference worksheet cells such as =Sheet2!A1 but how can I do the same for a cell range such as A1:F1 I tried =Sheet2!A1:F1 but it does not like the syntax.
I need to use Excel Formulas for this if possible.
Simple ---
I have created a Sheet 2 with 4 cells and Sheet 1 with a single Cell with a Formula:
=SUM(Sheet2!B3:E3)
Note, trying as you stated, it does not make sense to assign a Single Cell a value from a range. Send it to a Formula that uses a range to do something with it.
The formula that you have is fine. But, after entering it, you need to hit Control + Shift + Enter in order to apply it to the range of values. Specifically:
Select the range of values in the destination sheet.
Enter into the formula panel your desired formula, e.g. =Sheet2!A1:F1
Hit Control + Shift + Enter to apply the formula to the range.
Ok Got it, I downloaded a custom concatenation function and then just referenced its cells
Code
Function concat(useThis As Range, Optional delim As String) As String
' this function will concatenate a range of cells and return one string
' useful when you have a rather large range of cells that you need to add up
Dim retVal, dlm As String
retVal = ""
If delim = Null Then
dlm = ""
Else
dlm = delim
End If
For Each cell In useThis
if cstr(cell.value)<>"" and cstr(cell.value)<>" " then
retVal = retVal & cstr(cell.Value) & dlm
end if
Next
If dlm <> "" Then
retVal = Left(retVal, Len(retVal) - Len(dlm))
End If
concat = retVal
End Function
If you wish to concatenate multiple cells from different sheets, and you also want to add a delimiter between the content of each cell, the most straightforward way to do it is:
=CONCATENATE(Sheet1!A4, ", ", Sheet2!A5)
This works only for a limited number of referenced cells, but it is fast if you have only of few of these cells that you want to map.
You can put an equal formula, then copy it so reference the whole range (one cell goes into one cell)
=Sheet2!A1
If you need to concatenate the results, you'll need a longer formula, or a user-defined function (i.e. macro).
=Sheet2!A1&Sheet2!B1&Sheet2!C1&Sheet2!D1&Sheet2!E1&Sheet2!F1
Its quite simple but not easy to discover --- Go here to read more. its from the official microsoft website
Step 1 -
Click the cell or range of the source sheet (that contains the data you want to link to)
Step 2
Press Ctrl+C, or go to the Home tab, and in the Clipboard group, click Copy Button image .
Step 3
Clipboard group on the Home tab
Step 4
Press Ctrl+V, or go to the Home tab, in the Clipboard group, click Paste Link Button. By default, the Paste Options Button image button appears when you paste copied data.
Step 5
Click the Paste Options button, and then click Paste Link .
I rewrote the code provided by Ninja2k because I didn't like that it looped through cells. For future reference here's a version using arrays instead which works noticeably faster over lots of ranges but has the same result:
Function concat2(useThis As Range, Optional delim As String) As String
Dim tempValues
Dim tempString
Dim numValues As Long
Dim i As Long, j As Long
tempValues = useThis
numValues = UBound(tempValues) * UBound(tempValues, 2)
ReDim values(1 To numValues)
For i = UBound(tempValues) To LBound(tempValues) Step -1
For j = UBound(tempValues, 2) To LBound(tempValues, 2) Step -1
values(numValues) = tempValues(i, j)
numValues = numValues - 1
Next j
Next i
concat2 = Join(values, delim)
End Function
I can't help but think there's definitely a better way...
Here are steps to do it manually without VBA which only works with 1d arrays and makes static values instead of retaining the references:
Update cell formula to something like =Sheet2!A1:A15
Hit F9
Remove the curly braces { and }
Place CONCATENATE( at the front of the formula after the = sign and ) at the end of the formula.
Hit enter.
If these worksheets reside in the same workbook, a simple solution would be to name the range, and have the formula refer to the named range. To name a range, select it, right click, and provide it with a meaningful name with Workbook scope.
For example =Sheet1!$A$1:$F$1 could be named: theNamedRange. Then your formula on Sheet2! could refer to it in your formula like this: =SUM(theNamedRange).
Incidentally, it is not clear from your question how you meant to use the range. If you put what you had in a formula (e.g., =SUM(Sheet1!A1:F1)) it will work, you simply need to insert that range argument in a formula. Excel does not resolve the range reference without a related formula because it does not know what you want to do with it.
Of the two methods, I find the named range convention is easier to work with.