I have a webform displayed in a WebBrowser control in a Visual Basic application to allow users to upload files to my webserver. Unusually for this type of thing, I know in advance the folder they need to browse to (I want them to upload a file which the VB application has generated) and I would like the Browse dialog box to default to that folder but it seems to default to whichever folder was last used by any application for File / Open.
I've tried using ChDir in VB to set the current folder but that doesn't work.
Is there any way I can persuade the Browse box to default to my desired folder?
First of all, I ought to state that there is no reliable way of doing this. The reason why it is so hard is for security reasons. There is a defaultValue and value property for the INPUT TYPE=FILE element, but if programmers had access to this, this could be used to suck files from the client machine - definitely not a good idea.
In Internet Explorer, the browse file dialogue is actually implemented by a Windows Common Dialog component. However, you have no direct access to this component.
There is no reliable browser-independent way of doing this. And I certainly don't recommend you reverse-engineer the "Last Recently Used" file list for the Common Dialog control (see jac's link). Doing that is very dangerous, since it is an internal-algorithm likely to change. And worse, you are hacking global state to solve a local problem (see Old New Thing blog, ad nauseam).
A solution that doesn't violate global state, but still hacky is to take advantage of the fact we know what the text is on the file upload dialogue. After your document has loaded, you can use a Timer to wait for the dialogue to appear, and at that point, paste the correct directory into the dialogue.
In my sample, I have code where the web browser control sits, and the BrowserHack.bas module.
Form code:
Option Explicit
Private Sub cmdLoadPage_Click()
' Store the default path into the .Tag property.
tmrWaitForDialogue.Tag = "C:\Windows\System32"
' Load URL.
wbMain.Navigate "<URL>"
End Sub
Private Sub Form_Load()
tmrWaitForDialogue.Enabled = False
tmrWaitForDialogue.Interval = 100 ' 10th of a second delay.
End Sub
Private Sub tmrWaitForDialogue_Timer()
If BrowserHack.IsBrowserFileDialogueVisible Then
' We don't want the Timer to fire again.
tmrWaitForDialogue.Enabled = False
' Copy the directory onto the clipboard.
Clipboard.Clear
Clipboard.SetText tmrWaitForDialogue.Tag
' The focus will be on the file path text box in the Open dialogue.
' Use CTL-V to paste the text, and then followed by an Enter character to
' dismiss the dropdown, and another to Open the folder.
SendKeys "^V"
SendKeys "{ENTER}"
SendKeys "{ENTER}"
End If
End Sub
Private Sub wbMain_DocumentComplete(ByVal pDisp As Object, URL As Variant)
tmrWaitForDialogue.Enabled = True
End Sub
BrowserHack:
' Purpose: Code to look for the WebBrowser (Internet Explorer) dialogue which appears when a File Upload control is clicked.
' Notes: Internet Explorer version dependent.
Option Explicit
Private Declare Function EnumThreadWindows Lib "User32.dll" ( _
ByVal dwThreadId As Long, _
ByVal lpfn As Long, _
ByVal lParam As Long _
) As Long
Private Declare Function GetClassName Lib "User32.dll" Alias "GetClassNameW" (ByVal hWnd As Long, ByVal lpClassName As Long, ByVal nMaxCount As Long) As Long
Private Declare Function GetWindowText Lib "User32.dll" Alias "GetWindowTextW" (ByVal hWnd As Long, ByVal lpString As Long, ByVal nMaxCount As Long) As Long
Private Declare Function IsWindowVisible Lib "User32.dll" (ByVal hWnd As Long) As Long
Private Const APITRUE As Long = 1 ' Win32 API TRUE value
Private Const APIFALSE As Long = 0 ' Win32 API FALSE value
' This dialogue class is pretty universal.
Private Const m_ksDialogueClass As String = "#32770"
Private Const m_knDialogueClassLen As Long = 6
' This text may well change every time the browser is updated.
Private Const m_ksDialogueText As String = "Choose File to Upload"
Private Const m_knDialogueTextLen As Long = 21
' Buffers to be used for these strings.
Private m_sClassNameBuffer As String
Private m_sWindowNameBuffer As String
' Callback from the EnumThreadWindow() function.
Private Function EnumThreadWndProc( _
ByVal hWnd As Long, _
ByVal lParam As Long _
) As Long
Dim nRet As Long
' Filter out hidden windows.
If IsWindowVisible(hWnd) = APITRUE Then
' Retrieve the class name of the window.
' Note that this function requires you to allocate a buffer *including* the terminating null character.
' Since VB strings *always* are null terminated, we can add one to the string length.
nRet = GetClassName(hWnd, StrPtr(m_sClassNameBuffer), (m_knDialogueClassLen + 1))
' If the classes match, then try for a match on the window's text.
If m_sClassNameBuffer = m_ksDialogueClass Then
' Ditto GetClassName().
nRet = GetWindowText(hWnd, StrPtr(m_sWindowNameBuffer), (m_knDialogueTextLen + 1))
If m_sWindowNameBuffer = m_ksDialogueText Then
' This return value says "stop the enumeration".
' In this case EnumThreadWindow() with also return APIFALSE.
EnumThreadWndProc = APIFALSE
Exit Function
End If
End If
End If
EnumThreadWndProc = APITRUE
End Function
' Purpose: If the browser window is detected
Public Function IsBrowserFileDialogueVisible() As Boolean
' If this is the first time this function has been called, the buffers will not be allocated.
' Do this now.
If LenB(m_sClassNameBuffer) = 0 Then
m_sClassNameBuffer = Space$(m_knDialogueClassLen)
m_sWindowNameBuffer = Space$(m_knDialogueTextLen)
End If
' Enumerate through all windows on this thread. VB apps are single-threaded, and all GUI elements are forced to be on this thread, so this is ok.
If EnumThreadWindows(App.ThreadID, AddressOf EnumThreadWndProc, 0&) = APIFALSE Then
IsBrowserFileDialogueVisible = True
Else
IsBrowserFileDialogueVisible = False
End If
End Function
Related
I'm attempting to create a VB6 executable (not sure of the proper syntax) that will toggle the X-Mouse option in Tweak UI under Windows 98SE. Ideally, I would like to have two scripts - one that turns it off (regardless of its state) and one that turns it on (again, regardless of its state).
I have been able to open the TweakUI control panel with the code below.
Private Sub Form_Load()
Call Shell("rundll32.exe shell32.dll,Control_RunDLL tweakui.cpl", vbNormalFocus)
End Sub
If possible, I would like it to do it without opening the TweakUI control panel.
As far as I can tell, changing the registry setting doesn't work as I would have to reboot the computer for that to take effect.
I have Registry Monitor 7.04 running. It captures the following:
Path: C:\WINDOWS\RUNDLL32.EXE
Command Line: "C:\WINDOWS\RUNDLL32.EXE" "C:\WINDOWS\SYSTEM\TWEAKUI.CPL", Tweak UI
Other: hKey: 0xC2A066F0
Honestly, I'm not sure how to move forward.
Not sure the best way to show progress on this, I'll just edit.
This code is very close.
Private Declare Function SystemParametersInfo Lib "user32" Alias _
"SystemParametersInfoA" (ByVal uAction As Long, ByVal uParam As Long, _
ByRef lpvParam As Any, ByVal fuWinIni As Long) As Long
Const SPI_SETACTIVEWINDOWTRACKING = 4097
'Click on this button to Activate XMouse
Private Sub Command1_Click()
SystemParametersInfo SPI_SETACTIVEWINDOWTRACKING, 0, True, 0
End Sub
'Click on this button to Deactivate XMouse
Private Sub Command2_Click()
SystemParametersInfo SPI_SETACTIVEWINDOWTRACKING, 0, False, 0
End Sub
Button 1 works correctly and Activates XMouse. But button two does not deactivate it.
SPI_SETACTIVEWINDOWTRACKING is the parameter that does this.
systemparametersinfo is the function call that gets or sets settings like this. See https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-systemparametersinfoa
There is sample code using systemparametersinfo that changes the wallpaper. https://winsourcecode.blogspot.com/2019/06/changewallpaper.html
Thank you to all of the input. I was able to solve this problem.
Private Declare Function SystemParametersInfo Lib "user32" Alias _
"SystemParametersInfoA" (ByVal uAction As Long, ByVal uParam As Long, _
ByVal lpvParam As Boolean, ByVal fuWinIni As Long) As Long
Const SPI_SETACTIVEWINDOWTRACKING = 4097
Private Sub Command1_Click()
retVal = SystemParametersInfo(SPI_SETACTIVEWINDOWTRACKING, 0, True, 0)
End Sub
Private Sub Command2_Click()
retVal = SystemParametersInfo(SPI_SETACTIVEWINDOWTRACKING, 0, False, 0)
End Sub
In addition to the help here, I stumbled upon a few gems that gave me what I needed.
Control the mouse speed under Windows 98 / 2000
and
Controling Active Window Tracking
A couple things of note. I had to include this or else nothing happened:
Const SPI_SETACTIVEWINDOWTRACKING = 4097
Also, the 3rd parameter was
ByRef lpvParam As Boolean
Instead of
ByVal lpvParam As Boolean
I was passing a pointer to a pointer instead of a pointer to a value
i want achieve when shortcut link runs ... the form closes automatically, btw im new to vb coding so any help will be much appreciated, here's my code so far
Private Sub Form_Load()
Set ss = CreateObject("WScript.Shell")
ss.Run Chr(34) & ss.specialfolders("Desktop") & "\app\SOMEGAME.lnk" & Chr(34)
End Sub
Assuming you're using VB6 (which is what your code looks like) you can close your form by calling
Unload Me
at the end of the Form_Load event handler.
However, you don't need to use a form to launch a shortcut - you can add a module to your project (right-click your project, select Add -> Module) and then just call the ShellExecute() function to launch your shortcut like so:
'Declare the ShellExecute() API function
Private Declare Function ShellExecute Lib "shell32.dll" Alias "ShellExecuteA" _
(ByVal hwnd As Long, ByVal lpOperation As String, ByVal lpFile As String, _
ByVal lpParameters As String, ByVal lpDirectory As String, _
ByVal nShowCmd As Long) As Long
Private Const SW_SHOWNORMAL As Long = 1
'Entry point of your program
Public Sub Main()
Dim sPath As String
sPath = "C:\app\SOMEGAME.lnk"
ShellExecute 0, vbNullString, sPath, vbNullString, "C:\", SW_SHOWNORMAL
End Sub
To make this work, set the Startup Object under Project Properties to Sub Main.
Using this approach, you don't have a form - your program just runs from the command-line (or from its own shortcut). It's generally better not to create / show a form if your program doesn't need it since forms use extra resources.
With that said, you should try using VB.Net or C# to write programs for Windows - VB6 is old technology without support and it can't handle a number of new technologies. If you don't already know VB6 there's little point in learning it now - your time could be put to much better use by learning VB.Net / C#.
I need some code that will add the admin rights icon to command buttons and display the prompt when such buttons are clicked. How can I do this in VB6? Some actions require admin rights because they replace files and stuff where Windows Vista/7 don't allow the program normal access to the files.
Here's a VB6 example of ShellExecuteEx that will allow you to optionally execute any process with admin permissions. You can drop this into a module or class.
Option Explicit
Private Const SEE_MASK_DEFAULT = &H0
Public Enum EShellShowConstants
essSW_HIDE = 0
essSW_SHOWNORMAL = 1
essSW_SHOWMINIMIZED = 2
essSW_MAXIMIZE = 3
essSW_SHOWMAXIMIZED = 3
essSW_SHOWNOACTIVATE = 4
essSW_SHOW = 5
essSW_MINIMIZE = 6
essSW_SHOWMINNOACTIVE = 7
essSW_SHOWNA = 8
essSW_RESTORE = 9
essSW_SHOWDEFAULT = 10
End Enum
Private Type SHELLEXECUTEINFO
cbSize As Long
fMask As Long
hwnd As Long
lpVerb As String
lpFile As String
lpParameters As String
lpDirectory As String
nShow As Long
hInstApp As Long
lpIDList As Long 'Optional
lpClass As String 'Optional
hkeyClass As Long 'Optional
dwHotKey As Long 'Optional
hIcon As Long 'Optional
hProcess As Long 'Optional
End Type
Private Declare Function ShellExecuteEx Lib "shell32.dll" Alias "ShellExecuteExA" (lpSEI As SHELLEXECUTEINFO) As Long
Public Function ExecuteProcess(ByVal FilePath As String, ByVal hWndOwner As Long, ShellShowType As EShellShowConstants, Optional EXEParameters As String = "", Optional LaunchElevated As Boolean = False) As Boolean
Dim SEI As SHELLEXECUTEINFO
On Error GoTo Err
'Fill the SEI structure
With SEI
.cbSize = Len(SEI) ' Bytes of the structure
.fMask = SEE_MASK_DEFAULT ' Check MSDN for more info on Mask
.lpFile = FilePath ' Program Path
.nShow = ShellShowType ' How the program will be displayed
.lpDirectory = PathGetFolder(FilePath)
.lpParameters = EXEParameters ' Each parameter must be separated by space. If the lpFile member specifies a document file, lpParameters should be NULL.
.hwnd = hWndOwner ' Owner window handle
' Determine launch type (would recommend checking for Vista or greater here also)
If LaunchElevated = True Then ' And m_OpSys.IsVistaOrGreater = True
.lpVerb = "runas"
Else
.lpVerb = "Open"
End If
End With
ExecuteProcess = ShellExecuteEx(SEI) ' Execute the program, return success or failure
Exit Function
Err:
' TODO: Log Error
ExecuteProcess = False
End Function
Private Function PathGetFolder(psPath As String) As String
On Error Resume Next
Dim lPos As Long
lPos = InStrRev(psPath, "\")
PathGetFolder = Left$(psPath, lPos - 1)
End Function
Code examples can really run on, but here is a trivial one showing the "second instance of me" approach.
The program has a startup static module with a few public functions including an "elevated operation" handler, and a Form with just one CommandButton on it:
Module1.bas
Option Explicit
Private Const BCM_SETSHIELD As Long = &H160C&
Private Declare Sub InitCommonControls Lib "comctl32" ()
Private Declare Function IsUserAnAdmin Lib "shell32" () As Long
Private Declare Function SendMessage Lib "user32" _
Alias "SendMessageA" ( _
ByVal hWnd As Long, _
ByVal wMsg As Long, _
ByVal wParam As Long, _
ByRef lParam As Any) As Long
Private Declare Function ShellExecute Lib "shell32" _
Alias "ShellExecuteA" ( _
ByVal hWnd As Long, _
ByVal lpOperation As String, _
ByVal lpFile As String, _
ByVal lpParameters As String, _
ByVal lpDirectory As String, _
ByVal nShowCmd As VbAppWinStyle) As Long
Private mblnIsElevated As Boolean
Public Function IsElevated() As Boolean
IsElevated = mblnIsElevated
End Function
Public Sub OperationRequiringElevation(ByRef Params As Variant)
MsgBox "Insert logic here for: " & vbNewLine _
& Join(Params, vbNewLine)
End Sub
Public Sub RequestOperation( _
ByVal hWnd As Long, _
ByVal Focus As VbAppWinStyle, _
ByRef Params As Variant)
ShellExecute hWnd, "runas", App.EXEName & ".exe", _
Join(Params, " "), CurDir$(), Focus
End Sub
Public Sub SetShield(ByVal hWnd As Long)
SendMessage hWnd, BCM_SETSHIELD, 0&, 1&
End Sub
Private Sub Main()
If Len(Command$()) > 0 Then
'Assume we've been run elevated to execute an operation
'specified as a set of space-delimited strings.
OperationRequiringElevation Split(Command$(), " ")
Else
mblnIsElevated = IsUserAnAdmin()
InitCommonControls
Form1.Show
End If
End Sub
Form1.frm
Option Explicit
Private Sub Command1_Click()
Dim Params As Variant
Params = Array("ReplaceFile", "abc", "123")
If IsElevated() Then
OperationRequiringElevation Params
Else
RequestOperation hWnd, vbHide, Params
End If
End Sub
Private Sub Form_Load()
If Not IsElevated() Then
SetShield Command1.hWnd
End If
End Sub
The application has a simple "asInvoker" manifest selecting the Common Controls 6.0 assembly.
First, take the code that runs when someone clicks the button, and put it in a separate exe. Change your button-click code to launch the exe using ShellExecute. Second, build external manifests for each new exe and have it specify requireAdministrator. Third, send your buttons the BCM_SETSHIELD message (you will probably have to look up the numerical value of the message ID) to make the shield appear on them.
Move all of the code that requires elevation into external processes.
Send your buttons the BCM_SETSHIELD message to add the shield icon.
Embed manifests into those processes, telling Windows that they require elevation. See below.
In order to force Vista and higher to run a VB6 exe as administrator in UAC, you must embed a manifest xml as a resource inside of it. Steps follow;
Create the manifest file. Name it "YourProgram.exe.manifest" it should contain the following. The important line is the "requestedExecutionLevel". Change the attributes in to match your exe.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity
version="1.0.0.0"
processorArchitecture="X86"
name="YourProgram"
type="win32"
>
<description>application description</description>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security
<requestedPrivileges>
<requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>
</requestedPrivileges>
</security>
</trustInfo>
</assembly>
Create a file named "YourProgram.exe.manifest.rc". It should contain the following.
#define CREATEPROCESS_MANIFEST_RESOURCE_ID 1
#define RT_MANIFEST 24 CREATEPROCESS_MANIFEST_RESOURCE_ID
RT_MANIFEST "YourProgram.exe.manifest"
Compile your resource using rc.exe. It is located by default in C:\Program Files\Microsoft Visual Studio\COMMON\MSDev98\Bin. This will create a file called YourProgram.exe.manifest.RES. The syntax is;
rc /r YourProgram.exe.manifest.rc
Add the .RES file to your project. Do this using the Resource Editor Add-In in VB6. The icon on the toolbar looks like green blocks. If you do not have the icon, make sure it is enabled in the addin manager. If it is not in the addin manager, you need to regsvr32 on C:\Program Files\Microsoft Visual Studio\VB98\Wizards\Resedit.dll. Once you've got the resource editor open, click open and select your .RES file.
Compile your project.
To double check that the manifest was embedded properly, you can use a tool called InspectExe. In explorer, go to the properties of the exe, and if the manifest was embedded you should have a manifest tab (.Net assemblies will also have this manifest tab).
Try running your program on Vista or later. If UAC is indeed enabled, it should come up with the prompt right away.
I am trying to set a custom paper size by doing:
Printer.Height = 2160
Printer.Width = 11900
But it doesn't seen to have any effect. After setting this up, i ask for that values and it returns the default ones. And this:
Printer.PaperSize = 256
Returns an error...
Any ideas??
Either your printer doesn't allow these properties to be set, or you're exceeding their maximum allowed values. From the Visual Basic Reference
If you set the Height and Width
properties for a printer driver that
doesn't allow these properties to be
set, no error occurs and the size of
the paper remains as it was. If you
set Height and Width for a printer
driver that allows only certain values
to be specified, no error occurs and
the property is set to whatever the
driver allows. For example, you could
set Height to 150 and the driver would
set it to 144.
I don't know why you're getting an error when you set the Papersize property to 256. It works for me. Also, the documentation states, "Setting a printer's Height or Width property automatically sets PaperSize to vbPRPSUser.", which equals 256.
I was actually involved with the same problem but I just happen to find a breakthrough.
First you need to create a custom form that defines you custom paper size. Then, you need to
refer to Windows API to check the form name you've just created. You'll get the for name
from an array returned from a function and use the array index where the form name was found.
Finally use it as the value for printer.papersize
Example below:
Public Type PRINTER_DEFAULTS
pDatatype As Long
pDevMode As Long
DesiredAccess As Long
End Type
Public Type FORM_INFO_1
Flags As Long
pName As Long ' String
Size As SIZEL
ImageableArea As RECTL
End Type
Public Declare Function EnumForms Lib "winspool.drv" Alias "EnumFormsA" _
(ByVal hPrinter As Long, ByVal Level As Long, ByRef pForm As Any, _
ByVal cbBuf As Long, ByRef pcbNeeded As Long, _
ByRef pcReturned As Long) As Long
Public Declare Sub CopyMemory Lib "KERNEL32" Alias "RtlMoveMemory" _
(pDest As Any, pSource As Any, ByVal cbLength As Long)
Public Declare Sub Sleep Lib "KERNEL32" (ByVal dwMilliseconds As Long)
Public Declare Function OpenPrinter Lib "winspool.drv" Alias _
"OpenPrinterA" (ByVal pPrinterName As String, phPrinter As Long, _
pDefault As PRINTER_DEFAULTS) As Long
Public Declare Function ClosePrinter Lib "winspool.drv" _
(ByVal hPrinter As Long) As Long
Public Declare Function lstrcpy Lib "KERNEL32" Alias "lstrcpyA" _
(ByVal lpString1 As String, ByRef lpString2 As Long) As Long
'UDF
Public Function PtrCtoVbString(ByVal Add As Long) As String
Dim sTemp As String * 512, x As Long
x = lstrcpy(sTemp, ByVal Add)
If (InStr(1, sTemp, Chr(0)) = 0) Then
PtrCtoVbString = ""
Else
PtrCtoVbString = Left(sTemp, InStr(1, sTemp, Chr(0)) - 1)
End If
End Function
Public Function IsFormExist(ByVal DeviceName As String, ByVal isFormName As String, ByVal PrinterHandle As Long) As Long
Dim NumForms As Long, i As Long
Dim FI1 As FORM_INFO_1
Dim pd As PRINTER_DEFAULTS
Dim aFI1() As FORM_INFO_1 ' Working FI1 array
Dim Temp() As Byte ' Temp FI1 array
Dim FormIndex As Integer
Dim BytesNeeded As Long
Dim RetVal As Long
On Error GoTo cleanup
FormIndex = 0
ReDim aFI1(1)
' First call retrieves the BytesNeeded.
RetVal = OpenPrinter(DeviceName, PrinterHandle, pd)
If (RetVal = 0) Or (PrinterHandle = 0) Then
'Can't access current printer. Bail out doing nothing
Exit Function
End If
RetVal = EnumForms(PrinterHandle, 1, aFI1(0), 0&, BytesNeeded, NumForms)
ReDim Temp(BytesNeeded)
ReDim aFI1(BytesNeeded / Len(FI1))
' Second call actually enumerates the supported forms.
RetVal = EnumForms(PrinterHandle, 1, Temp(0), BytesNeeded, BytesNeeded, _
NumForms)
Call CopyMemory(aFI1(0), Temp(0), BytesNeeded)
For i = 0 To NumForms - 1
With aFI1(i)
If isFormName = PtrCtoVbString(.pName) Then
' Found the desired form
FormIndex = i + 1
Exit For
End If
End With
Next i
IsFormExist = FormIndex ' Returns the number when form is found.
cleanup:
'Release the printer handle
If (PrinterHandle <> 0) Then Call ClosePrinter(PrinterHandle)
End Function
'Here We Go
dim papercode as long, printername as string, formname as string
printername=printer.Devicename
formname = "myform"
papercode=IsFormExist(printername, formname, Printer.hdc)
if papercode<>0 then
printer.papersize=papercode
end if
Give it a try, good luck
Are you sure the error isn't related to the maximum print width of the printer itself? Many printers have a max print width of 8.25" (11880) to allow 1/4" margins on either side of a 8.5" wide paper.
Quickest way to check would be to simply set the print wide to 11880 or lower and see if it works.
Another possibility would be permissions to the printer. If it's a shared network resource it may be locked down.
The solution is to use windows 98. It does not work with win2k, neither winXP. The same code, the same printer.
Regards.
I'm testing this code, but I can not see the custom form I created using printers and scanners in the Control Panel Windows XP Professional SP3.
Note: I could check in regedit that this form exists and its ID is 512 in a string value and it contains the name of the form created in the printers control panel.
Why this function does not return my custom form, I am using an HP Laserjet 1020.
I am trying to check to see whether another instance of the application is already running. If it does, I want to keep on checking for another 15 seconds or so before going on...
if App.PrevInstance then
dim dtStart as date
dtStart = now
do while datediff("s", dtStart, Now) < 15
Sleep 1000 ' sleep for a second
if not App.PrevInstance then exit do
loop
end if
The problem is App.PrevInstance does not seem to refresh itself. it keeps the initial value no matter what.
Is there another way to approach this? Perhaps with API calls. Note that the application may or may not have a window, thus I can't check for an existence of a window with a certain caption.
You might want to give this a look: http://www.codeguru.com/forum/showthread.php?t=293730
I use the following class:
'--------------------------------------------------------------------------------------- ' Module : CApplicationSingleton ' DateTime : 24/03/2006 15:16 ' Author : Fernando ' Purpose : Enforces a single instance of an application. Uses a Mutex ' see http://www.codeguru.com/forum/showthread.php?s=&threadid=293730 ' http://www.codeguru.com/Cpp/W-P/system/processesmodules/article.php/c5745/ ' Copyright © 2001-2007 AGBO Business Architecture S.L. '---------------------------------------------------------------------------------------
Option Explicit
Private Declare Function CreateMutex Lib "kernel32" Alias "CreateMutexA" (lpMutexAttributes As SECURITY_ATTRIBUTES, ByVal bInitialOwner As Long, ByVal lpName As String) As Long Private Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
Private Type SECURITY_ATTRIBUTES
nLength As Long
lpSecurityDescriptor As Long
bInheritHandle As Long
End Type
Private Const ERROR_ALREADY_EXISTS = 183&
Private m_hMutex As Long Private m_lLastDllError As Long
Private Sub Class_Initialize() '
On Error GoTo errorBlock
'
Dim s As SECURITY_ATTRIBUTES
m_hMutex = CreateMutex(s, 0, rcString(8700)) m_lLastDllError = Err.LastDllError
'
exitBlock:
Exit Sub
errorBlock:
Call GError.handleError(Err.Number, Err.Description, Erl, "CApplicationSingleton", "Class_Initialize", GApp.copyDebugFiles())
Resume exitBlock
' End Sub
Private Sub Class_Terminate() On Error GoTo errorBlock
If m_hMutex > 0 Then
Call CloseHandle(m_hMutex) End If
exitBlock:
Exit Sub
errorBlock:
Call GError.handleError(Err.Number, Err.Description, Erl, "CApplicationSingleton.cls", "Class_Terminate")
Resume exitBlock
End Sub
Public Function IsAnotherInstanceRunning() As Boolean '
On Error GoTo errorBlock
'
IsAnotherInstanceRunning = (m_lLastDllError = ERROR_ALREADY_EXISTS)
'
exitBlock:
Exit Function
errorBlock:
Call GError.handleError(Err.Number, Err.Description, Erl, "CApplicationSingleton", "IsAnotherInstanceRunning", GApp.copyDebugFiles())
Resume exitBlock
' End Function
I used the Mutex class to work out the same issue with starting the same app multiple times. It appeared to be working then stopped working returning a false positive. What I found is that the vb6 IDE was also holding a mutex while the IDE was still open.
You've gotta use the code and compile it. The EXE will work fine after you close the IDE.. Who knew? Drove me crazy(ier) for a few minutes..
I'll post a sample if anyone wants it.