I am working on a sheet that will allow the users to pull up PDFs based on partial file names in different folders from just clicking on the cell.
My question has two parts.
I want the File Path "fp" to grab the active cell value at the top of the column selected. I want to do this for easy of user use down the road in the event the paths change.
I have looked up several methods to open a PDF from excel. None of which seem to be working and I am unsure why this is.
Option Explicit
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
'Activate Macro by click a cell
If Selection.Count = 1 Then
If Not Intersect(Target, Range("A3:B10000")) Is Nothing Then
Call OpenFile
End If
End If
End Sub
Function OpenAnyFile(strPath As String)
'Put this in as "ActiveWorkbook.FollowHyperlink OpenMe" was not working. Was trying the shell method.
Set objShell = CreateObject("Shell.Application")
objShell.Open (strPath)
End Function
Sub OpenFile()
Dim fp As String, fn As String, TheFile As String
fp = "R:\Procurement\Invoices\"
'(((Item 1))) I want it so that fp will return the top value from the top row of this sheet as i would like the path information to be there for easy user modification.
fn = ActiveCell.Value
TheFile = Dir(fp & fn & "*.pdf")
If CBool(Len(fn)) Then
MsgBox ("File Found")
'(((Item 2))) Opening the PDf does not work. Below are two ways i have tried to achieve this.
Call OpenAnyFile(TheFile)
'ActiveWorkbook.FollowHyperlink TheFile
End If
End Sub
The way you use whitespace is very confusing and makes it hard to understand your program.
Your code won't compile as written.
Why use a Function instead of a Sub if you're not returning a value?
You'll get an overflow error if a user presses Ctrl A to select the whole worksheet.
Read about the shell function https://learn.microsoft.com/en-us/office/vba/language/reference/user-interface-help/shell-function
Overflow errors can occur when Target.Count is used. Typically this happens with Ctrl + A. The expanding behavior if Ctrl + A provides very little protection. At best, you must press it three times to first selecting the region, then the used range, and then the entire worksheet. At worst, once is needed.
From MS Docs, re: Range object & Count vs CountLarge properties
https://learn.microsoft.com/en-us/office/vba/api/excel.range.count
The CountLarge property is functionally the same as the Count property, except that the Count property will generate an overflow error if the specified range has more than 2,147,483,647 cells (one less than 2,048 columns). The CountLarge property, however, can handle ranges up to the maximum size for a worksheet, which is 17,179,869,184 cells.
Shell function vs Shell object
The shell function is super easy to use, no need for the overhead that comes with creating an object.
For example, using the shell function to open page 4 of myfile.pdf in Internet Explorer:
Shell("C:\Program Files\Internet Explorer\iexplore.exe " + "C:\myfile.pdf#Page=4")
Note there is a space after `"[...]iexplore.exe [...]" that is important.
Replace the executable file path with the application of your choice
Note: many applications cannot open a PDF to a specific page. Simply omit the part #Page= segment of it's a problem.
Note: you can also get the PID of the new process like this:
vPID = Shell("C:\Program Files\Internet Explorer\iexplore.exe " + "C:\myfile.pdf#Page=4")
You can do this:
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Dim rng As Range
If Target.Count = 1 Then
Set rng = Application.Intersect(Target, Me.Range("A3:B10000"))
If Not rng Is Nothing Then
'Pass the cell value and the value from the top row
' of the selected column
OpenFile rng.Value, Me.Cells(1, rng.Column).Value
End If
End If
End Sub
Sub OpenFile(fn As String, fp As String)
'etc
I had a colleague of my give me a hand with this. Thank you for all the help! Answer is below. Let me know if you see anything that can be improved upon.
Option Explicit
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
If Selection.Count = 1 Then
If Not Intersect(Target, Range("A3:B10000")) Is Nothing Then
Call OpenFile
End If
End If
End Sub
Sub OpenFile()
Dim filepath As String
Dim filename As Variant
'So that the macro wont try to open nothing
If ActiveCell.Value = "" Then
Exit Sub
End If
'Filepath is at the top of the worksheet above each active cell
filepath = Cells(1, ActiveCell.Column).Value
'File name is just the first portion of the file. the "*" will determine the remaining file name
filename = Dir(filepath & ActiveCell.Value & "*")
'This will open the file below. Needs the filepath and the filenname together although the filepath is inbedded in the filename
If CBool(Len(filename)) Then
ActiveWorkbook.FollowHyperlink (filepath & filename)
End If
End Sub
Related
I am building an Excel 2016 Userform using VBA and need to collect the row and column of the cell from which the form is opened. I open the form on a cell double click with Worksheet_BeforeDoubleClick and then initialize the Userform with UserForm_Initialize(). I would like to pass the Target of the double click event to UserForm_Initialize() but am not sure how to. This forum thread addresses this issue, but the provided solutions did not work for me.
Here is my Worksheet_BeforeDoubleClick:
Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)
Column = Target.Column
Row = Target.Row
'Find the last non-blank cell in column B(2)
lRow = Cells(Rows.Count, 2).End(xlDown).Row
'Find the last non-blank cell in row 2
lCol = Cells(2, Columns.Count).End(xlToRight).Column
If Not Intersect(Target, Range(Cells(3, 3), Cells(lRow, lCol))) Is Nothing Then
Cancel = True
EdgeEntryForm.Show
End If
End Sub
And my UserForm_Initialize():
Private Sub UserForm_Initialize()
Dim Column As Long, Row As Long 'I would like to fill these with the Target values
MsgBox ("Row is " & Row & " Column is " & Column)
'Description.Caption = "Fill out this form to define a network edge from " & Cells(2, Row).Value & " to " & Cells(Column, 2).Value
End Sub
As suggested in my comments, one way would be to just use the ActiveCell and assign that to a variable.
Alternatively, if you do want to pass it as a variable, you can do it with a bit of a workaround, by having a global variable to temporarly hold that information:
In your worksheet code:
Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)
'.....
With UserForm1
Set .rngTarget = Target
.Show
End With
'.....
End Sub
In your userform:
Public rngTarget As Range
Private Sub UserForm_Activate()
'....
If Not rngTarget Is Nothing Then
MsgBox ("Row is " & rngTarget.Row & " Column is " & rngTarget.Column)
Else
MsgBox "something went wrong with assigning rngTarget variable"
End If
'....
End Sub
EDIT: I was trying initially to propose something similar to #MathieuGuindon's answer, but was failing due to my limited knowledge on the difference between initialise and activate (thanks Mathieu).
I've updated the answer to make use of the global variable at userform level, rather than use one from a module.
The form is shown modally, so ActiveCell isn't going to change on you, and should be safe to use in the form's code-behind.
The problem with that, is that you've now tied the form to ActiveSheet/ActiveCell, and now in order to test anything you need to Select or Activate a cell.
If the form code only needs to know about the cell's Address, then it shouldn't be given a Range (give it a Range and it can access any cell in any sheet in any workbook in the Application instance) - that's the principle of least knowledge at play. But this is obviously example code, so let's go with a Range:
Option Explicit
Private internalWorkingCell As Range
Public Property Get WorkingCell() As Range
Set WorkingCell = internalWorkingCell
End Property
Public Property Set WorkingCell(ByVal value As Range)
Set internalWorkingCell = value
End Property
Now your form code can use WorkingCell or internalWorkingCell to do its thing, and no global variable needs to float around;
With New UserForm1 ' Initialize handler runs here
Set .WorkingCell = Target
.Show ' Activate handler runs here
End With
The WorkingCell belongs to the form - it has no business being in global scope.
Careful with the Initialize handler in forms - especially when you use its default instance (i.e. when you don't New it up): you don't control when that handler runs, the VBA runtime does; UserForm_Initialize will run the first time the form instance is referenced (in your case, immediately before the .Show call), and then never again unless the instance is destroyed (clicking the red X button would do that).
A MsgBox call in the Initialize handler will run before the form is shown; you probably want to move that code to the Activate handler before it causes problems.
Currently I have a reset button for my excel sheet which will clear the contents except the formulas I have. Is there a way to dynamically count how many times I "pressed" the reset button and clear contents?
This..
Public countR As Long
Sub Alternative1() 'This will return to 0 when you close the workbook
countR = countR + 1
MsgBox "The document has been cleared " & countR & " time(s)"
End Sub
or..
Sub Alternative2()
Dim rng As Range
Set rng = Range("A1") 'Change to some cell that isn't cleared by your code
rng.Value = rng.Value + 1
End Sub
and call them like..
Sub WhatYourCodeMaybeLooksLike()
Range("B1:C100").ClearContents
Alternative1
Alternative2
End Sub
The comment by reportgunner above is correct, but if you're asking for VBA options then look at module level variables.
Public x As Integer
Sub Button1_Click()
x = x + 1
MsgBox x
End Sub
x will begin as 0 when you open the workbook and will then be incremented each time Button1 is hit, in this case.
Slightly different are static variables which you can declare in the procedure with the code. Both will work fine in VBA.
The variable will often reset if your code hits errors during debugging. So the cell option might be preferable if you never want to lose track of the number.
I use the SendKeys method, for user-friendliness, with an InputBox, for selecting multiple cells to use for calculations.
I want to avoid having the user hold CTRL while clicking each cell or pressing Shift-F8 to select multiple cells.
I want the InputBox to pop up and to select cells that are not necessarily right next to one another, without having to do anything else.
Here is a sample of what I have.
Sub CalculateIt()
Dim calcRange As Range
On Error Resume Next
SendKeys "+{F8}"
Set calcRange = Application.InputBox("Select the cells you would like to use.", Type:=8)
If Err.Number = 424 Then Exit Sub
Dim stuff As Double
For Each calcCell in calcRange
If IsNumeric(calcCell.Value) Then stuff = stuff + calcCell.Value
Next calcCell
MsgBox "The Solution: " & Sqr(stuff)
End Sub
I want to avoid using the SendKeys method because I've read a lot about it being finicky and fragile, and that it can cause problems. I have run into some scenarios where it doesn't work and I have to hold CTRL or do Shift-F8 anyways.
Possible solution... Add a checkbox to the worksheet then go into the Microsoft Excel Objects | ThisWorkbook window and enter the following code. If the checkbox is checked, then whatever formula you'd like executed will be as the user clicks each cell.
Private Sub Workbook_SheetSelectionChange(ByVal Sh As Object, ByVal Target As Range)
Dim nbrCell As Double
If IsNumeric(ActiveCell.Value) Then nbrCell = ActiveCell.Value Else nbrCell = 0
If ActiveSheet.CheckBox1.Value = True Then ActiveSheet.Range("A1").Value = ActiveSheet.Range("A1").Value + nbrCell
End Sub
You can also add a second checkbox to remove selected cells if you're concerned the user might accidentally click on the wrong cell and want that value removed.
Here is the vba I currently have. I am needing it to delete all data in any worksheet that starts with "Demand". The code is running but no data is being deleted. Any help is appreciated!!
Option Explicit
Sub ClearExcelContent()
Dim DemandData As Worksheet
For Each DemandData In ActiveWorkbook.Worksheets
If LCase(Left(DemandData.Name, 6)) = "Demand" Then
DemandData.Rows("2:" & Rows.Count).ClearContents
End If
Next DemandData
MsgBox "All Demand Data has been Deleted from Consolidation Tab"
End Sub
If LCase(Left(DemandData.Name, 6)) = "Demand" Then
The above statement will not match any worksheet, because the LHS is all lowercase and the RHS has an uppercase D. Replace Demand with lowercase demand.
If LCase(Left(DemandData.Name, 6)) = "demand" Then
These are 3 basic functions for edition of strings:
LCase, UCase, and WorksheetFunction.Proper
This is what they would return:
Demand Is Diamand
demand is diamand
DEMAND IS DIAMAND
if you run them like this:
Public Sub TestMe()
Dim strText As String
strText = "demaNd is dIamand"
Debug.Print WorksheetFunction.Proper(strText)
Debug.Print LCase(strText)
Debug.Print UCase(strText)
End Sub
In order to compare the result of the functions, you should be comparing with similar string. Probably the most sure thing is to put the same string functions on both parts of the comparison. Like this:
LCase(Left(DemandData.name, 6)) = LCase("demand") Then
In general, if you want to make sure that Vit=VIT then consider adding Option Compare Text on top of the module. Then this would be true:
Option Compare Text
Sub TestMe()
Debug.Print "Vit" = "VIT"
End Sub
I have two spreadsheets... when one gets modified in a certain way I want to have a macro run that modifies the second in an appropriate manner. I've already isolated the event I need to act on (the modification of any cell in a particular column), I just can't seem to find any concrete information on accessing and modifying another spreadsheet (this spreadsheet is located on a different LAN share also... the user has access to both, though).
Any help would be great. References on how to do this or something similar are just as good as concrete code samples.
In Excel, you would likely just write code to open the other worksheet, modify it and then save the data.
See this tutorial for more info.
I'll have to edit my VBA later, so pretend this is pseudocode, but it should look something like:
Dim xl: Set xl = CreateObject("Excel.Application")
xl.Open "\\the\share\file.xls"
Dim ws: Set ws = xl.Worksheets(1)
ws.Cells(0,1).Value = "New Value"
ws.Save
xl.Quit constSilent
You can open a spreadsheet in a single line:
Workbooks.Open FileName:="\\the\share\file.xls"
and refer to it as the active workbook:
Range("A1").value = "New value"
After playing with this for a while, I found the Michael's pseudo-code was the closest, but here's how I did it:
Dim xl As Excel.Application
Set xl = CreateObject("Excel.Application")
xl.Workbooks.Open "\\owghome1\bennejm$\testing.xls"
xl.Sheets("Sheet1").Select
Then, manipulate the sheet... maybe like this:
xl.Cells(x, y).Value = "Some text"
When you're done, use these lines to finish up:
xl.Workbooks.Close
xl.Quit
If changes were made, the user will be prompted to save the file before it's closed. There might be a way to save automatically, but this way is actually better so I'm leaving it like it is.
Thanks for all the help!
Copy the following in your ThisWorkbook object to watch for specific changes. In this case when you increase a numeric value to another numeric value.
NB: you will have to replace Workbook-SheetChange and Workbook-SheetSelectionChange with an underscore. Ex: Workbook_SheetChange and Workbook_SheetSelectionChange the underscore gets escaped in Markdown code.
Option Explicit
Dim varPreviousValue As Variant ' required for IsThisMyChange() . This should be made more unique since it's in the global space.
Private Sub Workbook-SheetChange(ByVal Sh As Object, ByVal Target As Range)
' required for IsThisMyChange()
IsThisMyChange Sh, Target
End Sub
Private Sub Workbook-SheetSelectionChange(ByVal Sh As Object, ByVal Target As Range)
' This implements and awful way of accessing the previous value via a global.
' not pretty but required for IsThisMyChange()
varPreviousValue = Target.Cells(1, 1).Value ' NB: This is used so that if a Merged set of cells if referenced only the first cell is used
End Sub
Private Sub IsThisMyChange(Sh As Object, Target As Range)
Dim isMyChange As Boolean
Dim dblValue As Double
Dim dblPreviousValue As Double
isMyChange = False
' Simple catch all. If either number cant be expressed as doubles, then exit.
On Error GoTo ErrorHandler
dblValue = CDbl(Target.Value)
dblPreviousValue = CDbl(varPreviousValue)
On Error GoTo 0 ' This turns off "On Error" statements in VBA.
If dblValue > dblPreviousValue Then
isMyChange = True
End If
If isMyChange Then
MsgBox ("You've increased the value of " & Target.Address)
End If
' end of normal execution
Exit Sub
ErrorHandler:
' Do nothing much.
Exit Sub
End Sub
If you are wishing to change another workbook based on this, i'd think about checking to see if the workbook is already open first... or even better design a solution that can batch up all your changes and do them at once. Continuously changing another spreadsheet based on you listening to this one could be painful.