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

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

[vb6] Extending VB's Image Control for PNG, TIFF, and more [v1.1]

$
0
0
UPDATED on 11 Oct 2015. Major revision. Added animated GIF and mutlipage TIFF support.
Any previous version must be thrown away. You do not want to run both in the same project, trust me!
12 Oct 2015: Added more robust method of activating images at runtime from controls loaded at design-time. See post #11 for more

The VB Image control is now more flexible. Want to display PNG and TIFF without using a 3rd party control? It can do that. Want to display 32 bit alpha-blended bitmaps? It can do that. Want to animate GIFs and display nice 32 bit alpha-blended icons? Now it can ;)

What's even more amazing is that this isn't limited to the image control. These images can be displayed in most things with a Picture property -- not every Picture property, but most. Want to add a nice PNG to a button, yep it can be done. Regardless, true transparency is maintained, nothing faked. Change the backcolor of some container, and you don't have to reload the image to fake transparency again. Someone changes themes on the pc & it doesn't effect the transparency.

Major Limitations:
1. Cannot see transparency in bitmaps while in design-view.
2. Cannot load PNG,TIFF while in design-view

How this class works.
1. Converts non-icon/gif/metafile formats to 32bpp premultiplied RGB bitmap if image contains transparency. AlphaBlend API is used to render the image in that case
2. Icons/Cursors are loaded as-is and rendered with DrawIconEx API if icon/cursor is 32bpp
3. GDI+ is used to load PNG/TIF/Animated GIF and convert to 32bpp or 24bpp bitmaps for display
4. Only images that need special rendering are custom drawn
5. Subclassing of the picture is performed for two reasons: a) handle rendering as just described and b) tell VB that a picture contains transparency so VB will repaint behind the image before it's drawn.

This version of the class has a few active methods.

1. SetImage. This is a replacement for VB's LoadImage and Set methods that pertain to images. It allows unicode filenames and can support TIF, PNG, 32bpp bitmaps & icons/cursors, PNG-encoded Vista-style icons, plus what VB supports.

2. SetSubImage. If a multipage TIFF or animated GIF has been loaded with option chosen for navigation, this method changes the page/frame.

3. WantEvents. Method allows you to receive an event to enable you to draw behind or on top of any image

4. EnableSubclassing. Allows you to turn off subclassing. By default, subclassing is enabled. But since subclassing in IDE is not safe, it is recommended you disable subclassing when working on your project. Turn it on when you want to view the images with transparency for a quick look-see. Then close your project normally and disable subclassing as desired. Subclassing cannot be disabled when project is compiled. Once disabled during IDE run-time, cannot be re-enabled until project closes -- safety measure.

5. SubImageCount. Returns number of frames/pages if animated GIF or multipage TIFF loaded

6. GetGIFAnimationInfo. Returns the frame duration and loop count for animated GIFs

I have included a very simple sample project. The project's images you see in design view are one that is premultiplied and one that is not. Both are 32 bit bitmaps that use the alpha channel.

Screenshot below are two VB image controls. You can clearly see they are drawing blended alpha channels. Background is transparent, soft edges and shadows. The updated zip below does contain an animated GIF example.

Name:  Untitled.jpg
Views: 69
Size:  36.6 KB

Suggestion: GIF/TIFF animation/navigation is not disabled when EnableSubclassing = False. I feel it should be. GDI+ can cause crashes too in IDE if it is not shut down properly and hitting END prevents proper shutdown. If you agree, you will want to make this modification:

Routine: SetImage
Add the following before the line: Select Case PropertyName
Code:

If Me.EnableSubclassing = False Then RequiredImageType = RequiredImageType And Not lifAllowNavigation
Attached Images
 
Attached Files

VBIDEUtils now open sources

$
0
0
Hi

I wrote very very long time ago (in 1999) the best VB6 addin : VBIDEUtils (You still can find it on a lot of web sites)
In the past I was selling it, but as I had shutdown VBDiamond 10 years ago, I haven't sell it anymore
I use it off course everyday in all my VB6 projects, to clean code, indent, add automatic error handling, optimize code...
It does better job than MZtools, and also far more other things.

Here is a small description.
VBIDEUtils is a great add-in for VB 5.0 and VB 6.0. With
this add-in, you can do :
- Code Repository
- Store VB Code, Classes, Projects
- Store files
- Store HTML pages
- Store HTML links
- Search through all the database
- Store VB Scripts
- Java Scripts
- Java
- Icons
- Use bookmarks
- Enhanced search
- Search through all the VB Web sites
- Synchronize your local DB with the DB of VBDiamond
- Synchronize code with the major VB Code sites
- Search for programming books on the Web
- Save your code as HTML pages
-
- .....
- Make search through a very extensive Book repository
- Indent easily your code, procedure, block, variables
- Add comment to your modules and procedures
- Find the corresponding ending block instruction
- Know all the APIs error name
- Clear the debug Window
- Change easily the taborder of all your controls
- Add customized error handler to your code
- Add enhanced error handler to your code with loggin, trace
- Show the KeyAscii table to help you coding functions
- An assistant to create your messagebox
- A Toolbar code generator
- Close all the unused windows in the VB IDE
- Spy the classname of each windows
- An Icon browser
- A DBCreator code generator
- An ActiveX documentor
- Export all code to HTML files (from the VB IDE or the VBCodedatabase)
- Import/Export to VCL and DCB files
- Export the VB Code from the VB IDE to HTML files
- Extract all the strings and translate them directly in the addin for further use of resources String and so internationalize your applications
- Change/Modify the tooltips all your controls
- Add new procedure/functions/properties easily with parameters, description....
- Get all dependencies of an executable or a VB project
- Analyze VB projects
- Search all the web in the VB sites directly from the addin
- Added a VB project explorer
- Profile your VB projects and detect dead code including dead variables, dead procedures...
- Add/remove line numbering in your code
- Control properties assistant
- Accelerator assistant
- Generate code to create toolbar at runtime
- Get easily code from several VB Code websites on the web
- Added an enhanced find in VB projects
- Added the automatic creation of connections strings for ADO
- Generate automatically DLL Base Adresses
- Generate GUID
- A lot of of other new features

I decided to release the sources in order to add new possibilities with the community here, and eventually, why not doing a MS Access version compatible, and a .NET Version.
I made a .NET version for the first version of .NET (very long time ago, in 2010), but due to a lack of time, I stopped it.

You will have certainly to compile it, and just call the function "AddToINI" to add it to your VB6 Addins list as I removed all the install part

So here are the sources, of VBIDEUtils.
Please, don't forget, it has bee written in 1999, so 15 years ago, and of course, if I had to rewrite it now, I will use other coding way for many things.
Also, some parts of the code are not used anymore, but, this is normal for a such old project.

If you add functionalities, please post them here it order to make it even better, and offer other to other VB Coder.

Otherwise, you can us the code in your own project, and if like VBIDEUtils or the code, just say hello to your neighbors and all people in the street, in real life, as there is a big lack of real life those days.

Enjoy.

Well, I tried to upload the ZIP with the whole sources, but it more than 2Mb.
I try to find a way

Well, the size of the attachment is limited to 500K
So if a moderator could do something for me?

In the meantime, here is a link : https://github.com/tannerhelland/VBIDEUtils

[VB6] Locale Sensitive Sorting

$
0
0
There are times when you need to sort in a locale-aware manner.

One of the more obvious cases is probably when generating cryptographic signatures for web services. These often require you to create a hash-based message authentication code (HMAC) based on inputs including a canonicalized URI, several HTTP header values including a timestamp, a secret key, and perhaps other items. These items normally have to be sorted so that the server end can reproduce the same HMAC by calculation, and that means both ends have to agree on the collating sequence.

Often you can get away with a lot because most of the characters are going to fall within the 7-bit ASCII range. But when they don't you need to be sure you are using the "invariant, string-oriented" collating sequence and not your user session collating sequence or one that takes language quirks into account.

Many HMAC sigs require that you hash the UTF-8 too, but it works if you first sort UTF-16LE Unicode and then re-encode as UTF-8 (same sequence).

And of course sorting gets used all over - though most uses aren't as sensitive as crypto processes can be.


Subtleties

Accented characters may sort earlier or later depending on the language. Ligatures (e.g. mediæval vs. mediaeval) need to be considered. "String compare" and "linguistic compare" differ. And on and on it goes.


Demo

This demo uses a simplistic Insertion Sort. This is quick and dirty, understood by most sorting fans, and importantly it is a stable sort so it will help showcase my point here.

Basically there is nothing special about it except that it uses CompareStringEx() in Kernel32.dll to compare strings within the sort. For those still using the unsupported Windows XP or earlier you may have to hack it a bit to make use of the aging CompareString() instead.

While the new entrypoint accepts locale string values instead of LCIDs, it may be worth noting that the older one comes in both ANSI and Unicode flavors.

The demo includes a sample list of string data as a Unicode text file. You can modify this with interesting cases you may know of. It has a brief list of "western" languages. You can add or remove values to that list within the code ot change the program to load them from a file too.

The list is loaded up and displayed in a flexgrid with back-colors from white through deepening greenish-blue shades that help make sorting differences easier to see when you try various collating sequence modifications. Because of the not-so-clever way this is done a string list of more than 255 elements will crash the program. ;)

Name:  sshot.png
Views: 113
Size:  25.4 KB


Requirements

VB6, because VB6 comes with MSHFlexgrid which is Unicode-aware. VB5 will work if you substitute another Unicode grid or use the crusty old MSFlexgrid and avoid "invalid in your locale's ANSI" characters.

Windows Vista or later, because of the new CompareStringEx() used here. If you modify the program to call CopmareSting() instead it works on downlevel unsupported Windows versions but you can't use locale strings and will have to change the pick list to use LCID values instead.

Sticking with Unicode support means "eastern" languages can be tested too.


Running the Demo

Nothing special required, and it should just unzip, open, and run even without compiling to EXE first. MSHFlexgrid comes with VB6 so you're set. VB5 users see Requirements section above.

Click the "Sort" button. Change the settings and "Sort" again. Scroll through the list of interesting cases - the scroll position should be stable between "Sorts" so look at the O'Leary case and flip sorting between "String Sort" and "Linguistic Sort" (i.e. "String Sort" not chosen). Ancien Régime is another interesting case.
Attached Images
 
Attached Files

[VB6] Use System's Format PropPage Dialog at Run Time

$
0
0
I've never needed this but a question here got me thinking it couldn't be that hard. Then I started searching the MSDN Library and found nothing I recognized as helpful. Then I searched the web and was shocked to find almost nothing at all.

Finally I found a forum post at another site that led to me Edanmo's old VB6 archives, and a breadcrumb of information there. That was great until I realized how little it covered... such as how to apply the results once you'd managed to raise the dialog in the first place!

As far as I can tell you're pretty much going to need UserControls to implement such features because I can't figure out how to get VB to let you "host" Property Page dialogs in a Form. But this is just a working sample to get you started, and there may be lots for you to discover once you begin fiddling with it.


Requirements

VB6 of course. This might also be converted to work in VB5 but I can't be sure since I haven't tried to.

Any 32- or 64-bit Windows versions that supports VB6 programs.

Microsoft Data Formatting Object Library 6.0 (SP6), i.e. msstdfmt.dll, which comes with VB6 (older service pack versions may possibly be compatible). This needs to be deployed since it isn't among the bits Microsoft ships as part of Windows these days.


What we have here

The demo package attached includes a UserControl that I have named "FmtTextBox" which is basically wrapping a MultiLine = False intrinsic TextBox, a clickable Image control "icon/button" of sorts, and a Variant. The idea is that instead of text, this control's value property (cleverly named "Value") can be any simple data type, and the visible/editable text is parsed-into/formatted-from this Variant Value.

So this makes a sort of non-bindable "TextBox" that handles formatting of many Variant subtypes... and lets the user change the format at run time.

There is also a helper Class that I have named "SettingsManager" designed to assist the program in persisting and restoring these settings between runs of the program.

Then there's the Standard EXE project with one Form that demonstrates the items above.


Running the Demo

Just unzip the attached archive and open the .VBP file in the VB6 IDE via Explorer. You can run it there or compile it first.

I have built "FmtTextBox" to hide its "edit the format button/icon" until the program toggles this on. In the demo a check/uncheck menu item controls this. Here's a peek:


Name:  sshot1.png
Views: 97
Size:  17.1 KB

The menu controlling the "edit formatting" button


Name:  sshot2.png
Views: 83
Size:  16.9 KB

User can click here to open the Property Page dialog


Name:  sshot3.png
Views: 79
Size:  22.0 KB

The Property Page dialog


Whew

This was a lot more than I bargained for when I started it. Partly because a UserControl was needed but more so because using a UserControl fronting a Variant added complexity, and mostly because there was a ton of "guess then cut-and-try work" involved in figuring out how to make use of the PPG dialog once I could get it to show up!

No claims this is bug-free. Consider it a technique demonstration. I'm not sure how practical it might be to do for other controls, but perhaps that isn't needed as much for most controls. As it is I've never needed any of this myself.

But it sure killed some time waiting for phone calls and such.
Attached Images
   
Attached Files

[VB6] clsCursor - Setting the IDC_HAND & Other Cursors Properly

$
0
0
Most solutions that addresses the MousePointer property's lack of support for some of the standard cursors (most notably the "hand" cursor) tend to be based on either converting the standard cursor to a MouseIcon/DragIcon or setting the cursor using the SetCursor API function during the MouseMove event. While both approaches produce generally acceptable results most of the time, they still have obvious shortcomings that makes them appear like cheap workarounds. Converting a standard cursor to a MouseIcon/DragIcon, for instance, doesn't support animated cursors. Setting the cursor during the MouseMove event, on the other hand, exhibits an annoying flickering as the cursor rapidly alternates between the class cursor and the specified cursor. The proper way of dealing with this, according to MSDN, is through subclassing:

Quote:

Originally Posted by MSDN
The Window Class Cursor

When you register a window class, using the RegisterClass function, you can assign it a default cursor, known as the class cursor. After the application registers the window class, each window of that class has the specified class cursor.

To override the class cursor, process the WM_SETCURSOR message. You can also replace a class cursor by using the SetClassLong function. This function changes the default window settings for all windows of a specified class. For more information, see Class Cursor.

The small and simple class module (and supporting standard module) in the attachment below contains all of the logic needed to set the desired standard cursor for all of the specified windowed and/or windowless controls. A demo project is also included that illustrates its use.


Name:  clsCursor Demo.png
Views: 96
Size:  5.8 KB


Subclassing, of course, has its disadvantages as well, especially when debugging in the IDE. However, for those seeking more professional looking results, there's no better way of overriding the class cursor than via subclassing.
Attached Images
 
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 Attachment 130681 ...
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

[VB6] JNode - JSON revisited

$
0
0
JNode is a "little brother" or alternative to my JsonBag.

This take on the subject is a kind of stripped down minimal implementation of a VB6 JSON handling Class. Don't read anything into the name (the "node" part is just a node, as in a node in a tree of objects).

JNode 1.0 weighs in at less than half the size of JsonBag 2.4 in source code terms. The main difference is that JNode makes no attempt to use any API calls or pointer operations to try to gain performance: everything is straight-up VB6 code. While this may reduce performance a bit most client-side real world JSON applications just aren't performance-critical anyway. In testing it seems plenty fast enough for most uses.


Requirements

Written as VB6 code, so you'd need VB6. But it is pretty "clean" VB6 so it may import right in and work in any VBA6 or later host, even 64-bit ones. Might work without changes in VB5 too, though I haven't looked at that.

A version of Windows that can run VB6 programs and has or can support Microsoft's Scripting Runtime - since JNode uses Dictionary objects.


Remarks

I had originally left out serializing adding white space. I decided to add that in though because a big hunk of JSON without it can be frustrating to read through when you are debugging.

This is not a drop-in replacement for JsonBag, but it should be reasonably close and usable as a replacement in many applications without a lot of trouble.

I haven't created any fancy documentation for JNode, which I have found almost nobody has been reading anyway. Perhaps the code in the test cases included in the demo Project is enough. I might address this later though.

I miss a few things, for example the CloneItem property in JsonBag. But not enough to bloat JNode by adding such a thing into it. Most of the things I use it for can by done simply by creating a new JNode and assigning its JSON property to the JSON property of the JNode I want to clone. Slow perhaps, but not that slow and good enough for most purposes anyway.
Attached Files

VB6 pipe-based UDT-serializing/deserializing InMemory

$
0
0
VB6 has a nice feature, when it comes to UDTs.

It has builtin serializing/deserializing routines, which are capable to write
an even complex and deeply nested UDT to a File per VBs Put-statement
(no matter whether this UDT contains dynamic members like Arrays or Strings) -
and later on it can read this UDT back from the File it was saved to (per Get).

Too bad, that this feature is restricted to the FileSystem (VBs Open, Put and Get calls) -
and not exposed in a way, to make it usable InMemory (writing and reading to ByteArrays).

The little Demo here does just that, with a little workaround (using Named-Pipes),
which VBs Open-Statement is able to understand and deal with.

The main-functionality sits in a little Class, named: cPipedUDTs ...
which throws an Event which allows you, to write your UDT for serialization
into the Pipe - and another Event for the opposite direction (the deserialization).

Not much code - and easy to understand I think (Demo contains comments as well):
UDTsPipeSerializing.zip

Have fun!

Olaf
Attached Files

VB6 LightWeight COM and vbFriendly-BaseInterfaces

$
0
0
Some stuff for the advanced VB-users among the community (or the curious) ...

I was recently working on some things in this area (preparations for the
C-Emitter of a new VB6-compiler, with regards to "C-style defined Classes") -
and this Tutorial is more or less a "by-product".

I've just brought parts of it into shape, since I think this stuff can be
useful for the community even whilst working with the old compiler.

To gain more IDE-safety (and keep some noise out of the Tutorial-Folders),
I've decided to implement the Base-stuff in its own little Dll-Project:
vbInterfaces.dll

The sources for this Helper-Dll are contained in an appropriate Folder
(vbFriendlyInterfaces\vbInterfaces-Dll\...) in this Tutorial-Zip here:
vbFriendlyInterfaces.zip

The Dll-Project currently contains vbFriendly (Callback-) Interfaces for:
- IUnknown
- IDispatch
- IEnumVariant
- IPicture

Feel free to contribute stuff you think would be useful to include in the
Dll-Project itself - although what it currently contains with regards to
IUnknown and IDispatch, allows to develop your own vtMyInterface-stuff
already "separately" (in a normal VB-StdExe-project for example).

Before entering the Tutorial-Folder and start running the Examples, please make
sure, that you compile the vbInterfaces.dll first from the above mentioned Folder.

The above Zip contains currently a set of 10 Tutorial-Apps, all in their own Folders
(numbered from 0 to 9, from "easy to more advanced") - and here is the
Tutorial-FolderList:
.. 0 - LightWeight COM without any Helpers
.. 1 - LightWeight LateBound-Objects
.. 2 - LightWeight EarlyBound-Objects
.. 3 - LightWeight Object-Lists
.. 4 - Enumerables per vbIEnumVariant
.. 5 - MultiEnumerations per vbIEnumerable
.. 6 - Performance of vbIDispatch
.. 7 - Dynamic usage of vbIDispatch
.. 8 - Simple SOAPDemo with vbIDispatch
.. 9 - usage of vbIPictureDisp

For the last two Tutorial-Demos above I will post separate CodeBank articles,
since they are larger ones - and deserve a few Extra-comments.

Maybe some explanations for NewComers to the topic, who want to learn what
the terms "LightWeight COM", or "C-style Class-implementation" mean:

First, there's a clear separation to be made between "a Class" and "an Object",
since these terms mean two different things really, which we need to look at separately.

- "a Class" is the "BluePrint", which lives in the static Memory of our running Apps or Dlls
- "an Object" (aka "an Instance of a Class") lives as a dynamic Memory-allocation (which refers back to the "BluePrint").

And VB-Objects (the ones we create as Instances from a VB-ClassModules "BluePrint" per New) are quite "large animals" -
since they will take up roughly 116 Bytes per instance-allocation, even when they don't contain any Private Variable Definitions.

A Lightweight COM-Object can be written in VB6 (later taking up only as few as 8Bytes per Instance),
when we resort to *.bas-Modules (similar to the code-modules one would write in plain C).

Here's some Code, how one would implement that (basically the same, as contained in Tutorial-Folder #0):

Let's say we want to implement a lightweight COM-Class (MyClass), which has only a single
Method (AddTwoLongs) in its Public Interface (IMyClass).

We start with the "BluePrint", and the VB-Module which implements that "C-style" would contain only:
Code:

Private Type tMyCOMcompatibleVTable
  'Space for the 3 Function-Pointers of the IUnknown-Interface
  QueryInterface As Long
  AddRef        As Long
  Release        As Long
  'followed by Space for the single Function-Pointer of our concrete Method
  AddTwoLongs    As Long
End Type

Private mVTable As tMyCOMcompatibleVTable 'preallocated (static, non-Heap) Space for the VTable

Public Function VTablePtr() As Long 'the only Public Function here (later called from modMyClassFactory)
  If mVTable.QueryInterface = 0 Then InitVTable 'initializes only, when not already done
  VTablePtr = VarPtr(mVTable) 'just hand out the Pointer to the statically defined mVTable-Variable
End Function

Private Sub InitVTable() 'this method will be called only once (and is thus not "performance-critical")
  mVTable.QueryInterface = FuncPtr(AddressOf modMyClassFactory.QueryInterface)
  mVTable.AddRef = FuncPtr(AddressOf modMyClassFactory.AddRef)
  mVTable.Release = FuncPtr(AddressOf modMyClassFactory.Release)
 
  mVTable.AddTwoLongs = FuncPtr(AddressOf modMyClassFactory.AddTwoLongs)
End Sub

I assume, the above is not that difficult to understand (most "static things" are easy this way) -
what it ensures is, that it "gathers things in one static place" - in this case:
"Function-Pointers in a certain Order" - this "List of Function-Pointers" remains (in its defined order)
behind the static UDT-variable mVTable - and that was it already...

What remains (perhaps a bit more difficult to understand to "make the leap") is,
how the above code-definition will interact, when we now come to the "dynamic part"
(the Objects and their instantiations from a BluePrint).

To have the dynamic part more separated, let's use an additional module (modMyClassFactory):

And as the choosen name (modMyClassFactory) suggests, this is the part which finally hands out
the new Instances (similar to one of the 4 exported Functions, which any ActiveX-Dll needs to support,
which is named 'DllGetClassFactory' for a reason).

So let's show the ObjectCreation-Function in that *.bas Module first:
Note, that UDT struct-definitions are only there for the compiler to "have info about needed space" -
(I've marked these Length-Info parts in light orange below - and the dynamic allocation part in magenta)...
Code:

Private Type tMyObject 'the Object-Instances will occupy only 8Bytes (that's half the size of a Variant-Type)
  pVTable As Long
  RefCount As Long
End Type
 
'Factory Helper-Function to create a new Class-Instance (a new Object) of type IMyClass
Public Function CreateInstance() As IMyClass '<- this Type is defined in a little TypeLib, contained in TutorialFolder #0
Dim MyObj As tMyObject 'we use our UDT-based Object-Type in a Stack-Variable for more convenience
    MyObj.pVTable = modMyClassDef.VTablePtr 'whilst filling its members (as e.g. pVTable here)
    MyObj.RefCount = 1 '<- the obvious value, since we are about to create a "fresh instance"

Dim pMem As Long
    pMem = CoTaskMemAlloc(LenB(MyObj)) 'allocate space for our little 8Byte large Object
    Assign ByVal pMem, MyObj, LenB(MyObj) 'copy-over the Data from our local MyObj-UDT-Variable
    Assign CreateInstance, pMem 'assign the new initialized Object-Reference to the Function-Result
End Function

What remains now, is to provide the Implementation-code for the 4 VTable-methods (which is contained in that same Module)
Code:

'IUnknown-Implementation
Public Function QueryInterface(This As tMyObject, ByVal pReqIID As Long, ppObj As stdole.IUnknown) As Long '<- HResult
  QueryInterface = &H80004002 'E_NOINTERFACE, just for safety reasons ... but there will be no casts in our little Demo
End Function

Public Function AddRef(This As tMyObject) As Long
  This.RefCount = This.RefCount + 1
  AddRef = This.RefCount
End Function

Public Function Release(This As tMyObject) As Long
  This.RefCount = This.RefCount - 1
  Release = This.RefCount
  If This.RefCount = 0 Then CoTaskMemFree VarPtr(This) '<- here's the dynamic part again, when a Class-instance dies
End Function

'IMyClass-implementation (IMyClass only contains this single method)
Public Function AddTwoLongs(This As tMyObject, ByVal L1 As Long, ByVal L2 As Long, Result As Long) As Long '<- HResult
  Result = L1 + L2 'note, that we set the Result ByRef-Parameter - not the Function-Result (which would be used for Error-Transport)
End Function

Finally (to have it complete) a Helper-Function and a few APIs, which are contained in another small *.bas Module
Code:

Declare Function CoTaskMemAlloc& Lib "ole32" (ByVal sz&)
Declare Sub CoTaskMemFree Lib "ole32" (ByVal pMem&)
Declare Sub Assign Lib "kernel32" Alias "RtlMoveMemory" (Dst As Any, Src As Any, Optional ByVal CB& = 4)
 
Function FuncPtr(ByVal Addr As Long) As Long 'just a small Helper for the AddressOf KeyWord
  FuncPtr = Addr
End Function

So, what was (codewise) posted above, is complete - and how a bare-minimum-implementation
for a lightweight "8-Byte large COM-object" could look like in VB6 (and not much different in C) -
no need to copy it over into your own Modules because as said, this is all part of the first little
Demo (in Tutorial-Folder #0, which also includes the needed TypeLib to run the thing).

Happy studying and experimenting... ;)

Olaf
Attached Files

[VB6, Vista+] A compact function to retrieve any property by name, locally formatted

$
0
0
This is related to the greatly expanded property system available in Vista+, and is closely related to the more complete tour of the system in my other projects.

While this method is inefficient and shouldn't be used for large numbers of properties or large numbers of files*, if you just need a few specific properties from a single file this method is a quick way to get them. The results appear as they do in Explorer's Details view; according to your locale, with units, etc. The key shortcut here is the SHGetPropertyStoreFromParsingName function and other PS_ APIs, which let us skip over all the IShellItem interface work.

Requirements
-Windows Vista or higher
-oleexp 2.0 or higher (no new release related to this code)

Usage
After putting the below code in a module, just call the GetPropertyDisplayString(file, property) function, it will return a string with the property as it appears in Explorer. For example, System.Dimensions on a JPG file might return "640 x 480", or System.Width as "100 pixels"; or an AVI's System.Length as "01:30:20". It's more than just raw numbers (although those can be retrieved too; see the larger project).
sResult = GetPropertyDisplayString("C:\myfile.jpg", "System.Width")

Code
Code:

Public Declare Function PSGetPropertyKeyFromName Lib "propsys.dll" (ByVal pszName As Long, ppropkey As PROPERTYKEY) As Long
Public Declare Function PSFormatPropertyValue Lib "propsys.dll" (ByVal pps As Long, ByVal ppd As Long, ByVal pdff As PROPDESC_FORMAT_FLAGS, ppszDisplay As Long) As Long
Public Declare Function SHGetPropertyStoreFromParsingName Lib "shell32" (ByVal pszPath As Long, pbc As Any, ByVal Flags As GETPROPERTYSTOREFLAGS, riid As UUID, ppv As Any) As Long
Public Declare Function PSGetPropertyDescription Lib "propsys.dll" (PropKey As PROPERTYKEY, riid As UUID, ppv 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 Function GetPropertyDisplayString(szFile As String, szProp As String) As String
'Gets the string value of the given canonical property; e.g. System.Company, System.Rating, etc
'This would be the value displayed in Explorer if you added the column in details view
Dim pkProp As PROPERTYKEY
Dim pps As IPropertyStore
Dim lpsz As Long
Dim ppd As IPropertyDescription

PSGetPropertyKeyFromName StrPtr(szProp), pkProp
SHGetPropertyStoreFromParsingName StrPtr(szFile), ByVal 0&, GPS_DEFAULT, IID_IPropertyStore, pps
PSGetPropertyDescription pkProp, IID_IPropertyDescription, ppd
PSFormatPropertyValue ObjPtr(pps), ObjPtr(ppd), PDFF_DEFAULT, lpsz
SysReAllocString VarPtr(GetPropertyDisplayString), lpsz
CoTaskMemFree lpsz


End Function

Include the following in your module only if you're not using the mIID.bas module from the oleexp thread:
Code:

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 Function IID_IPropertyStore() As UUID
'DEFINE_GUID(IID_IPropertyStore,0x886d8eeb, 0x8cf2, 0x4446, 0x8d,0x02,0xcd,0xba,0x1d,0xbd,0xcf,0x99);
Static IID As UUID
 If (IID.Data1 = 0) Then Call DEFINE_UUID(IID, &H886D8EEB, CInt(&H8CF2), CInt(&H4446), &H8D, &H2, &HCD, &HBA, &H1D, &HBD, &HCF, &H99)
  IID_IPropertyStore = IID
 
End Function
Public Function IID_IPropertyDescription() As UUID
'(IID_IPropertyDescription, 0x6f79d558, 0x3e96, 0x4549, 0xa1,0xd1, 0x7d,0x75,0xd2,0x28,0x88,0x14
Static IID As UUID
 If (IID.Data1 = 0) Then Call DEFINE_UUID(IID, &H6F79D558, CInt(&H3E96), CInt(&H4549), &HA1, &HD1, &H7D, &H75, &HD2, &H28, &H88, &H14)
  IID_IPropertyDescription = IID
 
End Function

ALTERNATIVE: Get directly by PROPERTYKEY
Now that I've published a complete list of PROPERTYKEY's from propkey.h, if you include the mPKEY.bas module from the oleexp project, you can use those directly like this:
Code:

Public Function GetPropertyKeyDisplayString(szFile As String, pkProp As PROPERTYKEY) As String
Dim pps As IPropertyStore
Dim lpsz As Long
Dim ppd As IPropertyDescription

SHGetPropertyStoreFromParsingName StrPtr(szFile), ByVal 0&, GPS_DEFAULT, IID_IPropertyStore, pps
PSGetPropertyDescription pkProp, IID_IPropertyDescription, ppd
PSFormatPropertyValue ObjPtr(pps), ObjPtr(ppd), PDFF_DEFAULT, lpsz
SysReAllocString VarPtr(GetPropertyKeyDisplayString), lpsz
CoTaskMemFree lpsz
End Function

Common Properties
For a full list of system properties, see propkey.h in the SDK (or unofficial copies online); or the larger projects I have that will enumerate them all.

Otherwise, see the MSDN post Metadata Properties for Media Files for the popular ones.

-------------------------------
* - When working with large numbers of files, or user-selectable properties, it's best to implement IShellItem and IPropertySystem based solutions from the ground up.

[vb6] SavePictureEx (Unicode compatible and a bit more)

$
0
0
Not anywhere close to deep-thought-provoking code nor is it any breakthrough. I thought I'd share a workaround I've been using for awhile.

VB's SavePicture uses existing APIs that have the ability to be unicode compatible. If we bypass VB and use those APIs instead, problem solved.

In addition, depending on how the picture was created and assigned in VB, the original data is cached and that data can be saved. For example, if you load a JPG during design-view into a VB picture property, the actual JPG data is preserved, but if you try to call VB's SavePicture, it is saved as a bitmap and not a JPG. We can save the the image as a JPG copy. This does not mean VB or the APIs can convert the image to JPG, it simply means that if the original image format is maintained, it can be saved. This also applies to GIFs and icons that contain multiple sub-icons. Anyone can take the routine provided below and super-size it to allow optional parameters that would be used to identify requests for image conversion to other formats. I'll leave that to you.

Rule of thumb is that VB will cache original data when pictures are loaded during design-time, not runtime.

In the code below, notice the blue-highlighted text? If the blue text were removed, then if the passed tgtPicture parameter contained the original image data for GIF/JPG, then the original image data would be saved.
Code:

' APIs used
Private Declare Function SHCreateStreamOnFileEx Lib "shlwapi.dll" (ByVal pszFile As Long, ByVal grfMode As Long, ByVal dwAttributes As Long, ByVal fCreate As Long, ByVal reserved As Long, ByRef ppstm As IUnknown) As Long
Private Declare Function GetFileAttributesW Lib "kernel32.dll" (ByVal lpFileName As Long) As Long

Code:

Public Sub SavePictureEx(tgtPicture As IPictureDisp, ByVal FileName As String)

    Dim oStream As IUnknown, oPicture As IPicture
    Dim lRtn As Long, bFlagCreate As Long
    Const INVALID_FILE_ATTRIBUTES As Long = -1&
    Const STGM_CREATE As Long = &H1000&
    Const STGM_WRITE As Long = &H1&
    Const FILE_ATTRIBUTE_NORMAL = &H80&
   
    If tgtPicture Is Nothing Then Exit Sub
    If tgtPicture.Handle = 0& Then Exit Sub
   
    If GetFileAttributesW(StrPtr(FileName)) = INVALID_FILE_ATTRIBUTES Then bFlagCreate = 1&
    lRtn = SHCreateStreamOnFileEx(StrPtr(FileName), STGM_WRITE Or (STGM_CREATE * bFlagCreate), _
                            FILE_ATTRIBUTE_NORMAL, bFlagCreate, 0&, oStream)
    If lRtn = 0& Then
        Set oPicture = tgtPicture
        If tgtPicture.Type = vbPicTypeBitmap Then
            oPicture.SaveAsFile ByVal ObjPtr(oStream), 1&, lRtn ' always save as bitmap
        Else
If oPicture.KeepOriginalFormat Then
            oPicture.SaveAsFile ByVal ObjPtr(oStream), 0&, lRtn ' save original data if it exists
        Else
            oPicture.SaveAsFile ByVal ObjPtr(oStream), 1&, lRtn ' save using VB's default SavePicture logic
        End If
        Set oStream = Nothing ' closes the file
    Else
        Err.Raise lRtn, "SavePictureEx"
    End If

End Sub

The above code is compatible with XP and above. The API SHCreateStreamOnFileEx doesn't exist on lower operating systems. If required, that API can be replaced with a custom function that:
- creates a compatible stream object (CreateStreamOnHGlobal API)
- saves the data to that stream (oPicture.SaveAsFile)
- creates a file (CreateFile API)
- reads the data from the stream pointer to the file (ReadFile API)
- close the file and unlock/release the stream

FYI: IUnknown and IPicture are valid objects in VB, they are just hidden by default from intellisense

[VB6] Register any control as a drop target that shows the Explorer drag image

$
0
0

Dragging from Explorer

Dragging from Firefox

So as we all know, the drag cursor for a VB drop target is a hideous relic of the Windows 3.1 days. No more! Ever since XP, there has been an interface called IDropTargetHelper that automatically shows the proper drag image. And not just for Explorer file drops; the drag image you see in any modern program will now also appear on your VB6 drop target. And more good news, it's only a tiny bit more complicated than using the normal OLEDragDrop features (this method completely replaces the native OLE DnD stuff and controls should be 'None' for OLEDropMode- the IDropTarget class has DragEnter, DragOver, DragLeave, and Drop events if you need them).

Requirements
-Windows XP or higher
-oleexp.tlb (any version; no new release is associated with this project and the interfaces used date back to the 1.x versions)

How It Works

-First, a class module that implements IDropTarget and contains an instance of IDropTargetHelper needs to be created
-The only tricky thing is getting the file list from the IDataObject; but the sample class handles this and just passes a file list back.
-Then, any control can call the RegisterDragDrop API to become a target supporting the new images!

Note that while the example just accepts file drops with the standard CF_HDROP format, you have the full data object passed from the source of the drag, and could retrieve any format it contains (there's tons of clipboard formats; text, html, images, etc).

Note on Unicode support: All the code is designed to support Unicode, but the file names in the sample project are displayed in a regular VB textbox which cannot show extended characters-- but the file names returned are in Unicode and if displayed in a Unicode-enabled control will be rendered correctly.

Code
cDropTarget
Code:

Option Explicit
Private Declare Function DragQueryFileW Lib "shell32.dll" (ByVal hDrop As Long, ByVal iFile As Long, Optional ByVal lpszFile As Long, Optional ByVal cch As Long) As Long
Private Declare Function SysReAllocStringLen Lib "oleaut32.dll" (ByVal pBSTR As Long, Optional ByVal pszStrPtr As Long, Optional ByVal Length As Long) As Long
Private Declare Function CoCreateInstance Lib "ole32" (rclsid As Any, ByVal pUnkOuter As Long, ByVal dwClsContext As Long, riid As Any, pvarResult As Any) As Long
Private Declare Function CLSIDFromString Lib "ole32" (ByVal lpszGuid As Long, pGuid As Any) As Long

'IDropTargetHelper is what lets us show the drag image
Private pDTH As IDropTargetHelper

Private Const CLSID_DragDropHelper = "{4657278A-411B-11D2-839A-00C04FD918D0}"
Private Const IID_IDropTarget = "{4657278B-411B-11D2-839A-00C04FD918D0}"

Implements IDropTarget

Private Sub Class_Initialize()
Dim dhiid As UUID
Dim dthiid As UUID

Call CLSIDFromString(StrPtr(CLSID_DragDropHelper), dhiid)
Call CLSIDFromString(StrPtr(IID_IDropTarget), dthiid)
Call CoCreateInstance(dhiid, 0&, CLSCTX_INPROC_SERVER, dthiid, pDTH)
End Sub

Private Sub IDropTarget_DragEnter(ByVal pDataObj As oleexp3.IDataObject, ByVal grfKeyState As Long, ByVal ptX As Long, ByVal ptY As Long, pdwEffect As oleexp3.DROPEFFECTS)
  Debug.Print "DragEnter"
 
  Dim pt As oleexp3.POINT
  pt.x = ptX
  pt.y = ptY
 
  pDTH.DragEnter Form1.Picture1.hWnd, pDataObj, pt, pdwEffect

End Sub

Private Sub IDropTarget_DragLeave()
Debug.Print "DragLeave"

pDTH.DragLeave
 
End Sub

Private Sub IDropTarget_DragOver(ByVal grfKeyState As Long, ByVal ptX As Long, ByVal ptY As Long, pdwEffect As oleexp3.DROPEFFECTS)
    Debug.Print "DragOver"

  Dim pt As oleexp3.POINT
  pt.x = ptX
  pt.y = ptY

    pDTH.DragOver pt, pdwEffect
   
    'Notice that the text shows 'Move' in the caption; you can change pdwEffect to something else
    'pdwEffect = DROPEFFECT_COPY
    'pdwEffect = DROPEFFECT_NONE 'this shows that a drop is not allowed, and the drop event won't fire
End Sub

Private Sub IDropTarget_Drop(ByVal pDataObj As oleexp3.IDataObject, ByVal grfKeyState As Long, ByVal ptX As Long, ByVal ptY As Long, pdwEffect As oleexp3.DROPEFFECTS)
Debug.Print "Drop"
Dim idx As Long
  Dim pt As oleexp3.POINT
  pt.x = ptX
  pt.y = ptY
 
 pDTH.Drop pDataObj, pt, pdwEffect
 
 'For this project, we're just going to accept the files and pass back what
 'operation we did with them. But to add more functionality, you can look
 'at grfKeyState; that will tell you if ctrl is being held so you can move,
 'or if the right mouse button is down and you should show a menu of options
 Dim fmt As FORMATETC
 fmt.cfFormat = CF_HDROP
 fmt.TYMED = TYMED_HGLOBAL
 fmt.dwAspect = DVASPECT_CONTENT
 fmt.lindex = -1
 
 Dim stg As STGMEDIUM
 
 If pDataObj.QueryGetData(fmt) = S_OK Then
    pDataObj.GetData fmt, stg
    Dim nFiles As Long, sFiles() As String
    Dim i As Long
    Dim sBuffer As String
    nFiles = DragQueryFileW(stg.Data, &HFFFFFFFF, 0, 0)
    ReDim sFiles(nFiles - 1)
    For i = 0 To nFiles - 1
        SysReAllocStringLen VarPtr(sBuffer), , DragQueryFileW(stg.Data, i)
        DragQueryFileW stg.Data, i, StrPtr(sBuffer), Len(sBuffer) + 1&
        sFiles(i) = sBuffer
    Next
Else
    Debug.Print "failed querygetdata"
End If
   

  pdwEffect = Form1.DropFiles(sFiles, grfKeyState)
End Sub

Sample Form
Code:

Option Explicit
Private Declare Function RegisterDragDrop Lib "ole32" _
        (ByVal hWnd As Long, ByVal DropTarget As IDropTarget) As Long
Private Declare Function RevokeDragDrop Lib "ole32" (ByVal hWnd As Long) As Long

Private cIDT As cDropTarget

Public Function DropFiles(sFiles() As String, KeyState As Long) As DROPEFFECTS
'Do whatever with the files
Text1.Text = ""
Text1.Text = Join(sFiles, vbCrLf)
DropFiles = DROPEFFECT_NONE 'We didn't do anything with the dropped files here,
                            'but if you do move/copy/link them, report that back
End Function

Private Sub Form_Load()
Set cIDT = New cDropTarget
Call RegisterDragDrop(Picture1.hWnd, cIDT)

End Sub

Private Sub Form_Unload(Cancel As Integer)
Call RevokeDragDrop(Picture1.hWnd)
End Sub

Dragging FROM controls
Note that if you combine this method with a control that's a drag source for files using my SHCreateDataObject/SHDoDragDrop method, you will now see the Explorer icon right on the control you're dragging from, and the filetype icon will now show up. No additional coding required. At some point in the future I'll release a sample combining them, but in the mean time they are completely compatible if someone else wants to. (I have tested and confirmed this, it's just ripping out the file listview that has dozens of other features and thousands of lines of code associated with it-- testing is easier on a fully complete file view-- isn't practical)

------------------------------------------
Project updated: Forgot DragDropHelper coclass can't be used on XP; updated to use it by CLSID with CoCreateInstance. Code for Class_Initialize updated in sample project and above in this post.
Attached Files

[VB6] PicSave - Simple SavePicture as GIF, PNG, JPEG

$
0
0
Sometimes you need a better SavePicture() function. Not a lot better, just one that can save in some compressed format instead of just BMP format. Like JPEG usually, or PNG. Well this one does that, and throws in GIF as well though as usual (being based on GDI+) those tend to come out dithered and sort of crap in general.

What we have here is a simple preclared-instance Class with one method: SavePicture().

You give it a StdPiture, a file name (yes, it can save using Unicode paths), which format you want, and for JPEG you can add a "quality" in percent. It saves to disk, not to Byte arrays.

Nothing here people haven't seen before. This is just a "stripped to essentials" rendition of the well worn theme.


It only requires a version of Windows with IE 5 or later. It uses GDI+ but most systems with IE 5 or later cover that as well. In any case it should work on nearly anything you run anymore.

There are no 3rd party DLLs required, and not even any typelibs. Just add PicSave.cls to your Projects.


The attachment contains a simple demo. Its bulk is all source image data.


The StdPicture you pass to it must have a bitmap handle. In practical terms this means you may have to pass it the persistant-image property (.Image) if you have drawn your picture onto a Form, PictureBox, etc. and there is no provision for dealing with metafile vector images.


Notes:

New attachment incorporating feedback from discussion below to address issues encountered when GDI v. 1.1 is in play, running on 64-bit Windows, etc.

Also note that this makes no effort to handle transparency or alpha-channel translucency for GIF or PNG output. It saves simple "whole bitmap" images. If you load a picture with transparency into a StdPicture and save it back using this class the transparency is lost.
Attached Files

[VB6] Creation of GIF-animation with the transparent background.

$
0
0
Hi everyone!
This project allows to create an GIF animations with the transparent background. As far as i know the GDI+ doesn't allow to set the property of "Disposal Method" in the "Graphic Control Extension" block, therefore each next frame is overlayed to the previous frame. For the opaque frames it's doesn't matter. In order to solve this issue i decide to change the needed bytes manually in the raw GIF file.
It allows to prevent the restrictions of the transparent frames. Also this example contains the oct-tree class, which calculates the optimal palette for the each frame. There are the ability of the additional settings: threshold of the transparency, duration, and number of the loops for entire animation. For the disabling of the transparency enough set the threshold to zero. The greater the value of the threshold field the greater semitransparent pixels become transparent completely.

Name:  Безымянный.png
Views: 56
Size:  105.4 KB
Regards,
Кривоус Анатолий.
Attached Images
 
Attached Files
Viewing all 1486 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>