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

[VB6] ListView / TreeView Extended and Custom Checkboxes

$
0
0

So I've mentioned this and posted snippets in a few threads, but thought it would be good to do a formal sample project on this, especially since I've never seen one done before.

By default, the ListView and TreeView controls, whether it's from the OCX or manually created, only has the basic checked or unchecked state. But what if you want to add the Partial check state? Or even more? Or customize the regular checked and unchecked look? It turns out it's actually quite easy: checkboxes are simply an imagelist, so all you have to do is create your own and assign it just like you do for the regular icons. The ListView/TreeView even manages the number of checkboxes for you; no special code is required to cycle through all the checkboxes then loop back to the beginning. There's 8 different checkboxes in the sample project, I'm not sure what the limit is but you almost certainly won't hit it.

The only thing that makes this even a little complex is that you have to drop down to the API level to set the imagelist, and subclass it just to prevent VB from accidentally removing the imagelist. The good news though is that it's entirely possible to do it with the regular Common Controls 5.0 ListView/TreeView control, which is what the sample project uses.

The new checkboxes are stored in a resource file and accessed from there, but I've also included the .ico's as normal files in the zip.

How it works

First we create a new API ImageList with our new checkboxes:
Code:

Dim hIco As Long

himlCheck = ImageList_Create(32, 32, ILC_COLOR32 Or ILC_ORIGINALSIZE, 1, 1)
ImageList_SetIconSize himlCheck, 16, 16
hIco = ResIconTohIcon("CHK_STD_UNCHKD", 16, 16)
Call ImageList_AddIcon(himlCheck, hIco)
Call DestroyIcon(hIco)
'rinse and repeat for all other checkboxes. Note that if you're doing this with a TreeView,
'you need a blank icon (not unchecked, entirely blank) as the first image, but with the ListView
'you just start with the first box in the series- usually unchecked.

The checkbox imagelist is the State ImageList, so when setting up the ListView, it's assigned as such:
ListView_SetImageList hLVS, himlCheck, LVSIL_STATE

That's all you have to do to get started- all items will default to the first checkbox in the list, then cycle through in order with each click, then after the last one returns to the beginning.

If you want to set the check state through code, you need to use API since True/False isn't good enough,
Code:

Dim li As ListItem
Dim lvi As LVITEM

    lvi.iItem = li.Index - 1 'get your li from ListView.Items.Add() and similar
    lvi.Mask = LVIF_STATE
    lvi.StateMask = LVIS_STATEIMAGEMASK
    lvi.State = IndexToStateImageMask(k) 'where k is the 1-based index of the checkbox you want
    ListView_SetItem ListView1.hWnd, lvi

True/False also doesn't work for retrieving the check state either, so you just have to reverse how it was done when added,
CheckIndex = StateImageMaskToIndex(ListView_GetItemState(hLVS, iItem, LVIS_STATEIMAGEMASK)) 'where iItem is zero-based

The procedure for the TreeView is virtually identical, with the important step of adding the blank image mentioned earlier, and needing to get the hItem since the APIs don't use the index (TVITEM.hItem = pvGetHItem(Comctllib.Node))

That covers the basic concept, all the other code is just standard setup.

Requirements
-Windows XP or higher
-Common Controls 6.0 Manifest - The sample project has the cc6.0 manifest embedded in its resource file so it will work when compiled, but to work in the IDE your VB6.exe must also be set up to use the 6.0 controls. See LaVolpe's excellent manifest creator project to generate the manifest and startup code for your own projects.
Attached Files

[VB6, Vista+] Core Audio - Change the system default audio device

$
0
0

Changing the system-wide default input and output audio devices

WARNING: This feature is not designed to be accessible to programs and uses a COM interface that is undocumented and unsupported by Microsoft. As such, it may not function in future versions of Windows.

Several times I've come across people asking how to change the default input/output devices through code, and usually the reply is that it isn't possible. Changing the device per-app is well documented, but many people want to be able to set the system-wide default like the Sound control panel applet does. Tonight I was looking into that a little deeper, and the applet does it through an undocumented private COM interface called IPolicyConfig. So naturally I immediately found the definition and added it to oleexp.

There's two versions of the interface included, one for Windows Vista (IPolicyConfigVista / CPolicyConfigVistaClient) and one for Windows 7 and higher (IPolicyConfig / PolicyConfigClient).
Using this interface to set the defaults is very easy:
Code:

Private pPolicyCfg As PolicyConfigClient

If (pPolicyCfg Is Nothing) Then
    Set pPolicyCfg = New PolicyConfigClient
End If
pPolicyCfg.SetDefaultEndpoint StrPtr(sDeviceID), eMultimedia
pPolicyCfg.SetDefaultEndpoint StrPtr(sDeviceID), eCommunications

It's actually far more complicated to figure out the device ID string that you need, as it's not name, it's a string like {0.0.1.00000000}.{b12f40bc-c3ec-4a74-afcc-4b6d0eb6914a}. The good news is enumerating all the devices and their IDs (as well as enabling them if you need to, as they need to be active to be set as default) was covered in my Core Audio Basics demo. The enumeration code is copied right out of that project.

Requirements
-Windows Vista or higher
-oleexp.tlb v4.11 or higher (new release for this demo)
-oleexp addon mIID.bas (included in oleexp download)
-oleexp addon mCoreAudio.bas (included in oleexp download)
-oleexp addon mPKEY.bas (included in oleexp download)
Attached Files

Determining when two numeric ranges overlap

$
0
0
The code below will report True if 2 ranges (x1-x2 & y1-y2) overlap. You can put the two range parameters in either ascending or descending order. The function will rearrange them to ascending order if necessary.

At the bottom of the function are the two lines that will determine whether the ranges overlap at any point. If the ranges merely 'touch' each other, partially overlap or completely overlap, the IsOverlapped function will, in any of these cases, report back True. Only one of the lines at the bottom of the function is needed. The other one needs to stay rem'ed out. Use either line according to your whim. :)

The line that starts out with "Not (. . .)" actually performs its 'deed' by determining if the ranges are not overlapping, and then reverses the result with "Not".

I can't take credit for the code. I found it on the stackoverflow site in a C forum looking for an efficient answer to this problem. I simply converted their solution to a VB format.

Both lines look pretty efficient, but my gut thinks the first line might have a little speed edge due to using mostly boolean operators, whereas, the second line uses mostly math. But the difference is probably splitting hairs.


Code:

Public Function IsOverlapped(ByVal x1 As Long, ByVal x2 As Long, _
                            ByVal y1 As Long, ByVal y2 As Long) As Boolean
Dim TmpSwap As Long

  ' the procedures below depend on both ranges being in
  ' ascending order so we have to test for that to make sure
  '
  If x1 > x2 Then
      TmpSwap = x1
      x1 = x2
      x2 = TmpSwap
  End If
 
  If y1 > y2 Then
      TmpSwap = y1
      y1 = y2
      y2 = TmpSwap
  End If

  ' either of these two lines will work
  ' I kinda think the 1st one is a wee bit faster
  '
  IsOverlapped = Not ((y1 > x2) Or (x1 > y2))
  'IsOverlapped = ((y2 - x1) * (x2 - y1)) >= 0
End Function

I've done some testing with various types of overlap and non-overlap and, so far, have not found any flaw in the two lines that determine overlap. If you find an example that provides an incorrect return I would appreciate hearing about it.

rdee

Multi Bit Manipulation Class

$
0
0
This submission is a class that handles bit manipulation in the following VB data types: Byte, Integer, Long, Single, Double, Currency, Date and One dimensional Long Arrays. (These are VarTypes 2 thru 7, 17, and 8195.)

One of the nice features is that all these data types use the same interface. You can find the class here (on PSC). I keep it there so that there is only one source that I need to keep track of.

Some of the features include:

Sign bit is manipulated same as other bits.

Long arrays are in place of Decimals.
Any size long array can be made to act like a single bitfield (even shifting and rotating them).

You can insert numeric values into a bit field making for a 'hybrid' bitfield.

The class uses GetMem/PutMem & CopyBytes for various memory movements.

The class also optionally uses direct casting for some numeric types and strings. (The idea came from a Bonnie West class.)

The class is intended as a sort of "Swiss-Army-Knife" for bit manipulation. A more complete description can be found at the link and in the remarks in the class.

Any advice, ideas, or improvements would be more than welcome so as to improve it.

rdee

[VB6] ColorList - Another example of a custom dropdown list control

$
0
0
ColorList:

A "dropdown list box" control for choosing among a list of predefined colors.

This is a sort of drop-down list box written in VB6 that accepts a series of color text/value pairs. Users may choose a color via the mouse or keyboard.


Name:  sshot.png
Views: 117
Size:  11.5 KB

When too near the bottom of the screen the
list "drops up" instead of down.


Selection is by "pair" i.e. even though text "names" are displayed the name can't be typed in to choose.

Instead to make a choice via keyboard the user presses the spacebar to drop down the list, arrows up and down within the list, then types space or enter to select a text/value pair. The user can also type escape or tab to "cancel." Both actions "retract" the drop-down list.

By mouse the user can click the drop-down triangle symbol to drop the list, then scroll as necessary and click on an item to choose it. Clicking "away from" the dropdown list retracts it.

There is just one Event implemented: Click. This Click Event is raised when the user selects a new value, but not when the user cancels.

Properties Color and Text can then be fetched to obtain the choice.


Property Colors:

The color list is defined via the Colors Property, a String value. This is a list of "pairs" separated by pipe (|) characters. Each pair is a text name, a comma (,), and an 8-digit hex color value of OLE_COLOR type (i.e. system color values can be used).

Example:

"Blue,00FF0000|Green,0000FF00|Red,000000FF|Button Face,8000000F"

The text ("names") can be any text that doesn't contain a comma or a pipe.

Until Colors has a valid value assigned (design-time or run-time) the dropdown action does not occur.


Property Color:

This is a Long (OLE_COLOR) value. RGB colors are in "BGR" sequence as usual, and values with bit 31 set are OLE system color values.

The initial value may be assigned at design-time or run-time.

When this is assigned at design-time or programmatically, the Color is looked up in Colors and Text is assigned (if not found Text gets the UNKNOWN_COLOR_TEXT Const value).

The Colors Property should be assigned first.


Property Text:

This is a String value.

The initial value may be assigned at design-time or run-time.

When this is assigned at design-time or programmatically, the Text is looked up in Colors and Color is assigned (if not found Color gets the UNKNOWN_COLOR Const value).

The Colors Property should be assigned first.


Requirements:

VB6

32-bit Windows, or 64-bit Windows with WOW64 subsystem.

Only tested on Windows 10, but should work on Win9x all the way back to Windows 95.


Pieces and Parts:

ColorList.ctl
ColorList.ctx
ColorListPopup.frm
Subclasser.bas

Subclasser.bas can be removed along with calls to it and some exception handling code if you don't need ther "dropdown" to get retracted automatically when the application loses focus. See comments within ColorListPopup.frm for more details.


Remarks:

This seems to be working fine on Windows 10, but more testing is needed on downlevel versions of Windows.

In particular I'd be concerned about proper positioning of the popup/dropdown Form. It should be right under the ColorList instance (or if positioned low on the screen, right ABOVE the ColorList). "Fat" Aero borders might be a factor that could throw this off.

This UserControl might also serve as a model for creating other kinds of custom "dropdown list" controls.

ColorList (and supporting modules) compile down pretty small, adding much less size than any standard OCX we might use for this.
Attached Images
 

Statistics in VB6

$
0
0
Alright, I'm embarking on this endeavor as one of those "spare time" projects. I've long thought that there should be a good VB6 open-source program for doing various statistical analyses, and I've actually got VB6 pieces to a great many of them. I'm going to take my time with this though, and paste updates to this #1 (OP) post as things progress. Also, thoughts and ideas are more than welcome.

My philosophy is going to be that the actual data massaging will take place in Excel. And then, when the data are ready to analyze, it'll be moved into this VB6 program and analyzed. This obviates the need for data massaging/manipulation routines. Excel already does all of this quite well. (Just as an FYI, yes, I know that Excel also does primitive hypothesis testing, but I hope that this will expand well beyond the abilities of Excel, possibly approaching the abilities of something like SPSS. Also, many may want to incorporate these routines into their existing VB6 projects.)

Here's a brainstorm list of what I'd like to see:
  • parametric hypotheses:
    • one group t-test (in first release)
    • two group t-test
    • one-way ANOVA
    • two-way ANOVA
    • Pearson's correlation
    • simple linear regression
    • multiple linear regression
    • general linear model analysis
      • overall analysis
      • forward, backward, and stepwise best fit models
  • logistic regression hypotheses
  • non-parametric hypotheses:
    • Mann-Whitney
    • Wilcoxon sign-rank
    • Kruskal-Wallis
    • Cochran test
    • Friedman test


Here's a list of statistics it currently does:
  • Descriptives
    • none at moment
  • Inferential
    • one group z-test
    • one group t-test


Clearly, there's much to do. However, even if a subset of the above could be accomplished, this may very well become a teaching tool for intro-stats classes, possibly giving VB6 a bit more clout. I'm not saying I'll complete the above list, but I do have sizable chunks of that code lying around, and I do have a strong knowledge of what's going on with this stuff.

Reverse chronological log:
1/13/17: First update, version 1.02. A great deal of clean-up and organization for going forward. Just briefly, organized the statistical distribution modules, added a Save/Open for datasets, added an Output for which includes printing and saving, made forms sizable and work well, and added drag-n-drop for selecting variables to analyze. Also, the one group z-test was added just to keep some headway on the actual statistical analysis.
1/11/17: Made first post, a start. It includes mechanisms for moving things back and forth from Excel, and it provides output (to Immediate window) for a one sample t-test.

The main dataset form:

Name:  main.gif
Views: 5
Size:  18.3 KB

The one group t-test form:

Name:  t.gif
Views: 5
Size:  13.8 KB

Sample of Output form:

Name:  Image1o.jpg
Views: 5
Size:  38.6 KB

If you know anything about statistics, hopefully you can see that this could be useful.

Again, I welcome assistance with anyone who would like to participate in this project. Here are some ideas that I'd welcome help on:
  • A bulletproof routine that would allow double-click editing of the dataset grid (including variable names, but excluding the record number column). Just editing of existing data. We'll take a look at adding rows/columns later.
  • A hard look at the routine that converts the Kolmogorov-Smirnov D to a p-value. This can be improved.
  • Beta testing, and reporting back with any errors/suspicions/suggestions.
  • For the Kolmogorov-Smirnov, I had to sort an array. I just threw together a primitive sort. Someone is welcome to slip in a quick-sort or some other better sort.

If you do decide to participate, possibly post your changes in posts below in-between [code][/code] with your changes highlighted in red. If they make sense, I'll absolutely incorporate them.

Just as an FYI, I know that my passion will be adding additional hypothesis test procedures to the project.

I'm going to save a couple of subsequent posts, just in case this thing actually takes off.

Best Regards to All,
Elroy
Attached Images
   

Linking a File with a Folder so they Copy (or Drag) Together

$
0
0
crud, this was suppose to be in the main VB6 threads. Attn moderators, please delete. I've re-posted over there.

[VB6] Port for use DX9 in VB6.


[VB6] SHFolderDlg class: BrowseForFolder

$
0
0
A VB6 Class with one Method: BrowseForFolder()

It raises a Shell dialog for browsing to a folder. You can specify the "browse root" as well as an optional StartPath under that for the dialog to show selected.

Requirements

Some features require Windows 2000/Me or later, or Win9x with IE 5. Others require Windows XP or later. See comments for the Shell32.dll version requirements of some of the features.

It may be portable to Office VBA, though for 64-bit versions it could need tweaks to the data type of variables holding pointer values.


Features

The Class wraps a call to SHBrowseForFolder() in Shell32.dll, but with a callback supporting a number of other features via Properties you assign values to before calling the Method:

  • StartPath, a path below the browse root.
  • Root, the browse root directory. This accepts CSIDL values.
  • RootPath supports a String path as an alternative to Root.
  • Instructions can be used to set the dialog's "Title" text (this is not the dialog window's "title bar" caption).
  • ExpandStartPath instructs the dialog to open with the StartPath expanded (or not).
  • OkCaption allows you to specify the "Ok" button's caption text.


The Instructions property only displays as ANSI characters but everything else should be fully Unicode.


Advantages

The main advantage over a simple call is that your programs can start the user browsing from some given location under the root. For example where they last browsed to, or perhaps a value you store in a user settings file.

This can also be used to browse for files.


Demo Project

'This demo starts with an empty FolderPath, then loops until "cancel" reusing the most recently selected FolderPath. This just shows that your programs can start the user browsing from some given location, such as where they last browsed to.



There is nothing exotic here, it just makes use of a few more features than most VB6 examples. It isn't anything we haven't seen before in other sample code, but it adds a few features. I think all of the potential memory leaks have been resolved and it should be stable compared to most older samples.
Attached Files

[VB6, Vista+] Core Audio - Monitor for disabled/active, default, and property changes

$
0
0

Monitoring audio hardware for changes

At first glance, the IMMNotificationClient interface looks like it's simple and straightforward. And in the IDE it is, but it turns out when compiled all normal ways of accessing the passed values results in a crash. Getting around this involves putting the variables into a public structure that can only be accessed and processed by subclassing the main form and using PostMessage to send the message to process things.

Setting up the notifications is easy enough:
Code:

Private Sub Command1_Click()
Command1.Enabled = False
hFrm = Me.hwnd
Subclass2 Me.hwnd, AddressOf F1WndProc, Me.hwnd
SetNotify
End Sub

Private Sub SetNotify()
Dim hr As Long

If (mDeviceEnum Is Nothing) Then
    Set mDeviceEnum = New MMDeviceEnumerator
End If

If (cMMNotify Is Nothing) Then
    Set cMMNotify = New cMMNotificationClient
    hr = mDeviceEnum.RegisterEndpointNotificationCallback(cMMNotify)
    AddMsg "Registered cMMNotify, hr=" & Hex$(hr)
End If
End Sub

Then the notification class, cMMNotify, is set up just to assign the data and fire off a window message:
Code:

Private Sub IMMNotificationClient_OnDeviceStateChanged(ByVal pwstrDeviceId As Long, ByVal dwNewState As DEVICE_STATE)
tPVC.lpszID = pwstrDeviceId
tPVC.pid = dwNewState
PostMessage hFrm, WM_MMONDEVICESTATECHANGE, 0&, ByVal 0&
End Sub

OnPropertyChanged, which uses a PROPERTYKEY, is even weirder. See the class module comments for more details.

Our custom WM_MMx messages then get handled in a normal subclassing WndProc:
Code:

Select Case uMsg
    Case WM_MMONDEVICESTATECHANGE
        sPtr = LPWSTRtoStr(tPVC.lpszID, False)
        Form1.AddMsg "StateChange: DeviceID: " & sPtr
        Form1.AddMsg "Name=" & Form1.GetDeviceName(sPtr)
        Form1.AddMsg "New status=" & GetStatusStr(tPVC.pid)

As the code suggests, this project also shows how to convert a device ID to the friendly name.

Since all messages are handled with API posting, the project should be free of instabilities that cause a crash. I haven't had crashes in testing both for the IDE and once compiled. Always unregister the notification callback before unloading the form containing an instance of the class, or the app crashes on exit (the demo project handles this automatically in Form_Unload)

Requirements
-Windows Vista or newer
-oleexp.tlb v4.0 or higher (only needed for the IDE)
-mPKEY.bas (included in oleexp download)
Attached Files

[VB6] ADO Recordset data to custom XML

$
0
0
This demo uses SAX to write an XML document in a custom format.

Even though an ADO Recordset.Save method can write XML output to disk, this uses a proprietary serialization schema your program has no control over. Sometimes you want a more compact or just a more specific output format. Sometimes you don't want to write to disk.

People who learned VB6 from copy/pasting Classic ASP VBScript snippets tend to be familiar with MSXML's DOM to some extent. However in large data scenarios or when performance and control are important using a SAX ("Simple API for XML") approach can be a handy alternative. MSXML also contains Microsoft's SAX2 implementation in addition to DOM and other objects.


Demo

There are two programs provided in VB6 source code form: CreateMDB and ExportXML. There is also some sample data provided in CSV format.

CreateDB is used to create an empty Jet 4.0 database and import supplied CSV data into it. If you try running this with some locale settings you may have trouble (for example the data contains decimal points as periods, and the commas are commas). However there is an included schema.ini file that may overcome this for you.

Name:  sshot1.png
Views: 22
Size:  1.5 KB

ExportXML performs a query against the tables in the previously-created Movies.mdb and exports this as XML via SAX as an Exported.xml file.

Name:  sshot2.png
Views: 13
Size:  1.8 KB

Result:

Code:

<ratings>
        <movie average="5.0" min="5.0" max="5.0">10 Attitudes (2001)</movie>
        <movie average="5.0" min="5.0" max="5.0">16 Wishes (2010)</movie>
        <movie average="5.0" min="5.0" max="5.0">29th and Gay (2005)</movie>
:
:
        <movie average="2.5" min="2.5" max="2.5">Zoom (2006)</movie>
</ratings>


Requirements

VB6 on a version of Windows that includes MSXML 6.0.

You could easily change the MSXML Library reference to MSXML 3.0 and adjust the data types to match (look for "60" suffixes and change them to "30" suffixes). That can help on unsupported downlevel OSs like Windows XP or even Windows 95 with IE 5.x installed.


Wrapping Up

While there is nothing spectacular about this, it shows a way to quickly export large rowsets from a database query in custom XML formats. It also serves as a demonstration of one use of the SAX2 portion of MSXML.

The attachment is large, but this is mainly due to the included sample data. See the ReadMe.txt file it contains.
Attached Images
  
Attached Files

[VB6] OWM: How lean is my program?

$
0
0
A tool meant to help answer the question "How lean is my program?"

OWM runs a specified program and samples its performance until completion. Then the final measurements are displayed, and may optionally be saved as a textual report or CSV data.

OWM is not a profiler. It only reports final bulk statistics. These are not precise since after process termination values are not available. Instead it samples roughly 10 times per second until statistics are no longer retrievable.


Saving

When data is saved to an existing report or CSV file it is appended to the end.

Effort is made to be locale-aware when writing reports and CSV data. This means respecting "comma as decimal point" and delimiting output columns with semicolons in such cases. Other locale formats should work automatically as well. In the report text the appropriate "thousands separators" should be used too.

CSV text values are always wrapped in quotes (") with quote characters escaped by doubling (as ""). No other escaping is
attempted.

CSV output files always use the ANSI encoding of the current locale.


Requirements

Requires at least Windows 2000, but some things might fail before Windows Vista.

Only tested on Windows 10.


Sample report (U.S. English locale)

Code:

                        Program ExportQuery.exe
              Run date and time 2/2/2017 1:09:14 PM
                          Note
                      Exit code 0
                    Termination Normal
                  Elapsed time 2,074 ms
              CPU: Kernel time 62 ms
                CPU: User time 953 ms
                CPU: Total time 1,015 ms
          I/O: Read operations 119
          I/O: Write operations 149
          I/O: Other operations 295
          I/O: Total operations 563
    I/O: Read bytes transferred 2,408,900 bytes
  I/O: Write bytes transferred 607,823 bytes
  I/O: Other bytes transferred 4,012 bytes
  I/O: Total bytes transferred 3,020,735 bytes
            Memory: Page faults 6,127
  Memory: Peak total workingset 22,450,176 bytes
  Memory: Avg total workingset 22,437,374 bytes
 Memory: Peak shared workingset 11,223,040 bytes
  Memory: Avg shared workingset 11,210,238 bytes
Memory: Peak private workingset 11,227,136 bytes
 Memory: Avg private workingset 11,227,136 bytes
  Memory: Peak pagedpool usage 166,912 bytes
Memory: Peak nonpagedpool usage 8,960 bytes
    Memory: Peak pagefile usage 13,578,240 bytes


There is more information in the included ReadMe.txt file.
Attached Files

Multi-Monitor Routines (and "Better" routines even for single monitor systems)

$
0
0
Well, I recently helped someone solve the problem of centering a form on a secondary monitor. At that time, I had the necessary routines, but they weren't cleaned up as much as I'd like. And a couple of you encouraged me to do so. So here they are, all cleaned up.

Also, it's important to make note that, in some ways, these are better than the built-in VB6 properties for doing the same thing. Specifically, if you intend to program for multi-monitor systems, or even for a single monitor system that has its monitor in portrait orientation. VB6 struggles when monitors aren't in the typical landscape orientation.

More specifically, you should abandon all use of Screen.Width, Screen.Height, Screen.TwipsPerPixelX, & Screen.TwipsPerPixelY. Those functions just return bad data in certain situations, and mine don't.

I'll let you peruse the functions available. With very little study, they should start making perfect sense. To use them, just throw them in a standard (BAS) module, and that's it.

The code is right at 15,000 characters (the VBForums limit) so I'll post it in the second post.

To test, you can do something as simple as throw a couple of command buttons on a Form1, and then put this code in the form:

Code:


Option Explicit

Private Sub Command1_Click()
    CenterWindowOnMonitor Me.hWnd, MonitorHandle(1), True
End Sub

Private Sub Command2_Click()
    CenterWindowOnMonitor Me.hWnd, MonitorHandle(2), True
End Sub

There's much more to it than that, but that'll give you the idea. It's all been thoroughly tested. In fact, the vast majority of it has been in production for many years.

Enjoy,
Elroy

vbRichClient5 SQLite Database Definition & Creation Helper Classes

$
0
0
This is some older code of mine that I've just updated to work with vbRichClient5 and to support Serialization/Deserialization. It may even have some bug fixes over the last publicly available version since I think it was updated a bit over the years.

It came up in the following thread: http://www.vbforums.com/showthread.p...=1#post5135705

So I thought I would post it here for posterity: JPBDbFactory.zip

UPDATE February 6, 2017

Added preliminary support for Foreign Keys. Let me know if I've missed any use cases with my object model.

Currently you can add foreign keys to any table (including Deferrable FKs with optional ON DELETE and ON UPDATE declarations).

What Is This?

It is a collection of helper classes that let you easily define Tables, Fields, Indexes, and Foreign Keys in code, then create SQLite databases and connection objects in memory or on disk. For example:

Code:

  Dim lo_Db As JPBDbFactory.CDbFactory
  Dim lo_Table As JPBDbFactory.CDbTableDef
  Dim la_DbSerial() As Byte
 
  ' Create the main DB definition helper class
  Set lo_Db = New JPBDbFactory.CDbFactory
 
  ' Add a table
  Set lo_Table = lo_Db.TableDefinitions.Add("my_table")
 
  With lo_Table
      ' Add fields to the table
      .Fields.Add "field_1", vbInteger, True  ' Create a primary field
      .Fields.Add "field_2", vbString, , , fielddefault_CurrentDateAndTime  ' Add a field that defaults to the current date and time (UTC ISO 8601)
      .Fields.Add "field_3", vbString, , False, fielddefault_Literal, "NEW ITEM"  ' Add a field that defaults to the text "NEW ITEM" and does notdoes not allow NULL values
     
      ' Index a field on the table
      With .Indexes.Add("idx_my_table_field3")
        .Add lo_Table.Fields.Item("field_3"), fieldsortorder_Ascending
      End With
  End With
 
  ' Build the schema and save the DB to disk (overwriting any existing file)
  lo_Db.CreateFileDatabase App.Path & "\test.sqlite", , True

I've also included 3 optional "useful" fields with associated TRIGGERs that you can activate with Boolean properties - AutoRowGuid (this will generate a GUID for every newly created row), AutoCreatedDate (this will be set to the current UTC date and time in ISO8601 format on record INSERT), and AutoModifiedDate (this will be set to the current UTC data and time in ISO8601 format on INSERT and UPDATE). The field names for the above "auto" fields are jpbdbf_rowguid, jpbdbf_created, and jpbdbf_lastmodified repspectively (for use with your SELECT statements).

As per CarlosRocha's requirements in the thread linked above, the classes as now fully serializable/deserializable. Just call the Serialize method on the CDbFactory class to get a byte array for storing state, and call Deserialize on a new copy of the CDbFactory class passing the results of a previous Serialize call to restore the state.

The code should be fairly self-explanatory, but I'm happy to answer any questions about it here.
Attached Files

[VB6] List and view Alternate Data Streams using GetFileInformationByHandleEx

$
0
0

So I was playing around with this last night and thought it would make a good demo, since while it seems like a single API would be straightforward it turns out this is fairly complex.

Alternate Data Streams are a hidden part of regular files that you can't normally see. They can be any length, but only the first one is reported in Windows size counts, so a 1KB file could actually be hiding 1GB in an alternate stream. The most common use of alternate streams is web browsers marking files downloaded from the internet, which is how Windows knows to ask you to confirm if you really want to run something you downloaded-- this is shown in the picture above, and getting/setting that was the subject of [VB6] Code Snippet: Get/set/del file zone identifier (Run file from internet? source). There's already code samples about these streams, notably Karl Peterson's, but I still wanted to post this since it's highly simplified and uses a different API- haven't seen any others that do it with GetFileInformationByHandleEx.


Code:

Option Explicit


Public Type FileStream
    StrmName As String
    StrmSize As Currency
    StrmAllocSize As Currency
End Type

Public Declare Function GetFileInformationByHandleEx Lib "kernel32" (ByVal hFile As Long, ByVal FileInformationClass As FILE_INFO_BY_HANDLE_CLASS, ByVal lpFileInformation As Long, ByVal dwBufferSize As Long) As Long
Public Declare Function CreateFileW Lib "kernel32" (ByVal lpFileName As Long, ByVal dwDesiredAccess As Long, ByVal dwShareMode As Long, ByVal lpSecurityAttributes As Any, ByVal dwCreationDisposition As Long, ByVal dwFlagsAndAttributes As Long, ByVal hTemplateFile As Long) As Long
Public Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
Public Const GENERIC_READ    As Long = &H80000000
Public Const FILE_SHARE_READ = &H1&
Public Const OPEN_EXISTING = 3&
Public Const FILE_FLAG_BACKUP_SEMANTICS = &H2000000
Public Type LARGE_INTEGER
    lowpart As Long
    highpart As Long
End Type
Public Type FILE_STREAM_INFO
  NextEntryOffset As Long
  StreamNameLength As Long
  StreamSize As LARGE_INTEGER
  StreamAllocationSize As LARGE_INTEGER
  StreamName(0) As Integer
End Type
Public Enum FILE_INFO_BY_HANDLE_CLASS
    FileBasicInfo = 0
    FileStandardInfo = 1
    FileNameInfo = 2
    FileRenameInfo = 3
    FileDispositionInfo = 4
    FileAllocationInfo = 5
    FileEndOfFileInfo = 6
    FileStreamInfo = 7
    FileCompressionInfo = 8
    FileAttributeTagInfo = 9
    FileIdBothDirectoryInfo = 10 ' 0xA
    FileIdBothDirectoryRestartInfo = 11 ' 0xB
    FileIoPriorityHintInfo = 12 ' 0xC
    FileRemoteProtocolInfo = 13 ' 0xD
    FileFullDirectoryInfo = 14 ' 0xE
    FileFullDirectoryRestartInfo = 15 ' 0xF
    FileStorageInfo = 16 ' 0x10
    FileAlignmentInfo = 17 ' 0x11
    FileIdInfo = 18 ' 0x12
    FileIdExtdDirectoryInfo = 19 ' 0x13
    FileIdExtdDirectoryRestartInfo = 20 ' 0x14
    MaximumFileInfoByHandlesClass = 2
End Enum
Public Declare Sub ZeroMemory Lib "NTDLL.DLL" Alias "RtlZeroMemory" (dest As Any, ByVal numBytes As Long)
Public Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
Public Const INVALID_HANDLE_VALUE = -1&
Public Function LargeIntToCurrency(li As LARGE_INTEGER) As Currency
    CopyMemory LargeIntToCurrency, li, LenB(li)
    LargeIntToCurrency = LargeIntToCurrency * 10000
End Function
Public Function GetFileStreams(sFile As String, tStreams() As FileStream) As Long
ReDim tStreams(0)

Dim hFile As Long
hFile = CreateFileW(StrPtr(sFile), GENERIC_READ, FILE_SHARE_READ, ByVal 0&, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0)
If hFile <> INVALID_HANDLE_VALUE Then
    Dim tFSI As FILE_STREAM_INFO
    Dim byBuf() As Byte
    Dim byName() As Byte
    Dim nErr2 As Long
    Dim dwNameOffset As Long
    Dim dwDirOffset As Long
    Dim nEntryNum As Long
   
    ReDim byBuf((LenB(tFSI) + CLng(260 * 2 - 3)) * CLng(&H10000))
    ReDim byName(0)
   
    If GetFileInformationByHandleEx(hFile, FileStreamInfo, VarPtr(byBuf(0)), UBound(byBuf) + 1) Then
'    nErr2 = GetLastError()
'    Debug.Print "lasterr=0x" & Hex$(nErr2)
    dwDirOffset = 0
    Do While 1
        ReDim Preserve tStreams(nEntryNum)
        ZeroMemory tFSI, LenB(tFSI)
        CopyMemory tFSI, ByVal VarPtr(byBuf(dwDirOffset)), LenB(tFSI)
        Erase byName
       
        dwNameOffset = dwDirOffset + &H18
        dwNameOffset = VarPtr(byBuf(dwNameOffset))
        ReDim byName(tFSI.StreamNameLength - 1)
        CopyMemory byName(0), ByVal dwNameOffset, tFSI.StreamNameLength

        tStreams(nEntryNum).StrmSize = LargeIntToCurrency(tFSI.StreamSize)
        tStreams(nEntryNum).StrmAllocSize = LargeIntToCurrency(tFSI.StreamAllocationSize)
        tStreams(nEntryNum).StrmName = CStr(byName)
        nEntryNum = nEntryNum + 1
       
        If tFSI.NextEntryOffset = 0 Then Exit Do
        dwDirOffset = dwDirOffset + tFSI.NextEntryOffset
    Loop
    GetFileStreams = nEntryNum
    End If
clhn:
    CloseHandle hFile
End If
End Function

Once you know the stream name, you can address it with normal file functions to open, save, and delete it. The sample project uses VB's Open:
Code:

Private Sub List1_Click()
If List1.ListIndex <> -1 Then
    Text2.Text = LoadFile(Text1.Text & tStrm(List1.ListIndex).StrmName)
End If
End Sub


Public Function LoadFile(ByVal FileName As String) As String
  Dim hFile As Long
  On Error GoTo Hell
  hFile = FreeFile
  Open FileName For Binary As #hFile
      LoadFile = Space$(LOF(hFile))
      Get #hFile, , LoadFile
  Close #hFile
  Exit Function
Hell:
    Debug.Print "LoadFile::" & Err.Description
End Function

Attached Files

Unicode aware VB6 functions replacement

$
0
0
Hi,

here is W replacement for several native VB6 functions / properties:

- Dir -> DirW()
- MkDir -> MkDirW()
- Environ -> EnvironW()
- App.Path -> AppPathW()
- App.ExeName -> AppExeNameW()
- App.Major, Minor, Revision -> AppVersionW()

All functions packed on archive as separate projects, available under the post below.

1) DirW

Prototype is: Dir$(Path with mask or backslash at the end, opt_mask of allowed attributes, opt_return folders? )

Difference to original VB6 Dir$():

Fixed bug, when Dir cannot handle attribute: read only (vbReadOnly)
Added attributes:
- reparse points (symlinks / junctions) (vbReparse)
- all file objects (vbAll)
- vbFile (files only, without folders)

Enum VbFileAttribute "overloaded" by own: I removed all superfluous attributes, which original VB6 dir() are still not used in any way; I leave meaningful attributes only.

+ 3-rd optional argument (FoldersOnly); if true, it will filter files and leave folders only in output.
Function also automatically filter folders-aliases "." и ".."

Code based on earlier VB6() Dir$ reversing by @The Trick.

2) MkDirW()

Prototype is: MkDirW(Path to folder to create, opt_LastComponentIsFile as boolean) as boolean

LastComponentIsFile - true, if you plan to specify filename as a last part of path component
Return value: true, if successfully created or if folder is already exist

3) EnvironW()

Prototype: EnvironW$( "%Environment variable(s)%" )

Difference to original VB6 Environ():

- support several env. var-s on one line, like EnvironW("%UserName% - %UserDomain%")
- require %var%, i.e. each environment variable should be surrounded by % signs.
- automatically correct and "right" (in the sense, like if we wanted to do so from a 64-bit application) to expand under WOW64 such env. variables as:
- %PROGRAMFILES%
- %COMMONPROGRAMFILES%
and get on 64-bit OS, respectively:
- C:\Program Files, but not C:\Program Files (x86)
- C:\Program Files\Common Files, but not C:\Program Files (x86)\Common Files

4,5) AppPathW(), AppExeNameW()

Prototypes are:
- AppPathW$(opt_bGetFullPath as boolean)
- AppExeNameW$(opt_WithExtension As Boolean)

Self-explained:
Code:

    Debug.Print AppPathW()                          'Folder
    Debug.Print AppPathW(bGetFullPath:=True)        'Folder\FileName.extension
    Debug.Print AppExeNameW()                      'FileName
    Debug.Print AppExeNameW(WithExtension:=True)    'FileName.extension

6) AppVersionW()

Prototype is: AppVersionW$()

# require AppPathW(), AppExeNameW()

Just append unicode-char to your exe-name /and-or path (e.g. ALT + 3333) and you'll understand why you may need this function replacement.

Returns: String, version of your program in format: Major.Minor.Build.Revision
___________________

Best wishes,
Alex.
Attached Files

Here's a better way to generate random nums than using Rnd

$
0
0
Using Rnd always gives you the same set of numbers, whenever you start the program. Even if you use Randomize first to set a random number seed at the start of the program, it will eventually cycle through all the numbers (plus there are certain patterns of numbers that you can see under certain situations). You can get a much better random number generator (in fact, cryptographically secure level of randomness) if you used the crypto API functions. Here's a simple program I made to demonstrate how to use the crypto API to generate random numbers.

Code:

Private Declare Function CryptAcquireContext Lib "advapi32.dll" Alias "CryptAcquireContextA" (ByRef phProv As Long, ByVal pszContainer As String, ByVal pszProvider As String, ByVal dwProvType As Long, ByVal dwFlags As Long) As Long
Private Declare Function CryptReleaseContext Lib "advapi32.dll" (ByVal hProv As Long, ByVal dwFlags As Long) As Long
Private Declare Function CryptGenRandom Lib "advapi32.dll" (ByVal hProv As Long, ByVal dwLen As Long, ByRef pbBuffer As Any) As Long

Private Declare Sub Sleep Lib "kernel32.dll" (ByVal dwMilliseconds As Long)

Dim hProv As Long
Dim Quit As Boolean

Private Sub Form_Load()
    Dim a As Long
    CryptAcquireContext hProv, vbNullString, vbNullString, 1, 0
   
    If hProv = 0 Then
        Unload Me
        Exit Sub
    End If
   
    Show
    Do Until Quit
        CryptGenRandom hProv, 4, a
        Cls
        Print a
        Sleep 100
        DoEvents
    Loop

End Sub

Private Sub Form_Unload(Cancel As Integer)
    Quit = True
    If hProv Then CryptReleaseContext hProv, 0
End Sub

Just paste the above code into your Form1, and make sure that its AutoRedraw property is set to True. Then run the program, it will generate a new random number every tenth of a second (that's what "Sleep 100" is for). I have it doing that instead of running at max speed, so as not to max out your CPU cycles and cause unnecessary heating of your computer if you leave it running for a while. If you don't intend to have it running for a long time, you can remove the Sleep 100 line of code, so it can run at maximum speed (limited only by your CPU speed). Just close the Form1 window to end the program (make sure you DON'T use the stop button in the VB6 IDE, or the Unload event won't fire, and it won't run the required cleanup function CryptReleaseContext.

CryptGenRandom will NEVER repeat a set of numbers, and will NEVER produce any visible pattern of numbers.

Where's all of the MSDN docmentation for GDI+

$
0
0
I want to use GDI+ in VB6, and I just looked here https://msdn.microsoft.com/en-us/lib...(v=vs.85).aspx hoping to see a complete reference for GDI+ functions. However they only have links to documentation on 12 functions. I know that GDI+ must have MANY more functions than that. Where can I find complete official documentation on ALL of the GDI+ functions?

[VB6] Convert CSV to Excel Using ADO

$
0
0
A piece of code I thought might be useful to some. Caveat first... Won't be the ideal solution for everyone.

Having Microsoft Excel on the computer is not a requirement. Including an ADO reference is. This code was tested using both ADO Library versions 2.8 and 6.1. The ADO dependency could be removed if code tweaked to use late-binding CreateObject() like VB scripts do.

This is unicode-friendly regarding file names. There are comments in the code should anyone want to tweak it to handle unicode file content. The routine will default to the more modern versions of Excel and can be forced to use the lower versions as desired.

A few common options are provided as function parameters and a schema.ini file would likely be needed for more complex options. Comments in the code talk a bit about that.

The code is really simple and allows ADO to do 100% of the work. Most of the routine below consists of sanity checks along with dealing with various options. The guts is an ADO connection to the csv file and an SQL execution on that connection to create the Excel file, create the tab/sheet, and copy the csv content to that sheet -- all done in that one execution.

Code:

' API used to check if file exists
Private Declare Function GetFileAttributes Lib "kernel32.dll" Alias "GetFileAttributesW" (ByVal lpFileName As Long) As Long

Private Function ConvertCSVtoExcel(CsvFile As String, _
                            Optional CsvHasHeaders As Boolean = True, _
                            Optional ExcelSheetName As String = "Sheet1", _
                            Optional ByVal ExcelVersion8 As Boolean = False, _
                            Optional DestinationPath As String = vbNullString, _
                            Optional ReturnedErrorString As String) As String

    ' Function will return full Excel file path & name if no errors & ReturnedErrorString will be null
    '  else function returns vbNullString and ReturnedErrorString contains error description
    '  Converted file name will be the same as the CSV except having an Excel extension
   
    ' Unicode. Handles unicode file names & sheet names.
    ' For those that want to also handle unicode CSV data, you will want to pass a new parameter and
    '  modify this routine. Key google term: CharacterSet=Unicode
   
    ' ensure CsvFile exists before calling this function
    ' ensure DestinationPath has write-access. By default this path is same as CsvFile path
    ' ExcelVersion8 can be accessed by most versions of Excel except very, very old versions
    '  if absolutely needed, you may want to rework this to pass an exact version, i.e., 5, 8, 12, etc
    '  If parameter is False, v12 (xlsx extension) will be attempted & falls back to v8 if needed
    '  Version 12 driver can be found here & requires at least Win7
    '  https://www.microsoft.com/en-us/download/details.aspx?id=13255
   
    ' Last but not least, some additional info
    '  many delimited file options can be used, but require a schema.ini file & no changes in this routine
    '      i.e., other delimiter than comma, specifying column data types, different column header names, etc
    '      https://docs.microsoft.com/en-us/sql/odbc/microsoft/schema-ini-file-text-file-driver
    '  if you need to play with xlsb (binary files) vs xlsx files, remove the XML from the v12 connection string

    Static v12ProviderAbsent As Boolean
    Const E_NOPROVIDER As Long = 3706&

    Dim cn As ADODB.Connection, p As Long
    Dim sSrcFile As String, sSrcPath As String
    Dim sSQL As String, sDest As String
    Dim sHDRprop As String, sVersion As String
   
    ' sanity checks and prep
    p = InStrRev(CsvFile, "\")
    sSrcFile = Mid$(CsvFile, p + 1)
    sSrcPath = Left$(CsvFile, p)
    If DestinationPath = vbNullString Then
        sDest = sSrcPath
    ElseIf Right$(DestinationPath, 1) <> "\" Then
        sDest = DestinationPath & "\"
    Else
        sDest = DestinationPath
    End If
    If v12ProviderAbsent = True Then ExcelVersion8 = True
    p = InStrRev(sSrcFile, ".")
    If p = 0 Then sDest = sDest & "." Else sDest = sDest & Left$(sSrcFile, p)
    If ExcelVersion8 Then sDest = sDest & "xls" Else sDest = sDest & "xlsx"
    If ExcelSheetName = vbNullString Then ExcelSheetName = "Data"
    If CsvHasHeaders Then sHDRprop = "Yes" Else sHDRprop = "No"
   
    ' prevent overwriting existing file; Excel file creation fails if file/sheet already exists
    Do
        If GetFileAttributes(StrPtr(sDest)) = -1& Then Exit Do
        If ExcelVersion8 Then sDest = sDest & ".xls" Else sDest = sDest & ".xlsx"
    Loop
   
    ' verify we can open the csv
    On Error Resume Next
    Set cn = New ADODB.Connection
    cn.CursorLocation = adUseClient
    If Not ExcelVersion8 Then
        cn.ConnectionString = "Provider=Microsoft.Ace.OLEDB.12.0;Data Source=" & _
            sSrcPath & ";Extended Properties='text;HDR=" & sHDRprop & ";FMT=CSVDelimited';"
        cn.Open
        If Err Then ' failure. Either version 12 not installed or O/S less than Win7
            If Err.Number = E_NOPROVIDER Then v12ProviderAbsent = True
            ExcelVersion8 = True                ' try again using lower Excel version
            sDest = Left$(sDest, Len(sDest) - 1)
            Err.Clear
        Else
            sVersion = "12.0 XML"
        End If
    End If
    If ExcelVersion8 Then
        cn.ConnectionString = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & _
            sSrcPath & ";Extended Properties='text;HDR=" & sHDRprop & ";FMT=CSVDelimited';"
        cn.Open
        If Err Then ' can't be done via this routine
            ReturnedErrorString = Err.Description
            Err.Clear
            GoTo ExitRoutine
        End If
        sVersion = "8.0"
    End If
   
    ' create base query should we need to call it more than once
    sSQL = "SELECT * INTO [" & ExcelSheetName & "] IN '' [Excel " & sVersion & _
            ";Database=" & sDest & "] FROM [%SOURCE%]"
           
    ' create the excel file, sheet, & import data in one call
    cn.Execute Replace(sSQL, "%SOURCE%", sSrcFile)
    If Err.Number = -2147217865 Then ' doesn't recognize the CSV's 'table' name
        ' likely that the csv file name is unicode; text driver that process above SQL statement
        '  has no problems with a unicode path, but errors on unicode file names. There is a tweak:
        '  The csv likely has 2+ table names associated with it, one is the unicode name that is
        '  causing this error and another won't be
        With cn.OpenSchema(adSchemaTables)
            Do Until .EOF = True
                Err.Clear
                cn.Execute Replace(sSQL, "%SOURCE%", .Fields("Table_Name").Value)
                If Err.Number = 0 Then Exit Do
                If Not Err.Number = -2147217865 Then Exit Do    ' not gonna happen today
                .MoveNext
            Loop
            .Close
        End With
    End If
    If Err Then
        ReturnedErrorString = Err.Description
        Err.Clear
    Else
        ReturnedErrorString = vbNullString
        ConvertCSVtoExcel = sDest
    End If
   
ExitRoutine:
    If cn.State Then cn.Close
    Set cn = Nothing
End Function

[VB6] VB6.tlb - Passing a ParamArray without Copying

$
0
0
The Issue: How do you access a ParamArray, and pass it to another function,
without copying it.
The Answer: Pointer magic. ParamArrays are really just a
Variant Array - managed by the runtime. For whatever reason though - the
runtime / ide doesn't allow you to get at it's address easily, or even pass it
to another function without making a copy

This particular technique I believe was first pioneered by Matt Curland.
Originally Curland used assembly to get the stack pointer,
add a hard coded offset (depending on the function parameters passed before
the ParamArray) to calculate where the SAFEARRAY** is on the stack, then
return the dereferenced pointer. Then you can safely swap the SAFEARRAY
reference with another SAFEARRAY.

Really though - it 's as simple as using a typelib defined __vbaRefVarAry.
Why does it have to be in a typelib? I don't know, but the side effect is
that the runtime marshals the ParamArray ByRef into a VARIANT, without
creating a copy first. This is not typically allowed by the IDE. I haven't
verified if this only applies to midl generated typelibs vs mktyplib.

Anyway... here's the code. Feel free to run it through a debugger to verify
that the ParamArray is indeed not copied.


edit: Should have done my homework... seems to still make a copy.
This can is tested by passing a variable (which passes ByRef in the ParamArray) and then modifying it.
It should modify the original Variable.
Attached Files
Viewing all 1479 articles
Browse latest View live