Quantcast
Channel: VBForums - CodeBank - Visual Basic 6 and earlier
Viewing all 1321 articles
Browse latest View live

LynxGrid..Modifications

$
0
0
This is a 'rar' file...I just added a '.zip' extension.

The modified LynxGrid that I abandoned for vhGrid might be useful to someone.

Note: the coding is very ugly or basic.

LucasMKG
saying Hala to Jonney and fafalone...for now...:D
Attached Files

Is there any way to control with references on any devices without missing error

$
0
0
Hi guys, i'm back to help me to solve this small problem (if could)

my problem not in coding
the problem when i try to run my (*.exe) file on other device, there's error shows like (*.dll) not found...

So, Is there any way to put the references file in "project.exe" without need to put the references on the new devices at every time ?

and thanks...

EXtract Gray Level Coocurence Matrix (Haralick Texture)

$
0
0
Re-Implementation of Haralick Texture Wrotten in Visual Basic Classic

for general information, haralick texture is algorithm to extract textural feature of an image. your can read more about haralick texture in here

Actually this is re-implmentation some-part of my bachelor thesis.

I re-wrote the GLCM texture from java here :

http://rsb.info.nih.gov/ij/plugins/d...M_Texture.java

Glad help ! ^^

if there any correction, or revision please tell, or just come to my github :

https://github.com/noxymon/GLCMVB6
Attached Files

Vibian - The Mini VB6 Framework

$
0
0
Good day:

I found a library which can compete with .net framework. Its was Vibian a single DLL but rich in VB6 functions. Please help them to improve this code and library it has a big potential.

Here is the statement from the author according to PSC website:

It is a simple but powerful COM library for VB6 which emulates/bring some VB.NET functions but utilizing Windows API, built in VB6 functions, and WMI instead of CLR or using .NET Framework. And its also makes VB6 programming more covenient.

This library has the ability to access the following:
* Network
* Power Management
* File System
* Registry
* Operating System Operations
* Advanced Math functions and Math constants
* Cryptography
* Windows Firewall
* Windows Services
* XML Parser
* INI File Operation
* ZIP File Operation
* Additional VB6 functions

It also emulates the "My" Keyword on VB.NET.

Notice: I do not own the whole code. Some parts of the code are owned by the respective authors. Developers who want to Improve the code of this library are welcome. Just upload the improved source code on PSC and please avoid changing the function names or delete the existing functions, enums, sub/function arguments, and constants in order to maintain the standards and to prevent messing the developers. You are allow to add functions or arguments (avoid deleting or altering the existing argument order, append optional if making additional argument in an existing fuction) on this library.


Available classes on this library file:
* StdRegistry (Registry)
* OSinfo (OS info)
* StdProcessor (Processor)
* StdInfo (PC Info)
* DiskInfo (Disk Drive Info)
* SpecialDirectory (Windows Special Directories)
* Math2 (Additional Math function)
* StdFileSystem (File System)
* MathConstant (Additional Math constants)
* StdNetwork (Network)
* StdPowerSource (Power Source)
* StdSystemAction (Computer Action)
* StdSettings (Manage App Settings)
* StdTaskbar (Taskbar Info)
* StdServices (Windows Services)
* StdSoundVolume (Sound Volume)
* TextFile (Text File Access)
* VB6Extension (Additional VB6 functions)
* StdComputer (Computer class)
* My ("My" keyword)
* CpuUsageObject (CPU usage, Must be initialize when the app starts. Dont declare it inside the timer events)
* CDPlayerAdvanced (Advanced CD Player)
* CompactDiscPlayerBasic (Basic CD Player)
* StdEnigma (Enigma Ecryption)
* SoundRecorder (Record Sound)
* StdFirewall (Windows Firewall)
* StdBase64 (Base 64 Encoding/decoding)
* StdCryptography (Cryptograpghy class)
* StdMD5 (MD5)
* StdSHA (SHA)
* StdCRC32 (CRC32)
* StdHTTP (HTTP)
* StdFTP (FTP)
* StdSerial (Serial Comm)
* StdDrives (Disk Drives)
* StdTimer (Timer)
* StdDateTime (Addition Date and Time functions)
* StdIniFile (Ini file)
* XMLDocumentFile (XML Parser)
* StdMouse (Mouse)
* ZipObject (Zip Files)
* StdKeyboard (Keyboard)

[VB6] Check If a Window is on the Desktop

$
0
0
If you save your window position when your application exits, it's good to check to make sure that the monitor it was on is still there and is the same size it used to be when you start up. This code takes in an hWnd and returns true if the window is entirely inside the user's desktop area, allowing you to decide if you need to move the window or not.

This must be placed in a module to work.

Code:

Option Explicit

Private Type Rect
  Left    As Long
  Top    As Long
  Right  As Long
  Bottom  As Long
End Type

Private Declare Function EnumDisplayMonitors Lib "user32" (ByVal hDC As Long, ByVal lprcClip As Long, ByVal lpfnEnum As Long, dwData As Long) As Long
Private Declare Function CombineRgn Lib "gdi32.dll" (ByVal hDestRgn As Long, ByVal hSrcRgn1 As Long, ByVal hSrcRgn2 As Long, ByVal nCombineMode As Long) As Long
Private Declare Function CreateRectRgnIndirect Lib "gdi32.dll" (ByRef lpRect As Rect) As Long
Private Declare Function GetWindowRect Lib "user32.dll" (ByVal hWnd As Long, ByRef lpRect As Rect) As Long
Private Declare Function DeleteObject Lib "gdi32.dll" (ByVal hObject As Long) As Long

Private Const API_TRUE = 1
Private Const RGN_OR As Long = 2
Private Const RGN_DIFF As Long = 4
Private Const NULLREGION As Long = 1

Private Function MonitorEnumProc(ByVal lngMonitorHandle As Long, _
                                ByVal lngMonitorHDC As Long, _
                                ByRef typWorkingScreen As Rect, _
                                ByRef lngDesktopRegionHandle As Long) As Long
   
    Dim lngWorkingScreenRegion As Long
   
    ' Make the screen's rect into a region
    lngWorkingScreenRegion = CreateRectRgnIndirect(typWorkingScreen)
   
    ' Combine it with all the desktops so far.
    CombineRgn lngDesktopRegionHandle, lngWorkingScreenRegion, lngDesktopRegionHandle, RGN_OR
       
    ' Dispose of the screen's region
    DeleteObject lngWorkingScreenRegion
       
    ' Proceeed to the next screen
    MonitorEnumProc = API_TRUE

End Function

Public Function hWndIsInDesktopRegion(ByVal hWnd As Long) As Boolean
   
    Dim typRect As Rect
    Dim lngSrcRegionHandle As Long
    Dim lngDesktopRegionHandle As Long
   
    ' Create an empty region that will be used to combine the desktop regions into
    ' typRect is empty at init per VB convention
    lngDesktopRegionHandle = CreateRectRgnIndirect(typRect)
   
    ' Create a region that is the window we are interested in
    GetWindowRect hWnd, typRect
    lngSrcRegionHandle = CreateRectRgnIndirect(typRect)
       
    ' Enum the monitors to create the desktop region.
    Call EnumDisplayMonitors(0&, 0&, AddressOf MonitorEnumProc, lngDesktopRegionHandle)
   
    ' Test to see if our region is in the combined region
    If CombineRgn(lngDesktopRegionHandle, lngSrcRegionHandle, lngDesktopRegionHandle, RGN_DIFF) = NULLREGION Then
        hWndIsInDesktopRegion = True
    Else
        hWndIsInDesktopRegion = False
    End If
       
    ' Dispose of the local and desktop regions
    DeleteObject lngSrcRegionHandle
    DeleteObject lngDesktopRegionHandle
   
End Function

VB6 Simple View-SQL-Editor for JET-mdb's

$
0
0
Just a little Demo (80 lines of code) which could easily be enhanced (with a little love) -
to real "Tool-Status".

For example when a "full Access-Installation" is not given on some machine or something - or when
you're tired to switch between Access' "SQL-View" and "Table-View", just to see results of your View-Defs
(or simply want to prevent all this "auto-intelligence, with an abundance of parentheses", Access will
happily throw into your hand-edited View-SQL anytime it feels in the mood...). ;)

The demo reads the current Views (and Tables) from any given JET-mdb (the Zip contains a small "BookStore" like one)...
and then displays the current SQL of existing Views, as well as the results this View-SQL will produce (in a DataGrid).

Here's the Demo-Zip: ViewEditor.zip

And a ScreenShot:



Olaf
Attached Files

VB6 StarRating-Control (cairo-Rendering)

$
0
0
This is an implementation, based on Vector-Drawing (with the cairo-Wrapper-Classes from vbRichClient5).
No (PNG- or other) Images were used - just plain Drawing-Commands (to gain more flexibility with regards to
the Stars Shape - and to easier allow for different Base-Colors to fill the interior of the given Star-DrawingPath).

A normal VB6-UserControl is used as the Host for these Drawings - fully transparent and alpha-aware.

The Star-Rendering will be smooth and antialiased, even when the Controls are resized
(to behave properly in DPI-aware Apps).

The MinHeight of the Control is 16-, its MaxHeight 56-Pixels.

The ScreenShot below shows, how the rendering behaves with different Sizes (and Colors).

The Value of the Control can be set also per Mouse-Interaction (Drag- or Click) - and
will (in Drag-Mode) show a darker "Hover-Overlay" (so the older Value can be seen for comparison,
until the Mouse-Button is released).

Here's the Source-Zip: StarRating2.zip
(updated with a fix for: "Allow Zero-Detection whilst clicking outside the first Star")

New version - (containing the fix above, but also a new modRC5Regfree.bas, which when included
into a Project, will ensure regfree-loading of the RC5-Main-Classes automatically - when:
- your App will run from a compiled Executable ... and
- when a \Bin\-SubFolder exists below your App.Path, which contains copies of the 3 RC5 BaseDlls
Version 3: StarRating3.zip

And here a ScreenShot:



Olaf
Attached Files

[VB6] IcoWriter - Yet another "hIcon to array/file saver"

$
0
0
IcoWriter is a VB6 class that can be used to save hIcon handles into ICO format. it can provide them as a Byte array or write them to a disk file you provide a name for.

Details

Unlike some sample code IcoWriter will save multiple images of different dimensions and color depths as a single ICO.

It does not handle "Vista" icon images (256x256 PNG images) but it handles most square and rectangular images below 256 pixels in both dimensions. I don't recommend it for anything larger than 48x48 though. Color depths supported are 1-bpp, 4-bpp, 8-bpp, 24-bpp, and 32-bpp with alpha channel.

You might obtain hIcon handles in several different ways in a VB6 program. IcoWriter is offered here embedded in a demo application that uses several of these.

It is also possible to load other bitmap images formats and use them via an ImageList control. The demo does this with PNG source images, though as written that requires WIA 2.0. You could modify the demo code to use GDI+ directly too.

IcoWriter takes a stab (ok, multiple stabs) at handling color depth reduction to get somewhat better results than it might otherwise produce. However "garbage in, garbage out" and so this isn't perfect. Especially for creating 1-bpp images out of 24-bpp source images!

A proper color quantization algorithm might improve on IcoWriter's current results. But I'm no Graphics Guru so I'll live with what I have for now. ;)


Requirements

VB6 to run or compile the demo of course.

Windows XP or later to work with 32-bpp alpha channel ("XP") icon images.

WIA 2.0 (included with Vista and later, can be installed into Windows XP SP 1 or later). This is just for the demo, IcoWriter does not use WIA.


Testing the demo

If you have a PC meeting the requirements, everything should be there. Just unzip the attachment into a folder and open it in VB6 by "double clicking" on the .VBP file. Then you should be able to run it. Do people still have Explorer set to double-click mode... in 2015?

Check the created Saved folder for the results. These are best examined using IcoFX or another decent icon editor.

The attachment's size is largely made up of source images.
Attached Files

[VB6] Basic unzip without 3rd party DLL or shell32- IStorage-based

$
0
0
So the only other method I've really seen to extract zip archives without shell32 or a 3rd party DLL is a full implementation of the ZIP algorithm, and while this isn't exactly a lightweight method, it's not nearly as complex as that was with all its class modules. As I've mentioned a few times, I'm definitely not a fan of the shell32 object, and I came across an unzip method using things I do like: shell interfaces. Thanks to low-level Windows ZIP integration, it's possible to extract the contents of a simple ZIP archive (doesn't support password-protected zips for example) using IStorage, IStream, and some API.

Requirements
A type library with IStorage and IStream is required, and I strongly recommend using oleexp for future compability (get it here)- any version is fine, there's no new version like new examples usually need; and the sample project is written for that. However, if you change a couple 'oleexp3.x' declares, the original olelib is supported (for the sample project, you'll need a new way of selecting the zip file too since it's using FileOpenDialog).

This method is compatible with Windows XP and higher, but note the sample project for simplicity has a Vista+ FileOpen

Code
Below is a free-standing module you can use without anything else in the demo project (besides oleexp or olelib with changes):

Code:

Option Explicit

Public Declare Function ILCreateFromPathW Lib "shell32" (ByVal pwszPath As Long) As Long
Public Declare Sub ILFree Lib "shell32" (ByVal pidl As Long)
Public Declare Function SHBindToParent Lib "shell32" (ByVal pidl As Long, riid As UUID, ppv As Any, pidlLast As Long) As Long
Public Declare Function SHCreateStreamOnFileEx Lib "shlwapi" (ByVal pszFile As Long, ByVal grfMode As STGM, ByVal dwAttributes As FILE_ATTRIBUTES, ByVal fCreate As Long, ByVal pstmTemplate As Long, ppstm As oleexp3.IStream) As Long
Public Declare Function PathFileExistsW Lib "shlwapi" (ByVal lpszPath As Long) As Long
Public Declare Function CreateDirectoryW Lib "kernel32" (ByVal lpPathName As Long, ByVal lpSecurityAttributes As Any) As Long
Public Declare Function SysReAllocString Lib "oleaut32.dll" (ByVal pBSTR As Long, Optional ByVal pszStrPtr As Long) As Long
Public Declare Sub CoTaskMemFree Lib "ole32.dll" (ByVal PV As Long) ' Frees memory allocated by the shell
Public Const NOERROR = 0&
Public Const FILE_ATTRIBUTE_NORMAL = &H80

Public Sub UnzipFile(sFile As String, Optional ByVal sTo As String = "")
'unzip without 3rd party dll
Dim psfParent As oleexp3.IShellFolder
Dim pidlFQ As Long
Dim pidlChild As Long
Dim pszDest As String

If sTo = "" Then
    'defaults to create a folder with the zip's name in the same folder as the zip
    pszDest = sFile
    pszDest = Left$(pszDest, Len(pszDest) - 4) 'remove .zip
Else
    pszDest = sTo
End If

'First, we need the parent pidl, child pidl, and IShellFolder
'These are all references to the file very common in shell programming
pidlFQ = ILCreateFromPathW(StrPtr(sFile))
Call SHBindToParent(pidlFQ, IID_IShellFolder, psfParent, pidlChild)
If (psfParent Is Nothing) Or (pidlChild = 0) Then
    Debug.Print "UnzipFile.Failed to bind to file"
    Exit Sub
End If

'Now that we have the IShellFolder, we want the IStorage object
'That is what we'll be able to extract from, thanks to the
'very low level system zip integration with zipfldr.dll
Dim pStg As oleexp3.IStorage
psfParent.BindToObject pidlChild, 0, IID_IStorage, pStg
If (pStg Is Nothing) Then
    Debug.Print "UnzipFile.Failed to bind to storage"
    Exit Sub
End If
Debug.Print "UnzipFile.extract to " & pszDest

StgExtract pStg, pszDest

Set pStg = Nothing
Set psfParent = Nothing
ILFree pidlFQ


End Sub
Private Sub StgExtract(pStg As oleexp3.IStorage, pszTargetDir As String, Optional fOverwrite As Long = 0)
'This function is recursively called to extract zipped files and folders

'First, create the target directory (even if you're extracting to an existing folder, it create subfolders from the zip)
If (PathFileExistsW(StrPtr(pszTargetDir)) = 0) Then
    Call CreateDirectoryW(StrPtr(pszTargetDir), ByVal 0&)
    If (PathFileExistsW(StrPtr(pszTargetDir)) = 0) Then
        Debug.Print "StgExtract.Failed to create directory " & pszTargetDir
        Exit Sub
    End If
End If

'The enumerator will loop through each storage object
'Here, that will be zipped files and folders
Dim pEnum As IEnumSTATSTG
Set pEnum = pStg.EnumElements(0, 0, 0)
If (pEnum Is Nothing) Then
    Debug.Print "StgExtract.pEnum==nothing"
    Exit Sub
End If

Dim celtFetched As Long
Dim stat As STATSTG
Dim pszPath As String

    Do While (pEnum.Next(1, stat, celtFetched) = NOERROR)
        pszPath = SysAllocString(stat.pwcsName) 'contains a file name
'        Debug.Print "pszPath on alloc=" & pszPath
        If (Len(pszPath) > 1) Then
            pszPath = AddBackslash(pszTargetDir) & pszPath 'combine that with the path (recursive, so can be zipped folder path)
'            Debug.Print "pszPath on combine=" & pszPath
            If stat.Type = STGTY_STORAGE Then 'subfolder
                Dim pStgSubfolder As oleexp3.IStorage
                Set pStgSubfolder = pStg.OpenStorage(SysAllocString(stat.pwcsName), 0, STGM_READ, 0, 0)
                If (pStgSubfolder Is Nothing) Then
                    Debug.Print "StgExtract.pstgsubfolder==nothing"
                    Exit Sub
                End If
                StgExtract pStgSubfolder, pszPath 'and if there's more subfolders, we'll go deeper
            ElseIf stat.Type = STGTY_STREAM Then 'file
                'the basic idea here is that we obtain an IStream representing the existing file,
                'and an IStream representing the new extracted file, and copy the contents into the new file
                Dim pStrm As oleexp3.IStream
                Set pStrm = pStg.OpenStream(SysAllocString(stat.pwcsName), 0, STGM_READ, 0)
                Dim pStrmFile As oleexp3.IStream
               
                'here we add an option to not overwrite existing files; but the default is to overwrite
                'set fOverwrite to anything non-zero and the file is skipped
                'If we are extracting it, we call an API to create a new file with an IStream to write to it
                If PathFileExistsW(StrPtr(pszPath)) Then
                    If fOverwrite Then
                        Call SHCreateStreamOnFileEx(StrPtr(pszPath), STGM_CREATE Or STGM_WRITE, FILE_ATTRIBUTE_NORMAL, 1, 0, pStrmFile)
                    End If
                Else
                    Call SHCreateStreamOnFileEx(StrPtr(pszPath), STGM_CREATE Or STGM_WRITE, FILE_ATTRIBUTE_NORMAL, 1, 0, pStrmFile)
                End If
                If (pStrmFile Is Nothing) = False Then
                    'Debug.Print "StgExtract.Got pstrmfile"
                    Dim cbSize As Currency 'the STATSTG cbSize is ULONGLONG (equiv. to Currency), so files >2GB should be fine
                    pStrm.CopyTo pStrmFile, stat.cbSize, 0, cbSize
                    Set pStrmFile = Nothing
                    'Debug.Print "StgExtract.bytes written=" & CStr(cbSize)
                Else
                    'either an error or skipped an existing file; either way we don't exit, we'll move on to the next
                    'Debug.Print "StgExtract.pstrmfile==nothing"
                End If
                Set pStrm = Nothing
            End If
        End If
        pszPath = ""
        Call CoTaskMemFree(stat.pwcsName) 'this memory needs to be freed, otherwise you'll leak memory
    Loop
   
    Set pEnum = Nothing
   

End Sub
Public Function LPWSTRtoStr(lPtr As Long, Optional ByVal fFree As Boolean = True) As String
SysReAllocString VarPtr(LPWSTRtoStr), lPtr
If fFree Then
    Call CoTaskMemFree(lPtr)
End If
End Function
Public Function AddBackslash(s As String) As String

  If Len(s) > 0 Then
      If Right$(s, 1) <> "\" Then
        AddBackslash = s & "\"
      Else
        AddBackslash = s
      End If
  Else
      AddBackslash = "\"
  End If

End Function

Public Function IID_IStorage() As UUID
'({0000000B-0000-0000-C000-000000000046})
Static iid As UUID
 If (iid.Data1 = 0) Then Call DEFINE_OLEGUID(iid, &HB, 0, 0)
 IID_IStorage = iid
End Function

'-----------------------------------------------------------
'Below this is not needed if you're using mIID.bas
'(check if the above IID_IStorage exists or not, because this was released before the update that included it)
'-----------------------------------------------------------
Public Sub DEFINE_UUID(Name As UUID, L As Long, w1 As Integer, w2 As Integer, B0 As Byte, b1 As Byte, b2 As Byte, B3 As Byte, b4 As Byte, b5 As Byte, b6 As Byte, b7 As Byte)
  With Name
    .Data1 = L
    .Data2 = w1
    .Data3 = w2
    .Data4(0) = B0
    .Data4(1) = b1
    .Data4(2) = b2
    .Data4(3) = B3
    .Data4(4) = b4
    .Data4(5) = b5
    .Data4(6) = b6
    .Data4(7) = b7
  End With
End Sub
Public Sub DEFINE_OLEGUID(Name As UUID, L As Long, w1 As Integer, w2 As Integer)
  DEFINE_UUID Name, L, w1, w2, &HC0, 0, 0, 0, 0, 0, 0, &H46
End Sub
Public Function IID_IShellFolder() As UUID
  Static iid As UUID
  If (iid.Data1 = 0) Then Call DEFINE_OLEGUID(iid, &H214E6, 0, 0)
  IID_IShellFolder = iid
End Function

If anyone knows how I could add password support or create a zip file, definitely post ideas in the comments as I'll be working on it.

Thanks
This code is based on code using this method in C by sapero, found here.

------------------
Note: The file I uploaded was named UnzipNew.zip, I have no idea why VBForums keeps renaming it to U.zip. Have tried removing and reattaching several times.
Attached Files

[VB6] Code Snippet: Get/set/del file zone identifier (Run file from internet? source)

$
0
0
So I'm sure everyone has been asked by Explorer whether or not they want to open a file they downloaded from the internet. But how does Explorer know whether or not to ask this question? This information is recorded in an Alternative Data Stream, which is a disk level entry that's attached to the file, but not inside the file itself. Think of it like the date stamps on a file- they're not in the file itself right? A blank text file has them. And not in some database or registry, it's low level associated data on the disk itself.

There's several other uses for alternative data streams, and it's possible to read and write to them like a normal file, e.g. Open "C:\file.txt:Zone.Identifier" For... (if you know their name; not easy but covered by Karl Peterson).

But here we don't have to do that. For the specific case of the Zone Identifier, Windows provides an interface that allows for very simple access to read it, change it, or delete it. IZoneIdentifier, with its default implementation PersistentZoneIdentifier, makes this easy.

Requirements
-Version 3.1 of oleexp, released the same time this post was made, 18 Sep 2015; or a more recent version. Add oleexp3.tlb as a Reference to your project. IDE-only, you don't need to include anything when distributing your compiled app.
-Windows XP SP2 or higher
-This only works on NTFS file systems. If your hard drive is formatted as FAT32 or something else, this does not work.

Code
Code:

Public Function GetFileSecurityZone(sFile As String) As URLZONE
'returns the Zone Identifier of a file, using IZoneIdentifier
'This could also be done by ready the Zone.Identifier alternate
'data stream directly; readfile C:\file.txt:Zone.Identifier

Dim lz As Long
Dim pZI As PersistentZoneIdentifier
Set pZI = New PersistentZoneIdentifier

Dim pIPF As IPersistFile
Set pIPF = pZI

pIPF.Load sFile, STGM_READ
pZI.GetId lz
GetFileSecurityZone = lz

Set pIPF = Nothing
Set pZI = Nothing

End Function

Public Sub SetFileSecurityZone(sFile As String, nZone As URLZONE)
'As suggested in the enum, you technically can set it to custom values
'If you do, they should be between 1000 and 10000.
Dim pZI As PersistentZoneIdentifier
Set pZI = New PersistentZoneIdentifier

pZI.SetId nZone
Dim pIPF As IPersistFile
Set pIPF = pZI
pIPF.Save sFile, 1

Set pIPF = Nothing
Set pZI = Nothing

End Sub

Public Sub RemoveFileSecurityZone(sFile As String)
Dim pZI As PersistentZoneIdentifier
Set pZI = New PersistentZoneIdentifier

pZI.Remove
Dim pIPF As IPersistFile
Set pIPF = pZI
pIPF.Save sFile, 1

Set pIPF = Nothing
Set pZI = Nothing
End Sub

It's that simple. No other code needed.

Now you can go get rid of those prompts for your downloads, or find files that were downloaded.

Thanks
Credit all goes to Raymond Chen at Old New Thing for demonstrating this technique.

VB6 ViewPort-Handling per cairo

$
0
0
A simple Demo, how to work with a larger Image in conjunction with a smaller ViewPort, using
Cairo-ImageSurfaces and their Transformation-methods (Translate, Scale, Rotate),
to make this task a quite easy one...

The code of that Demo is sitting in one single VB-Form only, since it is not very large.

The left Picturebox will support "shifting the original Image per Mouse-Dragging" -
and the two ComboBoxes above it, allow for Rotation and Zooming of that Image.

The semitransparent Selection-Box is adjustable at its four sides per Mouse-Dragging as well,
and the area which this Selection-Box currently covers, is then rendered in the PicBox to the right
(respecting the current Aspect-Ratio of the Selection).

The Main-Window is adjusting the two PicBoxes (picSrc and picDst) dynamically, when it is resized.

The Demo requires the cairo-wrapper-classes from vbRichClient5 (just google for it)...
Here's the SourceCode (containing a TestImage): ViewPortHandlingCairo.zip

And here a ScreenShot:




Olaf
Attached Files

[VB6] Enumerate Services

IStream Manager: Consolidate, Access Stream Data

$
0
0
Attached is a self-contained class that manages IStream objects. These IStreams can be created by the class and/or passed to the class for management and accessibility. The class creates an IStorage interface object and maintains owned streams within the IStorage object. This object can contain up to 2GB worth of stream data that is cached in virtual memory as needed. This allows heavy data to be cached outside the user's memory space, greatly reducing the potential of out-of-memory errors for projects that want to cache large data in memory vs. individual files.

Note: This class is not a full implementation of IStorage. It does not permanently save anything. It was designed as a run-time only virtual cache of data, destroyed on release.

The class organizes IStreams into 2 categories: Owned and External.
Each class method that allows creation of a stream has an option to create it as owned or external.
Owned Streams
Streams that are part of the IStorage object. Owned streams should not be detached from the class. Once the class terminates, any owned streams are invalid
External Streams
These are streams that are created in the class but opted to be created in the user's memory space on an hGlobal memory address. Additionally, any externally created stream that is passed to the class is considered external. External streams can always be detached from the class.

The next post in this thread briefly describes each of the class methods/properties. The class is heavily commented.

This class is not very low level, but it does require some care if you are using a few of its more generic methods which allow you to pass/receive data via memory addresses. You must ensure you do not pass parameters that would cause the class to attempt to read or write past the memory space represented by the passed memory pointer. Crashes will occur. Bottom line. If you tell the class that the available memory exists for the pointer you provided, at least that amount of memory better exist.

Why would you use this? Briefly touched in first paragraph above. Consider a graphics program that allows various levels of 'undos'. Instead of keeping potentially large data in the user's memory space, you can store that data to IStreams and recall them on demand, as needed. If you need to back up source data while you are working with it, while making sure it doesn't get deleted by the user/system, store it an IStream and purge it or recall it as needed. Though 2GB is fairly large, it isn't never ending. This class may make it easy to abuse virtual memory, not the intent.

Couple of super-simple examples. Each IStream is provided a key, the key allows you to choose which IStream you want to access. In the examples below, it is assumed the class is declared at module/form level, public to your project. We'll say the an instance of the IStreamManager class is named: cStrmMgr. In each example, we are assuming the stream's seek pointer is at beginning of the stream. To be sure, we can always call the cStrmMgr.SetSeekPointer method

Example: Back up a 2D array and recall it
Code:

' a Long array was sized as: myArray(0 to 49, 0 to 2499)  and you want to save it
cStrmMgr.AddStreamFromPointer myArray(0,0), 500000, "Spreadsheet1"

' now lets say you want to recall that data
Dim bytesRead As Long
ReDim myArray(0 to 49, 0 to 2499)
' next line identifies the receiving buffer, bytes to read, variable to return bytes read, where to start reading & what Key
cStrmMgr.ReadStream myArray(0, 0), 500000, bytesRead, 0, "SpreadSheet1"
If bytesRead = 500000 Then ' read all data

Example. Read a file into a backup stream
Code:

cStrmMgr.AddStreamFromFile "C:\Temp\SomeFile.any", "History"

' Let's say you don't need the history file any longer and just want to purge it
cStrmMgr.ReleaseStream "History"

Example: Backup a RTF document from the RichtTextBox control
Code:

cStrmMgr.AddStreamFromStringVB RTB.TextRTF, "RTFbackup"

' and lets say you want to return that stream into a VB string:
Dim sText As String
cStrmMgr.SaveStreamToStringVB sText, "RTFbackup"

Example: Detach IStream from the class
Code:

Dim myStream As IUnknown ' or IStream if you have such a TLB
Set myStream = cStrmMgr.StreamObject(myKey) ' get instance of stream
cStrmMgr.ReleaseStream myKey ' detach from class, no longer maintained by the class

Last example. Let's say you are managing undo stuff via a DIB you have a memory pointer for the bits: pDIBits
Code:

cStrmMgr.AddStreamFromPointer pDIBits, picScanWidth * picHeight, "Undo1"

' And now let's say you want to apply that Undo back to the DIB directly
DIm bytesRead As Long, amoutToRead As Long
amountToRead = picScanWidth * picHeight
cStrmMgr.ReadStream pDIBits, amountToRead, bytesRead, 0, "Undo1"
If bytesRead = amountToRead Then ' read all data

Known Limitations
1) 2 GB IStorage object. Trying to exceed this should result in failure
2) 2 GB IStream is largest that can be created and if done, likely to max out IStorage
3) Class does not expose hGlobal address if stream has one. Use GetHGlobalFromStream API
4) Undefined Streams can be added, i.e., length of zero on memory pointer of zero. These can be added to willy-nilly and the stream auto-expands as needed. The 2GB limitation still applies

After downloading the text file, simply remove the .txt extension
Attached Files

[VB6] Push to Pushbullet Demo

$
0
0
Pushbullet is a service and set of applications that can mirror notifications, files, links, SMS messages, etc. among your devices (Android, iOS, PC, browser) and recently added chat features.

The service also offers the PushBullet API, whcih makes it possible for you to write programs that interoperate with the service.

This thread presents a demo program that makes some basic use of the API in VB6.


Requirements

You need a Pushbullet account.

The program requires GDI+ and MSXML 3.0, and should run on any 32-bit x86 (or WOW64) OS from Windows 95 forward as long as these two items have been installed.

Edanmo's IStream interfaces & functions typelib (STRM.tlb, included in attachment).


Scenario

Here we have small (and silly) application: KwayZKalk, a simple four-function calculator.

When an exception is raised (division by zero) KwayZKalk will pop up a Submit Error Report dialog. There the user can enter comments, a contact email address, and then click on Send Report or they can click on Don't Send instead.

If they click Send Report a capture of the main Form is done and pushed to the author along with a summary of the error and the user's optional comments and email address.

The author gets these "pushes" and can use them to take action (normally this would be problem diagnosis) and perhaps contact the user for more information or to send a fix. The author could have another VB6 program to extract these reports and log them into a database for action, but in simple cases they'll just get them on their PC, phone, tablet, wherever they have the Pushbullet app installed.

The program could also be designed to push only to one specific device.


Preparing KwayZKalk

First you need a Pushbullet account. Then you should install one of the client apps or at least go to the site and log on via the browser. This gives you somewhere to receive the "pushes."

Next you can go to your account settings on the web site to retrieve your account's AuthToken value.

Then you can go into the ConfigScrambler subfolder of the attachment and compile ConfigScrambler.vbp, an accessory utility used to apply some trivial encryption. Real applications should use much stronger standards-based encryption techniques than this program does.

Now you can open the template PBConfig.txt file (Unicode JSON text) using Notepad and replace the dummy AuthToken value with your real AuthToken. Save the file and exit Notepad.

Run ConfigScrambler.exe, which will read your PBConfig.txt and write a new PBConfig.dat file. Hex dumps of both are presented by ConfigScrambler.exe to help satisfy your curiosity. Now you can exit the program.

Cut and paste PBConfig.dat from this folder into the parent (KwayZKalk) folder where KwayZKalk can find it.

Finally compile KwayZKalk.vbp to create the dummy application, or you can run it from within the IDE.


Running KwayZKalk

Run the program. It should already be set to divide by zero, so go ahead and click on the "equals" button (just has a horizontal line on it).

This should divide by zero, firing off the Send Error Report dialog. Fill in the comments and email fields and then click on Send Report.

This should screen-capture the KwayZKalk main form and push the image and text information to the Pushbullet service, showing a simple upload animation until complete.

That's about it, and then you should be able to go into the Pushbullet client app on any device (or the web client in your browser) to see the Error Report.


Name:  Flow.png
Views: 93
Size:  15.9 KB

Flow of Error Reporting


Name:  PushCapture.png
Views: 72
Size:  30.1 KB

"Push" as it appears in the Windows PC Client


Beyond KwayZKalk

It is also possible to have your code bundle things up into a ZIP archive and upload and "push" that.

There are also quite a few other things you can do using the Pushbullet API.
Attached Images
  
Attached Files

Easy image disp/edit; scale/rotate, show animated gifs, conv2JPG, +more; No GDI+/DLL

$
0
0
Shell Image Interfaces

Project Description
Windows provides a good bit of image functionality built in and accessible through simple interfaces rather than fairly complicated API. A number of these were added to the latest release of oleexp. This projects demonstrates several of these interfaces:

IShellImageData
Easy to create with ShellImageDataFactory object.
Set pFact = New ShellImageDataFactory
pFact.CreateImageFromFile StrPtr(sISID), pShImg
pShImg.Decode SHIMGDEC_DEFAULT, 10, 10

This is the most useful interface, it can:
-Display basic info about an image (see picture)
-Step frame-by-frame through an animated GIF, or use a timer to 'play' it - I added this feature in after I took the screen shot-- it's included in the attached project
-View multi-page images
-Scale images with different algorithm options e.g. bicubic
-Rotate an image at any angle
-Draw onto a picturebox with transparency
-Save changed image (supports user-defined encoder parameters, but not shown in demo)
...all through single-line calls,
Code:

pShImg.ScaleImage CLng(Text5.Text), CLng(Text6.Text), InterpolationModeBicubic
pShImg.NextFrame
pShImg.Rotate CLng(Text4.Text)
'saving is equally easy:
Dim ipf As IPersistFile
Set ipf = pShImg
ipf.Save sFullPath, 1

IImageTranscode
This interface allows you to convert any image file supported by Windows into a JPG or BMP with only a few lines of code:
Code:

Private Sub DoTranscode(psiSrc As IShellItem, psiDest As IShellItem, nTo As TI_FLAGS)
'The included module provides a standalone implemention of this routine if you're starting
'from only the file paths. This version uses a number of shortcuts getting an IShellItem
'directly from FileOpenDialog gives us

Dim lpDest As Long
Dim pStrm As IStream
Dim pTI As ImageTranscode
Dim pwi As Long, phg As Long

Set pTI = New ImageTranscode

psiDest.GetDisplayName SIGDN_FILESYSPATH, lpDest

Call SHCreateStreamOnFileEx(lpDest, STGM_CREATE Or STGM_READWRITE, FILE_ATTRIBUTE_NORMAL, 1, 0, pStrm)
pTI.TranscodeImage psiSrc, 0, 0, nTo, pStrm, pwi, phg
pStrm.Commit STGC_DEFAULT

Set pStrm = Nothing
Set pTI = Nothing
Call CoTaskMemFree(lpDest)

End Sub

IImageList/IImageList2
These interfaces are very similar to API imagelists (and indeed you can get an API imagelist handle you can use with those functions or assign to a control just by using ObjPtr(pIML)), but apart from being slightly easier to work with also allow resizing on the fly, instead of having to reconstruct. This is also the only way to scale up, because as with API imagelists, you cannot add images smaller than the size the imagelist was created as.
It's important to note than you can create one from scratch, but not with = New ImageList, you need to use ImageList_CoCreateInstance, as shown in the sample project.

Project Requirements
-Windows Vista or higher
-oleexp3.tlb version 3.1 or higher (released 18Sep2015). Only required for the IDE, you don't need to include it with the compiled program.

-----
Some sample images to play around with are included in the ZIP; I didn't make them.
Attached Files

VB6 in AppServer-scenarios (DCOM Replacement per RC5)

$
0
0
In the early days of VB6-usage there was DCOM (later superseded by COM+).

It came with the promise of easy cross-machine-calls (RPCs) by simply using the second
(optional) Parameter [ServerName] of the CreateObject-call...

Now, is there anybody out there (aside from myself), who ever used that (or anybody who's still using it)?
I guess not - and there's a reason for it.

Don't get me wrong - DCOM/COM+ is a great technology - which still works to this day -
*but* - for proper usage you will have to study a few books about that topic, before you
make your first serious steps ... -> right into "config-hell".

So, basically "nice stuff" (and used to this day in some LAN-scenarios, after a "config-orgy"
and countless Proxy-installs on the clients) - but firing it up as easily as the CreateObject-call
suggests? ... Forget about it.

Well, the RichClient5 offers an alternative to DCOM/COM+, which in contrast supports:
- not touching the Registry (serverside Dlls don't need to be registered)
- avoidance of clientside Proxy-installs (to match the interfaces of the serverside COM-Dlls)
- easy movement of the RC5-RPC serverside part to a different Machine per X-Copy of the Server-RootFolder
- same performance as DCOM/COM+ (thousands of Remote-Requests per second in multiple WorkerThreads)
. but using only a single Port ... whereas DCOM/COM+ needs a complete Port-Range
- usable therefore also in Internet-Scenarios, also due to strong authentication/encryption and built-in compression

Ok, so where's the beef - how to use that thing?

Here's the Code for a SimpleRPC-Demo SimpleRPC.zip ...
and a short description with some background follows below...

A finished solution consists of three things (three VB6-Projects):


VB-Project #1: The Server-Application (providing the HostProcess for the AppServer-Listener)
- in the above Zip, this is the Project sitting in Path: ..\RPCServer\RPCServer.vbp

This is the most easy of the three parts, since it is not "ClientApp- or Server-Dll specific" -
just a hosting Exe-Project for the Service which will work with any ServerDll and any Client.

You will only have to compile it once - and can then forget about it...

Here's the complete SourceCode for this ServerHost-Executable (all in a little Form):
Code:

Private RPCListener As cRPCListener 'define the RPC-Server-Listener
Private IP As String, Port As Long, DllPath As String 'Start-Parameters

Private Sub Form_Load()
  'normally this part is contained in a Windows-Service-Executable (without any UI)
 
  IP = New_c.TCPServer.GetIP("")      'get the default-IP of the current machine
  Port = 22222                        'set a Port (22222 is the RC5-RPC default-port)
  DllPath = App.Path & "\RPCDlls\"  'Path, where the Server is looking for the RPCDlls
 
  Set RPCListener = New_c.RPCListener 'create the RPC-Listener-instance
 
  If RPCListener.StartServer(IP, Port, , , , , DllPath) Then '... now we try to start the RPC-Server
    Caption = "Server is listening on: " & IP & ":" & Port
  Else
    Caption = "Server-Start was not successful"
  End If
End Sub

Private Sub Form_Terminate()
  If Forms.Count = 0 Then New_c.CleanupRichClientDll
End Sub

That's it with regards to the ServerHost-instance (a normal UserMode-Executable in our Demo-case).


VB-Project(s) #2: One (or more) ActiveX-Server-Dll(s)
- in the above Zip, this is the Project sitting in Path: ..\RPCServer\RPCDlls\SimpleServerLib.vbp

When you look at the above code for the Service-Host - and its RPCListener.StartServer-function, you will see that it receives a
StartParameter 'DllPath' which in this case points to a SubFolder of the Serverhost-Executable: App.Path & "\RPCDlls\"

And this place (this RPCDlls-Folder) is, where you will have to put your compiled Server-Dlls into.
The Public Subs and Functions you will put into the Class(es) of these Dlls will be, what you later on call remotely
(without the need to register these Dlls).

Here's the whole code of the single Class (cServerClass), this Dll-Project contains -
and yes, you can write this code as any other VB6-Code, as normal Public Subs and Functions
(this little Dll-Project doesn't even have a reference to vbRichClient5, the only reference it contains,
is the one to "ADO 2.5", since it will transfer an ADO-Recordset back to the clientside later on).

Code:

Private Cnn As ADODB.Connection
 
Public Function StringReflection(S As String) As String
  StringReflection = StrReverse(S)
End Function

Public Function AddTwoLongs(ByVal L1 As Long, ByVal L2 As Long) As Long
  AddTwoLongs = L1 + L2
End Function

Public Function GetADORs(SQL As String) As ADODB.Recordset
  If Cnn Is Nothing Then OpenCnn
  Set GetADORs = New ADODB.Recordset
      GetADORs.Open SQL, Cnn, adOpenStatic, adLockBatchOptimistic 'return the ADO-Rs (its content will be auto-serialized)
End Function

Private Sub OpenCnn()
  Set Cnn = New Connection
      Cnn.CursorLocation = adUseClient
      Cnn.Open "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & App.Path & "\Test.mdb"
End Sub

That's it - nothing more is needed for the "active part" of the serverside (the Server-Dlls).
The serverside code is hereby (with #1 and #2) completely finished!


VB-Project #3: The Client-App
- in the above Zip, this is the Project sitting in Path: ..\ClientApp\SimpleRPC.vbp

What remains now, is the clientside part of the RPC - the one which *initiates* an
RPC-(Remote-Procedure-call).

The behaviour (to make the program-flow easier) is in case of the RC5-RPCs *always*
synchronously. That means, that RPCs will not return, until we got a Result, or an
Error-message - or a TimeOut-Error back from such a Remote-Method-call against the Server.

Although also the Clientside-Code is not more than 50 lines or so, I will put only
this smaller excerpt of the client-sides Form-code here into a code-section to explain...:

Code:

Private Const ServerDll$ = "SimpleServerLib.dll" 'Name of the used Dll in the \RPCDlls\-Folder
Private Const ServerCls$ = "cServerClass" 'Name of the Class, which is contained in above Dll
 
Private RPCConn As cRPCConnection 'define the Var for the clientside RPC-connection
 
Private Sub Form_Load()
  Set RPCConn = New_c.RPCConnection 'create the clientside-RPCConnection-instance
      RPCConn.DebugMode = (chkDEBUGMode.Value = vbChecked) 'Debug-Mode (should be switched Off when running as an Executable)
      RPCConn.Host = ""        'put an explicit Server-IP here later on, e.g. read from an Ini-File
      RPCConn.Port = 22222    'Port-Nr the Server is listening on (22222 is the RC5-RPC-default)
      RPCConn.KeepAlive = True 'set KeepAlive for better performance
End Sub

'... snipped the two other Methods, which we also wrap in this Form

Private Sub cmdAddTwoLongs_Click() 'an example Remote-Method-Call
On Error GoTo ErrMsg
 
  txtAdd.Text = RPCConn.RPC(ServerDll, ServerCls, "AddTwoLongs", 3, _
                CLng(txtL1.Text), CLng(txtL2.Text)) '<- Parameter-List (two Long-Values in this case)
 
ErrMsg: If Err Then MsgBox Err.Description
End Sub

You will notice the red-colored Object-Variable (of type cRPCConnection) -
which resembles in its usage a bit, how one would work with e.g. the WinHTTP 5.1 Object...
Simply put - it encapsulates "the needed Socket-stuff" which is necessary, to be able to
work across machine-boundaries.

After this Object was "set up" (in Form_Load or in Sub Main - or also in a dedicated little
Wrapper-Class), what remains is to look at, where "the RPC-call happens"...
(for simplicity's sake, in this Demo not in an additional WrapperClass, but directly in the Forms: cmdAddTwoLongs_Click()

Just ask yourselves - what will need to happen under the covers of: RPCConn.RPC(...)?
Right (please look at the Strings I've marked blue in the above code):
- to be able to instantiate a Dll regfree from within the serversides \RPCDlls\ folder, we will need the DllName and the ClassName
. (so that we can create an Object-instance, which we will call LateBound then)...
- and to be able to perform a LateBound-Call (per CallByName), we will need the third blue string: "AddTwoLongs" (the Method-name)
- another requirement in the Parameter-List will be a TimeOut-Value (in the above call this is the 4th argument, the '3')
- and then finally the two arguments, which the AddTwoLongs-Method expects at the serverside (a VB6-Param-Array came in handy here)

So that's it basically with regards to a little "How-To-Do RPC-calls the easy way" with the vbRichClient5.

Note, that the RichClient RPC-Classes are in use at hundreds of Client-installations worldwide - and
that these Classes were included from the very beginning of the RichClient-project (over a decade ago).
So, this stuff was quite hardened over the years - and is not a "toy-implementation".

4) One last thing, I'd like to mention still with regards to the Demo (before you run it):

The RPC-Classes support a DebugMode (as contained in the last code-snippet above over: RPCConn.DebugMode = ...)

When this Property is True, then one can do an easy "RoundTrip-Debugging", when the
serverside Dll-Project in question is included in a VB-ProjectGroup.

The Demo will start (without the need to compile any Binaries) per Default in DebugMode -
and an appropriate \SimpleRPC\RPC_Test_Group.vbg File is included in the Root-Folder of the Demo.

Check this mode out first (leaving the DebugMode-CheckBox checked) -
later, when you e.g. have stepped through an RPC-call (per <F8> Key),
you can disable the Debug-Mode - but before you do so, you will have to compile:
- the ServerHost-Project I've mentioned in #1
- the ServerDll-Project I've mentioned in #2 (please make sure, that you compile the Dll into the \RPCDlls\-Folder)
- followed by starting the compiled ServerRPC-Executable
After that you can switch DebugMode Off - and perform "real RPC-calls over sockets"

Here's a ScreenShot of the little Client-App:



Have fun.

Olaf
Attached Files

Dev tool: typedef Converter - Convert C/C++/IDL typedef struct and typedef enum to VB

$
0
0
After spending way too much time doing this manually, this idea came to be. I use this extraordinarily frequently, so thought someone else might one day have a use for it. The title pretty much sums it up; here's some notes:

-Automatically detects if typedef struct or typedef enum
-Types support automatic variable type changing and have the most common ones built in (e.g. DWORD = Long, LPSTR = String)
-Arrays are supported for types, both when defined by number var[10]->var(0 To 9) and by variable, var[MAX_PATH]->var(0 To (MAX_PATH - 1))
-Comments have the option to be included or removed
-Enums that don't have an = sign (sequential) are supported, both with and without an initial entry with =0 or =1
-Option for public or private
-Option to remove 'tag' in names
-Various automatic syntax corrections

Samples
typedef enum _tagPSUACTION
{
PSU_DEFAULT = 1 // gets security URL and returns its domain.
,PSU_SECURITY_URL_ONLY // gets just the security URL
} PSUACTION;
Public Enum PSUACTION
PSU_DEFAULT=1 ' gets security URL and returns its domain.
PSU_SECURITY_URL_ONLY = 2 ' gets just the security URL
End Enum
typedef struct SMDATA
{
DWORD dwMask; // SMDM_* values
DWORD dwFlags; // Not used
long hmenu; // Static HMENU portion.
HWND hwnd; // HWND owning the HMENU
UINT uId; // Id of the item in the menu (-1 for menu itself)
UINT uIdParent; // Id of the item spawning this menu
UINT uIdAncestor[80]; // Id of the very top item in the chain of ShellFolders
//IUnknown* punk; // IUnkown of the menuband
long punk; //use pointer??
long pidlFolder;// pidl of the ShellFolder portion
long pidlItem; // pidl of the item in the ShellFolder portion
//IShellFolder* psf; // IShellFolder for the shell folder portion
long psf; //use pointer??
WCHAR pvUserData[MAX_PATH]; // User defined Data associated with a pane.
} SMDATA;
Public Type SMDATA
dwMask As Long ' SMDM_* values
dwFlags As Long ' Not used
hmenu As long ' Static HMENU portion.
hwnd As Long ' HWND owning the HMENU
uId As Long ' Id of the item in the menu (-1 for menu itself)
uIdParent As Long ' Id of the item spawning this menu
uIdAncestor(0 To 79) As Long ' Id of the very top item in the chain of ShellFolders
'IUnknown* punk; // IUnkown of the menuband
punk As long 'use pointer??
pidlFolder As long ' pidl of the ShellFolder portion
pidlItem As long ' pidl of the item in the ShellFolder portion
'IShellFolder* psf; // IShellFolder for the shell folder portion
psf As long 'use pointer??
pvUserData(0 To (MAX_PATH - 1)) As Integer ' User defined Data associated with a pane.
End Type

Those two really show it all...
(the VB output is properly indented, can't see it here)

I might change this into an add-in that could do convert-on-paste or convert from the right click menu, if anyone is interested in that let me know.

NOTE: I believe the people who would use a tool like this would also not need extensive documentation of the code or e.g. not be ok with the only way to add type replacements being to add another line in a function... this isn't for beginners so don't be too harsh about the cryptic code :)
Also, I rely on VB to do things like correct the case of native data types (long isn't replaced with Long), and change &H0001 to &H1; it's not worth doing manually.

If anyone is interested I also have a utility that will turn a UUID into a IID_IWhatever function like the ones in mIID.bas in oleexp.

PS- Don't actually use that SMDATA type; I altered it to show features.
Attached Files

[VB6] UserControl Ambient.UserMode workaround

$
0
0
For you usercontrol (UC) creators out there. Everyone else -- won't apply to you.

Ambient.UserMode tells us whether the UC's container is in design mode or user mode/run-time. Unfortunately, this isn't supported in all containers. Word, IE may not report what you expect. Some containers may not implement that property.

VB always implements the Ambient.UserMode property. However, that can be misleading. If you have a UC on a form in design view, UC says Ambient.UserMode = False; great. But if you are creating a new UC and inside that new UC, you add an existing/different UC, that inner UC will report False also; great because this new UC is in design view. Here's the kicker. Now you add that new UC to the form. The inner UC now reports Ambient.UserMode as True, even though the form is in design view

Is this a problem for you? Maybe, only if you are actually testing that property. Let's say you use that property to determine whether or not to start subclassing, whether to start some image animation, maybe start API timers, whatever. You designed your control to not do that if the UC's container is in design view. Works well until your control is placed in another control that is placed on some other container. When your control (compiled or not) is a grandchild, container-wise, it will report Ambient.UserMode as True within VB. Other containers may report different things. The suggestion below allows your customer/user to override and properly set that property.

Let me use a real world example. I designed an image control. That control has a property to begin animation when the UC is in run-time. Well, someone wanted to add my control to a custom UC they were designing. They wanted the animation to occur when their new UC was in run-time. Animation started when their UC was placed on a form in design-time. Not what they wanted. Since my control had a property to start/stop animation, the simple solution was to default not to start animation and also for their UC to check its own Ambient.UserMode and depending on its value, start animation.

This worked well. But what if my UC began doing stuff when its Ambient.UserMode was True, but had no way for the containing control to tell it to stop or don't start at all? That containing control is out of luck.

The following is a workaround that if became a template for all your UCs, you can avoid this problem in any UC you create. Any paying customers for your UC can be educated to the new property and how to use it for their purposes.

Here is a sample of the 'template'. It exposes a Public UserMode property that allows the UC's container to dictate/tell the UC what UserMode it should use. This could be ideal for other non-VB6 containers that either report incorrectly or don't report at all the Ambient.UserMode.

Code:

Public Enum AmbientUserModeENUM
    aumDefault = 0
    aumDesignTime = 1
    aumRuntime = 2
End Enum
Private m_UserMode As AmbientUserModeENUM

Public Property Let UserMode(newVal As AmbientUserModeENUM)
    If Not (newVal < aumDefault Or newVal > aumRuntime) Then
        m_UserMode = newVal
        Call pvCheckUserMode
        PropertyChanged "UserMode"
    End If
End Property
Public Property Get UserMode() As AmbientUserModeENUM
    UserMode = m_UserMode And &HFF
End Property

Private Sub pvCheckUserMode()
    Select Case (m_UserMode And &HFF)
    Case aumDefault
        m_UserMode = (m_UserMode And &HFF) Or UserControl.Ambient.UserMode * &H100&
    Case aumRuntime
        m_UserMode = (m_UserMode And &HFF) Or &H100
    Case Else
        m_UserMode = m_UserMode And &HFF
    End Select
   
    If (m_UserMode And &H100) Then  ' user mode is considered True
        ' do whatever is needed. Maybe set the UserMode property of any child usercontrols

    Else                            ' user mode is considered False
        ' do whatever is needed. Maybe set the UserMode property of any child usercontrols

    End If

End Sub


Private Sub UserControl_InitProperties()
    ' set any new control, initial properties
   
    ' apply any actions needed for UserMode
    Call pvCheckUserMode
End Sub

Private Sub UserControl_ReadProperties(PropBag As PropertyBag)
    ' read all written properties

    ' apply any actions needed for UserMode
    m_UserMode = PropBag.ReadProperty("AUM", aumDefault)
    Call pvCheckUserMode
End Sub

Private Sub UserControl_WriteProperties(PropBag As PropertyBag)
    PropBag.WriteProperty "AUM", (m_UserMode And &HFF), aumDefault
End Sub

Though the call to pvCheckUserMode is placed in Init/ReadProperties, it could be moved to UserControl_Show if desired, depending on your needs.

SHBrowseForFolder: Handling a choice of Libraries (or Library), Computer, or Network

$
0
0
ChooseFolderEx

Project Summary
So if you've ever used a folder choose based on SHBrowseForFolder, you'll notice that most functions that turn its result (a pidl) into a file system path will return nothing, or at best a cryptic string starting with :: (followed by a GUID). But things like Libraries, My Computer, and Network contain folders- and if you're going to be doing something like searching for files, the user may well expect that selecting one of those would search its locations. Thanks to oleexp, the code to find out what those folders are is at least somewhat manageable.

Project Requirements
-At least Windows Vista; Libraries are a Win7+ thing.
-oleexp3.tlb - my fork of olelib with modern interfaces (get it here). This must be added as a reference under Project->References, but doesn't need to be included with a compiled program. No new version was released with this project, so if you already have it you don't need to upgrade this time.



So we begin with calling the Browse API; the wrapper called here is just a standard routine.
Code:

Public Function SelectFolderEx(hWnd As Long, sPrompt As String, dwFlags As BF_Flags, out_Folders() As String, Optional sStartDir As String, Optional sRoot As String) As Long
'Enhanced folder chooser
Dim pidlStart As Long
Dim pidlRoot As Long
Dim lpRes As Long, szRes As String
ReDim out_Folders(0)
If sStartDir <> "" Then
    pidlStart = ILCreateFromPathW(StrPtr(sStartDir))
End If
If sRoot <> "" Then
    pidlRoot = ILCreateFromPathW(StrPtr(sRoot))
End If

lpRes = BrowseDialogEx(hWnd, sPrompt, dwFlags, pidlRoot, pidlStart)
If lpRes = 0 Then
    SelectFolderEx = -1
    Exit Function
End If


szRes = GetPathFromPIDLW(lpRes)
If (szRes = "") Or (szRes = vbNullChar) Then
    'here's where we do some magic. if GetPathFromPIDLW returned nothing, but we did receive
    'a valid pidl, we may have a location that still might be valid. at this time, i've made
    'functions that will return the paths for the Library object, any individual library,
    'My Computer, and the main Network object and network paths
    Dim sAPP As String 'absolute parsing path
    sAPP = GetAbsoluteParsingPath(lpRes)
    If (Left$(sAPP, 2) = "\\") Or (Left$(sAPP, 2) = "//") Then
        'network locations can't be resolved as normal, but are valid locations
        'for most things you'll be passing a folder location to, including FindFirstFile
        'the only caveat here, is the network pc itself resolves here but can't be passed
        'so we want it enumed too, but not past that
       
        Dim sTMP As String
        sTMP = Mid$(sAPP, 3)
        If (InStr(sTMP, "/") = 0) And (InStr(sTMP, "\") = 0) Then
            'so this should be a top-level computer needing to be enum'd
            SelectFolderEx = EnumSpecialObjectPaths(sAPP, out_Folders)
            GoTo cfdone
        End If
        out_Folders(0) = sAPP
        SelectFolderEx = 1
        GoTo cfdone

    End If
    SelectFolderEx = EnumSpecialObjectPaths(sAPP, out_Folders)
Else
    out_Folders(0) = szRes
    SelectFolderEx = 1
End If

cfdone:
Call CoTaskMemFree(lpRes)
End Function

The difference here is that instead of giving up and returning a blank or error if we don't get a path, we're going to check to see if it's an object that does contain file system folders.

The next step is to see which, if any, object we can enumerate:
Code:

Public Function EnumSpecialObjectPaths(szID As String, sPaths() As String) As Long
'objects like Libraries and My Computer can't be passed to a file search algorithm
'but they contain objects which can. this function enumerates the searchable paths
'return value is the count of sPaths, or -1 if the GUID was not an enumerable loc
Debug.Print "esop enter " & szID
    If szID = FolderGUID_Computer Then
        'here we can just use the GetLogicalDriveStrings API
        Dim sBuff As String * 255
        Dim i As Long
        i = GetLogicalDriveStrings(255, sBuff)
        sPaths = Split(Left$(sBuff, i - 1), Chr$(0))

    ElseIf (szID = FolderGUID_Libraries) Then 'library master
        ListAllLibraryPaths sPaths
       
    ElseIf (Left$(szID, 41) = FolderGUID_Libraries & "\") Then 'specific library
        ListLibraryPaths szID, sPaths
   
    ElseIf (szID = FolderGUID_Network) Then 'Network master
        ListNetworkLocs sPaths
       
    ElseIf (Left$(szID, 2) = "\\") Then
        ListNetComputerLocs szID, sPaths
       
    Else 'not supported or not file system
        EnumSpecialObjectPaths = -1
        Exit Function
    End If

EnumSpecialObjectPaths = UBound(sPaths) + 1

End Function

For My Computer, the job was easy, just had to call the GetLogicalDriveStrings API.
For the rest, we need a more complex enumerator. This is made possible by the fact IShellItem can represent anything, and can enumerate anything, not just normal folders.
There's 2 Library options; if an individual library is selected, that's still not a normal path so has to be handled here- the IShellLibrary interface can tell us which folders are included in the library, so we can go from there. The other is for the main 'Libraries' object being selected- there we get a list of all the libraries on the system (note that we can't just check the standard ones, because custom libraries can be created).
If the Network object is chosen, we filter it down to browseable network paths, since the enum also returns the various non-computer objects that appear there.

Code:

Public Sub ListAllLibraryPaths(sOut() As String)
'Lists all paths in all libraries
Dim psi As IShellItem
Dim piesi As IEnumShellItems
Dim psiLib As IShellItem
Dim isia As IShellItemArray
Dim pLibEnum As IEnumShellItems
Dim pLibChild As IShellItem
Dim lpPath As Long
Dim szPath As String
Dim pLib As ShellLibrary
Set pLib = New ShellLibrary
Dim nPaths As Long
Dim pclt As Long

ReDim sOut(0)

Call SHCreateItemFromParsingName(StrPtr(FolderGUID_Libraries), ByVal 0&, IID_IShellItem, psi)
If (psi Is Nothing) Then
    Debug.Print "could't parse lib master"
    Exit Sub
End If
psi.BindToHandler 0, BHID_EnumItems, IID_IEnumShellItems, piesi

Do While (piesi.Next(1, psiLib, pclt) = S_OK)
    psiLib.GetDisplayName SIGDN_NORMALDISPLAY, lpPath
    szPath = LPWSTRtoStr(lpPath)
    Debug.Print "Enumerating Library " & szPath
    pLib.LoadLibraryFromItem psiLib, STGM_READ
    pLib.GetFolders LFF_ALLITEMS, IID_IShellItemArray, isia
       
    isia.EnumItems pLibEnum

    Do While (pLibEnum.Next(1, pLibChild, 0) = 0)

        pLibChild.GetDisplayName SIGDN_FILESYSPATH, lpPath
        szPath = LPWSTRtoStr(lpPath, True)
        Debug.Print "lib folder->" & szPath
        If Len(szPath) > 2 Then
            ReDim Preserve sOut(nPaths)
            sOut(nPaths) = szPath
            nPaths = nPaths + 1
        End If
        Set pLibChild = Nothing

    Loop
    Set psiLib = Nothing
Loop
End Sub


Public Sub ListLibraryPaths(sPN As String, sOut() As String)
'list the paths of a single library
'sPN is the full parsing name- what is returned from ishellfolder.getdisplayname(SHGDN_FORPARSING)
Dim psiLib As IShellItem
Dim pLib As ShellLibrary
Set pLib = New ShellLibrary
Dim psia As IShellItemArray
Dim pEnum As IEnumShellItems
Dim psiChild As IShellItem
Dim lpPath As Long, szPath As String, nPaths As Long
Dim pclt As Long

Call SHCreateItemFromParsingName(StrPtr(sPN), ByVal 0&, IID_IShellItem, psiLib)
If (psiLib Is Nothing) Then
    Debug.Print "Failed to load library item"
    Exit Sub
End If
pLib.LoadLibraryFromItem psiLib, STGM_READ
pLib.GetFolders LFF_ALLITEMS, IID_IShellItemArray, psia
If (psia Is Nothing) Then
    Debug.Print "Failed to enumerate library"
    Exit Sub
End If

ReDim sOut(0)
psia.EnumItems pEnum

Do While (pEnum.Next(1, psiChild, pclt) = S_OK)
    If (psiChild Is Nothing) = False Then
        psiChild.GetDisplayName SIGDN_FILESYSPATH, lpPath
        szPath = LPWSTRtoStr(lpPath)
        If Len(szPath) > 2 Then
            ReDim Preserve sOut(nPaths)
            sOut(nPaths) = szPath
            nPaths = nPaths + 1
        End If
    End If
    Set psiChild = Nothing
Loop
Set pEnum = Nothing
Set psia = Nothing
Set pLib = Nothing
Set psiLib = Nothing
End Sub


Public Sub ListNetworkLocs(sOut() As String) '
Dim psi As IShellItem
Dim piesi As IEnumShellItems
Dim psiNet As IShellItem
Dim isia As IShellItemArray
Dim pNetEnum As IEnumShellItems
Dim pNetChild As IShellItem
Dim lpPath As Long
Dim szPath As String
Dim nPaths As Long
Dim pclt As Long

Call SHCreateItemFromParsingName(StrPtr(FolderGUID_Network), ByVal 0&, IID_IShellItem, psi)
If psi Is Nothing Then Exit Sub
ReDim sOut(0)
psi.BindToHandler 0, BHID_EnumItems, IID_IEnumShellItems, piesi
Do While (piesi.Next(1, pNetChild, pclt) = S_OK)
    pNetChild.GetDisplayName SIGDN_DESKTOPABSOLUTEPARSING, lpPath
    szPath = LPWSTRtoStr(lpPath)
    If (Left$(szPath, 2) = "//") Or (Left$(szPath, 2) = "\\") Then 'objects besides valid paths come up, like routers, devices, etc
                                    'but they don't start with //, only searchable network locations should
        Debug.Print "netpath " & szPath
        ReDim Preserve sOut(nPaths)
        sOut(nPaths) = szPath
        nPaths = nPaths + 1
    End If
    Set pNetChild = Nothing
Loop
Set piesi = Nothing
Set psi = Nothing
End Sub


Public Sub ListNetComputerLocs(szID As String, sOut() As String)
'lists an individual network computer
Dim psiComp As IShellItem
Dim pEnum As IEnumShellItems
Dim psiChild As IShellItem
Dim lpPath As Long
Dim szPath As String
Dim nPaths As Long
Dim pclt As Long
Debug.Print "ListNetComputerLocs " & szID
Call SHCreateItemFromParsingName(StrPtr(szID), ByVal 0&, IID_IShellItem, psiComp)
If psiComp Is Nothing Then Exit Sub
ReDim sOut(0)
psiComp.BindToHandler 0, BHID_EnumItems, IID_IEnumShellItems, pEnum
Do While (pEnum.Next(1, psiChild, pclt) = S_OK)
    psiChild.GetDisplayName SIGDN_DESKTOPABSOLUTEPARSING, lpPath
    szPath = LPWSTRtoStr(lpPath)
    If Len(szPath) > 2 Then
        Debug.Print "netpath " & szPath
        ReDim Preserve sOut(nPaths)
        sOut(nPaths) = szPath
        nPaths = nPaths + 1
    End If
Loop

End Sub

The results of this are normal file system paths you can treat like normal results that never returned a blank.

Everything there is designed to support Unicode; but the VB textbox in the sample can't display it. But if you pass the results to something Unicode enabled, like a TextBoxW for example, you'll see the correct names.
Attached Files

SHChangeNotifyRegister updated and corrected, including new delivery method

$
0
0
So there's two reasons why I wanted to post this,
1) The examples on popular sites like VBNet and Brad Martinez's site have several errors, and
2) MSDN states that as of XP and later, all clients should be using a new delivery method that uses shared memory. The only example of this in VB is some obscure, hard to connect to chinese forum posts.

If you're not already familiar with SHChangeNotifyRegister, it allows your program to be notified of any changes to files, folders, and other shell objects. See the SHCNE enum below for the events it has.

Code:

Private Declare Function SHChangeNotifyRegister Lib "shell32" _
                              (ByVal hWnd As Long, _
                              ByVal fSources As SHCNRF, _
                              ByVal fEvents As SHCN_EventIDs, _
                              ByVal wMsg As Long, _
                              ByVal cEntries As Long, _
                              lpps As SHChangeNotifyEntry) As Long

The uFlags argument is not SHCNF values. It's always returned in pidls. SHCNF is for when your program calls SHChangeNotify (I should make a separate thread about that since nobody does that when they should). One of the new SHCNRF values is SHCNRF_NEWDELIVERY, which changes the way you handle the WM_SHNOTIFY message:
Code:

Public Function F1WndProc(ByVal hWnd As Long, ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long, ByVal uIdSubclass As Long, ByVal dwRefData As Long) As Long

    Select Case uMsg

        Case WM_SHNOTIFY
            Dim lEvent As Long
            Dim pInfo As Long
            Dim tInfo As SHNOTIFYSTRUCT
            Dim hNotifyLock As Long
            hNotifyLock = SHChangeNotification_Lock(wParam, lParam, pInfo, lEvent)
            If hNotifyLock Then
                CopyMemory tInfo, ByVal pInfo, LenB(tInfo)
                Form1.HandleNotify tInfo.dwItem1, tInfo.dwItem2, lEvent
                Call SHChangeNotification_Unlock(hNotifyLock)
            End If

Other than demonstrating those changes, it's just a straightforward SHChangeNotifyRegister example that also uses the newer, easier, and safer SetWindowSubclass API for its subclassing.

Requirements
-Windows XP or higher

Code
For quicker implementation, here the full module from the sample; the form just calls start/stop and handles the pidls.
Code:

Option Explicit

Public m_hSHNotify As Long
Public Const WM_SHNOTIFY = &H488 'WM_USER through &H7FF

Public Enum SHCN_EventIDs
  SHCNE_RENAMEITEM = &H1          '(D) A non-folder item has been renamed.
  SHCNE_CREATE = &H2              '(D) A non-folder item has been created.
  SHCNE_DELETE = &H4              '(D) A non-folder item has been deleted.
  SHCNE_MKDIR = &H8              '(D) A folder item has been created.
  SHCNE_RMDIR = &H10              '(D) A folder item has been removed.
  SHCNE_MEDIAINSERTED = &H20      '(G) Storage media has been inserted into a drive.
  SHCNE_MEDIAREMOVED = &H40      '(G) Storage media has been removed from a drive.
  SHCNE_DRIVEREMOVED = &H80      '(G) A drive has been removed.
  SHCNE_DRIVEADD = &H100          '(G) A drive has been added.
  SHCNE_NETSHARE = &H200          'A folder on the local computer is being
                                  '    shared via the network.
  SHCNE_NETUNSHARE = &H400        'A folder on the local computer is no longer
                                  '    being shared via the network.
  SHCNE_ATTRIBUTES = &H800        '(D) The attributes of an item or folder have changed.
  SHCNE_UPDATEDIR = &H1000        '(D) The contents of an existing folder have changed,
                                  '    but the folder still exists and has not been renamed.
  SHCNE_UPDATEITEM = &H2000      '(D) An existing non-folder item has changed, but the
                                  '    item still exists and has not been renamed.
  SHCNE_SERVERDISCONNECT = &H4000 'The computer has disconnected from a server.
  SHCNE_UPDATEIMAGE = &H8000&    '(G) An image in the system image list has changed.
  SHCNE_DRIVEADDGUI = &H10000    '(G) A drive has been added and the shell should
                                  '    create a new window for the drive.
  SHCNE_RENAMEFOLDER = &H20000    '(D) The name of a folder has changed.
  SHCNE_FREESPACE = &H40000      '(G) The amount of free space on a drive has changed.

'#If (WIN32_IE >= &H400) Then
  SHCNE_EXTENDED_EVENT = &H4000000 '(G) Not currently used.
'#End If

  SHCNE_ASSOCCHANGED = &H8000000  '(G) A file type association has changed.
  SHCNE_DISKEVENTS = &H2381F      '(D) Specifies a combination of all of the disk
                                  '    event identifiers.
  SHCNE_GLOBALEVENTS = &HC0581E0  '(G) Specifies a combination of all of the global
                                  '    event identifiers.
  SHCNE_ALLEVENTS = &H7FFFFFFF
  SHCNE_INTERRUPT = &H80000000    'The specified event occurred as a result of a system
                                  'interrupt. It is stripped out before the clients
                                  'of SHCNNotify_ see it.
End Enum

'#If (WIN32_IE >= &H400) Then
  Public Const SHCNEE_ORDERCHANGED = &H2 'dwItem2 is the pidl of the changed folder
'#End If
Public Enum SHCNRF
    SHCNRF_InterruptLevel = &H1
    SHCNRF_ShellLevel = &H2
    SHCNRF_RecursiveInterrupt = &H1000
    SHCNRF_NewDelivery = &H8000&
End Enum


Public Enum SHCN_ItemFlags
  SHCNF_IDLIST = &H0                ' LPITEMIDLIST
  SHCNF_PATHA = &H1              ' path name
  SHCNF_PRINTERA = &H2        ' printer friendly name
  SHCNF_DWORD = &H3            ' DWORD
  SHCNF_PATHW = &H5              ' path name
  SHCNF_PRINTERW = &H6        ' printer friendly name
  SHCNF_TYPE = &HFF
  ' Flushes the system event buffer. The function does not return until the system is
  ' finished processing the given event.
  SHCNF_FLUSH = &H1000
  ' Flushes the system event buffer. The function returns immediately regardless of
  ' whether the system is finished processing the given event.
  SHCNF_FLUSHNOWAIT = &H2000

'I prefer to always specify A or W, but you can also do it the way previous examples have
' (but this doesn't apply to SHChangeNotifyRegister, just SHChangeNotify, not covered here)
'#If UNICODE Then
'  SHCNF_PATH = SHCNF_PATHW
'  SHCNF_PRINTER = SHCNF_PRINTERW
'#Else
'  SHCNF_PATH = SHCNF_PATHA
'  SHCNF_PRINTER = SHCNF_PRINTERA
'#End If
End Enum



Private Type SHNOTIFYSTRUCT
  dwItem1 As Long
  dwItem2 As Long
End Type

Private Type SHChangeNotifyEntry
  ' Fully qualified pidl (relative to the desktop folder) of the folder to monitor changes in.
  ' 0 can also be specifed for the desktop folder.
  pidl As Long
  ' Value specifying whether changes in the folder's subfolders trigger a change notification
  '  event (it's actually a Boolean, but we'll go Long because of VB's DWORD struct alignment).
  fRecursive As Long
End Type

Private Declare Function SHChangeNotifyRegister Lib "shell32" _
                              (ByVal hWnd As Long, _
                              ByVal fSources As SHCNRF, _
                              ByVal fEvents As SHCN_EventIDs, _
                              ByVal wMsg As Long, _
                              ByVal cEntries As Long, _
                              lpps As SHChangeNotifyEntry) As Long

Private Declare Function SHChangeNotifyDeregister Lib "shell32" Alias "#4" (ByVal hNotify As Long) As Boolean

Private Declare Function SHChangeNotification_Lock Lib "shell32" (ByVal hChange As Long, _
                                                                ByVal dwProcId As Long, _
                                                                pppidl As Long, _
                                                                plEvent As Long) As Long
                                                               
Private Declare Function SHChangeNotification_Unlock Lib "shell32" (ByVal hLock As Long) As Long
Private Declare Function SHGetPathFromIDListW Lib "shell32.dll" (ByVal pidl As Long, ByVal pszPath As Long) As Long
Public Declare Function SHGetSpecialFolderLocation Lib "shell32.dll" (ByVal hWndOwner As Long, ByVal nFolder As SHSpecialFolderIDs, pidl As Long) As Long
Public Enum SHSpecialFolderIDs
    'See full project or somewhere else for the full enum, including it all ran over the post length limit
    CSIDL_DESKTOP = &H0

End Enum

Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)

Private Declare Function DefSubclassProc Lib "comctl32.dll" Alias "#413" (ByVal hWnd As Long, ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Private Declare Function SetWindowSubclass Lib "comctl32.dll" Alias "#410" (ByVal hWnd As Long, ByVal pfnSubclass As Long, ByVal uIdSubclass As Long, Optional ByVal dwRefData As Long) As Long
Private Declare Function RemoveWindowSubclass Lib "comctl32.dll" Alias "#412" (ByVal hWnd As Long, ByVal pfnSubclass As Long, ByVal uIdSubclass As Long) As Long
Private Const WM_DESTROY = &H2
Public Const MAX_PATH = 260

Public Function StartNotify(hWnd As Long, Optional pidlPath As Long = 0) As Long
  Dim tCNE As SHChangeNotifyEntry
  Dim pidl As Long
 
  If (m_hSHNotify = 0) Then
        If pidlPath = 0 Then
            tCNE.pidl = VarPtr(0) 'This is a shortcut for the desktop pidl (to watch all locations)
                                  'only use this shortcut as a one-off reference immediately passed
                                  'to an API and not used again
        Else
            tCNE.pidl = pidlPath 'You can specify any other fully qualified pidl to watch only that folder
                                'Use ILCreateFromPathW(StrPtr(path))
        End If
      tCNE.fRecursive = 1
     
      'instead of SHCNE_ALLEVENTS you could choose to only monitor specific ones
      m_hSHNotify = SHChangeNotifyRegister(hWnd, SHCNRF_ShellLevel Or SHCNRF_InterruptLevel Or SHCNRF_NewDelivery, SHCNE_ALLEVENTS Or SHCNE_INTERRUPT, WM_SHNOTIFY, 1, tCNE)
     
     
      StartNotify = m_hSHNotify
       
  End If  ' (m_hSHNotify = 0)

End Function
Public Function StopNotify() As Boolean
StopNotify = SHChangeNotifyDeregister(m_hSHNotify)
End Function
Public Function LookUpSHCNE(uMsg As Long) As String

Select Case uMsg

Case &H1: LookUpSHCNE = "SHCNE_RENAMEITEM"
Case &H2: LookUpSHCNE = "SHCNE_CREATE"
Case &H4: LookUpSHCNE = "SHCNE_DELETE"
Case &H8: LookUpSHCNE = "SHCNE_MKDIR"
Case &H10: LookUpSHCNE = "SHCNE_RMDIR"
Case &H20: LookUpSHCNE = "SHCNE_MEDIAINSERTED"
Case &H40: LookUpSHCNE = "SHCNE_MEDIAREMOVED"
Case &H80: LookUpSHCNE = "SHCNE_DRIVEREMOVED"
Case &H100: LookUpSHCNE = "SHCNE_DRIVEADD"
Case &H200: LookUpSHCNE = "SHCNE_NETSHARE"
Case &H400: LookUpSHCNE = "SHCNE_NETUNSHARE"
Case &H800: LookUpSHCNE = "SHCNE_ATTRIBUTES"
Case &H1000: LookUpSHCNE = "SHCNE_UPDATEDIR"
Case &H2000: LookUpSHCNE = "SHCNE_UPDATEITEM"
Case &H4000: LookUpSHCNE = "SHCNE_SERVERDISCONNECT"
Case &H8000&: LookUpSHCNE = "SHCNE_UPDATEIMAGE"
Case &H10000: LookUpSHCNE = "SHCNE_DRIVEADDGUI"
Case &H20000: LookUpSHCNE = "SHCNE_RENAMEFOLDER"
Case &H40000: LookUpSHCNE = "SHCNE_FREESPACE"
Case &H4000000: LookUpSHCNE = "SHCNE_EXTENDED_EVENT"
Case &H8000000: LookUpSHCNE = "SHCNE_ASSOCCHANGED"
Case &H2381F: LookUpSHCNE = "SHCNE_DISKEVENTS"
Case &HC0581E0: LookUpSHCNE = "SHCNE_GLOBALEVENTS"
Case &H7FFFFFFF: LookUpSHCNE = "SHCNE_ALLEVENTS"
Case &H80000000: LookUpSHCNE = "SHCNE_INTERRUPT"

End Select
End Function
Public Function GetPathFromPIDLW(pidl As Long) As String
  Dim pszPath As String
  pszPath = String(MAX_PATH, 0)
  If SHGetPathFromIDListW(pidl, StrPtr(pszPath)) Then
    If InStr(pszPath, vbNullChar) Then
        GetPathFromPIDLW = Left$(pszPath, InStr(pszPath, vbNullChar) - 1)
    End If
  End If
End Function
Public Function Subclass(hWnd As Long, lpfn As Long, Optional uId As Long = 0&, Optional dwRefData As Long = 0&) As Boolean
If uId = 0 Then uId = hWnd
    Subclass = SetWindowSubclass(hWnd, lpfn, uId, dwRefData):      Debug.Assert Subclass
End Function

Public Function UnSubclass(hWnd As Long, ByVal lpfn As Long, pid As Long) As Boolean
    UnSubclass = RemoveWindowSubclass(hWnd, lpfn, pid)
End Function
Public Function FARPROC(pfn As Long) As Long
  FARPROC = pfn
End Function

Public Function F1WndProc(ByVal hWnd As Long, ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long, ByVal uIdSubclass As Long, ByVal dwRefData As Long) As Long

    Select Case uMsg

        Case WM_SHNOTIFY
            Dim lEvent As Long
            Dim pInfo As Long
            Dim tInfo As SHNOTIFYSTRUCT
            Dim hNotifyLock As Long
            hNotifyLock = SHChangeNotification_Lock(wParam, lParam, pInfo, lEvent)
            If hNotifyLock Then
                CopyMemory tInfo, ByVal pInfo, LenB(tInfo)
                Form1.HandleNotify tInfo.dwItem1, tInfo.dwItem2, lEvent
                Call SHChangeNotification_Unlock(hNotifyLock)
            End If

      Case WM_DESTROY
     
        Call UnSubclass(hWnd, PtrF1WndProc, uIdSubclass)
        'Exit Function
  End Select
 
  ' Pass back to default message handler.

      F1WndProc = DefSubclassProc(hWnd, uMsg, wParam, lParam)


Exit Function

End Function
Private Function PtrF1WndProc() As Long
PtrF1WndProc = FARPROC(AddressOf F1WndProc)
End Function

The form is just the start/stop buttons and a list:
Code:

Option Explicit

Public Function HandleNotify(dwItem1 As Long, dwItem2 As Long, idEvent As Long) As Long
Dim sArg1 As String, sArg2 As String
If dwItem1 Then
    sArg1 = GetPathFromPIDLW(dwItem1)
End If
If dwItem2 Then
    sArg2 = GetPathFromPIDLW(dwItem2)
End If
Dim sEvent As String
sEvent = LookUpSHCNE(idEvent)

List1.AddItem sEvent & ", Item1=" & sArg1 & ", Item2=" & sArg2


End Function

Private Sub cmdStart_Click()
StartNotify Me.hWnd
End Sub

Private Sub cmdStop_Click()
StopNotify
End Sub

Private Sub Form_Load()
Subclass Me.hWnd, AddressOf F1WndProc
End Sub

Private Sub Form_Unload(Cancel As Integer)
StopNotify
End Sub

Private Sub Form_Resize()
On Error Resume Next
List1.Width = Me.Width - 220
List1.Height = Me.Height - 1000
End Sub

Attached Files
Viewing all 1321 articles
Browse latest View live


Latest Images