I want to use something similar to
GetObject(,"Excel.Application") to get back the application I created.
I call CreateObject("Excel.Application") to create Excel instances. Later if the VBA project resets, due to debugging and coding, the Application object variables are lost but the Excel instances are running in the background. Kind of a memory leak situation.
I want to re-attach to either re-use (preferred way) or close them.
To list the running instances of Excel:
#If VBA7 Then
Private Declare PtrSafe Function AccessibleObjectFromWindow Lib "oleacc" ( _
ByVal hwnd As LongPtr, ByVal dwId As Long, riid As Any, ppvObject As Object) As Long
Private Declare PtrSafe Function FindWindowExA Lib "user32" ( _
ByVal hwndParent As LongPtr, ByVal hwndChildAfter As LongPtr, _
ByVal lpszClass As String, ByVal lpszWindow As String) As LongPtr
#Else
Private Declare Function AccessibleObjectFromWindow Lib "oleacc" ( _
ByVal hwnd As Long, ByVal dwId As Long, riid As Any, ppvObject As Object) As Long
Private Declare Function FindWindowExA Lib "user32" ( _
ByVal hwndParent As Long, ByVal hwndChildAfter As Long, _
ByVal lpszClass As String, ByVal lpszWindow As String) As Long
#End If
Sub Test()
Dim xl As Application
For Each xl In GetExcelInstances()
Debug.Print "Handle: " & xl.ActiveWorkbook.FullName
Next
End Sub
Public Function GetExcelInstances() As Collection
Dim guid&(0 To 3), acc As Object, hwnd, hwnd2, hwnd3
guid(0) = &H20400
guid(1) = &H0
guid(2) = &HC0
guid(3) = &H46000000
Set GetExcelInstances = New Collection
Do
hwnd = FindWindowExA(0, hwnd, "XLMAIN", vbNullString)
If hwnd = 0 Then Exit Do
hwnd2 = FindWindowExA(hwnd, 0, "XLDESK", vbNullString)
hwnd3 = FindWindowExA(hwnd2, 0, "EXCEL7", vbNullString)
If AccessibleObjectFromWindow(hwnd3, &HFFFFFFF0, guid(0), acc) = 0 Then
GetExcelInstances.Add acc.Application
End If
Loop
End Function
This would be best as a comment on Florent B.'s very useful function that returns a collection of the open Excel instances, but I don't have sufficient reputation to add comments. In my tests, the collection contained "repeats" of the same Excel instances i.e. GetExcelInstances().Count was larger than it should have been. A fix for that is the use of the AlreadyThere variable in the version below.
Private Function GetExcelInstances() As Collection
Dim guid&(0 To 3), acc As Object, hwnd, hwnd2, hwnd3
guid(0) = &H20400
guid(1) = &H0
guid(2) = &HC0
guid(3) = &H46000000
Dim AlreadyThere As Boolean
Dim xl As Application
Set GetExcelInstances = New Collection
Do
hwnd = FindWindowExA(0, hwnd, "XLMAIN", vbNullString)
If hwnd = 0 Then Exit Do
hwnd2 = FindWindowExA(hwnd, 0, "XLDESK", vbNullString)
hwnd3 = FindWindowExA(hwnd2, 0, "EXCEL7", vbNullString)
If AccessibleObjectFromWindow(hwnd3, &HFFFFFFF0, guid(0), acc) = 0 Then
AlreadyThere = False
For Each xl In GetExcelInstances
If xl Is acc.Application Then
AlreadyThere = True
Exit For
End If
Next
If Not AlreadyThere Then
GetExcelInstances.Add acc.Application
End If
End If
Loop
End Function
#PGS62/#Philip Swannell has the correct answer for returning a Collection; I can iterate all instances; and it is brilliant, as #M1chael comment.
Let's not confuse Application objects with Workbook objects... ...Of
course it would be possible to write a nested loop that loops over the
workbooks collection of each application object
This is the nested loop implemented and fully functional:
Sub Test2XL()
Dim xl As Excel.Application
Dim i As Integer
For Each xl In GetExcelInstances()
Debug.Print "Handle: " & xl.Application.hwnd
Debug.Print "# workbooks: " & xl.Application.Workbooks.Count
For i = 1 To xl.Application.Workbooks.Count
Debug.Print "Workbook: " & xl.Application.Workbooks(i).Name
Debug.Print "Workbook path: " & xl.Application.Workbooks(i).path
Next i
Next
Set xl = Nothing
End Sub
And, for Word instances, the nested loop:
Sub Test2Wd()
Dim wd As Word.Application
Dim i As Integer
For Each wd In GetWordInstancesCol()
Debug.Print "Version: " & wd.System.Version
Debug.Print "# Documents: " & wd.Application.Documents.Count
For i = 1 To wd.Application.Documents.Count
Debug.Print "Document: " & wd.Application.Documents(i).Name
Debug.Print "Document path: " & wd.Application.Documents(i).path
Next i
Next
Set wd = Nothing
End Sub
For Word you have to use what is explained in the end of this thread
I use the following to check if two instances are running, and display a message. It could be altered to close other instance... This may be of help... I need code to return a specific instance, and return for use similar to GetObject(,"Excel.Application")... I don't think it possible though
If checkIfExcelRunningMoreThanOneInstance() Then Exit Function
In module (some of the declarations are possible used for other code):
Const MaxNumberOfWindows = 10
Const HWND_TOPMOST = -1
Const SWP_NOSIZE = &H1
Const SWP_NOMOVE = &H2
Type RECT
Left As Long
Top As Long
Right As Long
Bottom As Long
End Type
Public Declare Function ShowWindow Lib "user32" (ByVal hwnd As Long, ByVal nCmdShow As Long) As Long
Global ret As Integer
Declare Function GetWindow Lib "user32" (ByVal hwnd As Long, ByVal wCmd As Long) As Long
Private Declare Function SetWindowPos Lib "user32" (ByVal hwnd As Long, ByVal hWndInsertAfter As Long, ByVal x As Long, ByVal y As Long, ByVal cx As Long, ByVal cy As Long, ByVal wFlags As Long) As Long
Public Declare Function GetWindowRect Lib "user32" (ByVal hwnd As Long, lpRect As RECT) As Long
Declare Function GetKeyNameText Lib "user32" Alias "GetKeyNameTextA" (ByVal lParam As Long, ByVal lpBuffer As String, ByVal nSize As Long) As Long
Declare Function MapVirtualKey Lib "user32" Alias "MapVirtualKeyA" (ByVal wCode As Long, ByVal wMapType As Long) As Long
Declare Function GetDesktopWindow Lib "user32" () As Long
Public Declare Function GetWindowText Lib "user32" Alias "GetWindowTextA" (ByVal hwnd As Long, ByVal lpString As String, ByVal cch As Long) As Long
Declare Function GetWindowLong Lib "user32" Alias "GetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long) As Long
Public Declare Function GetParent Lib "user32" (ByVal hwnd As Long) As Long
Private Declare Function FindWindow Lib "user32" _
Alias "FindWindowA" _
(ByVal lpClassName As String, _
ByVal lpWindowName As String) As Long
Private Const VK_CAPITAL = &H14
Private Declare Function GetKeyState Lib "user32" _
(ByVal nVirtKey As Long) As Integer
Private Declare Function OpenProcess Lib "kernel32" ( _
ByVal dwDesiredAccess As Long, ByVal bInheritHandle As Long, ByVal dwProcessId As Long) As Long
Private Declare Function CloseHandle Lib "kernel32" ( _
ByVal hObject As Long) As Long
Private Declare Function EnumProcesses Lib "PSAPI.DLL" ( _
lpidProcess As Long, ByVal cb As Long, cbNeeded As Long) As Long
Private Declare Function EnumProcessModules Lib "PSAPI.DLL" ( _
ByVal hProcess As Long, lphModule As Long, ByVal cb As Long, lpcbNeeded As Long) As Long
Private Declare Function GetModuleBaseName Lib "PSAPI.DLL" Alias "GetModuleBaseNameA" ( _
ByVal hProcess As Long, ByVal hModule As Long, ByVal lpFileName As String, ByVal nSize As Long) As Long
Private Const PROCESS_VM_READ = &H10
Private Const PROCESS_QUERY_INFORMATION = &H400
Global ExcelWindowName$ 'Used to switch back to later
Function checkIfExcelRunningMoreThanOneInstance()
'Check instance it is 1, else ask user to reboot excel, return TRUE to abort
ExcelWindowName = excel.Application.Caption 'Used to switch back to window later
If countProcessRunning("excel.exe") > 1 Then
Dim t$
t = "Two copies of 'Excel.exe' are running, which may stop in cell searching from working!" & vbCrLf & vbCrLf & "Please close all copies of Excel." & vbCrLf & _
" (1 Then press Alt+Ctrl+Del to go to task manager." & vbCrLf & _
" (2 Search the processes running to find 'Excel.exe'" & vbCrLf & _
" (3 Select it and press [End Task] button." & vbCrLf & _
" (4 Then reopen and use PostTrans"
MsgBox t, vbCritical, ApplicationName
End If
End Function
Private Function countProcessRunning(ByVal sProcess As String) As Long
Const MAX_PATH As Long = 260
Dim lProcesses() As Long, lModules() As Long, N As Long, lRet As Long, hProcess As Long
Dim sName As String
countProcessRunning = 0
sProcess = UCase$(sProcess)
ReDim lProcesses(1023) As Long
If EnumProcesses(lProcesses(0), 1024 * 4, lRet) Then
For N = 0 To (lRet \ 4) - 1
hProcess = OpenProcess(PROCESS_QUERY_INFORMATION Or PROCESS_VM_READ, 0, lProcesses(N))
If hProcess Then
ReDim lModules(1023)
If EnumProcessModules(hProcess, lModules(0), 1024 * 4, lRet) Then
sName = String$(MAX_PATH, vbNullChar)
GetModuleBaseName hProcess, lModules(0), sName, MAX_PATH
sName = Left$(sName, InStr(sName, vbNullChar) - 1)
If Len(sName) = Len(sProcess) Then
If sProcess = UCase$(sName) Then
countProcessRunning = countProcessRunning + 1
End If
End If
End If
End If
CloseHandle hProcess
Next N
End If
End Function
The I found:
Dim xlApp As Excel.Application
Set xlApp = GetObject("ExampleBook.xlsx").Application
Which gets the object if you know the name of the sheet currently active in Excel instance. I guess this could be got from the application title using the first bit of code. In my app I do know the filename.
This can accomplish what you want.
Determine if an instance of Excel is open:
Dim xlApp As Excel.Application
Set xlApp = GetObject(, "Excel.Application")
If an instance is running you can access it using the xlApp object. If an instance is not running you will get a run-time error (you might need/want an error handler). The GetObject function gets the first instance of Excel that had been loaded. You can do your job with it, and to get to others, you can close that one and then try GetObject again to get the next one, etc.
So you will be attaining your ok-but-second-preferred objective
(taken from http://excelribbon.tips.net/T009452_Finding_Other_Instances_of_Excel_in_a_Macro.html).
For attaining your preferred objective, I think that https://stackoverflow.com/a/3303016/2707864 shows you how.
Create an array of objects and store the newly created Excel.Application in the array. That way you can reference them as and when you need. Let's take a quick example:
In a module:
Dim ExcelApp(2) As Object
Sub Test()
Set ExcelApp(1) = CreateObject("Excel.Application")
ExcelApp(1).Visible = True
Set ExcelApp(2) = CreateObject("Excel.Application")
ExcelApp(2).Visible = True
End Sub
Sub AnotherTest()
ExcelApp(1).Quit
ExcelApp(2).Quit
End Sub
Run Test() macro and you should see two Excel Applications pop up. Then run AnotherTest() and the Excel Applications will quit. You can even set the array to Nothing after you are done.
You can get handle of running Excel applications using the script published on http://www.ozgrid.com/forum/showthread.php?t=182853. That should get you where you want to go.
You should use this code every time you need an Excel application object. This way, your code will only ever work with one application object or use a pre-existing one. The only way you could end up with more than one is if the user started more than one. This is both the code to open Excel and attach and reuse, like you want.
Public Function GetExcelApplication() As Object
On Error GoTo openExcel
Set GetExcelApplication = GetObject(, "Excel.Application")
Exit Function
openExcel:
If Err.Number = 429 Then
Set GetExcelApplication = CreateObject("Excel.Application")
Else
Debug.Print "Unhandled exception: " & Err.Number & " " & Err.Description
End If
End Function
If you wanted to close multiple instances you would need to call GetObject followed by .Close in a loop until it throws the error 429.
The details can be found in this Article
Related
I'm working on a problem for a few days now and can't seem to find the answer. I have a third party dll written in C, which i have to use in an VB6 application.
The function inside dll looks something like this:
someFunction(WContext* context, const unsigned char* seed, int seedLength, const unsigned char* name, int nameLength,
const unsigned char* pin, int pinLength, const char* description)
I have an example written in c#. I tried it out and it works just fine. This is what it looks like in C#
[DllImport("Filename.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
private static extern int someFunction(IntPtr context, byte[] seed, int seedLength, byte[] name, int nameLength,
byte[] pin, int pinLength, string description)
This is later used in the example like this:
byte[] seed = Encoding.ASCII.GetBytes("seed")
byte[] name = Encoding.ASCII.GetBytes("name")
byte[] pin = Encoding.ASCII.GetBytes("1234")
string description = "description"
int result = someFunction(context, seed, seed.length, name, name.Length, pin, pin.Length, description)
Now this works just fine in C#. I get 0 as a result which means in that case that the operation was a success.
I want to make this work in VB6. I tried it with some other functions of the dll and they work just like they should. This one gives me a headache. Here is what I tried just like with any other function:
First I imported the function into my code so I could use it later. I did this with a few other functions the same way and it worked fine.
Private Declare Function someFunction Lib "C:\DllPath\Filename.dll" (ByVal context As Long, ByRef seed as Byte, _
ByVal seedLength As Integer, ByRef name As Byte, _
ByVal nameLength As Integer, ByRef pin As Byte, _
ByVal pinLength As Integer, ByVal description As String) As Integer
Next Step was for me to call the function. I did it this way:
(I do get the context from another function earlier so this already has a value. Other functions work fine with that variable)
Dim seed() As Byte
Dim seedLength As Integer
Dim name() As Byte
Dim nameLength As Integer
Dim pin() As Byte
Dim pin As Integer
Dim description As String
Dim result as Integer
seed = StrConv("seed", vbFromUnicode)
seedLength = UBound(seed) + 1
name = StrConv("name", vbFromUnicode)
nameLength = UBound(name) + 1
pin = StrConv("1234", vbFromUnicode)
pinLength = UBound(pin) + 1
description = "description"
result = someFunction(context, seed(0), seedLength, name(0), nameLength, pin(0), pinLength, description)
The value for result is 1. By the documentation I got this means invalid parameter. Now I researched a lot. Read that in VB6 I have to give the first element of the byte array just like I did in my code. Tried it first with the whole array, got the same result. I think it has something to do with the arrays since its the first function that I've taken over to my code that had those. I'm also not really that native with VB6 but I have to do this. Maybe some of you guys know why this doesn't work. Probably just a minor mistake by myself.
In VB6 it's as simple as this
Option Explicit
Private Declare Function someFunction Lib "C:\DllPath\Filename.dll" (ByVal context As Long, ByVal seed As String, _
ByVal seedLength As Long, ByVal name As String, _
ByVal nameLength As Long, ByVal pin As String, _
ByVal pinLength As Long, ByVal description As String) As Long
Private Sub Form_Load()
Dim context As Long
Dim seed As String
Dim name As String
Dim pin As String
Dim description As String
Dim result As Long
seed = "seed"
name = "name"
pin = "1234"
description = "description"
result = someFunction(context, seed, Len(seed), name, Len(name), pin, Len(pin), description)
End Sub
Just don't use Integer at all (only Longs) use ByVal ... As String for the runtime to do the Unicode->ANSI conversion and be done with it.
The original API is weirdly declared with unsigned char * pointer and separate int length parameter probably because the seed, name and pin strings can contain \0 embedded while description is plain zero-terminated string which explains the PITA w/ the C/C++ prototype.
VB6 strings are length-prefixed (and zero-terminated) from get go and can contain Chr$(0) by design so there is no need to take any additional measures like in C# sample w/ explicit byte[] arrays conversion and function prototype signature fiddling. Just use ByVal ... As String in the API declare.
When calling a declared API, VB6 will convert a String to an ANSI byte array automatically, so you can declare seed, name, and pin as ByVal Strings.
Description is a bit confusion to me though, since I don't see a length being passed for that one. Is this really a null-terminated string (pszDescription)? How does the API you are calling know how long this character buffer is?
You can try to change the function declaration in VB, making seed, name, and pin ByRef As Byte(),
note the parentheses:
Private Declare Function someFunction Lib "C:\DllPath\Filename.dll" (ByVal context As Long, ByRef seed as Byte(), _
ByVal seedLength As Integer, ByRef name As Byte(), _
ByVal nameLength As Integer, ByRef pin As Byte(), _
ByVal pinLength As Integer, ByVal description As String) As Integer
Then, when calling the function, pass the entire arrays, not the first elements:
result = someFunction(context, seed, seedLength, name, nameLength, pin, pinLength, description)
I'm working from VBA into .NET.
I have a working version of an interface using CLI and stdcall
I'm trying to remove the resulting dependency on the C++ 2015 runtime, and it looks like I can do it using UnmanagedExports.
But I have a couple of questions.
Can I just use "ref string" as a parameter and have it work?
If so, can I replace it with "out string"?
In either cases, do I have to do any string/string length management?
I currently pass a couple of callbacks in as "int". From an example I saw elsewhere, it looks like on the C# side, using this, I should be able to replace those parameters with Func for a callback such as Function A(p1 as String, p2 as Long, p3 as String) as Long
Any advice would be much appreciated.
You need a combination of StrPtr, possibly StrConv on the Access side, and IntPtr on the .NET side:
'VBA7
Private Declare PtrSafe Function Command Lib "External.dll" (ByVal CommandName As String, ByVal Result As LongPtr, ByRef ResultLength As Long) As Long
'VBA pre7
Private Declare Function Command Lib "External.dll" (ByVal CommandName As String, ByVal Result As Long, ByRef ResultLength As Long) As Long
'Example to use.
'Result will be up to "i" characters - no new string involved
Dim i As Long, x As Long, strResult As String
i = 100
strResult = Space(i)
x = Command(CommandName, Arguments, StrPtr(strResult), i)
If you use StrConv, the string type is up to you. If you don't, the pointer will be pointing to a Unicode array.
C# side:
[DllExport("Command", CallingConvention.StdCall)]
public static int Command(string commandName, string arguments, IntPtr result, out /*or ref*/ int resultLength)
{
string inputStr = Marshal.PtrToStringUni(result); //Unicode
resultLength = inputStr.Length;
int x = MainFunc.Command(commandName, arguments, ref inputStr);
if(null == inputStr)
{
inputStr = "";
}
if(inputStr.Length > resultLength)
{
inputStr = inputStr.Substring(0, resultLength);
}
byte[] outputBytes = Encoding.Unicode.GetBytes(inputStr);
Marshal.Copy(outputBytes, 0, result, outputBytes.Length);
resultLength = inputStr.Length;
return x;
}
I am trying to recreate the following C# code in VB6:
private void ChangeTab(string tabName, bool clearAll = true)
{
Yadyyada(tabName);
if (clearAll)
{
DoMoreStuff();
}
}
Here is what I have so far:
Private Sub ChangeTab(ByVal tabName As String, Optional ByVal clearAll As Boolean)
Yadyyada(tabName)
If clearAll = True Then
DoMoreStuff
End If
End Sub
So far so good apart from the default parameter. Can I assign clearAll a default value of true in the method signature in the same way I can in C# or do I just need to do this at the start of the method?
Thanks
Wow this takes me back.. can I ask why you're converting backwards technology-wise?
Anyway, you can use the Optional keyword:
Private Sub ChangeTab(ByVal tabName As String, Optional ByVal clearAll As Boolean = True)
Your issue is using ByVal. From memory, everything in VB6 was ByVal unless explicitly stated.
EDIT: I'm wrong. Default was ByRef.. it's been so long!
Yes, you could do the same thing as in C#
Private Sub ChangeTab(ByVal tabName As String, Optional ByVal clearAll As Boolean = True)
Debug.Print "Value for clearAll=" & clearAll
End Sub
calling with
ChangeTab("AName")
will print True
Try:
Private Sub ChangeTab(ByVal tabName As String, Optional clearAll As Boolean = True)
Call Yadyyada(tabName)
If clearAll Then
DoMoreStuff
End If
End Sub
See http://msdn.microsoft.com/en-us/library/aa266305%28v=vs.60%29.aspx
You can use IsMissing Function like this
Private Sub ChangeTab(ByVal tabName As String, Optional ByVal clearAll As Boolean)
Yadyyada(tabName)
If IsMissing(clearAll) = True Or clearAll = True Then
DoMoreStuff
End If
End Sub
My Mistake! Setting a default true value for optional parameter and check for this in code is the best solution!
Private Sub ChangeTab(ByVal tabName As String, Optional ByVal clearAll As Boolean = True)
Yadyyada(tabName)
If clearAll = True Then
DoMoreStuff
End If
End Sub
I'm taking a lot of time trying to figure this out, so I thought I could get some help here.
Basically I have a DLL function declared like this in IDL:
[id(1), helpstring("method findFile")] HRESULT findFile(
[in] BSTR fileName,
[out] LONG* someValue
);
How exactly do I declare and invoke from either C++ / C#?
Note: there is a VB6 app that successfully invokes the function. Declaration is:
Private Declare Function findFile Lib "thedll.dll" ( _
ByVal fileName As String, _
ByRef someValueAs Long _
)
The call:
Dim a As String
Dim b As Long
Dim r As long
a = "image.jpg"
b = -1
r = findFile(a, b)
Addendum:
I cannot guarantee that the VB6 code looks like that because I have the executable, I was only told what that portion looks like, so maybe you guys are right and it doesn't match. I did author the C++ DLL, and now I need to get together some code myself that successfully calls the DLL in order to try out stuff and not depend on that exe.
C++ implementation of the DLL function looks like this:
STDMETHODIMP CFinder::findFile(BSTR fileName, LONG* someValue)
{
*someValue = 8;
return S_OK;
}
Untested C# declaration:
[DllImport("thedll.dll", SetLastError=true)]
static extern int findFile([MarshalAs(UnmanagedType.BStr)]string fileName, out int someValue);
Is it possible to translate the following C# code into VB.NET, using VB 9.0?
delegate Stream StreamOpenerDelegate(String name);
void Exec1()
{
WorkMethod( x => File.OpenRead(x));
}
void Exec2()
{
StreamOpenerDelegate opener = x => return File.OpenRead(x) ;
WorkMethod(opener);
}
Can I do something like this?:
Private Delegate Function StreamOpenerDelegate(ByVal name As String) As Stream
Private Sub WorkMethod(ByVal d As StreamOpenerDelegate)
''
End Sub
Private Sub Exec1()
Me.WorkMethod(Function (ByVal x As String)
Return File.OpenRead(x)
End Function)
End Sub
Private Sub Exec2()
Dim opener As StreamOpenerDelegate = Function (ByVal x As String)
Return File.OpenRead(x)
End Function
Me.WorkMethod(opener)
End Sub
I'm trying to write some documentation, but I don't know VB syntax. Often I use Reflector to translate it, but I'm not sure it's working in this case. I'm also not clear on where I would need line continuation characters.
ANSWER
In VB9, it's not possible to have multi-line lambdas (or Sub lambdas, which I did not ask about). In VB9, all lambdas return a value, and must be a single expression. This changes in VB10. VB10 will allow the above syntax, but VB9 will not. In VB9, if the logic involves multiple code lines, it must not be a lambda; you must put it into a named Function and reference it explicitly. Like this:
Private Delegate Function StreamOpenerDelegate(ByVal name As String) As Stream
Private Sub WorkMethod(ByVal d As StreamOpenerDelegate)
''
End Sub
Function MyStreamOpener(ByVal entryName As String) As Stream
'' possibly multiple lines here
Return File.OpenRead(entryName)
End Function
Private Sub Exec1()
Me.WorkMethod(AddressOf MyStreamOpener)
End Sub
site: Mike McIntyre's blog
This should work:
Private Sub Exec1()
Me.WorkMethod(Function (x) File.OpenRead(x))
End Sub
Private Sub Exec2()
Dim opener As StreamOpenerDelegate = Function (x) File.OpenRead(x)
Me.WorkMethod(opener)
End Sub
You need the line continuation character to split a single line statement into multiple lines, like so:
Private Sub Exec1()
Me.WorkMethod(Function (x) _
File.OpenRead(x))
End Sub
Private Sub Exec2()
Dim opener As StreamOpenerDelegate = Function (x) _
File.OpenRead(x)
Me.WorkMethod(opener)
End Sub
In any case, in VS2010 there is implicit line continuation after certain characters. So I wouldn't worry about it too much.