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

YFrameowrk - A framework that will shorter development time for data oriented apps

$
0
0
Hello,

I have been using this set of routines (YFramework) for almost 10 years now.

I developed to shorted my development time for business apps that are for data oriented.

There is not rocket science involved here! It is just very simple once you understand the fundamentals.

In the zip file I have also provided a working application to show as to how to use YFramework.

I have used VBRichClinet5's SQLite (as I am its fan) so you will require this framework's DLLs to use it. Just download it from links on this forum.

How it works?
It works by loading and saving data from unbound controls to and from database. For this to work what you have to do when designing your forms (windows) is to name the controls as per the field names whose data you want to load in that particular control. For example if you want to load data from a file named UserName then place a TextBox on the form and name the TextBox as UserName.

There are many other useful functions and procedures in the framework. Please do explore it. Like for example to select the content of TextBox when it gets focus, force typing of number keys only for accepting numeric inputs, easily retrieve value of one particular field by just calling a single function, in the same way easily update value of one single field by calling a single function

All kind of feedback (whether good or bad is welcome).

I want to expand this framework but as it seems to satisfy my development requirements I have not expanded it as much as I should have. Olaf your ideas are welcome here as YFramework is based on VBRichClient5.

Note what I am sharing is not complete as some of the code is dependent on commercial ActiveX Components like Essential Toolkit, TextControl and Xtreme SuitePro.

Hope members of this forum will benefit from YFramework. And enjoy using it as much as I enjoy using it even today.

Thank you,

Yogi Yang

[VB6] Code Snippet: Get file overlay (e.g. shortcut arrow), inc. customs like DropBox

$
0
0
Everyone is familiar with the shortcut arrow-- this is an example of an overlay icon, a status indicator placed on top of another icon. Most existing VB file browser examples handle showing these by checking the attributes to see if it's a link or shared. But there's other icons- several more placed by Windows indicating things like offline files, security locks, permission shields, as well as custom ones- one of the most popular is DropBox. So if you want your app to display these as well, you need to look beyond file attributes to the IShellIconOverlay interface.

Requirements
-Windows XP or higher
-oleexp v3.3 or higher (03 Dec 2015 release or newer)
-oleexp addon mIID.bas added (included in oleexp download)

Usage
The GetOverlayIconIndex returns a 1-based index number, so you should determine a valid choice by checking if >0. Assigning an invalid choice (<1 or >15) may result in the main icon not being rendered at all.
If you're using a control such as a ListView or TreeView and are not already assigning overlays, they're typically added like this:
lvi.StateMask = LVIS_OVERLAYMASK
lvi.State = INDEXTOOVERLAYMASK(overlayindex)

where lvi is an LVITEM and this is followed with LVM_INSERTITEM or LVM_SETITEM. TreeViews are nearly identical. Do not set the overlay if there is none (the valid results mentioned above... do not set the statemask/state if the index is 0 or -1).

The Code
Code:

Public Declare Function ILCreateFromPathW Lib "shell32" (ByVal pwszPath As Long) As Long
Public Declare Sub CoTaskMemFree Lib "ole32.dll" (ByVal PV As Long)
Public Declare Function SHGetDesktopFolder Lib "shell32" (ppshf As IShellFolder) As Long

Public Function GetOverlayIconIndex(sPath As String, sFile As String) As Long
'Returns the overlay index for a file icons (like the shortcut arrow)
Dim iDL As Long
Dim psf As IShellFolder
Dim povr As IShellIconOverlay
Dim pUnk As oleexp3.IUnknown
Dim pcid As Long, pche As Long, lAt As Long

iDL = ILCreateFromPathW(StrPtr(sPath))
If iDL Then
    Set psf = GetIShellFolder(isfDesktop, iDL)
    psf.ParseDisplayName 0&, 0&, StrPtr(sFile), pche, pcid, 0&
    If (psf Is Nothing) = False Then
        Set pUnk = psf
        pUnk.QueryInterface IID_IShellIconOverlay, povr
        If (povr Is Nothing) Then
            Debug.Print "GetOverlayIconIndex failed to get ishelliconoverlay " & sFile
        Else
            If pcid Then
                Dim pio As Long
                On Error Resume Next 'CRITICAL: files with no overlay return -1 AND raise a runtime error
                povr.GetOverlayIndex pcid, VarPtr(pio)
                GetOverlayIconIndex = pio
                On Error GoTo 0
            Else
                Debug.Print sFile & "::GetOverlayIconIndex no child pidl"
            End If
        End If
    Else
        Debug.Print "GetOverlayIconIndex::no IShellFolder"
    End If
    Call CoTaskMemFree(pcid)
    Call CoTaskMemFree(iDL)
Else
    Debug.Print "GetOverlayIconIndex::no pidl"
End If

End Function

'Generic support functions you may already have if working with IShellFolder
Public Function GetIShellFolder(isfParent As IShellFolder, pidlRel As Long) As IShellFolder
  Dim isf As IShellFolder
  On Error GoTo out

  Call isfParent.BindToObject(pidlRel, 0, IID_IShellFolder, isf)

out:
  If Err Or (isf Is Nothing) Then
    Set GetIShellFolder = isfDesktop
  Else
    Set GetIShellFolder = isf
  End If

End Function
Public Function isfDesktop() As IShellFolder
  Static isf As IShellFolder
  If (isf Is Nothing) Then Call SHGetDesktopFolder(isf)
  Set isfDesktop = isf
End Function

Notes
-If a file doesn't have an overlay, the COM interface throws a runtime error (0x80004005 automation error unspecified). The code snippet above uses On Error Resume Next to suppress this, but if you have 'Break On All Errors' enabled, it will come up.

-The overlay index returned includes the standard shortcut and share overlays; you can eliminate code checking for them separately.

-Here's the INDEXTOOVERLAYMASK function mentioned earlier if you need it:
Code:

Public Function INDEXTOOVERLAYMASK(iOverlay As Long) As Long
  '  INDEXTOOVERLAYMASK(i)  ((i) << 8)
  INDEXTOOVERLAYMASK = iOverlay * (2 ^ 8)
End Function

UPDATE- Code updated to free child pidl as well; not freeing it causes memory leakage. Call CoTaskMemFree(pcid)

[vb6] Compressing Multiple Property Values into a Long

$
0
0
A lot of verbiage for a couple of 1-line functions. This posting can be beneficial to usercontrol creators and anyone else that would like to reduce the number of variables that are used primarily for property values that contain ranges.
Code:

Private Sub pvSetProperty(PackedValues As Long, PropMask As Long, NewValue As Long)
    PackedValues = (PackedValues And Not PropMask) Or ((PropMask And -PropMask) * NewValue)
End Sub
Private Function pvGetProperty(PackedValues As Long, PropMask As Long)
    pvGetProperty = (PackedValues And PropMask) \ (PropMask And -PropMask)
End Function

I think we all know that a Long value has 32 bits. Most of us also know that we can compress multiple separate values into a long value to save 'space'. For simplicity, we'll define 'space' as individual property settings including their variable declarations and any action required to persist/save those individual settings.

Let's first review different ways of looking at the 32 bits of a Long value
- 2 words, 1 word = 16 bits, 16 bits * 2 words = 32 bits
- 4 bytes, 1 byte = 8 bits, 8 bits * 4 bytes = 32 bits
- 8 nibbles, 1 nibble = 1/2 a byte = 4 bits, 4 bits * 8 nibbles = 32 bits
A word can hold 65536 different values, 0-65535 inclusively, 2^16 bits = 65536
A byte can hold 256 different values, 0-255 inclusively, 2^8 bits = 256
A nibble can hold 16 different values, 0-15 inclusively, 2^4 bits = 16
Another way to look at a Long is as a hexadecimal: FF FF FF FF. Each F is a nibble, each FF is a byte
(*) For our purposes, we will not be using Words. Since we are discussing saving multiple property settings into one Long value, it is highly unlikely you will have a property setting that will consist of 65K options.

So how many property settings can you jam into one Long value? It depends on how many options each property has and how many bits are needed to cover the range of those options. For example, if you had 32 boolean properties, you could fit all 32 into one Long value since a boolean only needs 1 bit: 0=false, 1=true. How do we determine how many bits are needed for the range of the property? A table is useful for those that hate to do the bit count and bit shifts needed.

Range ... Bits Needed
0-1 ... 1 :: 2^1-1 = 0 to 1, 2 options max, i.e., False or True
0-3 ... 2 :: 2^2-1 = 0 to 3, 4 options max
0-7 ... 3 :: 2^3-1 = 0 to 7, 8 options max
0-15 ... 4 :: 2^4-1 = 0 to 15, 16 options max
0-31 ... 5 :: 2^5-1 = 0 to 31, 32 options max
0-63 ... 6 :: 2^6-1 = 0 to 63, 64 options max
0-127 ... 7 :: 2^7-1 = 0 to 127, 128 options max
0-255 ... 8 :: 2^8-1 = 0 to 255, 256 options max
(*) Caveat. For the functions provided above, the high bit (bit 4 in nibble 8) of the Long can only be used for a boolean value. It must never be part of a property that requires more than 1 bit. Nor must the high bit be part of the mask sent to those functions. This is simply to keep the functions simple vs. dealing with the toggling of the sign bit. For ranged properties, consider a Long as a maximum of 31 available bits. Boolean properties can use the functions posted above, but it is easier to toggle them directly. That is also shown below as the 'tip" at end of posting.

Once you know how many bits are needed, you then need to find a location in the Long to place those bits. Simply put, you need consecutive bits available within the Long. The bits can wrap around from 1 nibble to another nibble. In this example we don't have any properties yet assigned to the Long, so 2 bits will fit into the first nibble, leaving 2 bits left in that nibble. If we had another property that required more than 2 bits, we can use the remaining 2 bits of the 1st nibble and the difference placed in the 2nd nibble. We could start in any nibble, on any bit, as long as consecutive bits are available. Do not include the high bit in any ranged property, only as a boolean value.

Now that you can determine how many bits are needed for each property and can determine which of your properties can fit into the Long, you need a bit mask to be able to locate those bits and shift those bits. The shifting is needed in order to place the bits in the proper location within the Long and also to extract those bits and return them to a value that fits within the range of the property options. Again, not all like the math involved with bit shifting, so we will kinda use some shortcuts.

You have bits counted for a property. What is its mask?
Instead of the steps below, you can copy & paste this into your immediate window instead, just fill in the 3 parameters
Code:

' if bits shift into the high bit, overflow error will occur
' bitPos is where in the nibble the first bit will be stored: 1-4
' nrBits is total number of bits for the value: 1-31
' nibble is the one where start of value is stored: 1-8
' FYI: nibble 8, bit position 4 is: &H80000000
bitPos=1:nrBits=1:nibble=1:? "&H" & Hex(((2^nrBits-1)*2^(bitPos-1))*(16^(nibble-1))) & IIF((nibble-1)*4+nrBits+bitPos-1=16,"&","")

Step 1. Start with a blank mask, hexadecimal, so we are looking at each nibble: &H00000000. Each 0 is a nibble and the first nibble is the far right zero. Updating the mask starts with the zero that matches the the nibble position where the 1st bit of the property will be stored.

Step 2. Calculate the mask for that nibble and replace the zero with the hex code for the mask
Start bit position in nibble, number of bits used for that nibble, mask needed
Bit position 1 :: 1 bit used &H1, 2 bits used &H3, 3 bits used &H7, 4 bits used &HF
Bit position 2 :: 1 bit used &H2, 2 bits used &H6, 3 bits used &HE, just 3 bits remain from position #2
Bit position 3 :: 1 bit used &H4, 2 bits used &HC, just 2 bits remain from position #3
Bit position 4 :: always &H8 since only 1 bit is available at position #4
Name:  Nibble.jpg
Views: 65
Size:  24.4 KB
Example
:: 2 bits needed to store the property and starting on nibble 1, bit position 3
:: Blank mask to start with, targeting nibble #1: &H00000000
:: Get mask for nibble. Starting on bit #3, needing 2 bits, mask is &HC. Update mask: &H0000000C

Step 3. If the bits needed to store the property have not all been placed in the mask, move to the next higher nibble, first bit position and repeat previous step until all bits have been accounted for. Example of wrap-around bits:
:: 6 bits needed to store the property and starting on nibble 5, bit position 4 (final bit in that nibble)
:: Blank mask to start with, targeting nibble #5: &H00000000
:: Get mask for nibble 5. Starting on final bit, so mask is &H8. Update mask: &H00080000
:: Get mask for nibble 6. Starting on 1st bit, all 4 bits needed. Update mask: &H00F80000
:: Get mask for nibble 7. Starting on 1st bit, 1 bit needed. Update mask: &H01F80000

(*) Note. If the mask ends up being first 4 nibbles and bit 4 of nibble 4 is used, you must append an ampersand to the end of mask else VB will treat it as a negative Integer value, i.e., &H8000&

And a quick example. Lets say we have a property that requires 3 bits and starts in nibble #6 at bit position #4. Using the steps above, the mask for the property would be &H3800000. The Long variable that holds the property settings is named: m_Properties
Code:

Public Property Get WidgetStyle() As WidgetStyleEnum
    WidgetValue = pvGetProperty(m_Properties, &H3800000)
End Property
Public Property Let WidgetStyle(Value As WidgetStyleEnum)
    ' validate Value is within the range of WidgetStyleEnum else abort
    pvSetProperty m_Properties, &H3800000, Value
End Property

Tip: For boolean properties, it is easier to change the property directly using XOR than to call the above 1-liners. Those 1-liners can still be called, but you must convert the boolean True to an absolute value, not -1. Using XOR is really simple since you have the mask. Let's say the mask is &H80000000, using bit #4 in nibble #8
Code:

Public Property Get AutoSize() As Boolean
    AutoSize = (m_Properties And &H80000000)
End Property
Public Property Let AutoSize(Value As Boolean)
    If Not Value = Me.AutoSize Then
        m_Properties = m_Properties Xor &H80000000
    End If
End Property

Note: If the high bit of the Long is used for a boolean property, it must be handled like the tip above. The 1-liners are not designed to handle the high bit. They can be modified to handle it via IFs.

Tip: If a boolean property is very often used in your code, it is a good strategy to have it occupy the high bit of the Long variable used to store the properties. Why? You can easily test to see if it is set by testing the sign of the Long variable, i.e., if m_Properties < 0 then the high bit is set. Easier than testing if (m_Properties And &H80000000) is non-zero.
Attached Images
 

VB6 - The case for UTF-8

$
0
0
Some people have been critical of the fact that my clsCNG.cls does not preserve Unicode. So with this post, I have attempted to correct that situation. I am by no means any kind of expert on Unicode, and until recently I have only cursed its existence. The Unicode standards are very loose (much like SMTP), but at least there is a fair amount of information out there if you are willing to dig for it.

ClsCNG.cls is a general purpose class designed to perform encryption services on anything that is passed to it. With one small change to the StrToByte routine, it now detects double-wide characters and passes the entire string instead of the just the low order bytes. But with that flexibility comes a new "gotcha". In the image below, you will see the Russian Unicode string does not produce the correct Hash. That is because it is a mixed string, consisting of both ASCII and Russian Unicode. This is not uncommon in HTML code, and this particular string was intercepted from http://www.humancomp.org/unichtm/unichtm.htm using a packet sniffer. There are a couple of ways around that issue. One way is to remove the NULL characters associated with the ASCII characters. The other way is to encode the string using UTF-8. This is the preferred method and is demonstrated using the "Hash UTF-8" button. I should mention at this point that I am using the TextBox provided by the Microsoft Forms 2.0 Object Library to display the Unicode characters. The regular TextBox only accepts ASCII.

The change to the StrToByte routine allowed the implementation of 2 new routines called "ByteToStrShort" and "HexToStrShort". These routines create a string without the intermediate NULL bytes and shorten the process time.

Using UTF-8 introduces another "gotcha". The Unicode standard, and in particular UTF-8, only works with true ASCII characters less than 128 (&H80). If there is any chance that your application could pass ANSI characters above &H7F, you should provide a detection routine to avoid passing it to "clsCNG.cls". DO NOT use "StrConv", as it will cause problems, especially if you are using a non-Latin System Locale.

That's the easy part. Recognizing an incoming byte string as Double-wide Unicode or UTF-8 is difficult to say the least. There is no standard methodology to deal with it. HTTP and XML will announce their intention to use UTF-8. For the Russian page below, the line:
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
was provided. There is another methodology called BOM which is sometimes employed. It stands for Byte Order Mark, and is used to specify "Big Endian" or "Little Endian" order for encoded strings. Since UTF-8 uses bytes instead of words, Endian has little meaning, and it is often referred to as a "UTF-8 Signature" (EF BB BF). HTML5 requires an application to respect it, and it takes precedence over the notification. Unicode standards do not restrict or require it's use, but if you are building an application where you can control both ends, it would make sense to use it. In either case, your application should be prepared to recognize and remove it before display.

Mozilla (and I assume other browsers as well) will use the information provided to determine the type of encoding used on incoming data, and if that fails or is not provided, it then uses a heuristic approach. So I set out to provide my own routine to detect UTF-8. My first reaction was to question the need to scan the data twice. If you are going to convert the string if UTF-8 is detected, why not just attempt to convert the string and respond to any errors. Unfortunately, MultiByteToWideChar does not return encoding errors; it just does the best that it can. So the scan is necessary to detect if the incoming string is indeed UTF-8. The IsUTF8 routine is my interpretation of a C++ routine that I found on the net. It has not been tested extensively, and it could probably be executed more efficiently. Determining if an incoming string is Unicode or not is a different story, and I have not found a reliable way to do that. I tested the Microsoft "IsTextUnicode" function, but as most of the literature indicated, it is virtually useless.

I discovered another "gotcha" with MultiByteToWideChar. It will return NULL characters at the end of the string, depending on the length. That is not a problem with C++, as NULL characters signify the end of the string. But with VB, that is a problem because it identifies the string length in it's definition. So the FromUTF8 routine was modified to remove any NULL characters.

If you convert the Russian sample, you will notice that the UTF-8 string is shorter than the original (due to ASCII NULL removal), but the Chinese sample converts to a longer string. That is because the Chinese sample converts 2 byte characters to mostly 3 byte characters. Even considering the downsides, UTF-8 appears to be the most logical solution.

J.A. Coutts
Attached Images
 
Attached Files

Here's my non CopyMemory based solution for swapping byte order

$
0
0
Often times I find myself trying to write a file for another program to read, or read a file written by another program, in which that other program uses Big Endian byte order, even though VB6 uses Little Endian byte order. I've used CopyMemory solutions before, which are easy to write as you don't have to think about how to multiply and divide in order to perform bit shifts. I've even written a DLL file in assembly language (see this thread http://www.vbforums.com/showthread.p...89#post5000089 ) which has functions that directly use the SHL and SHR bit shifting opcodes, but then I need to make sure my DLL file is always packaged with my VB6 program or the program won't run on somebody else's computer when they try to run it. So below is my code that uses only mathematical operations to rearange the bytes in Integer and Long data types in order to swap byte order. Any Little Endian number will be converted to Big Endian, and any Big Endian number will be converted to Little Endian.

Code:

Private Function FixInteger(ByVal Value As Integer) As Integer
FixInteger = (Value And &H7FFF) \ &H100
If Value < 0 Then FixInteger = FixInteger Or &H80&
FixInteger = FixInteger Or (Value And &H7F&) * &H100
If Value And &H80 Then FixInteger = FixInteger Or &H8000
End Function

Private Function FixLong(ByVal Value As Long) As Long
FixLong = (Value And &H7FFFFFFF) \ &H1000000
If Value < 0 Then FixLong = FixLong Or &H80&
FixLong = FixLong Or (Value And &HFF00&) * &H100&
FixLong = FixLong Or (Value And &HFF0000) \ &H100&
FixLong = FixLong Or (Value And &H7F&) * &H1000000
If Value And &H80& Then FixLong = FixLong Or &H80000000
End Function

Change Private to Public if you are going to put these function definitions in a Module and plan to call them from elsewhere in your program.


The one type of thing that you CAN'T swap byte order with, using only mathematical techniques, is a floating point (Single or Double) variable. Currency type variables MIGHT be able to be byte swapped with purely mathematical techniques, but it would be quite difficult, due to the multiple of 10000 that you need to take into account. Short of using a dedicated DLL file, or a CopyMemory technique, floating point values will remain Little Endian when working with VB6.

VB6 - DNS Filter Service

$
0
0
Version 2.5 of DNSFilSvc utilizes version 3.2 of WinpkFilter from NT Kernel Resources. This version of WinpkFilter is not compatible with older versions because it supports both IPv4 and IPv6. The driver has been signed with a Microsoft approved certificate, so it now loads on all versions of Windows. Although the driver supports IPv6, DNSFilSvc does not. It uses long words (4 bytes) to store IPv4 addresses, whereas IPv6 requires 16 bytes for each address.

Like Version 2, DNSFilSvc consists of 2 programs; the actual service, and a management program to load and manage the service. Although the service can install itself, the management program is needed to store a couple of parameters. Because the Service runs in Session 0, the Registry values must be placed in the Registry in a location that allows System access. Because the management program runs in Session 1 or more, it has no actual interaction with the service. It deals entirely with the Service Manager (services.msc), and because it accesses restricted parts of the Registry, the management program must be "Run as Administrator".

Why would you need this kind of filter? If you operate a DNS Server, hackers can use that server to launch DoS (Denial of Service) attacks against other networks. Because DNS uses UDP packets on port 53, the advertized IP addresses where responses are sent, is not necessarily where the requests originated from. They can be spoofed. How do we know this? Here is just one example:
21:23:25 Request from 99.239.40.201 for any record for isc.org.
Doing an online port scan on this address:
Domain: CPE6c198ff33353-CM00fc8db88650.cpe.net.cable.rogers.com
IP Address: 99.239.40.201
City: Barrie
Region: ON
Country Name: Canada
Country Code: CA CA

Port Type Status
53 domain Closed
This machine does not even offer DNS service (at least not to the outside world).

DNSFilSvc filters out the excess requests. However, hackers figured out that they could get around the filter by slightly modifying each request. In this example:
00:19:11 Request from 183.56.172.145 for A-record for 6483220-0-3073944721-3608005795.ns.183-56-172-145-ns.dns-spider.myxns.cn.
the first number (6483220) was incremented for each request. So I added a DropList feature, where addresses for abusers could be manually added. The DropList file will be created in the same directory as the service the first time the service is activated. After adding an address to the file, the service must be restarted. The first 3 addresses I added to my own file were for a German provider that refused to address the over 13,000 requests per day that their servers were sending to our server (and those were just the ones that made it past the filter). All 3 servers had been blocked twice by our DNS server for sending over 20 requests per second, and after twice warning them, they are now permanently blocked. Any requests from these 3 servers are simply dropped by the filter.

This filter would not be suitable for a high volume server. For that kind of server, direct use of the Kernel Mode driver would be more appropriate.

Note: DNSFilSvc was designed to be run in Development mode as well as a Service. To compile the service, change the IsService flag to True.

J.A. Coutts
Attached Images
 
Attached Files

This allows you to convert any normal array into a byte array.

$
0
0
It uses a Variant for the input parameter so that you don't need to have a specific array type (Byte, Integer, etc) when putting the array into the parameter. It then uses API functions, rather than VB6 functions, for handling the SafeArray so as to be usable regardless of the data type of the array, regardless of the number of dimensions, and regardless of the lower bounds of the dimensions. It copies the entire content of the array (as long as it's a fairly normal type, not something with variable length entries like an array of strings or variants) to a byte array. This is very useful if you want to treat the array as a single chunk of data, such as for input to various functions that act on a single large piece of data. These might be checksum, CRC, or hash type functions, or even an encryption function. This should work with any arrays of any of the numeric data types (Byte, Integer, Long, Currency, Single, or Double).

Below is the code for this function, as well as the declare statements that you will need to make it work.
Code:

Private Declare Sub CopyMemory Lib "kernel32.dll" Alias "RtlMoveMemory" (ByRef Destination As Any, ByRef Source As Any, ByVal Length As Long)
Private Declare Sub SafeArrayAccessData Lib "oleaut32.dll" (ByVal psa As Long, ByRef ppvData As Any)
Private Declare Sub SafeArrayUnaccessData Lib "oleaut32.dll" (ByVal psa As Long)
Private Declare Function SafeArrayGetDim Lib "oleaut32.dll" (ByVal psa As Long) As Long
Private Declare Function SafeArrayGetElemsize Lib "oleaut32.dll" (ByVal psa As Long) As Long



Private Function AnyArrayToBytes(ByVal SafeArray As Variant) As Byte()
Dim SArrayPtr As Long
Dim ElemSize As Long
Dim DimCount As Long
Dim ElemsInDim As Long
Dim TotalElems As Long
Dim DataSize As Long
Dim DataPtr As Long
Dim Bytes() As Byte
Dim n As Long

CopyMemory SArrayPtr, ByVal VarPtr(SafeArray) + 8, 4
If SArrayPtr = 0 Then Exit Function
DimCount = SafeArrayGetDim(SArrayPtr)
ElemSize = SafeArrayGetElemsize(SArrayPtr)

TotalElems = 1
For n = 0 To DimCount - 1
    CopyMemory ElemsInDim, ByVal SArrayPtr + 16 + n * 8, 4
    TotalElems = TotalElems * ElemsInDim
Next n

DataSize = TotalElems * ElemSize
ReDim Bytes(DataSize - 1)
SafeArrayAccessData SArrayPtr, DataPtr
CopyMemory Bytes(0), ByVal DataPtr, DataSize
SafeArrayUnaccessData SArrayPtr

AnyArrayToBytes = Bytes()
End Function



Here's some code to test it out. Make sure that your Form1 has the property AutoRedraw set to True.
Code:

Private Sub Form_Load()
Dim a(3, 1) As Currency
Dim b() As Byte

b() = AnyArrayToBytes(a)
Print UBound(b)
End Sub


The value printed on Form1 should be 63.
Here's why. There are 2 dimensions. The upper bounds are 3 and 1. The lower bounds are both 0. So this is a size of 4 in the first dimension, and 2 in the second dimension. This makes 8 elements. Since each element is of Currency type, which occupies 8 bytes, this gives 8*8=64 bytes. Since the byte array returned from the function has 0 as the lower bound, the upper bound is 63.

StdDataFormat as a data parser

$
0
0
People often rely on String to binary data type coercion, but it can fall down (hard) in many scenarios.

There is always the cross-locale issue with many data types (Booleans, numbers, and date/time in particular). Then there are custom values e.g. Yes/No.


I'd hoped that this ParseTypes class, awkward as it is, would help parse and convert string text into strongly typed data. ParseTypes is based on the StdDataFormat object used by data binding.

I can't find the interfaces of this object that get used by data binding sources and sinks. In any case they don't seem to be exposed by the typelib of MSSTDFMT.DLL at all.

So as an aid to experimenting with this I am using a one-row fabricated ADO Recordset with DataFormat properties set on its fields. One field for each data type. Then I create a clone of this Recordset without DataFormat objects so I can get the strongly typed data out.

Ideally if this was going to be useful we'd find a way to work with StdDataFormat objects more directly and omit the use of these Recordsets. However after testing this doesn't seem anywhere near as useful as I had originally hoped anyway. :mad:

See the screen shot:

Name:  sshot.png
Views: 16
Size:  18.6 KB

Some of these are using Format strings, others are using additional StdDataFormat properties.

Case 4 might appear to be working but it isn't. The Format gets ignored and default CDate() conversion gets used instead, yielding an incorrect value. Case 5 addresses this by using the UnFormat event and "manual" code to perform the parsing and conversion.

Cases 6 to 8 are also flawed and are working only because those are the default CCur() conversions. The Format strings do not get used.

Caes 9 and 10 are a little weird, but working as desired. See Form1's code to see how ParseTypes1 was set up for TypeName = "String" data.


So while this isn't complete (as in completely working) it still works for some cases, so I'm posting it here as sample code. Hopefully somebody will be able to point out improvements... or ideally some other library that can do this very thing in a lighter manner with broader case coverage (for example actually parsing date/time string values according to a format string).

Dealing with date/time parsing in the UnFormat event works, but at that point you may as well skip all of this plumbing and just write a simpler date/time parsing function with the same logic in it. My hope was that even if relatively low-performance StdDataFormat would make it easy to change formats simply by changing the Format string instead of rewriting parsing code. Oh well!

I'm not sure where the "magic" happens here, at least the magic of the working cases. This seems to be mostly within MSSTDFMT.DLL even though the documentation for StdDataFormat suggests this is done by "the database" (which probably means by ADO or DAO depending on the case).
Attached Images
 
Attached Files

VB6 Threading-Examples using the vbRichClient5 ThreadHandler

$
0
0
As the Title says, two Threading-Examples which make use of the vbRichClient5-cThreadHandler-Class.
(as requested in this thread here: http://www.vbforums.com/showthread.p...=1#post4991011)

Make sure (in addition to downloading the SourceCode), that you download and install a new RC5-version (>= 5.0.40)
first before running the second, more advanced example (since it requires SQLites support for "FileURIs",
which were not yet recognized in the cConnection.CreateNewDB-method in RC5-versions below 5.0.40).

Here's the SourceCode for the two examples:
ThreadingRC5.zip

The Zip contains two Project-Folders (_Hello World and ThreadedDirScan) -
please make sure, before running the Main-VB-Projects in those Folders,
to compile the appropriate ThreadLib-Dlls from their own LibProject-SubFolders - then
placing the compiled Thread-Dll-Files in the ParentFolder (where the Main-Projects reside).

Ok, how does it work - best we start with the simpler Example, the one in the _Hello World-Folder:

VB6-Threading works best and most stable (since it was designed for that), when the
"threaded Routines" reside in a compiled ActiveX-Dll(Class) - that's the one thing which
is a bit of a "hurdle" for those who never used or compiled ActiveX-Dll-Projects so far.

But it's really quite simple... When you start out fresh - and plan to use threading
(because you have a routine which is a long-runner, blocking your UI) - then the
first step should be, to move that critical Function (and its Sub-Helper-Routines) into:
1) a Private Class in your Main-Project first
- test it there, so that you're sure everything works Ok
- also check that this Class gets everything over Function-Parameters and doesn't rely on "global Variables" outside of it

if you already have such a Class in your Main-Project - all the better - you can now move this Class:
2) as a Public Class into a new ActiveX-Dll-Project (setting its Class-Instancing-Property to 5 - MultiUse)

In case of the _Hello World-Demo, this ThreadClass' is named cThread and its Code-Content looks entirely normal:
Code:

Option Explicit

Public Function GetThreadID() As Long
  GetThreadID = App.ThreadID
End Function

Public Function StringReflection(S As String) As String
  StringReflection = StrReverse(S)
End Function

Just two simple Routines, you plan to execute on the new Thread, which
your compiled SimpleThreadLib.dll is later instantiated on (by the Main-Thread).

As alread mentioned, you can now compile this ActiveX-Dll Project into its ParentFolder (_Hello World),
where the Main-StdExe-Project resides.

This Project (ThreadCall.vbp in _Hello World) contains only a single Form, which in turn has this code:

For instantiation of the above ThreadDll-ThreadClass (cThread)
Code:

Option Explicit
 
Private WithEvents TH As cThreadHandler 'the RC5-ThreadHandler will ensure the communication with the thread-STA

Private Sub Form_Load() 'first let's instantiate the ThreadClass (regfree) on its own thread, returning "a Handler" (TH)
  Set TH = New_c.RegFree.ThreadObjectCreate("MyThreadKey", App.Path & "\SimpleThreadLib.dll", "cThread")
End Sub

And for Execution of the two Thread-Functions (from within Form_Click) it contains:
Code:

Private Sub Form_Click()
Dim StrResult As String, ThreadID As Long
  Cls
  Print Now; " (ThreadID of the Main-Thread: " & App.ThreadID & ")"; vbLf
  Print "Let's perform a few calls against the ThreadClass which now runs on its own STA "; vbLf
 
  'first we demonstrate synchronous Calls against the Thread-Instance, which was created regfree in Form_Load
  StrResult = TH.CallSynchronous("StringReflection", "ABC")
  Print "Direct (synchronous) StringReflection-Call with result: "; StrResult
 
  ThreadID = TH.CallSynchronous("GetThreadID")
  Print "Direct (synchronous) GetThreadID-Call with result: "; ThreadID; vbLf
 
  'now the calls, which are more common in threading-scenarios - the asynchronous ones, which don't
  'make the caller wait for the result (instead the results will be received in the Event-Handler below)

  TH.CallAsync "StringReflection", "ABC"
  TH.CallAsync "GetThreadID"
 
  Print "The two async calls were send (now exiting the Form_Click-routine)..."; vbLf
End Sub
 
'Our TH-Object is the clientside ThreadHandler, who's able to communicate with the Thread
'raising appropriate Events here, when results come back (in case of the async-calls)

Private Sub TH_MethodFinished(MethodName As String, Result As Variant, ErrString As String, ErrSource As String, ByVal ErrNumber As Long)
  If ErrNumber Then Print "TH-Err:"; MethodName, ErrString, ErrSource, ErrNumber: Exit Sub
 
  Print "MethodFinished-Event of TH for: "; MethodName; " with Result: "; Result
End Sub

That's all - I hope the above code-comments are sufficient - feel free to ask, when something is not clear.

Forgot to attach a ScreenShot of the Output produced by the Form_Click-Event above:


Will describe the second, more advanced example in a follow-up post in this thread.

Olaf
Attached Files

Visual Basic Advance Timer

$
0
0
Did you coded or experienced a timer with more then 60 seconds interval
or a timer with maximum cap limit ?

Here it all now :

Functions:-
  1. Intervals 1 , 5 , 10 and 15 minutes
  2. Maximum Timer Life 1 , 2 ,3 and infinity hours


Name:  TimerCodePreview.jpg
Views: 78
Size:  45.5 KB

Code:
Code:

Public Function StartTimer(bInterval As Double, Optional bMax As Double)
Dim MiliCounter  As Double, Infinity As Boolean
If bMax = 0 Then
Infinity = True
End If
mMax = bMax ‘Maximum Second , -1 for infinity
mInterval = bInterval ‘Interval Second
MiliCounter = -1 ‘bypass millisecods (timer is for seconds only)
StartTime = GetTickCount() ‘Get currunt system timer tick
Do While TimerEnabled = True
TimePassed = Int((GetTickCount() – StartTime) / 1000) ‘Convert to seconds
If Infinity = False Then
If TimePassed > mMax Then TimerEnabled = False ‘look for Maximum Second
End If
If (TimePassed Mod mInterval = 0) And (TimePassed > MiliCounter) Then
‘TimePassed Mod mInterval = 0  ‘look for Interval Second
‘TimePassed > MiliCounter      ‘look for next second
MiliCounter = TimePassed
‘/////////////Main Interval Code////////////
Debug.Print “time consumed “; TimePassed
Call InvokeIntervalEvent
‘///////////////////////////////////////////
End If
DoEvents
Loop
End Function

Call the function as This:
call StartTimer( 1 * 60, 1 * 3600)
i.e StartTimer for 60 sec intervel and 1 hour life

Happy Coding…:)


Example is coded in following Post
Attached Images
 

mdlSSE: Using SSE instructions (floating point related) in VB6 (in ASM)

$
0
0
Hello all!
I've developed this module to allow the use of SSE (SSE2 and 3) operations, to compute floating point operations directly by the CPU (and in 1 clock!).
SSE support the sum, sub, mul, div, and some other functions, applied in "matrices". Those are just arrays of floats. SSE (one) supports 8 operands (single precision), and it will apply the same operation to all. For example, we can have 2 arrays A and B, with their items labeled as A1, A2, etc. So, if we apply the sum operation, the result would be RESULT = A + B, so RESULT1 = A1 + B1, and so on until RESULT4 = A4 + B4.
SSE2 handles double precision, but it does use just 4 operands (A1, A2, B1, B2).

Overall I've seen some nice stuff done with SSE, like obtaining the size (projection) of a vector into other vector; mostly 3D related. But this module it's just a simple wrapper for the simpler operations.

This module allocates some memory which later is loaded with some assembly. Since the "operations" that can be done with SSE are stored in just 1 byte of the assembly, every time you call the module to do some operation, the code changes that byte accordingly.
When there is no need to use the SSE module anymore, call the free function, which releases the memory allocated previously. Source ASM file inclueded (compiled with fasm).

However, I thought that it would be nice to share it with all the devs.
The attached file includes a crude example.

Licence: Do whatever you want with the source, but a shout in your "about" would be great :bigyello:
Attached Files

[VB6] GetAdaptersInfo Example

$
0
0
See GetAdaptersInfo function for the details.

This sample program makes some basic use of the API call and the results it returns to report on network adapters in your system. If you need more information you can easily expand upon it to extract multiple IP addresses where they exist, etc.

There are some limitations when using early versions of Windows, so see the MSDN link above if you need support for Windows XP or earlier.

Sample output, Adapter 0 is not connected to a network:

Code:

2 adapter(s) found.

Adapter 0:
        Description = Realtek RTL8191SU Wireless LAN 802.11n USB 2.0 Network Adapter
        AdapterIndex = 23
        Name = {E9D4C1E7-8714-4545-A74B-A2FF60453A00}
        Type = 71
        Address = 00-12-71-BA-C4-34
        IP = 0.0.0.0
        GatewayIP = 0.0.0.0

Adapter 1:
        Description = Realtek PCIe GBE Family Controller
        AdapterIndex = 11
        Name = {F4747718-7BF6-4369-97C0-76A31249F698}
        Type = 6
        Address = 00-14-71-21-A3-11
        IP = 192.168.0.100
        GatewayIP = 192.168.0.1

This works even when WMI is not installed, the WMI Service is stopped, or WMI has "gone bye bye" and started returning bogus results.

Note that Address varies by adapter type. For Ethernet, WiFi, and similar network media adapters this is the MAC Address.


This might look like a lot of code, but much of it consists of structure definitions (UDTs). For specific purposes you can trim out things you do not need which can reduce it further.
Attached Files

VB6 - NewSocket 2.5

$
0
0
The orginal CSocket & cSocket2 were designed to emulate the Microsoft Winsock Control. Data would be sent and recovered as variants. On input, the variant was converted to byte array, converted to string using StrConv, added and subtracted to a string buffer, converted back to byte array, and finally handed over to the Socket API. Receipt of data was similar. Verion 2 of NewSocket eliminated all the back and forth conversion, and interfaced using a single conversion of string to byte. Because the StrConv function caused problems with systems using a non-latin character set, it also was eliminated.

Version 2.5 sends and accepts both ASCII strings and byte arrays. Since the Socket API requires byte strings, it made sense to allow direct input and output of byte arrays. This meant converting the string buffers (m_strSendBuffer and m_strRecvBuffer) to byte arrays and writing routines to replace the simple string concatenation and string search functions. At the same time, the functions to send and receive data were changed from a single call which included the data as a string, to 2 separate calls; one to place the data in a buffer, and one to perform the actual function. This enabled the option of using ASCII string data or byte array data.
Code:

Old Way                                  New Way
Socket.SendData strData                  Socket.sOutBuffer = strData '(ASCII String)
                                    or    Socket.bOutBuffer = bData '(Byte Array)
                                          Socket.SendData

Socket.GetData strData                    Socket.RecoverData
                                          strData = Socket.sInBuffer '(ASCII String)
                                    or    bData = Socket.bInBuffer '(Byte Array)

This posting just includes the "NewSocket.cls" and it's companion "mWinsock.bas", as well as a small program (prjTest.vbp) to test the pair. Once these have proved themselves, I will publish the corresponding OCX.

J.A. Coutts
Attached Files

[vb6] AddressOf for Class, Form, UC methods

$
0
0
This will be of little use to the masses, but can be of good use for thunk creators/users and the curious.

VB gives us the AddressOf function which can return the function address of any method within a bas module. We have no such function to return addresses within non-module code pages like classes, forms, usercontrols, etc. This can be a suitable substitute.

Why would you even need these addresses? Normally, you wouldn't. The most obvious case would be if you wanted to call a class function, particularly a private one, from a thunk. Can apply if wanting to do same thing from a bas module, but there are easier workarounds for calling private class methods from a module.

CODE REMOVED. I could not get a reliable function address of a specific private nor public method other than the final private method in all VB code pages.

I concede and tip my hat to Paul Caton whose method seems to be the most reliable. At this point, I cannot top it, except for one specific case: classes. Unfortunately, my logic will not work reliably in any other code page that is built on other interfaces: form, usercontrol, property page, etc.

If interested in Paul Caton's method, it is a matter of googling for "self-subclasser Paul Caton".

I'll ask the moderators to remove this thread.

Filter Listview User control

$
0
0
I will attach a zip here for archival, any updates will be on github, free for any use.

https://github.com/dzzie/libs/tree/master/filterList

Description:

small usercontrol that gives you a listview control
with a built in filter textbox on the bottom.

very similar to use as original, couple bonus functions thrown
in on top.

simple but very useful.

set integer FilterColumn to determine which column it searchs
1 based. You can set this any time, including before setting
column headers. You can also specify it in the call to
SetColumnHeaders by including an * in the column you want.

The user can also change FilterColumn on the fly from the popup
menu, or through entering /[index] in filter textbox and hitting
return.

The filter popup has a help message with more details.

When the control is locked no events will be generated or
processed. filter textbox locked and grayed.

If allowDelete property is set, user can hit delete key to
remove items from list box. This supports removing items
from the filtered results as well. (Even if the user resorted
the columns with the built in column click sort handler)

When you resize the control, the last listview item column
header will grow. You specify initial column widths in set header
call. When running in the IDE, there is a debug menu item available
that will give you the current column width settings to copy out for
the set column header call. So just manually adjust them, then use
the menu item, then you can easily set them as startup defaults.

the current list count is always available on the popup menu along
with some basic macros to allow the user to copy the whole table,
copy a specific column, copy selected entries etc.


examples:

lvFilter.SetColumnHeaders "test1,test2,test3*,test4", "870,1440,1440"

Set li = lvFilter.AddItem("text" & i)
li.subItems(1) = "taco1 " & i

Set li = lvFilter.AddItem("text", "item1", "item2", "item3")
lvFilter.SetLiColor li, vbBlue

Name:  filterList.jpg
Views: 16
Size:  38.6 KB
Attached Images
 
Attached Files

[vb6] - Filter Listview User control

$
0
0
I will attach a zip here for archival, any updates will be on github, free for any use.

https://github.com/dzzie/libs/tree/master/filterList

Description:

small usercontrol that gives you a listview control
with a built in filter textbox on the bottom.

very similar to use as original, couple bonus functions thrown
in on top.

simple but very useful.

set integer FilterColumn to determine which column it searchs
1 based. You can set this any time, including before setting
column headers. You can also specify it in the call to
SetColumnHeaders by including an * in the column you want.

The user can also change FilterColumn on the fly from the popup
menu, or through entering /[index] in filter textbox and hitting
return.

The filter popup has a help message with more details.

When the control is locked no events will be generated or
processed. filter textbox locked and grayed.

If allowDelete property is set, user can hit delete key to
remove items from list box. This supports removing items
from the filtered results as well. (Even if the user resorted
the columns with the built in column click sort handler)

When you resize the control, the last listview item column
header will grow. You specify initial column widths in set header
call. When running in the IDE, there is a debug menu item available
that will give you the current column width settings to copy out for
the set column header call. So just manually adjust them, then use
the menu item, then you can easily set them as startup defaults.

the current list count is always available on the popup menu along
with some basic macros to allow the user to copy the whole table,
copy a specific column, copy selected entries etc.


examples:

lvFilter.SetColumnHeaders "test1,test2,test3*,test4", "870,1440,1440"

Set li = lvFilter.AddItem("text" & i)
li.subItems(1) = "taco1 " & i

Set li = lvFilter.AddItem("text", "item1", "item2", "item3")
lvFilter.SetLiColor li, vbBlue

Name:  filterList.jpg
Views: 35
Size:  38.6 KB
Attached Images
 
Attached Files

vb6 - OCX: Javascript Engine, debugger, IDE

$
0
0
source: https://github.com/dzzie/duk4vb

I have been tinkering with this project for a while now. I think it should be ready to share in the codebank now. binary compatibility has not been set yet but is probably about ready to. Figured I would open it up for feedback before finalizing the interface.

Name:  screenshot.jpg
Views: 66
Size:  30.6 KB

The project contains all of the logical stages of development in its various sub folders, from first getting the C javscript engine working with VB, to implementing basic COM integration for vb form elements, to integrating with the js engine debugger api as a standalone executable, to the final ocx which wraps it all into an easy to use component.

The Javascript engine is the C DukTape engine ( http://duktape.org/ )

The syntax highlight edit control is Scintilla (again done in C http://www.scintilla.org/ ) wrapped in another OCX done in vb6. The scivb ocx was originally done by Stewart Collier and Stu and released open source. I have also spent some time in there jiggling it around a bit. its repo is here:

https://github.com/dzzie/scivb2

The COM integration is done on the vb6 side but is not automatic like in teh script control. You have to generate javascript wrappers to represent the objects in js. This may be an intermediate stage, it is possible to generate these on the fly but will be allot of work and a ton of testing. Static files is a good safe route for now. There is a standalone vb6 generator for them. The intellisense lists also work off of parsing these generated class wrappers.

It supports breakpoints, step in/over/out, and mouse over variable values in tooltips.

I have already started using this control in a test project video below:
(This page also contains an installer that can setup all the dependencies for you.)

http://sandsprite.com/blogs/index.php?uid=7&pid=361
Attached Images
 

I just found this really cool code sample for getting the MAC address.

$
0
0
I have been looking for something like this for a while now, and somebody on stackoverflow.com had this piece of code that gets the MAC address of the default network card, and also the current IP address assigned to that network card, and then displays these 2 pieces of info in message boxes. A slight modification of this code could easily turn it into a function that returns the MAC address as the function's return value, instead of popping up message boxes. This would be very useful for designing copyprotection that is locked to hardware, by using an activation key that is tied to the computer's main network card's MAC address.

Note that even though I'm posting this code here, I'm not the one who figured it out (I don't even know exactly how it works). That credit goes to Jerome Teisseire on stackoverflow.com. I'm just posting it here for the sake of archiving it (so it will be present on more than just one website) and also helping to redistribute it to others who might come to vbforums.com looking for how to do this. More places this code is on the net, the more likely somebody who's looking to figure out how to do it will be able to find it. And the nice thing is it's only a few lines of code, not some huge thing with dozens of API calls.

Code:

Dim myWMI As Object, myObj As Object, Itm

Set myWMI = GetObject("winmgmts:\\.\root\cimv2")
Set myObj = myWMI.ExecQuery("SELECT * FROM " & _
                "Win32_NetworkAdapterConfiguration " & _
                "WHERE IPEnabled = True")
For Each Itm In myObj
    MsgBox (Itm.IPAddress(0))
    MsgBox (Itm.MACAddress)
    Exit For
Next

[VB6] ProgramData for common files

$
0
0
The Wild West

Back in those DOS and Win9x days you could pretty much dump files anywhere since there was no real filesystem security. On Windows 2000 and then on its minor update Windows XP, people carried on working in "DOS Mentality" by just making all users members of an elevated rights group such as Administrators or Power Users.

This gave Microsoft a black eye because users logged on with such accounts who used the Internet had opened a gaping hole for malware to come in and wreak havoc. A lot of email spam comes from zombied Windows XP machines even today.

In response this was modified beginning in Windows Vista through user Account Control (UAC). With UAC even if you were silly enough to have users log on as an Administrators account (Power Users was removed entirely) your session was no longer elevated. Instead elevated rights require special actions that raise a dialog on a Secure Desktop that malware can't just hijack and blat messages at to "click approval."

However when combined with NT security (pretty much the same model since at least NT 4.0) users can't just dump files and folders willy-nilly anymore. Lots of secured filesystem locations became off limits. This meant installed programs (in Program Files) began to either fail or run afoul of appcompat filesystem virtualization.


What To Do?

Well, there are lots of writeable locations. Each user has a Desktop, a Documents, and even AppData locations in which he can create, modify, delete, and do other things with folders and files. These work fine if the programmer takes any time to make use of them. But these don't work well for files "shared" among different users of the same PC.

Instead Windows has a CommonAppData special folder with special security on it. The DOS-visible name of this file can vary: on recent versions of Windows an English-language system calls this ProgramData.

The security on CommonAppData/ProgramData is such that a folder or file created within it has a special "Owner Access" applied to it. If user Joe creates a folder there he has full access, and all other users have basically read access. However since Joe "owns" it he can change the folder's security without elevation, and this altered security will be inherited by any folders or files created within it.

In order to avoid collisions between applications using ProgramData the convention is to create a "company" subfolder there, and within that create "product" or "application" subfolders to contain the folders and files of a given application that all users need access to.


How To Do It?

Windows Explorer, also known as Shell32, knows how to locate ProgramData by invariant code and can return the path to your programs. These "codes" are numeric values, assigned names prefixed ssf/CSIDL_ such as ssfCOMMONAPPDATA.

That gets you to the folder's path, and you can use MkDir to create subfolders, so you're nearly there!

To alter the security I've posted SetSec.bas before, but nobody seems to be using it. In an attempt to simplify this I have written ProgramData.bas as a "wrapper" for it. This gets things down to a simple function call.


Demo

The attached archive contains a simple program AnyUserUpdate.vbp, which doesn't do much.

The program looks for an application common data path and creates it as required, altering security at each created level to "Full Access" for members of the "Users" group. This is very liberal access and not correct for all situations, but it emulates the Wild West of those DOS/Win9x days to simplify programming for people. "Users" differs from "Everyone" in the post-XP era (starting in Windows Vista, "Everyone" no longer includes "Guest" accounts).

Once the demo program has this common folder and its path, it loads Text.txt into a TextBox for possible user editing.

When the program ends it checks for changes to the TextBox. If changes have been made it writes the altered Text.txt back out to disk, then logs the change to Log.txt (timestamp and user name) and exits.

Name:  FolderStructure.png
Views: 134
Size:  12.3 KB

All of the work required is now down to a one-liner:

Code:

Path = ProgramData.Path(App.CompanyName & "\" & App.ProductName)

Beyond ProgramData

A ProgramData subfolder is a good place for data common to all users. That might be program settings INI files, Jet MDB databases, or any other files common to all users that your application's program(s) need to be able to create, alter, or delete.

However you often have per-user settings and such too. These should go into a similar folder structure underneath ssfLOCALAPPDATA, but that's easy enough since there is no need to use SetSec.bas to alter security there. However you might want to add a new function to ProgramData.bas to look-up/create such a path too.


Installed Programs

If you use an installer to put your program(s) into Program Files you can still use ProgramData. For example maybe your program ships with an initial empty Jet MDB that all users will be updating.

Just have your program use ProgramData.Path() as above. Then check to see whether your XXX.MDB exists there, and if not copy XXX.MSB from App.Path to this ProgramData path. Then open the database as you normally would, but in the ProgramData path instead of in App.Path.

It's as easy as that!
Attached Images
 
Attached Files

[VB6] MapStitcher.cls - Create imagemaps from images

$
0
0
Sometimes it can be handy to make use of the PictureClip control or similar approaches to grab and use small images "cut out" from a larger image set up in a grid layout. But usually we have separate images to work with, and stitching them together into a larger "imagemap" image can be tedious.

MapStitcher.cls is a VB6 class that makes use of WIA 2.0 to create such "maps." A really simple thing but you might find it useful.


Limitations

Since WIA 2.0 only offers limited capabilities for handling transparency, this works best when you have images that make use of mask color transparency. If you give it source images with PNG or GIF transparency it can "stamp" these onto a backdrop making use of the transparency but there isn't any way to retrieve a composite image with any transparency itself.

This isn't all bad, since so many VB6 controls and operations make use of mask color transparency anyway. But note that to be effective your source images should use the same mask color, and you'd want to set the composite image's backdrop color to match that.


Demo

I've attached the class in a demo Project "StitchMap."

StitchMap loads the images that it finds in the directory "Samples" and assumes a magenta mask/backdrop color (255, 0, 255). It looks at the first loaded image and places its dimensions in pixels into the Cell width and Cell Height TextBoxes, and puts a 4 into the Cells per row TextBox. You can change those values.

When you click the Stitch button it passes these values to MapStitcher, which creates a stitched image. The demo displays this:


Name:  sshot1.png
Views: 61
Size:  14.0 KB

Oops! One image got cut off.


Name:  sshot2.png
Views: 55
Size:  14.5 KB

Changed dimensions to match the large image


Then you could click the Save as BMP button and it will save this to Stitched.bmp and reset.

It also works with a longer list of input images, though this will run a little longer:

Name:  sshot3.png
Views: 53
Size:  45.1 KB


Summary

Sometimes you need to create imagemaps from small images, but positioning them using Paint, etc. can be a pain.

MapStitcher shows a way to do this more easily. You could wrap this in a program with more options: browse to the input folder, list the input files and their dimensions, a backdrop color-picker, save-as dialog, etc.

You could also modify MapStitcher, replacing WIA 2.0 by GDI+ flat API calls to obtain more options such as saving the composite image as a PNG with a transparent backdrop.
Attached Images
   
Attached Files
Viewing all 1470 articles
Browse latest View live


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