# ftsItem_content

c0Title: How do I add or delete columns from an existing table in SQLite.
c1Code:
SQLite has limited ALTER TABLE support that you can use to add a column to the end of a table or to change the name of a table. If you want to make more complex changes in the structure of a table, you will have to recreate the table. You can save existing data to a temporary table, drop the old table, create the new table, then copy the data back in from the temporary table.

For example, suppose you have a table named "t1" with columns names "a", "b", and "c" and that you want to delete column "c" from this table. The following steps illustrate how this could be done:

BEGIN TRANSACTION;
CREATE TEMPORARY TABLE t1_backup(a,b);
INSERT INTO t1_backup SELECT a,b FROM t1;
DROP TABLE t1;
CREATE TABLE t1(a,b);
INSERT INTO t1 SELECT a,b FROM t1_backup;
DROP TABLE t1_backup;
COMMIT;


c0Title: How do I list all tables/indices contained in an SQLite database
c1Code:
If you are running the sqlite3 command-line access program you can type ".tables" to get a list of all tables. Or you can type ".schema" to see the complete database schema including all tables and indices. Either of these commands can be followed by a LIKE pattern that will restrict the tables that are displayed.

From within a C/C++ program (or a script using Tcl/Ruby/Perl/Python bindings) you can get access to table and index names by doing a SELECT on a special table named "SQLITE_MASTER". Every SQLite database has an SQLITE_MASTER table that defines the schema for the database. The SQLITE_MASTER table looks like this:

CREATE TABLE sqlite_master (
  type TEXT,
  name TEXT,
  tbl_name TEXT,
  rootpage INTEGER,
  sql TEXT
);
For tables, the type field will always be 'table' and the name field will be the name of the table. So to get a list of all tables in the database, use the following SELECT command:

SELECT name FROM sqlite_master
WHERE type='table'
ORDER BY name;
For indices, type is equal to 'index', name is the name of the index and tbl_name is the name of the table to which the index belongs. For both tables and indices, the sql field is the text of the original CREATE TABLE or CREATE INDEX statement that created the table or index. For automatically created indices (used to implement the PRIMARY KEY or UNIQUE constraints) the sql field is NULL.

The SQLITE_MASTER table is read-only. You cannot change this table using UPDATE, INSERT, or DELETE. The table is automatically updated by CREATE TABLE, CREATE INDEX, DROP TABLE, and DROP INDEX commands.

Temporary tables do not appear in the SQLITE_MASTER table. Temporary tables and their indices and triggers occur in another special table named SQLITE_TEMP_MASTER. SQLITE_TEMP_MASTER works just like SQLITE_MASTER except that it is only visible to the application that created the temporary tables. To get a list of all tables, both permanent and temporary, one can use a command similar to the following:

SELECT name FROM 
   (SELECT * FROM sqlite_master UNION ALL
    SELECT * FROM sqlite_temp_master)
WHERE type='table'
ORDER BY name


c0Title: Building fts1
c1Code:
fts1 can be built either as a standalone shared library, or statically linked into the SQLite library.

As a shared library

To build the module as a shared library, compile all source files in the fts1 directory into a shared library (.so or .dll) on your platform. (Sorry - there's no makefile checked in yet. Coming soon.)

Statically linked

To statically link fts1 into SQLite, add all .c files from the fts1 directory to the Makefile you use to compile SQLite so that they will be linked into the SQLite image. You must define the preprocessor symbols SQLITE_CORE and SQLITE_ENABLE_FTS1 when compiling these files. Add SQLITE_ENABLE_FTS3_PARENTHESIS for support of AND, OR, and nested parentheses


c0Title: Using fts1
c1Code:
Full-text tables store one or more columns of fully indexed text. You can create a full-text table using the CREATE VIRTUAL TABLE statement. For example, the following creates a table with columns name and ingredients:

  sqlite>create virtual table recipe using fts1(name, ingredients);
You can insert rows into a full-text table in the same way as into an ordinary table with columns of type TEXT:

  sqlite>insert into recipe (name, ingredients) values ('broccoli stew', 'broccoli peppers cheese tomatoes');
  sqlite>insert into recipe (name, ingredients) values ('pumpkin stew', 'pumpkin onions garlic celery');
  sqlite>insert into recipe (name, ingredients) values ('broccoli pie', 'broccoli cheese onions flour');
  sqlite>insert into recipe (name, ingredients) values ('pumpkin pie', 'pumpkin sugar flour butter');
The MATCH operator performs a full-text match on a column in a full-text table:

  sqlite> select rowid, name, ingredients from recipe where name match 'pie';
  3|broccoli pie|broccoli cheese onions flour
  4|pumpkin pie|pumpkin sugar flour butter
  sqlite>
As can be seen in the preceding output, every row in a full-text table has a unique rowid, just as in any other SQLite table.

A query may contain multiple terms, in which case it will return only documents containing all of the terms:

  sqlite> select name, ingredients from recipe where ingredients match 'onions cheese';
  broccoli pie|broccoli cheese onions flour
  sqlite>


c0Title: OR queries
c1Code:
Inside a query, the OR operator may be used to retrieve documents containing either of two terms:

  sqlite> select name, ingredients from recipe where ingredients match 'onions OR cheese';
  broccoli stew|broccoli peppers cheese tomatoes
  pumpkin stew|pumpkin onions garlic celery
  broccoli pie|broccoli cheese onions flour
  sqlite>
Note that the OR in this query must be capitalized.

The OR operator binds more tightly than the implicit AND between two adjacent terms. Thus, the query 'onions OR cheese pumpkin' matches text which contains either "onions" or "cheese", and also contains "pumpkin":

  sqlite> select name, ingredients from recipe where ingredients match 'onions OR cheese pumpkin';
  pumpkin stew|pumpkin onions garlic celery
  sqlite>
fts1 does not currently provide any grouping operator (i.e. parentheses) for overriding this default precedence.


c0Title: Excluding terms
c1Code:
The - operator excludes any documents containing the term which follows it:

  sqlite> select name, ingredients from recipe where ingredients match 'onions -celery';
  broccoli pie|broccoli cheese onions flour
  sqlite>
Note that a query must contain at least one non-excluded term:

  sqlite> select name, ingredients from recipe where ingredients match '-celery';  /* invalid! */
  SQL error: SQL logic error or missing database
  sqlite>


c0Title: Phrase searches
c1Code:
Phrases may be enclosed in double quotes:

  sqlite> select name, ingredients from recipe where ingredients match '"broccoli cheese"';
  broccoli pie|broccoli cheese onions flour
  sqlite>


c0Title: Querying multiple columns
c1Code:
To query all columns in a full-text table simultaneously, use the table's name on the left-hand side of the MATCH operator:

  sqlite> select name, ingredients from recipe where recipe match 'pie';
  broccoli pie|broccoli cheese onions flour
  pumpkin pie|pumpkin sugar flour butter
  sqlite>
When an all-column query contains multiple terms, a row will match even if the terms appear in different columns in the row:

  sqlite> select name, ingredients from recipe where recipe match 'sugar pie';
  pumpkin pie|pumpkin sugar flour butter
  sqlite>
Any term in a query string may be preceded by the name of a particular column to use for matching that term:

  sqlite> select name, ingredients from recipe where recipe match 'name:pie ingredients:onions';
  broccoli pie|broccoli cheese onions flour
  sqlite>
The following are entirely equivalent:

  sqlite> select name from recipe where ingredients match 'sugar';
  sqlite> select name from recipe where recipe match 'ingredients:sugar';
When a specific column name appears to the left of the MATCH operator, that column is used for matching any term without an explicit column qualifier. Thus, the following are equivalent:

  sqlite> select name from recipe where recipe match 'name:pie ingredients:onions';
  sqlite> select name from recipe where name match 'pie ingredients:onions';
Note that fts1 currently requires a query to contain at most one instance of the MATCH operator. This means that to match multiple specific columns you must use field specifiers as described above; the following won't work:

  sqlite> select name, ingredients from recipe
     ...>        where name match 'pie' and ingredients match 'onions';   /* invalid! */
  SQL error: unable to use function MATCH in the requested context
  sqlite>


c0Title: Retrieving offset information
c1Code:
When returning full-text matches, fts1 can return the character offsets of individual term matches. To retrieve offset information in a query, call the offsets function and pass it the name of your full-text table:

  sqlite> select name, ingredients, offsets(recipe) from recipe where recipe match 'sugar pie';
  pumpkin pie|pumpkin sugar flour butter|0 1 8 3 1 0 8 5
  sqlite>
offsets returns a string containing a series of integers separated by spaces. Each match is represented by a series of four consecutive integers:

The index of the column containing the match. Columns are numbered starting from 0.
The term in the query expression which was matched. Terms are numbered starting from 0.
The byte offset of the first character of the matching phrase, measured from the beginning of the column's text.
Number of bytes in the match.
For example, in the query above two matches are returned, which we can decode as follows:

In column 0 (name), term 1 ("pie") matched at byte offset 8; the match is 3 bytes long (the length of "pie").
In column 1 (ingredients), term 0 ("sugar") matched at byte offset 8; the match is 5 bytes long (the length of "sugar").


c0Title: Generating snippets
c1Code:
fts1 can generate snippets of text surrounding each match returned in a full-text query. To produce a snippet, call the snippet function and pass it the name of your full-text table:

  sqlite> create virtual table poem using fts1(name, text);
  sqlite> insert into poem values ('ozymandias', 'i met a traveller from an antique land who said: two vast and trunkless legs of stone stand in the desert');
  sqlite> select name, snippet(poem) from poem where text match 'land';
  ozymandias|i met a traveller from an antique <b>land</b> who said: two vast and trunkless legs of <b>...</b>
  sqlite>
By default, each matching term in a snippet is surrounded with the delimiters "<b>" and "</b>", and an ellipsis "..." indicates text not included in a snippet. (Note that the ellipsis is itself surrounded by delimiters "<b>" and "</b>".)

You can specify your own term delimiters and ellipsis text by specifying extra arguments to the snippet function. Its arguments are as follows:

The name of the virtual table being queried.
Markup to put before each matching word. Default: <b>
Markup to put after each matching word. Default: </b>
Ellipsis markup. Default: <b>...</b>
For example:

  sqlite> select name, snippet(poem, '[', ']', '%%') from poem where text match 'land';
  ozymandias|i met a traveller from an antique [land] who said: two vast and trunkless legs of %%
  sqlite>


c0Title: Joining full-text data
c1Code:
A full-text table stores only full-text-indexed strings. To store full-text-indexed values along with other values, it's convenient to use both a full-text table and an ordinary SQLite table, joined by rowid. For example, suppose that you'd like to store a set of messages; each message has a sender and priority, which are not full-text indexed, and a subject and body, which are full-text indexed. You can use the following schema:

  create table email(sender text, priority integer);
  create virtual table email_text using fts1(subject, body);
You can insert a new message as follows:

  insert into email (sender, priority) values ('ashley@foo.com', 4);
  insert into email_text (rowid, subject, body) values (last_insert_rowid(), 'update', 'coming home now');
To find the sender of all messages containing the word "jam" you can issue a query joining the email and email_text tables:

  select sender, subject from email join email_text on email.rowid = email_text.rowid
                       where body match 'jam';


c0Title: Tokenization
c1Code:
As the module indexes a piece of text, it converts the text to a sequence of tokens. Each token becomes a term in the index and can be matched using a full-text query.

The module currently uses the following generic tokenization mechanism. A token is a contiguous sequence of alphanumeric ASCII characters (A-Z, a-z and 0-9). All non-ASCII characters are ignored. Each token is converted to lowercase before it is stored in the index, so all full-text searches are case-insensitive. The module does not perform stemming of any sort.

Soon, we hope to allow applications to define their own tokenizers (we in fact already have a generic tokenizer mechanism in our code; we just have yet to expose it to the outside world).


c0Title: Performance
c1Code:
There are two steps you can take to greatly improve performance when inserting documents into a full-text index. First, you can set the synchronous pragma to OFF:

   sqlite>pragma synchronous = off;
In our testing we've found that this dramatically increases indexing speed. Of course, you should study the SQLite documentation (see http://www.sqlite.org/pragma.html) to understand the safety/speed tradeoff which this pragma controls and to determine the setting which is right for your application.

Secondly, you can index more than one document per transaction. In our testing, we've found throughput to be best when we index at least 50 or so documents in each transaction; this dramatically improves performance over the one-document-per-transaction case.

We're still in the process of assessing and improving performance.


c0Title: Creating and Destroying Tables
c1Code:
sql-command ::= CREATE VIRTUAL TABLE [ database-name .] table-name USING fts3 [( [ argument [, argument ]*] )]
argument ::= name | TOKENIZE tokenizer

tokenizer ::= SIMPLE | PORTER | user-defined

If no tokenizer is requested, then the SIMPLE tokenizer is used. Additional user-defined tokenizers may be built into a particular SQLite library, SIMPLE and PORTER are the only tokenizers provided by default. If no column names are requested, then the single column content is provided. Thus, the following statements all create a table with a single column content using tokenizer SIMPLE:

  CREATE VIRTUAL TABLE t USING fts3;
  CREATE VIRTUAL TABLE t USING fts3();
  CREATE VIRTUAL TABLE t USING fts3(content);
  CREATE VIRTUAL TABLE t USING fts3(TOKENIZE simple);
  CREATE VIRTUAL TABLE t USING fts3(content, TOKENIZE simple);
The column names docid and tokenize are reserved and should not be used in fts3 create statements. Multiple column names can be provided:

  CREATE VIRTUAL TABLE t USING fts3(name, address);
Only the first word of a column definition is considered in creating the table - all extraneous text is ignored. So the following two statements are identical:

  CREATE VIRTUAL TABLE t USING fts3(name);
  CREATE VIRTUAL TABLE t USING fts3(name NOT NULL UNIQUE);
Any additional information, including constraints and type information, is ignored.

Destroying an fts table uses the standard SQLite DROP TABLE syntax.


c0Title: Accessing fts tables
c1Code:
fts tables are accessed using SELECT, INSERT, UPDATE, and DELETE statements exactly as regular SQLite tables. The set of columns which can be referenced are exactly those enumerated in the create statement, all of which will appear to be of type TEXT. The table has the implicit unique index on rowid, plus the full-text index (see next section), but otherwise allows for no indices. A query such as:

  SELECT rowid FROM t WHERE content LIKE 'This%';
will perform a full table scan over all rows.

In addition to the explicitly-enumerated columns, the following columns are implicitly present:

  rowid - a unique 64-bit integer per row, as for a regular table.
  docid - the preferred alias for rowid.
  <table-name> - a "magic" column with the same name as the table, used for snippet generation.
These columns should not be referenced in the create statement, and will not appear to exist except when explicitly referenced by name in SELECT or INSERT statements.

When inserting into an fts table, if no docid is provided, then one is generated in the same way as values are generated for INTEGER PRIMARY KEY columns in regular SQLite tables. This docid can be accessed using LAST_INSERT_ROWID():

  INSERT INTO t VALUES ('This is a test');
  SELECT LAST_INSERT_ROWID();
The rowid or docid can be provided to INSERT statements, but providing both is an error:

  INSERT INTO t (rowid, content) VALUES (10, 'This is a test');
  INSERT INTO t (docid, content) VALUES (11, 'This is a test');
  -- Generates a new docid
  INSERT INTO t (docid, content) VALUES (null, 'This is a test');
  -- Throws an error
  INSERT INTO t (docid, rowid, content) VALUES (10, 11, 'This is a test');
fts tables do not allow UPDATE on docid, the following statements throw an error:

  UPDATE t SET docid = docid + 1 WHERE content = 'This is a test';
  UPDATE t SET rowid = rowid + 1 WHERE content = 'This is a test';


c0Title: Using the Fulltext Index
c1Code:
fts tokenizes its input data and builds an index over the resulting terms. To do queries using this index, a new MATCH operator is introduced. An example fts table:

  CREATE VIRTUAL TABLE recipe USING fts3(name, ingredients);
  INSERT INTO recipe VALUES (
    'broccoli stew', 'broccoli peppers cheese tomatoes'
  );
  INSERT INTO recipe VALUES (
    'pumpkin stew', 'pumpkin onions garlic celery'
  );
  INSERT INTO recipe VALUES (
    'broccoli pie', 'broccoli cheese onions flour'
  );
  INSERT INTO recipe VALUES (
    'pumpkin pie', 'pumpkin sugar flour butter'
  );
To find recipes which are made with onions and cheese:

  SELECT docid FROM recipe WHERE ingredients MATCH 'onions cheese';
The string to the right of the MATCH is tokenized, each token is found in the index, and the set of documents where both tokens are present is returned. Since the MATCH has column ingredients on the left, this only finds recipes where the tokens are present in that column. A search over all columns can be done using the column with the same name as the table:

  SELECT docid FROM recipe WHERE recipe MATCH 'stew';
The query language for the right-hand-side of the MATCH operator has a few variants, here described by example:

  -- Recipes with 'onions' and 'cheese' each in any column of the row.
  SELECT * FROM recipe WHERE recipe MATCH 'onions cheese';
  -- Recipes with either 'onions' or 'cheese' each in any column of
  -- the row.  OR must be upper-case.
  SELECT * FROM recipe WHERE recipe MATCH 'onions OR cheese';
  -- Recipes with 'stew' in the name column and 'onions' in the
  -- ingredients column.
  SELECT * FROM recipe WHERE recipe MATCH 'name:stew ingredients:onions';
  -- Recipes with 'green' and 'onions' next to each other in that
  -- order, in any field.  Known as "phrase search".
  SELECT * FROM recipe WHERE recipe MATCH '"green onions"';
  -- Recipes which use onions but not cheese.
  SELECT * FROM recipe WHERE recipe MATCH 'onions -cheese';
  -- Recipes which have 'onions' in any field and 'cheese' in none.
  SELECT * FROM recipe WHERE recipe MATCH 'onions -cheese';
  -- Prefix search, finds recipes with words that start with the
  -- characters 'bu' (such as "butter").
  SELECT * FROM recipe WHERE recipe MATCH 'bu*';
NOTE that the table-named column will not participate if you alias a table in a statement. So this statement will not work:

  SELECT docid FROM recipe AS ra WHERE ra MATCH 'stew';
it must be written as:

  SELECT docid FROM recipe AS ra WHERE recipe MATCH 'stew';
NOTE that MATCH cannot be used multiple times against the same table. So the following query, while valid syntax, will cause an error:

  SELECT docid FROM recipe WHERE recipe MATCH 'onions' AND recipe MATCH 'cheese';
It must be written as:

  SELECT docid FROM recipe WHERE recipe MATCH 'onions cheese';


c0Title: Complex
c1Code:
The keywords AND and OR can be used but must be capitalized. 

Parentheses may be used to group terms

To search for a word near another, use NEAR in all capitals or NEAR/x where x is the maximum number of words in between (default is 10 words).


c0Title: Compilation Options For SQLite
c1Code:
For most purposes, SQLite can be built just fine using the default compilation options. However, if required, the compile-time options documented below can be used to omit SQLite features (resulting in a smaller compiled library size) or to change the default values of some parameters.

Every effort has been made to ensure that the various combinations of compilation options work harmoniously and produce a working library. Nevertheless, it is strongly recommended that the SQLite test-suite be executed to check for errors before using an SQLite library built with non-standard compilation options.

1.1 Options To Set Default Parameter Values

SQLITE_DEFAULT_AUTOVACUUM=<1 or 0>

This macro determines if SQLite creates databases with the auto_vacuum flag set by default. The default value is 0 (do not create auto-vacuum databases). In any case the compile-time default may be overridden by the PRAGMA auto_vacuum command.

SQLITE_DEFAULT_CACHE_SIZE=<pages>

This macro sets the default size of the page-cache for each attached database, in pages. This can be overridden by the PRAGMA cache_size command. The default value is 2000.

SQLITE_DEFAULT_FILE_FORMAT=<1 or 4>

The default schema-level file format used by SQLite when creating new database files is set by this macro. The file formats are all very similar. The difference between formats 1 and 4 is that format 4 understands descending indices and has a tighter encoding for boolean values.

SQLite (as of version 3.6.0) can read and write any file format between 1 and 4. But older versions of SQLite might not be able to read formats greater than 1. So that older versions of SQLite will be able to read and write database files created by newer versions of SQLite, the default file format is set to 1 for maximum compatibility.

The file format for a new database can be set at runtime using the PRAGMA legacy_file_format command.

SQLITE_DEFAULT_MEMSTATUS=<1 or 0>

This macro is used to determine whether or not the features enabled and disabled using the SQLITE_CONFIG_MEMSTATUS argument to sqlite3_config() are available by default. The default value is 1 (SQLITE_CONFIG_MEMSTATUS related features enabled).

SQLITE_DEFAULT_PAGE_SIZE=<bytes>

This macro is used to set the default page-size used when a database is created. The value assigned must be a power of 2. The default value is 1024. The compile-time default may be overridden at runtime by the PRAGMA page_size command.

SQLITE_DEFAULT_TEMP_CACHE_SIZE=<pages>

This macro sets the default size of the page-cache for temporary files created by SQLite to store intermediate results, in pages. It does not affect the page-cache for the temp database, where tables created using CREATE TEMP TABLE are stored. The default value is 500.

YYSTACKDEPTH=<max_depth>

This macro sets the maximum depth of the LALR(1) stack used by the SQL parser within SQLite. The default value is 100. A typical application will use less than about 20 levels of the stack. Developers whose applications contain SQL statements that need more than 100 LALR(1) stack entries should seriously consider refactoring their SQL as it is likely to be well beyond the ability of any human to comprehend.

1.2 Options To Set Size Limits

There are compile-time options that will set upper bounds on the sizes of various structures in SQLite. The compile-time options normally set a hard upper bound that can be changed at run-time on individual database connections using the sqlite3_limit() interface.

The compile-time options for setting upper bounds are documented separately. The following is a list of the available settings:

SQLITE_MAX_ATTACHED
SQLITE_MAX_COLUMN
SQLITE_MAX_COMPOUND_SELECT
SQLITE_MAX_EXPR_DEPTH
SQLITE_MAX_FUNCTION_ARG
SQLITE_MAX_LENGTH
SQLITE_MAX_LIKE_PATTERN_LENGTH
SQLITE_MAX_PAGE_COUNT
SQLITE_MAX_PAGE_SIZE
SQLITE_MAX_SQL_LENGTH
SQLITE_MAX_VARIABLE_NUMBER
1.3 Options To Control Operating Characteristics

SQLITE_OS_OTHER=<0 or 1>

The option causes SQLite to omit its built-in operating system interfaces for Unix, Windows, and OS/2. The resulting library will have no default operating system interface. Applications must use sqlite3_vfs_register() to register an appropriate interface before using SQLite. Applications must also supply implementations for the sqlite3_os_init() and sqlite3_os_end() interfaces. The usual practice is for the supplied sqlite3_os_init() to invoke sqlite3_vfs_register(). SQLite will automatically invoke sqlite3_os_init() when it initializes.

This option is typically used when building SQLite for an embedded platform with a custom operating system.

SQLITE_SECURE_DELETE

This compile-time option causes SQLite to overwrite deleted information with zeros in addition to marking the space as available for reuse. Without this option, deleted data might be recoverable from a database using a binary editor. However, there is a performance penalty for using this option.

This option does not cause deleted data is securely removed from the underlying storage media.

SQLITE_THREADSAFE=<0 or 1 or 2>

This option controls whether or not code is included in SQLite to enable it to operate safely in a multithreaded environment. The default is SQLITE_THREADSAFE=1 which is safe for use in a multithreaded environment. When compiled with SQLITE_THREADSAFE=0 all mutexing code is omitted and it is unsafe to use SQLite in a multithreaded program. When compiled with SQLITE_THREADSAFE=2, SQLite can be used in a multithreaded program so long as no two threads attempt to use the same database connection at the same time.

To put it another way, SQLITE_THREADSAFE=1 sets the default threading mode to Serialized. SQLITE_THREADSAFE=2 sets the default threading mode to Multi-threaded. And SQLITE_THREADSAFE=0 sets the threading mode to Single-threaded.

The value of SQLITE_THREADSAFE can be determined at run-time using the sqlite3_threadsafe() interface.

When SQLite has been compiled with SQLITE_THREADSAFE=1 or SQLITE_THREADSAFE=2 then the threading mode can be altered at run-time using the sqlite3_config() interface together with one of these verbs:

SQLITE_CONFIG_SINGLETHREAD
SQLITE_CONFIG_MULTITHREAD
SQLITE_CONFIG_SERIALIZED
The SQLITE_OPEN_NOMUTEX and SQLITE_OPEN_FULLMUTEX flags to sqlite3_open_v2() can also be used to adjust the threading mode of individual database connections at run-time.

Note that when SQLite is compiled with SQLITE_THREADSAFE=0, the code to make SQLite threadsafe is omitted from the build. When this occurs, it is impossible to change the threading mode at start-time or run-time.

See the threading mode documentation for additional information on aspects of using SQLite in a multithreaded environment.

SQLITE_TEMP_STORE=<0 through 3>

This option controls whether temporary files are stored on disk or in memory. The meanings for various settings of this compile-time option are as follows:

SQLITE_TEMP_STORE	Meaning
0	Always use temporary files
1	Use files by default but allow the PRAGMA temp_store command to override
2	Use memory by default but allow the PRAGMA temp_store command to override
3	Always use memory
The default setting is 1. Additional information can be found in tempfiles.html.

1.4 Options To Enable Features Normally Turned Off

SQLITE_ENABLE_ATOMIC_WRITE

If this C-preprocessor macro is defined and if the xDeviceCharacteristics method of sqlite3_io_methods object for a database file reports (via one of the SQLITE_IOCAP_ATOMIC bits) that the filesystem supports atomic writes and if a transaction involves a change to only a single page of the database file, then the transaction commits with just a single write request of a single page of the database and no rollback journal is created or written. On filesystems that support atomic writes, this optimization can result in significant speed improvements for small updates. However, few filesystems support this capability and the code paths that check for this capability slow down write performance on systems that lack atomic write capability, so this feature is disabled by default.

SQLITE_ENABLE_COLUMN_METADATA

When this C-preprocessor macro is defined, SQLite includes some additional APIs that provide convenient access to meta-data about tables and queries. The APIs that are enabled by this option are:

sqlite3_column_database_name()
sqlite3_column_database_name16()
sqlite3_column_table_name()
sqlite3_column_table_name16()
sqlite3_column_origin_name()
sqlite3_column_origin_name16()
sqlite3_table_column_metadata()
SQLITE_ENABLE_FTS3

When this option is defined in the amalgamation, version 3 of the full-text search engine is added to the build automatically.

SQLITE_ENABLE_FTS3_PARENTHESIS

This option modifies the query pattern parser in FTS3 such that it supports operators AND and NOT (in addition to the usual OR and NEAR) and also allows query expressions to contain nested parenthesesis.

SQLITE_ENABLE_ICU

This option causes the International Components for Unicode or "ICU" extension to SQLite to be added to the build.

SQLITE_ENABLE_IOTRACE

When both the SQLite core and the Command Line Interface (CLI) are both compiled with this option, then the CLI provides an extra command named ".iotrace" that provides a low-level log of I/O activity. This option is experimental and may be discontinued in a future release.

SQLITE_ENABLE_LOCKING_STYLE

This option enables additional logic in the OS interface layer for Mac OS X. The additional logic attempts to determine the type of the underlying filesystem and choose and alternative locking strategy that works correctly for that filesystem type. Five locking strategies are available:

POSIX locking style. This is the default locking style and the style used by other (non Mac OS X) Unixes. Locks are obtained and released using the fcntl() system call.
AFP locking style. This locking style is used for network file systems that use the AFP (Apple Filing Protocol) protocol. Locks are obtained by calling the library function _AFPFSSetLock().
Flock locking style. This is used for file-systems that do not support POSIX locking style. Locks are obtained and released using the flock() system call.
Dot-file locking style. This locking style is used when neither flock nor POSIX locking styles are supported by the file system. Database locks are obtained by creating and entry in the file-system at a well-known location relative to the database file (a "dot-file") and relinquished by deleting the same file.
No locking style. If none of the above can be supported, this locking style is used. No database locking mechanism is used. When this system is used it is not safe for a single database to be accessed by multiple clients.
Additionally, five extra VFS implementations are provided as well as the default. By specifying one of the extra VFS implementations when calling sqlite3_open_v2(), an application may bypass the file-system detection logic and explicitly select one of the above locking styles. The five extra VFS implementations are called "unix-posix", "unix-afp", "unix-flock", "unix-dotfile" and "unix-none".

SQLITE_ENABLE_MEMORY_MANAGEMENT

This option adds extra logic to SQLite that allows it to release unused memory upon request. This option must be enabled in order for the sqlite3_release_memory() interface to work. If this compile-time option is not used, the sqlite3_release_memory() interface is a no-op. Since sqlite3_soft_heap_limit() depends on sqlite3_release_memory(), this option is also necessary for the correct operation of sqlite3_soft_heap_limit().

SQLITE_ENABLE_MEMSYS3

This option includes code in SQLite that implements an alternative memory allocator. This alternative memory allocator is only engaged when the SQLITE_CONFIG_HEAP option to sqlite3_config() is used to supply a large chunk of memory from which all memory allocations are taken. The MEMSYS3 memory allocator uses a hybrid allocation algorithm patterned after dlmalloc(). Only one of SQLITE_ENABLE_MEMSYS3 and SQLITE_ENABLE_MEMSYS5 may be enabled at once.

SQLITE_ENABLE_MEMSYS5

This option includes code in SQLite that implements an alternative memory allocator. This alternative memory allocator is only engaged when the SQLITE_CONFIG_HEAP option to sqlite3_config() is used to supply a large chunk of memory from which all memory allocations are taken. The MEMSYS5 module rounds all allocations up to the next power of two and uses a first-fit, buddy-allocator algorithm that provides strong guarantees against fragmentation and breakdown subject to certain operating constraints.

SQLITE_ENABLE_RTREE

This option causes SQLite to include support for the R*Tree index extension.

SQLITE_ENABLE_UPDATE_DELETE_LIMIT

This option enables an optional ORDER BY and LIMIT clause on UPDATE and DELETE statements.

If this option is defined, then it must also be defined when using the 'lemon' tool to generate a parse.c file. Because of this, this option may only be used when the library is built from source, not from the amalgamation or from the collection of pre-packaged C files provided for non-Unix like platforms on the website.

YYTRACKMAXSTACKDEPTH

This option causes the LALR(1) parser stack depth to be tracked and reported using the sqlite3_status(SQLITE_STATUS_PARSER_STACK,...) interface. SQLite's LALR(1) parser has a fixed stack depth (determined at compile-time using the YYSTACKDEPTH options). This option can be used to help determine if an application is getting close to exceeding the maximum LALR(1) stack depth.

1.5 Options To Disable Features Normally Turned On

SQLITE_DISABLE_LFS

If this C-preprocessor macro is defined, large file support is disabled.

SQLITE_DISABLE_DIRSYNC

If this C-preprocessor macro is defined, directory syncs are disabled. SQLite typically attempts to sync the parent directory when a file is deleted to ensure the directory entries are updated immediately on disk.

1.6 Options To Omit Features

The following options can used to reduce the size of the compiled library by omitting optional features. This is probably only useful in embedded systems where space is especially tight, as even with all features included the SQLite library is relatively small. Don't forget to tell your compiler to optimize for binary size! (the -Os option if using GCC). Telling your compiler to optimize for size usually has a much larger impact on library footprint than employing any of these compile-time options. You should also verify that debugging options are disabled.

The macros in this section do not require values. The following compilation switches all have the same effect:
-DSQLITE_OMIT_ALTERTABLE
-DSQLITE_OMIT_ALTERTABLE=1
-DSQLITE_OMIT_ALTERTABLE=0

If any of these options are defined, then the same set of SQLITE_OMIT_XXX options must also be defined when using the 'lemon' tool to generate the parse.c file and when compiling the 'mkkeywordhash' tool which generates the keywordhash.h file. Because of this, these options may only be used when the library is built from the full source, not from the amalgamation or from the collection of pre-packaged C files provided for non-Unix like platforms on the website. Any SQLITE_OMIT_XXX options which can be used directly with the amalgamation are listed below, however, the warnings in the following paragraph should be noted.

SQLITE_OMIT_AUTHORIZATION
SQLITE_OMIT_AUTOINIT
SQLITE_OMIT_BETWEEN_OPTIMIZATION
SQLITE_OMIT_BLOB_LITERAL
SQLITE_OMIT_BUILTIN_TEST
SQLITE_OMIT_CHECK
SQLITE_OMIT_COMPLETE
SQLITE_OMIT_DATETIME_FUNCS
SQLITE_OMIT_DECLTYPE
SQLITE_OMIT_DEPRECATED
SQLITE_OMIT_DISKIO
SQLITE_OMIT_FLAG_PRAGMAS
SQLITE_OMIT_FLOATING_POINT
SQLITE_OMIT_GET_TABLE
SQLITE_OMIT_INCRBLOB
SQLITE_OMIT_INTEGRITY_CHECK
SQLITE_OMIT_LIKE_OPTIMIZATION
SQLITE_OMIT_LOAD_EXTENSION
SQLITE_OMIT_LOCALTIME
SQLITE_OMIT_MEMORYDB
SQLITE_OMIT_OR_OPTIMIZATION
SQLITE_OMIT_PAGER_PRAGMAS
SQLITE_OMIT_PROGRESS_CALLBACK
SQLITE_OMIT_QUICKBALANCE
SQLITE_OMIT_SCHEMA_PRAGMAS
SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS
SQLITE_OMIT_SHARED_CACHE
SQLITE_OMIT_SUBQUERY
SQLITE_OMIT_TCL_VARIABLE
SQLITE_OMIT_TRACE
SQLITE_OMIT_TRUNCATE_OPTIMIZATION
SQLITE_OMIT_UTF16
SQLITE_OMIT_WSD
SQLITE_OMIT_XFER_OPT
All of the SQLITE_OMIT_XXX options are considered unsupported. Any of these options may be removed from the code in a future release and without warning. For any particular release, some of these options may cause compile-time or run-time failures, particularly when used in combination with other options.

SQLITE_OMIT_ALTERTABLE

When this option is defined, the ALTER TABLE command is not included in the library. Executing an ALTER TABLE statement causes a parse error.

SQLITE_OMIT_ANALYZE

When this option is defined, the ANALYZE command is omitted from the build.

SQLITE_OMIT_ATTACH

When this option is defined, the ATTACH and DETACH commands are omitted from the build.

SQLITE_OMIT_AUTHORIZATION

Defining this option omits the authorization callback feature from the library. The sqlite3_set_authorizer() API function is not present in the library.

SQLITE_OMIT_AUTOINCREMENT

This option is used to omit the AUTOINCREMENT functionality. When this is macro is defined, columns declared as "INTEGER PRIMARY KEY AUTOINCREMENT" behave in the same way as columns declared as "INTEGER PRIMARY KEY" when a NULL is inserted. The sqlite_sequence system table is neither created, nor respected if it already exists.

SQLITE_OMIT_AUTOINIT

For backwards compatibility with older versions of SQLite that lack the sqlite3_initialize() interface, the sqlite3_initialize() interface is called automatically upon entry to certain key interfaces such as sqlite3_open(), sqlite3_vfs_register(), and sqlite3_mprintf(). The overhead of invoking sqlite3_initialize() automatically in this way may be omitted by building SQLite with the SQLITE_OMIT_AUTOINIT C-preprocessor macro. When built using SQLITE_OMIT_AUTOINIT, SQLite will not automatically initialize itself and the application is required to invoke sqlite3_initialize() directly prior to beginning use of the SQLite library.

SQLITE_OMIT_AUTOVACUUM

If this option is defined, the library cannot create or write to databases that support auto_vacuum. Executing a PRAGMA auto_vacuum statement is not an error (since unknown PRAGMAs are silently ignored), but does not return a value or modify the auto-vacuum flag in the database file. If a database that supports auto-vacuum is opened by a library compiled with this option, it is automatically opened in read-only mode.

SQLITE_OMIT_BETWEEN_OPTIMIZATION

This option disables the use of indices with WHERE clause terms that employ the BETWEEN operator.

SQLITE_OMIT_BLOB_LITERAL

When this option is defined, it is not possible to specify a blob in an SQL statement using the X'ABCD' syntax.

SQLITE_OMIT_BUILTIN_TEST

A standard SQLite build includes a small amount of logic controlled by the sqlite3_test_control() interface that is used to exercise parts of the SQLite core that are difficult to control and measure using the standard API. This option omits that built-in test logic.

SQLITE_OMIT_CAST

This option causes SQLite to omit support for the CAST operator.

SQLITE_OMIT_CHECK

This option causes SQLite to omit support for CHECK constraints. The parser will still accept CHECK constraints in SQL statements, they will just not be enforced.

SQLITE_OMIT_COMPLETE

This option causes the sqlite3_complete() and sqlite3_complete16() interfaces to be omitted.

SQLITE_OMIT_COMPOUND_SELECT

This option is used to omit the compound SELECT functionality. SELECT statements that use the UNION, UNION ALL, INTERSECT or EXCEPT compound SELECT operators will cause a parse error.

SQLITE_OMIT_CONFLICT_CLAUSE

In the future, this option will be used to omit the ON CONFLICT clause from the library.

SQLITE_OMIT_DATETIME_FUNCS

If this option is defined, SQLite's built-in date and time manipulation functions are omitted. Specifically, the SQL functions julianday(), date(), time(), datetime() and strftime() are not available. The default column values CURRENT_TIME, CURRENT_DATE and CURRENT_DATETIME are still available.

SQLITE_OMIT_DECLTYPE

This option causes SQLite to omit support for the sqlite3_column_decltype() and sqlite3_column_decltype16() interfaces.

SQLITE_OMIT_DEPRECATED

This option causes SQLite to omit support for interfaces marked as deprecated. This includes sqlite3_aggregate_count(), sqlite3_expired(), sqlite3_transfer_bindings(), sqlite3_global_recover(), sqlite3_thread_cleanup() and sqlite3_memory_alarm() interfaces.

SQLITE_OMIT_DISKIO

This option omits all support for writing to the disk and forces databases to exist in memory only.

SQLITE_OMIT_EXPLAIN

Defining this option causes the EXPLAIN command to be omitted from the library. Attempting to execute an EXPLAIN statement will cause a parse error.

SQLITE_OMIT_FLAG_PRAGMAS

This option omits support for a subset of PRAGMA commands that query and set boolean properties.

SQLITE_OMIT_FLOATING_POINT

This option is used to omit floating-point number support from the SQLite library. When specified, specifying a floating point number as a literal (i.e. "1.01") results in a parse error.

In the future, this option may also disable other floating point functionality, for example the sqlite3_result_double(), sqlite3_bind_double(), sqlite3_value_double() and sqlite3_column_double() API functions.

SQLITE_OMIT_FOREIGN_KEY

If this option is defined, FOREIGN KEY clauses in column declarations are ignored.

SQLITE_OMIT_GET_TABLE

This option causes support for sqlite3_get_table() and sqlite3_free_table() to be omitted.

SQLITE_OMIT_INCRBLOB

This option causes support for incremental BLOB I/O to be omitted.

SQLITE_OMIT_INTEGRITY_CHECK

This option omits support for the integrity_check pragma.

SQLITE_OMIT_LIKE_OPTIMIZATION

This option disables the ability of SQLite to use indices to help resolve LIKE and GLOB operators in a WHERE clause.

SQLITE_OMIT_LOAD_EXTENSION

This option omits the entire extension loading mechanism from SQLite, including sqlite3_enable_load_extension() and sqlite3_load_extension() interfaces.

SQLITE_OMIT_LOCALTIME

This option omits the "localtime" modifier from the date and time functions. This option is sometimes useful when trying to compile the date and time functions on a platform that does not support the concept of local time.

SQLITE_OMIT_MEMORYDB

When this is defined, the library does not respect the special database name ":memory:" (normally used to create an in-memory database). If ":memory:" is passed to sqlite3_open(), sqlite3_open16(), or sqlite3_open_v2(), a file with this name will be opened or created.

SQLITE_OMIT_OR_OPTIMIZATION

This option disables the ability of SQLite to use an index together with terms of a WHERE clause connected by the OR operator.

SQLITE_OMIT_PAGER_PRAGMAS

Defining this option omits pragmas related to the pager subsystem from the build.

SQLITE_OMIT_PRAGMA

This option is used to omit the PRAGMA command from the library. Note that it is useful to define the macros that omit specific pragmas in addition to this, as they may also remove supporting code in other sub-systems. This macro removes the PRAGMA command only.

SQLITE_OMIT_PROGRESS_CALLBACK

This option may be defined to omit the capability to issue "progress" callbacks during long-running SQL statements. The sqlite3_progress_handler() API function is not present in the library.

SQLITE_OMIT_QUICKBALANCE

This option omits an alternative, faster B-Tree balancing routine. Using this option makes SQLite slightly smaller at the expense of making it run slightly slower.

SQLITE_OMIT_REINDEX

When this option is defined, the REINDEX command is not included in the library. Executing a REINDEX statement causes a parse error.

SQLITE_OMIT_SCHEMA_PRAGMAS

Defining this option omits pragmas for querying the database schema from the build.

SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS

Defining this option omits pragmas for querying and modifying the database schema version and user version from the build. Specifically, the schema_version and user_version PRAGMAs are omitted.

SQLITE_OMIT_SHARED_CACHE

This option builds SQLite without support for shared-cache mode. The sqlite3_enable_shared_cache() is omitted along with a fair amount of logic within the B-Tree subsystem associated with shared cache management.

SQLITE_OMIT_SUBQUERY

If defined, support for sub-selects and the IN() operator are omitted.

SQLITE_OMIT_TCL_VARIABLE

If this macro is defined, then the special "$" syntax used to automatically bind SQL variables to TCL variables is omitted.

SQLITE_OMIT_TEMPDB

This option omits support for TEMP or TEMPORARY tables.

SQLITE_OMIT_TRACE

This option omits support for the sqlite3_profile() and sqlite3_trace() interfaces and their associated logic.

SQLITE_OMIT_TRIGGER

Defining this option omits support for TRIGGER objects. Neither the CREATE TRIGGER or DROP TRIGGER commands are available in this case, and attempting to execute either will result in a parse error.

WARNING: If this macro is defined, it will not be possible to open a database for which the schema contains TRIGGER objects.

SQLITE_OMIT_TRUNCATE_OPTIMIZATION

A default build of SQLite, if a DELETE statement has no WHERE clause and operates on a table with no triggers, an optimization occurs that causes the DELETE to occur by dropping and recreating the table. Dropping and recreating a table is usually much faster than deleting the table content row by row. This is the "truncate optimization".

SQLITE_OMIT_UTF16

This macro is used to omit support for UTF16 text encoding. When this is defined all API functions that return or accept UTF16 encoded text are unavailable. These functions can be identified by the fact that they end with '16', for example sqlite3_prepare16(), sqlite3_column_text16() and sqlite3_bind_text16().

SQLITE_OMIT_VACUUM

When this option is defined, the VACUUM command is not included in the library. Executing a VACUUM statement causes a parse error.

SQLITE_OMIT_VIEW

Defining this option omits support for VIEW objects. Neither the CREATE VIEW nor the DROP VIEW commands are available in this case, and attempting to execute either will result in a parse error.

WARNING: If this macro is defined, it will not be possible to open a database for which the schema contains VIEW objects.

SQLITE_OMIT_VIRTUALTABLE

This option omits support for the Virtual Table mechanism in SQLite.

SQLITE_OMIT_WSD

This options builds a version of the SQLite library that contains no Writable Static Data (WSD). WSD is global variables and/or static variables. Some platforms do not support WSD, and this option is necessary in order for SQLite to work those platforms.

Unlike other OMIT options which make the SQLite library smaller, this option actually increases the size of SQLite and makes it run a little slower. Only use this option if SQLite is being built for an embedded target that does not support WSD.

SQLITE_OMIT_XFER_OPT

This option omits support for optimizations that help statements of the form "INSERT INTO ... SELECT ..." run faster.

SQLITE_ZERO_MALLOC

This option omits both the default memory allocator and the debugging memory allocator from the build and substitutes a stub memory allocator that always fails. SQLite will not run with this stub memory allocator since it will be unable to allocate memory. But this stub can be replaced at start-time using sqlite3_config(SQLITE_CONFIG_MALLOC,...) or sqlite3_config(SQLITE_CONFIG_HEAP,...). So the net effect of this compile-time option is that it allows SQLite to be compiled and linked against a system library that does not support malloc(), free(), and/or realloc().

1.7 Analysis and Debugging Options

SQLITE_DEBUG

The SQLite source code contains literally thousands of assert() statements used to verify internal assumptions and subroutine preconditions and postconditions. These assert() statements are normally turned off (they generate no code) since turning them on makes SQLite run approximately three times slower. But for testing and analysis, it is useful to turn the assert() statements on. The SQLITE_DEBUG compile-time option does this. SQLITE_DEBUG also turns on some other debugging features.

SQLITE_MEMDEBUG

The SQLITE_MEMDEBUG option causes an instrumented debugging memory allocator to be used as the default memory allocator within SQLite. The instrumented memory allocator checks for misuse of dynamically allocated memory. Examples of misuse include using memory after it is freed, writing off the ends of a memory allocation, freeing memory not previously obtained from the memory allocator, or failing to initialize newly allocated memory.

This page last modified 2009/02/05 01:45:49 UTC


c0Title: Optimize FTS Index Size
c1Code:
For performance reasons, fts3 stores new updates in new segments (in
ft_segments), and over time merges them together.  So index
information doesn't always immediately change to reflect "truth".
Over time as additional updates happen, older segments are merged and
older data is dropped (as newer data supercedes it), so you should
eventually see things stabilize.

Additionally, fts3 implements delete as a sort of negative posting
list.  Unfortunately, when implementing things I was not able to think
of a clear way to handle negative posting lists supercede positive
posting lists while still maintaining correctness across segments.  I
have an implementation which implements this, but haven't polished it
up enough to check it in.

Over the summer I added an fts3 function called optimize() which can
be used to deal with some of this.  If you do something like this:

   SELECT optimize(ft) FROM ft LIMIT 1;

fts3 will combine all index data into a single segment, and drop
unnecessary negative posting lists.  Also, if you happen to delete all
data from your fts3 table, the index will be cleared out.

Long-term, the ability to have negative posting lists annihilate
positive posting lists is obviously the better solution.  I h


c0Title: FTS3 and JOIN, cannot use MATCH operator
c1Code:
Ticket 3281: FTS3 and JOIN, cannot use MATCH operator

Using a fts3-enabled sqlite and the following database:
  create table indexes(docid int);
  create virtual table texts using fts3(reading);
  insert into texts values('text1');
  insert into indexes values(last_insert_rowid());
Running the following query:

  select *
    from indexes join texts on texts.docid = indexes.docid
   where texts.reading match 'text1';
fails with error message: SQL error: unable to use function MATCH in the requested context

Quoting Scott Hess from the mailing-list: "I suspect that what's happening is that somewhere SQLite is doing the join on docid first, because it is certainly going to be efficient, but since you can't MATCH outside the index, it's not the right choice. One option would be to implement MATCH outside the index, but that would be potentially VERY slow, and I don't think you can express "This join costs N if you choose this index, and N*1000 if you don't"."

Workaround: turn the query into

  select *
    from texts join indexes on texts.docid = indexes.docid
   where texts.docid IN (
      SELECT docid FROM texts WHERE reading match 'text1'
   );


c0Title: FTS3 Tokenizers
c1Code:
When creating a new full-text table, FTS3 allows the user to select the text tokenizer implementation to be used when indexing text by specifying a "tokenize" clause as part of the CREATE VIRTUAL TABLE statement:

    CREATE VIRTUAL TABLE <table-name> USING fts3(
      <columns ...> [, tokenize <tokenizer-name> [<tokenizer-args>]]
    );

The built-in tokenizers (valid values to pass as <tokenizer name>) are "simple" and "porter".

  <tokenizer-args> should consist of zero or more white-space separated
  arguments to pass to the selected tokenizer implementation. The 
  interpretation of the arguments, if any, depends on the individual 
  tokenizer.


c0Title: fts3 README.syntax 1.2
c1Code:
sqlite/ext/fts3/README.syntax 1.2

1. OVERVIEW

  This README file describes the syntax of the arguments that may be passed to
  the FTS3 MATCH operator used for full-text queries. For example, if table 
  "t1" is an Fts3 virtual table, the following SQL query:

    SELECT * FROM t1 WHERE <col> MATCH <full-text query>

  may be used to retrieve all rows that match a specified for full-text query. 
  The text "<col>" should be replaced by either the name of the fts3 table 
  (in this case "t1"), or by the name of one of the columns of the fts3 
  table. <full-text-query> should be replaced by an SQL expression that 
  computes to a string containing an Fts3 query.

  If the left-hand-side of the MATCH operator is set to the name of the
  fts3 table, then by default the query may be matched against any column
  of the table. If it is set to a column name, then by default the query
  may only match the specified column. In both cases this may be overriden
  as part of the query text (see sections 2 and 3 below).

  As of SQLite version 3.6.8, Fts3 supports two slightly different query 
  formats; the standard syntax, which is used by default, and the enhanced
  query syntax which can be selected by compiling with the pre-processor
  symbol SQLITE_ENABLE_FTS3_PARENTHESIS defined.

    -DSQLITE_ENABLE_FTS3_PARENTHESIS

2. STANDARD QUERY SYNTAX

  When using the standard Fts3 query syntax, a query usually consists of a 
  list of terms (words) separated by white-space characters. To match a
  query, a row (or column) of an Fts3 table must contain each of the specified
  terms. For example, the following query:

    <col> MATCH 'hello world'

  matches rows (or columns, if <col> is the name of a column name) that 
  contain at least one instance of the token "hello", and at least one 
  instance of the token "world". Tokens may be grouped into phrases using
  quotation marks. In this case, a matching row or column must contain each
  of the tokens in the phrase in the order specified, with no intervening
  tokens. For example, the query:

    <col> MATCH '"hello world" joe"

  matches the first of the following two documents, but not the second or
  third:

    "'Hello world', said Joe."
    "One should always greet the world with a cheery hello, thought Joe."
    "How many hello world programs could their be?"

  As well as grouping tokens together by phrase, the binary NEAR operator 
  may be used to search for rows that contain two or more specified tokens 
  or phrases within a specified proximity of each other. The NEAR operator
  must always be specified in upper case. The word "near" in lower or mixed
  case is treated as an ordinary token. For example, the following query:

    <col> MATCH 'engineering NEAR consultancy'

  matches rows that contain both the "engineering" and "consultancy" tokens
  in the same column with not more than 10 other words between them. It does
  not matter which of the two terms occurs first in the document, only that
  they be seperated by only 10 tokens or less. The user may also specify
  a different required proximity by adding "/N" immediately after the NEAR
  operator, where N is an integer. For example:

    <col> MATCH 'engineering NEAR/5 consultancy'

  searches for a row containing an instance of each specified token seperated
  by not more than 5 other tokens. More than one NEAR operator can be used
  in as sequence. For example this query:

    <col> MATCH 'reliable NEAR/2 engineering NEAR/5 consultancy'

  searches for a row that contains an instance of the token "reliable" 
  seperated by not more than two tokens from an instance of "engineering",
  which is in turn separated by not more than 5 other tokens from an
  instance of the term "consultancy". Phrases enclosed in quotes may
  also be used as arguments to the NEAR operator.

  Similar to the NEAR operator, one or more tokens or phrases may be 
  separated by OR operators. In this case, only one of the specified tokens
  or phrases must appear in the document. For example, the query:

    <col> MATCH 'hello OR world'

  matches rows that contain either the term "hello", or the term "world",
  or both. Note that unlike in many programming languages, the OR operator
  has a higher precedence than the AND operators implied between white-space
  separated tokens. The following query matches documents that contain the
  term 'sqlite' and at least one of the terms 'fantastic' or 'impressive',
  not those that contain both 'sqlite' and 'fantastic' or 'impressive':

    <col> MATCH 'sqlite fantastic OR impressive'

  Any token that is part of an Fts3 query expression, whether or not it is
  part of a phrase enclosed in quotes, may have a '*' character appended to
  it. In this case, the token matches all terms that begin with the characters
  of the token, not just those that exactly match it. For example, the 
  following query:

    <col> MATCH 'sql*'

  matches all rows that contain the term "SQLite", as well as those that
  contain "SQL".

  A token that is not part of a quoted phrase may be preceded by a '-'
  character, which indicates that matching rows must not contain the 
  specified term. For example, the following:

    <col> MATCH '"database engine" -sqlite'

  matches rows that contain the phrase "database engine" but do not contain
  the term "sqlite". If the '-' character occurs inside a quoted phrase,
  it is ignored. It is possible to use both the '-' prefix and the '*' postfix
  on a single term. At this time, all Fts3 queries must contain at least
  one term or phrase that is not preceded by the '-' prefix.

  Regardless of whether or not a table name or column name is used on the 
  left hand side of the MATCH operator, a specific column of the fts3 table
  may be associated with each token in a query by preceding a token with
  a column name followed by a ':' character. For example, regardless of what
  is specified for <col>, the following query requires that column "col1"
  of the table contains the term "hello", and that column "col2" of the
  table contains the term "world". If the table does not contain columns
  named "col1" and "col2", then an error is returned and the query is
  not run.

    <col> MATCH 'col1:hello col2:world'

  It is not possible to associate a specific table column with a quoted 
  phrase or a term preceded by a '-' operator. A '*' character may be
  appended to a term associated with a specific column for prefix matching.

3. ENHANCED QUERY SYNTAX

  The enhanced query syntax is quite similar to the standard query syntax,
  with the following four differences:

  1) Parenthesis are supported. When using the enhanced query syntax,
     parenthesis may be used to overcome the built-in precedence of the
     supplied binary operators. For example, the following query:

       <col> MATCH '(hello world) OR (simple example)'

     matches documents that contain both "hello" and "world", and documents
     that contain both "simple" and "example". It is not possible to forumlate
     such a query using the standard syntax.

  2) Instead of separating tokens and phrases by whitespace, an AND operator
     may be explicitly specified. This does not change query processing at
     all, but may be used to improve readability. For example, the following
     query is handled identically to the one above:

       <col> MATCH '(hello AND world) OR (simple AND example)'

     As with the OR and NEAR operators, the AND operator must be specified
     in upper case. The word "and" specified in lower or mixed case is 
     handled as a regular token.

  3) The '-' token prefix is not supported. Instead, a new binary operator,
     NOT, is included. The NOT operator requires that the query specified
     as its left-hand operator matches, but that the query specified as the
     right-hand operator does not. For example, to query for all rows that
     contain the term "example" but not the term "simple", the following
     query could be used:

       <col> MATCH 'example NOT simple'

     As for all other operators, the NOT operator must be specified in
     upper case. Otherwise it will be treated as a regular token.

  4) Unlike in the standard syntax, where the OR operator has a higher
     precedence than the implicit AND operator, when using the enhanced
     syntax implicit and explict AND operators have a higher precedence
     than OR operators. Using the enhanced syntax, the following two
     queries are equivalent:

       <col> MATCH 'sqlite fantastic OR impressive'
       <col> MATCH '(sqlite AND fantastic) OR impressive'

     however, when using the standard syntax, the query:

       <col> MATCH 'sqlite fantastic OR impressive'

     is equivalent to the enhanced syntax query:

       <col> MATCH 'sqlite AND (fantastic OR impressive)'

     The precedence of all enhanced syntax operators, in order from highest
     to lowest, is:

       NEAR       (highest precedence, tightest grouping)
       NOT
       AND
       OR         (lowest precedence, loosest grouping)

  Using the advanced syntax, it is possible to specify expressions enclosed
  in parenthesis as operands to the NOT, AND and OR operators. However both
  the left and right hand side operands of NEAR operators must be either
  tokens or phrases. Attempting the following query will return an error:

    <col> MATCH 'sqlite NEAR (fantastic OR impressive)'

  Queries of this form must be re-written as:

    <col> MATCH 'sqlite NEAR fantastic OR sqlite NEAR impressive'


c0Title: Full-Text Searching with SQLite
c1Code:
17th Dec 2008, by Scott MacVicar:
http://phpadvent.org/2008/full-text-searching-with-sqlite-by-scott-macvicar

I�ve evangelized SQLite a lot this year, speaking at various conferences and user groups to try encourage use and support within many web applications. So, it should be no surprise that this is the topic of my article for PHP Advent. I�m going to be covering a lesser known feature: full-text searching.

Most users are spoiled by Google and its ability to index just about everything it can get its hands on. The end result is that any popular application that wants to remain competitive has to have some way to search. There are many solutions available to application developers, such as using the native implementations within MySQL and PostgreSQL, or standalone software such as Sphinx and Lucene. These, however, can either be tricky to set up, or overkill for the purpose.

Luckily, Google have contributed some resources to SQLite in order to help. The first full-text searching implementation is available in version 3.3.8. This version provides the functionality to create a virtual table that relies on an external extension: in this case, a full-text searching algorithm for any of the text columns within the virtual table. Support for this was only enabled by default�with PDO and SQLite3�in PHP 5.3.0. Earlier versions of PHP can use the SQLite3 extension in PECL.

Creating a search index usually happens as follows:

Split the text into tokens.
Convert to lowercase.
Determine the root word.
Build the index.
Setting It All Up

SQLite provides two basic tokenizers by default, SIMPLE and PORTER. These control the way words are separated. The simple tokenizer will split text into tokens based upon spaces and punctuation. The porter tokenizer is designed for use with English and will collapse the extensions of many words to their base form. Words such as consolidate, consolidated, and consolidating will be changed to consolid.

Unfortunately, there is currently no removal of stop words within SQLite, so common words such as the, of, and to will be populated within the index. This can greatly increase the size of the index and slow down searching. The simplest solution to this is to strip out the stop words manually prior to inserting the entry into the table.

Now for some code to show you how to create your first full text index. SQLite does this by creating a virtual table using the FTS3 extension. Only the text columns within this virtual table will be searchable, and the last column is used to identify which tokenizer to use.

CREATE VIRTUAL TABLE example
USING FTS3(title TEXT, TOKENIZE SIMPLE)
After creating the table, you can query it using SELECT, INSERT, UPDATE, and DELETE statements. There is one caveat: no further indices can be created on the table, so simple queries will result in a full table scan.

Once you insert some data, you can try it out. For the rest of the examples in this article, I�m going to utilize all the titles from the English version of Wikipedia. There are 5,453,838 rows in my database, and with the fulltext index, it is 233 MB in size compared to 146 MB without the index.

Searching

Searching the index is done with the MATCH operator. The query can contain multiple terms, in which case it will return only rows containing all of the terms. There is also support for OR queries, which exclude terms, exact phrase matching, and prefix searching.

SELECT rowid, title FROM example WHERE title MATCH 'tea bag'
SELECT rowid, title FROM example WHERE title MATCH 'tea OR bag'
SELECT rowid, title FROM example WHERE title MATCH 'tea -bag'
SELECT rowid, title FROM example WHERE title MATCH '"tea bag"'
SELECT rowid, title FROM example WHERE title MATCH 'tea*'
Note that the OR is case sensitive, and only a single MATCH operator is allowed in a query.

Creating Snippets

In order to provide context to the matches within a search result, a snippet() function is available. This function will highlight any of the search words that were within the column in which a result was found.

SELECT title, snippet(example)
FROM   example
WHERE  title
MATCH  'Advent'
ADVENT|ADVENT
Advent|Advent
Advent,_Cornwall|Advent,_Cornwall
Advent,_West_Virginia|Advent,_West_Virginia
Advent:_One-Winged_Angel|Advent:_One-Winged_Angel
Advent_(band)|Advent_(band)
Several additional arguments are available to indicate the markup used to surround the matched term, and any ellipsis for cutting the result short. These are the second, third, and fourth arguments respectively, the first being used to indicate the table name.

Non-English Tokenizing

For those wishing to perform full-text searching for non-English text, there is some basic functionality provided via the ICU library. This library is a core part of PHP 6 and is also available in PHP 5.3 with the intl extension. In either of these cases, SQLite will have an additional tokenizer.

The ICU tokenizer will break words on boundaries as identified by the rules for the language and locale specified. This will work for some locales, but not all. The syntax is as follows:

CREATE VIRTUAL TABLE example
USING FTS3(title TEXT, TOKENIZE icu en_GB)
The second argument here is composed of language, country, and variant information.


c0Title: Using Plus Sign When Joining FTS Tables
c1Code:
select text from fts, r 
  where +fts.rowid = r.fts_id and text match 'abc' 
  order by rank desc 
  limit 2; 

This query works well, even if the '+' prefixing the RowID is a little awkward. However, it is necessary to avoid an SQLite error, which I do not know if it is rooted in the virtual table or FTS implementation. I would certainly appreciate if FTS queries could be freely joined with other tables without adding '+' prefixes. 



c0Title: SQLite Tutorial
c2File: sqlite_examples.zip
c1Code:
Copyright (c) 2004 by Mike Chirico mchirico@users.sourceforge.net.
This material may be distributed only subject to the terms and conditions set forth in the Open Publication License v1.0, 8 June 1999 or later.

Download: http://prdownloads.sourceforge.net/souptonuts/sqlite_examples.tar.gz?download

Updated: Thu Dec 23 18:52:31 EST 2004

This article explores the power and simplicity of sqlite3, first by starting with common commands and triggers, then the attach statement with the union operation is introduced in a way that allows multiple tables, in separate databases, to be combined as one virtual table, without the overhead of copying or moving data. Next, the simple sign function and the amazingly powerful trick of using this function in SQL select statements to solve complex queries with a single pass through the data is demonstrated, after making a brief mathematical case for how the sign function defines the absolute value and IF conditions.

Although the sign function currently does not exist in sqlite3, it is very easy to create in the "/src/func.c" file so that this function will be permanently available to all sqlite applications. Normally, user functions are created in C, Perl, or C++ which is also documented in this article. sqlite3 has the ability to store "blob", binary data. The sample program in the download "eatblob.c" reads a binary file of any size into memory and stores the data into a user specified field.

All examples can be found in sqlite_examples.tar.gz , and I would encourage you to download and run these examples as you read through this document.

The homepage for sqlite3 is www.sqlite.org and the source for sqlite3 can be downloaded at www.sqlite.org/download.htm. This tutorial was done with the source version 3.0.8

Getting Started - Common Commands

To create a database file, run the command "sqlite3" followed by the database name. For example, to create the database "test.db" run the sqlite3 command as follows:

     $ sqlite3 test.db
     SQLite version 3.0.8
     Enter ".help" for instructions
     sqlite> .quit
     $
The database file test.db will be created, if it does not already exist. Running this command will leave you in the sqlite3 environment. There are 3 ways to safely exit this environment (.q, .quit, or .exit).

You do not have to enter the sqlite3 interactive environent. Instead, you could perform all commands at the shell prompt, which is ideal when running bash scripts and comands in an ssh string. Below is an example of how you would create a simple table from the command prompt.

     $ sqlite3 test.db  "create table t1 (t1key INTEGER 
                  PRIMARY KEY,data TEXT,num double,timeEnter DATE);"
After table t1 has been created, data can be inserted as follows:

     $ sqlite3 test.db  "insert into t1 (data,num) values ('This is sample data',3);"
     $ sqlite3 test.db  "insert into t1 (data,num) values ('More sample data',6);"
     $ sqlite3 test.db  "insert into t1 (data,num) values ('And a little more',9);"
As expected, doing a select returns the data in the table. Note, the primary key "t1key" auto increments; however, there are no default values for timeEnter. To populate the timeEnter field with the time, an update trigger is needed. An important note on the PRIMARY KET: do not use the abbreviated "INT" when working with the PRIMARY KEY. You must use "INTEGER", for the primary key to update.

     $ sqlite3 test.db  "select * from t1 limit 2";
     1|This is sample data|3|
     2|More sample data|6|
In the statement above, the limit clause is used and only 2 rows are displayed. For a quick reference of SQL syntax statements avaliable with SQLite, see the link syntax. There is an offset option to the limit clause. For instance, the third row is equal to the following: "limit 1 offset 2".

     $ sqlite3 test.db "select * from t1 order by t1key limit 1 offset 2";
     3|And a little more|9|
The ".table" command shows the table names. For a more comprehensive list of tables, triggers, and indexes created in the database, query the master table "sqlite_master" as shown below.

     $ sqlite3 test.db ".table"
     t1

     $ sqlite3 test.db "select * from sqlite_master"
     table|t1|t1|2|CREATE TABLE t1 (t1key INTEGER 
                  PRIMARY KEY,data TEXT,num double,timeEnter DATE)

All SQL information and data inserted into a database can be extracted with the ".dump" command.

     $ sqlite3 test.db ".dump"
     BEGIN TRANSACTION;
     CREATE TABLE t1 (t1key INTEGER
                  PRIMARY KEY,data TEXT,num double,timeEnter DATE);
     INSERT INTO "t1" VALUES(1, 'This is sample data', 3, NULL);
     INSERT INTO "t1" VALUES(2, 'More sample data', 6, NULL);
     INSERT INTO "t1" VALUES(3, 'And a little more', 9, NULL);
     COMMIT;
The contents of the ".dump" can be filtered and piped to another database. Below table t1 is changed to t2 with the sed command, and it is piped into the test2.db database.

      $ sqlite3 test.db ".dump"|sed -e s/t1/t2/|sqlite3 test2.db
Triggers

An insert trigger is created below in the file "trigger1". The Coordinated Universal Time (UTC) will be entered into the field "timeEnter", and this trigger will fire after a row has been inserted into the table t1. Again, this trigger will fire after the row has been inserted.

     -- ********************************************************************
     --   Creating a trigger for timeEnter
     --     Run as follows:
     --            $ sqlite3 test.db < trigger1
     -- ********************************************************************
     CREATE TRIGGER insert_t1_timeEnter AFTER  INSERT ON t1
     BEGIN
      UPDATE t1 SET timeEnter = DATETIME('NOW')  WHERE rowid = new.rowid;
     END;
     -- ********************************************************************
The AFTER specification in ..."insert_t1_timeEnter AFTER..." is necessary. Without the AFTER keyword, the rowid would not have been generated. This is a common source of errors with triggers, since AFTER is NOT the default, so it must be specified. In summary, if your trigger depends on newly created data, in any of the fields from the created row, which was the case for us in this example since we need the rowid, then, the AFTER specification is needed. Otherwise, the trigger is a BEFORE trigger, and will fire before rowid or other pertinent data is entered into the field.

Comments are preceeded by "--". If this script was created in the file "trigger1", you could easily execute this script as follows.

     $ sqlite3 test.db < trigger1
Now try entering a new record as before, and you should see the time in the field timeEnter.

     sqlite3 test.db  "insert into t1 (data,num) values ('First entry with timeEnter',19);"
Doing a select reveals the following data:

     $ sqlite3 test.db "select * from t1";
     1|This is sample data|3|
     2|More sample data|6|
     3|And a little more|9|
     4|First entry with timeEnter|19|2004-10-02 15:12:19
If you look at the statement above, the last value has the timeEnter filled in automatically with Coordinated Universal Time - or (UTC). If you want localtime, then, use select datetime('now','localtime'). See the note at the end of this section regarding UTC and localtime.

For examples that follow the table "exam" and the database "examScript" will be used. The table and trigger are defined below. Just like the trigger above, UTC time will be used.

     -- *******************************************************************
     --  examScript: Script for creating exam table                        
     --   Usage:                                                           
     --       $ sqlite3 examdatabase < examScript                          
     --                                                                    
     --   Note: The trigger insert_exam_timeEnter                          
     --          updates timeEnter in exam                                 
     -- *******************************************************************
     -- *******************************************************************
     CREATE TABLE exam (ekey      INTEGER PRIMARY KEY,                     
                        fn        VARCHAR(15),                             
                        ln        VARCHAR(30),                             
                        exam      INTEGER,                                 
                        score     DOUBLE,                                  
                        timeEnter DATE);                                   
                                                                           
     CREATE TRIGGER insert_exam_timeEnter AFTER  INSERT ON exam            
     BEGIN                                                                 
                                                                           
     UPDATE exam SET timeEnter = DATETIME('NOW')                           
              WHERE rowid = new.rowid;                                     
     END;                                                                  
     -- *******************************************************************
     -- *******************************************************************
After the script file, it can be executed, by redirecting the contents of the script file into the sqlite3 command, followed by the database name. See the example below:

     $ sqlite3 examdatabase < examScript                            
     $ sqlite3 examdatabase "insert into exam (ln,fn,exam,score)   
            values ('Anderson','Bob',1,75)"                        
                                                                   
     $ sqlite3 examdatabase "select * from exam"                   
                                                                   
     1|Bob|Anderson|1|75|2004-10-02 15:25:00                       
And, as a check, the PRIMARY KEY and current UTC time have been updated correctly, as seen from the above example.

Logging All Inserts, Updates, and Deletes

The script below creates the table examlog and three triggers update_examlog, insert_examlog, and delete_examlog to record update, inserts, and deletes made to the exam table. In other words, anytime a change is made to the exam table, the changes will be recorded in the examlog table, including the old value and the new value. By the way if you are familiar with MySQL, the functionality of this log table is similiar to MySQL's binlog. See ( TIP 2, TIP 24 and TIP 25) if you would like more information on MySQL's log file.

     -- *******************************************************************
     --  examLog: Script for creating log table and related triggers       
     --   Usage:                                                           
     --       $ sqlite3 examdatabase < examLOG                             
     --                                                                    
     --                                                                    
     -- *******************************************************************
     -- *******************************************************************
     CREATE TABLE examlog (lkey INTEGER PRIMARY KEY,                       
                       ekey INTEGER,                                       
                       ekeyOLD INTEGER,                                    
                       fnNEW   VARCHAR(15),                                
                       fnOLD   VARCHAR(15),                                
                       lnNEW   VARCHAR(30),                                
                       lnOLD   VARCHAR(30),                                
                       examNEW INTEGER,                                    
                       examOLD INTEGER,                                    
                       scoreNEW DOUBLE,                                    
                       scoreOLD DOUBLE,                                    
                       sqlAction VARCHAR(15),                              
                       examtimeEnter    DATE,                              
                       examtimeUpdate   DATE,                              
                       timeEnter        DATE);                             
                                                                           
     --  Create an update trigger                                          
     CREATE TRIGGER update_examlog AFTER UPDATE  ON exam                   
     BEGIN                                                                 
                                                                           
       INSERT INTO examlog  (ekey,ekeyOLD,fnOLD,fnNEW,lnOLD,               
                             lnNEW,examOLD,examNEW,scoreOLD,               
                             scoreNEW,sqlAction,examtimeEnter,             
                             examtimeUpdate,timeEnter)                     
                                                                           
               values (new.ekey,old.ekey,old.fn,new.fn,old.ln,             
                       new.ln,old.exam, new.exam,old.score,                
                       new.score, 'UPDATE',old.timeEnter,                  
                       DATETIME('NOW'),DATETIME('NOW') );                  
                                                                           
     END;                                                                  
     --                                                                    
     --  Also create an insert trigger                                     
     --    NOTE  AFTER keyword ------v                                     
     CREATE TRIGGER insert_examlog AFTER INSERT ON exam                    
     BEGIN                                                                 
     INSERT INTO examlog  (ekey,fnNEW,lnNEW,examNEW,scoreNEW,              
                           sqlAction,examtimeEnter,timeEnter)              
                                                                           
               values (new.ekey,new.fn,new.ln,new.exam,new.score,          
                       'INSERT',new.timeEnter,DATETIME('NOW') );           
                                                                           
     END;                                                                  
                                                                           
     --  Also create a DELETE trigger                                      
     CREATE TRIGGER delete_examlog DELETE ON exam                          
     BEGIN                                                                 
                                                                           
     INSERT INTO examlog  (ekey,fnOLD,lnNEW,examOLD,scoreOLD,              
                           sqlAction,timeEnter)                            
                                                                           
               values (old.ekey,old.fn,old.ln,old.exam,old.score,          
                       'DELETE',DATETIME('NOW') );                         
                                                                           
     END;                                                                  
     -- *******************************************************************
     -- *******************************************************************
Since the script above has been created in the file examLOG, you can execute the commands in sqlite3 as shown below. Also shown below is a record insert, and an update to test these newly created triggers.

     $ sqlite3 examdatabase < examLOG                            
                                                                    
     $ sqlite3 examdatabase "insert into exam                       
                               (ln,fn,exam,score)                   
                             values                                 
                               ('Anderson','Bob',2,80)"             
                                                                    
     $ sqlite3 examdatabase "update exam set score=82               
                                where                               
                              ln='Anderson' and fn='Bob' and exam=2"

Now, by doing the select statement below, you will see that examlog contains an entry for the insert statement, plus two updates. Although we only did one update on the command line, the trigger "insert_exam_timeEnter" performed an update for the field timeEnter -- this was the trigger defined in "examScript". On the second update we can see that the score has been changed. The trigger is working. Any change made to the table, whether by user interaction or another trigger is recorded in the examlog.

  $ sqlite3 examdatabase "select * from examlog"

  1|2||Bob||Anderson||2||80||INSERT|||2004-10-02 15:33:16
  2|2|2|Bob|Bob|Anderson|Anderson|2|2|80|80|UPDATE||2004-10-02 15:33:16|2004-10-02 15:33:16
  3|2|2|Bob|Bob|Anderson|Anderson|2|2|82|80|UPDATE|2004-10-02 15:33:16|2004-10-02 15:33:26|2004-10-02 15:33:26
Again, pay particular attention to the AFTER keyword. Remember by default triggers are BEFORE, so you must specify AFTER to insure that all new values will be available, if your trigger needs to work with any new values.

UTC and Localtime

Note, select DATETIME('NOW') returns UTC or Coordinated Universal Time. But select datetime('now','localtime') returns the current time.

     sqlite> select datetime('now');
     2004-10-18 23:32:34

     sqlite> select datetime('now','localtime');
     2004-10-18 19:32:46
There is an advantage to inserting UTC time like we did with the triggers above, since UTC can easily be converted to localtime after UTC has been entered in the table. See the command below. By inserting UTC, you avoid problems when working with multiple databases that may not share the same timezone and or dst settings. By starting with UTC, you can always obtain the localtime. 
(Reference: Working with Time)

     CONVERTING TO LOCALTIME:

     sqlite> select datetime(timeEnter,'localtime') from exam;
Other Date and Time Commands

If you look in the sqlite3 source file "./src/date.c", you will see that datetime takes other options. For example, to get the localtime, plus 3.5 seconds, plus 10 minutes, you would execute the following command:

     sqlite> select datetime('now','localtime','+3.5 seconds','+10 minutes');
     2004-11-07 15:42:26
It is also possible to get the weekday where 0 = Sunday, 1 = Monday, 2 = Tuesday ... 6 = Saturday.

       sqlite> select datetime('now','localtime','+3.5 seconds','weekday 2');
       2004-11-09 15:36:51
The complete list of options, or modifiers as they are called in this file, are as follows:

       NNN days
       NNN hours
       NNN minutes
       NNN.NNNN seconds
       NNN months
       NNN years
       start of month
       start of year
       start of week
       start of day
       weekday N
       unixepoch
       localtime
       utc
In addition, there is the "strftime" function, which will take a timestring, and convert it to the specified format, with the modifications. Here is the format for this function:

     **    strftime( FORMAT, TIMESTRING, MOD, MOD, ...)
     **
     ** Return a string described by FORMAT.  Conversions as follows:
     **
     **   %d  day of month
     **   %f  ** fractional seconds  SS.SSS
     **   %H  hour 00-24
     **   %j  day of year 000-366
     **   %J  ** Julian day number
     **   %m  month 01-12
     **   %M  minute 00-59
     **   %s  seconds since 1970-01-01
     **   %S  seconds 00-59
     **   %w  day of week 0-6  sunday==0
     **   %W  week of year 00-53
     **   %Y  year 0000-9999
Below is an example.

     sqlite> select strftime("%m-%d-%Y %H:%M:%S %s %w %W",'now','localtime');
     11-07-2004 16:23:15 1099844595 0 44
ATTACH Command: Build a Virtual Table that Spans Multiple Tables on Separate Databases.

This is a very powerful concept. As you have seen, sqlite3 works with a local database file. And within this local database multiple tables can be created. This section will examine a technique to combine multiple tables, with the same field layout that exist in separate database files, into a single virtual table. On this single virtual table, you will see how selects can be performed. The name "virtual table" will be used. There is no overhead in copying or moving data. No data gets copied or moved, period. This is the ideal situation, when working with very large tables. Suppose the computers on your network record port scans from snort to a local sqlite3 file. Then, provided you have access to the individual database files, via NFS mount or samba mount, you could virtually combine the tables from all your computers into one virtual table to perform database queries in an effort to identify global patterns of attack against your network.

This example will be done with the examdatabase, since we still have the scripts that were used for the exam table. We can easily create a new database "examdatabase2", along with a new exam table, by executing the following script from the bash shell as follows:

    $ sqlite3 examdatabase2 < examScript 
    $ sqlite3 examdatabase2 < examLOG 
    $ sqlite3 examdatabase2 "insert into exam (ln,fn,exam,score) values ('Carter','Sue',1,89); 
                             insert into exam (ln,fn,exam,score) values ('Carter','Sue',2,100);"     

    $ sqlite3 examdatabase2 "select * from exam"
    1|Sue|Carter|1|89|2004-10-02 16:04:12
    2|Sue|Carter|2|100|2004-10-02 16:04:12
To combine the two database files, use the attach command. The alias for examdatabase will be e1, and the alias for examdatabase2 will be e2. The shorter names will come in handy when the tables are joined with the union clause. Yes, that is how tables are combined: "union" which is a standard SQL command.

After the "attach" database command is performed, the ".database" command can be used to show the location of the individual database files. The location follows the alias. See the example below.

   $ sqlite3
   SQLite version 3.0.8
   Enter ".help" for instructions
   sqlite> attach database 'examdatabase' as e1;
   sqlite> attach database 'examdatabase2' as e2;
   sqlite> .database
   seq  name             file                                                      
   ---  ---------------  ----------------------------------------------------------
   0    main                                                                       
   2    e1               /work/cpearls/src/posted_on_sf/sqlite_examples/sqlite_exam
   3    e2               /work/cpearls/src/posted_on_sf/sqlite_examples/sqlite_exam
   sqlite>
 
To select all data from both tables, perform the union of two select statements as demonstrated below. Note by add 'e1' and 'e2' to the respective selects, it is possible to identify which database the returned records are coming from.

   sqlite> select 'e1',* from e1.exam union select 'e2',* from e2.exam;

   e1|1|Bob|Anderson|1|75|2004-10-02 15:25:00
   e1|2|Bob|Anderson|2|82|2004-10-02 15:33:16
   e2|1|Sue|Carter|1|89|2004-10-02 16:04:12
   e2|2|Sue|Carter|2|100|2004-10-02 16:04:12
To summarize a query was performed on two tables that resided in separate databases. This union created the virtual table. The select syntax is as follows: SELECT <expression> FROM <TABLE>. For the table option we have used the complete string "(select 'e1' as db,* from e1.exam union select 'e2' as db,* from e2.exam)", which is our virtual table.

Here is a query example performed on this virtual table. Suppose you wanted the maximum score by exam across databases.

      sqlite> select  exam,max(score) from 
             (select 'e1' as db,* from e1.exam union select 'e2' as db,* from e2.exam) 
               group by exam;

      1|89
      2|100
No problem. Above you got the maximum score for each exam, but who does it below to? Find the ln and fn. But be careful, if you add "ln" and "fn" on the first part of the select, you will get the WRONG answer.

       sqlite> select  exam,max(score),ln,fn from 
             (select 'e1' as db,* from e1.exam union select 'e2' as db,* from e2.exam) 
               group by exam;

          ** THIS IS INCORRECT it should be Carter|Sue **

       1|89|Anderson|Bob
       2|100|Anderson|Bob
The above answer is incorrect. "Anderson", "Bob" happens to be the name that dropped down in this select statement. Again, it is not the correct answer. If by chance you got the correct answer doing this query, then, it is because you entered the names in a different order. If that is the case, perform the query below which takes the min(score) - will will get an error on one of these examples.

Here the min(score) is queried. By chance, because of the order data was entered into this table, the correct answer is displayed.

      sqlite> select  exam,min(score),ln,fn from 
             (select 'e1' as db,* from e1.exam union select 'e2' as db,* from e2.exam) 
               group by exam;
  
         ** correct answer  -- just chance **
      1|75|Anderson|Bob
      2|82|Anderson|Bob
Clearly there needs to be a better way of finding out who got the maximum and minimum scores for each exam. By the way, here is the CORRECT sql statement that will always give you the correct answer.

      sqlite> select db,ln,fn,exam,score from  
              (select 'e1' as db,* from e1.exam union select 'e2' as db,* from e2.exam)
	          where
	              (
	                score=(
	                select max(score) from 
                   (select 'e1' as db,* from e1.exam union select 'e2' as db,* from e2.exam)
	                  where exam=1
	                     )
	                and exam = 1
	                )
	                OR                                                                            
	                (
	                score=(
	                 select max(score) from 
                    (select 'e1' as db,* from e1.exam union select 'e2' as db,* from e2.exam)
	                  where exam=2
	                     )
	                and exam = 2
	                  )  ;

        e2|Carter|Sue|1|89
        e2|Carter|Sue|2|100

Or it can be done as two independent select statements as follows:

    sqlite> select db,ln,fn,exam,score from 
           (select 'e1' as db,* from e1.exam union select 'e2' as db,* from e2.exam) 
            where exam=1 order by score desc limit 1;

     e2|Carter|Sue|1|89
Here is the second select.

     sqlite> select db,ln,fn,exam,score from 
            (select 'e1' as db,* from e1.exam union select 'e2' as db,* from e2.exam) 
            where exam=2 order by score desc limit 1;

     e2|Carter|Sue|2|100
A Pivot Table

What if you wanted a pivot table where the scores are listed across the top: exam1,exam2,..examN for each person. For example the results will look like the following:

    fn  ln         exam1  exam2
    --- --------   -----  -----
    Bob Anderson   75     82
    Sue Carter     89     100
Also, is there a way to display the deltas between exams, to have a 5th column that would show 7 points (82-75) or the delta between exam1 and exam2 and similar data for Sue Carter?

Such power select statements can be done with the sign function. And unlike the case statement, the sign function can be place in the group by and having clause. The sign function does not exist in sqlite; however, that is not a problem, since we can easily create it.

As a side note, you wonder why you should create the sign function. Instead, why not create an IF or IIF function? The main reason is the IF statement is not standard on all databases. And, on some databases where it is standard, MySQL, it needs to be wrapped in an sum() function. Take a look at the following Reference ( LONGWINDED TIP 1) for an example of MySQL's IF statement that must be inside a sum statement, and how the sign function solves this problem. (This was pointed out to me by Paul DuBois [paul at snake.net] )

Power of the Sign Function - A Mathematical Explanation

It may come as a shock, but the problems in the last section, and much more, can be solved using the sign function. This is just the simple sign function where sign(-200)=-1,.. sign(-1)=-1, sign(0)=0, sign(1)=1,.. sign(300)=1. So if the number is > 0 a 1 is returned. Zero is the only number that returns zero. All negative numbers return -1. Again, this simple function does not exist in sqlite. But, you can easily create it, permanently. The next section will focus on the creation of this function; however, here the mathematical properties are explained.

The sign function can define the absolute value function abs() as the value of a number times it's sign. Or sign(x)*(x), abbreviated sign(x)(x). Here is a more detailed look at this function.

       sign(x)(x) = abs(x)

       Example, assume x=3

       sign(3)(3) = abs(3)
       1*3 = 3

       Example, assume x=-3

       sign(-3)(-3) = abs(-3)
       -1*-3 = 3

       Example, assume x=0

       sign(0)(0) = abs(0)
       0*0   = 0

Comparsions can be made with the sign function betwen two variables x and y. For instance, if sign(x-y) is 1, then, x is greater than y.

          sign(x-y) is equal to 1 if  x > y
 
          sign(x-y) is equal to 0 if  x = y

          sign(x-y) is equl to -1 if  x < y

Now look closely at the 3 statements below. The sign function starts to resemble an IF statement, where a 1 is returned if and only if x = y. Thoroughly understanding the statements below is important, as rest of the discussion quickly builds from these examples.

     IF ( X==Y ) return 1;  ELSE return 0;

       can be expressed as follows:

          1 - abs(sign(x-y)) is equal to 0 if  x > y
 
          1 - abs(sign(x-y)) is equal to 1 if  x = y

          1 - abs(sign(x-y)) is equl to  0 if  x < y
It is possible to return a 1 if and only if x < y, otherwise return a zero.

     IF ( X < Y ) return 1;  ELSE return 0;

       can be expressed as follows:


          1-sign(1+sign(x-y)) is equal to 0 if  x > y

          1-sign(1+sign(x-y)) is equal to 0 if  x = y

          1-sign(1+sign(x-y)) is equal to 1 if  x < y
The last example is known as the delta for x<y, or Delta[x<y]. This Delta notation will be used instead of writing it out in long form or using the IF statement. Therefore, the following is a summarized table of all the Delta functions or comparison operators.

          Delta[x=y]   =   1 - abs(sign(x-y))

	  Delta[x!=y]  =   abs(sign(x-y))

          Delta[x<y]   =   1-sign(1+sign(x-y))

          Delta[x<=y]  =   sign(1-sign(x-y))

          Delta[x>y]   =   1-sign(1-sign(x-y))

	  Delta[x>=y]  =   sign(1+sign(x-y))


          Delta[z=x AND z=y] = sign( Delta[z=x]*Delta[z=y] )

          Delta[z=x OR  z=y] = sign( Delta[z=x]+Delta[z=y] )

          Delta[z>x AND  z<y] = sign( Delta[z>x]*Delta[z<y] )

          ... more can be defined ... but you get the idea
To summarize the following if statement, note the introduction of a third variable z, below

      if( x==y )
	     return z;
           else
	     return 0;
The above expression, in Delta notation, is the following:

         z*Delta[x=y]
Here is an interesting example.

     create table logic (value int);

     insert into logic (value) values (1);
     insert into logic (value) values (0);
     insert into logic (value) values (-1);

First take the Cartesian product to show all possible combinations of x and y.

      sqlite> .header on                                
      sqlite> .mode column                              
      sqlite> select x.value,y.value from logic x, logic y;

      x.value     y.value   
      ----------  ----------
      1            1         
      1            0         
      1           -1        
      0            1         
      0            0         
      0           -1        
      -1           1         
      -1           0         
      -1          -1        
Then after the sign function is created, which we will do in the next section, using the above table we could examin Delta[x!=y] as follows;

      sqlite> .header on                                
      sqlite> .mode column                              
      sqlite> select x.value,y.value,abs(sign(x.value-y.value)) from logic x, logic y;


      x.value     y.value     abs(sign(x.value-y.value))
      ----------  ----------  --------------------------
       1           1           0                         
       1           0           1                         
       1          -1           1                         
       0           1           1                         
       0           0           0                         
       0          -1           1                         
      -1           1           1                         
      -1           0           1                         
      -1          -1           0                         
Note above that every time x is not equal to y, abs(sign(x.value-y.value)) returns a 1. After the sign function is created, these example will run. This is extremely powerful. To show that we have created a condition statement without using the where or group by statements, consider the following example. The z.value will only be displayed in the right hand column, only when x.value != y.value.

  sqlite> select x.value,y.value,z.value,
                  z.value*abs(sign(x.value-y.value)) 
                  from logic x, logic y, logic z;

      x.value     y.value     z.value     z.value*abs(sign(x.value-y.value))
      ----------  ----------  ----------  ----------------------------------
       1            1            1           0                                 
       1            1            0           0                                 
       1            1           -1           0                                 
       1            0            1           1                                 
       1            0            0           0                                 
       1            0           -1          -1                                
       1           -1            1           1                                 
       1           -1            0           0                                 
       1           -1           -1          -1                                
       0            1            1           1                                 
       0            1            0           0                                 
       0            1           -1          -1                                
       0            0            1           0                                 
       0            0            0           0                                 
       0            0           -1           0                                 
       0           -1            1           1                                 
       0           -1            0           0                                 
       0           -1           -1          -1                                
      -1            1            1           1                                 
      -1            1            0           0                                 
      -1            1           -1          -1                                
      -1            0            1           1                                 
      -1            0            0           0                                 
      -1            0           -1          -1                                
      -1           -1            1           0                                 
      -1           -1            0           0                                 
      -1           -1           -1           0                                 

Modifying the Source: Creating a Permanent Sign Function

Sqlite functions are defined in "./src/func.c". In this file the name of this function will be "signFunc". The user will call this function in sqlite as sign(n). It will hold only a single variable.

It is helpful to model the sign function after the abs function "absFunc", since they are very similar. If fact, I would highly recommend looking at the abs function anytime a new version of sqlite is released.

You will want to follow these steps. First copy the abs function "absFunc" and make the following changes:

Change function name from absFunc to signFunc
The variable iVal will need to be changed. iVal should equal -1 if sqlite3_value_type(argv[0]) is less than zero. Note this value here is an integer. Otherwise, if this integer is zero, return zero. Or if this integer is greater than zero, return 1. All of this can simply be expressed as follows:
             iVal = ( iVal > 0) ? 1 : ( iVal < 0 ) ? -1 : 0; 
          
Perform the same steps above for rVal, which is the real value, as opposed to the integer value above.
             rVal = ( rVal > 0) ? 1 : ( rVal < 0 ) ? -1 : 0; 
          
Add the following entry in aFuncs[]
          { "sign", 1, 0, SQLITE_UTF8, 0, signFunc },
        
Recompile sqlite from the main directory and install.
        $ ./configure
        $ make && make install
       
For a closer look, below is the section that changed. Look here for the complete file func.c .
       From ./src/func.c                                                                
                                                                                        
     ... cut ...                                                                        
     /*                                                                                 
     ** Implementation of the sign() function                                           
     */                                                                                 
     static void signFunc(sqlite3_context *context, int argc, sqlite3_value **argv){    
       assert( argc==1 );                                                               
       switch( sqlite3_value_type(argv[0]) ){                                           
         case SQLITE_INTEGER: {                                                         
           i64 iVal = sqlite3_value_int64(argv[0]);                                     
      /* 1st change below. Line below was:  if( iVal<0 ) iVal = iVal * -1;     */       
                                                                                        
           iVal = ( iVal > 0) ? 1 : ( iVal < 0 ) ? -1 : 0;                              
           sqlite3_result_int64(context, iVal);                                         
           break;                                                                       
         }                                                                              
         case SQLITE_NULL: {                                                            
           sqlite3_result_null(context);                                                
           break;                                                                       
         }                                                                              
         default: {                                                                     
      /* 2nd change below. Line for abs was: if( rVal<0 ) rVal = rVal * -1.0;  */       
                                                                                        
           double rVal = sqlite3_value_double(argv[0]);                                 
           rVal = ( rVal > 0) ? 1 : ( rVal < 0 ) ? -1 : 0;                              
           sqlite3_result_double(context, rVal);                                        
           break;                                                                       
         }                                                                              
       }                                                                                
     }                                                                                  
     ... cut ...                                                                        
                                                                                        
       } aFuncs[] = {                                                                   
         { "min",               -1, 0, SQLITE_UTF8,    1, minmaxFunc },                 
         { "min",                0, 0, SQLITE_UTF8,    1, 0          },                 
         { "max",               -1, 2, SQLITE_UTF8,    1, minmaxFunc },                 
         { "max",                0, 2, SQLITE_UTF8,    1, 0          },                 
         { "typeof",             1, 0, SQLITE_UTF8,    0, typeofFunc },                 
         { "length",             1, 0, SQLITE_UTF8,    0, lengthFunc },                 
         { "substr",             3, 0, SQLITE_UTF8,    0, substrFunc },                 
         { "substr",             3, 0, SQLITE_UTF16LE, 0, sqlite3utf16Substr },         
         { "abs",                1, 0, SQLITE_UTF8,    0, absFunc    },                 
         /*  Added here */                                                              
         { "sign",               1, 0, SQLITE_UTF8,    0, signFunc   },                 
         { "round",              1, 0, SQLITE_UTF8,    0, roundFunc  },                 
         { "round",              2, 0, SQLITE_UTF8,    0, roundFunc  },                 
     ... cut ...                                                                        
Using the New Sign Function

Now, back to the problem of creating a pivot table for displaying exam scores in spreadsheet like format. First, more data is is needed. By the way, if have not added any data, the following script enterExamdata will create the necessary tables and insert the data.

  $ sqlite3 examdatabase "insert into exam (ln,fn,exam,score) values ('Anderson','Bob',3,92)"
  $ sqlite3 examdatabase "insert into exam (ln,fn,exam,score) values ('Anderson','Bob',4,95)"
  $ sqlite3 examdatabase "insert into exam (ln,fn,exam,score) values ('Stoppard','Tom',1,88)"
  $ sqlite3 examdatabase "insert into exam (ln,fn,exam,score) values ('Stoppard','Tom',2,90)"
  $ sqlite3 examdatabase "insert into exam (ln,fn,exam,score) values ('Stoppard','Tom',3,92)"
  $ sqlite3 examdatabase "insert into exam (ln,fn,exam,score) values ('Stoppard','Tom',4,95)"
  $ sqlite3 examdatabase2 "insert into exam (ln,fn,exam,score) values ('Carter','Sue',3,99)"
  $ sqlite3 examdatabase2 "insert into exam (ln,fn,exam,score) values ('Carter','Sue',4,95)"
Below is the select statement for generating a pivot table for 4 exams on the table exams.

     select ln,fn,
        sum(score*(1-abs(sign(exam-1)))) as exam1,
        sum(score*(1-abs(sign(exam-2)))) as exam2,
        sum(score*(1-abs(sign(exam-3)))) as exam3,
        sum(score*(1-abs(sign(exam-4)))) as exam4
        from exam group by ln,fn;

Shown below is the select statement, like the statement above; however, it works on the virtual table, or the combined exam tables from database examdatabase and examdatabase2.

   $ sqlite3
   SQLite version 3.0.8
   Enter ".help" for instructions
   sqlite> attach database examdatabase as e1;
   sqlite> attach database examdatabase2 as e2;
   sqlite> .database
   seq  name             file
   ---  ---------------  ----------------------------------------------------------
   0    main
   2    e1               /work/cpearls/src/posted_on_sf/sqlite_examples/sqlite_exam
   3    e2               /work/cpearls/src/posted_on_sf/sqlite_examples/sqlite_exam 
   sqlite> .header on
   sqlite> .mode column
   sqlite> select ln,fn,sum(score*(1-abs(sign(exam-1)))) as exam1,
                               sum(score*(1-abs(sign(exam-2)))) as exam2, 
                               sum(score*(1-abs(sign(exam-3)))) as exam3, 
                               sum(score*(1-abs(sign(exam-4)))) as exam4 
            from (select 'e1' as db,* from e1.exam union select 'e2' as db,* from e2.exam)    
                              group by ln,fn;                                                           

   ln          fn          exam1       exam2       exam3       exam4 
   ----------  ----------  ----------  ----------  ----------  ----------
   Anderson    Bob         75          82          92          95
   Carter      Sue         89          100         99          95 
   Stoppard    Tom         88          90          92          95 

   sqlite>                                                                                                  
Taking a closer look at the results, it's very easy to see that Anderson, Bob got a 75 on the first exam, 82 on the second, 92 on the third, and 95 on the forth. Likewise, Carer received 88,90,92 and 95 respectively.

   ln          fn          exam1       exam2       exam3       exam4 
   ----------  ----------  ----------  ----------  ----------  ----------
   Anderson    Bob         75          82          92          95
   Carter      Sue         89          100         99          95 
   Stoppard    Tom         88          90          92          95 
Now back to the question of finding the top scores for each exam in one select statement. That is, find the top scores for the combined tables. First, a look at all the data.

      $ sqlite3                                                                                        
      SQLite version 3.0.8                                                                       
      Enter ".help" for instructions                                                             
      sqlite> attach database examdatabase as e1;                                                
      sqlite> attach database examdatabase2 as e2;                                               
      sqlite> .header on                                                                         
      sqlite> .mode column                                                                       
      sqlite> select 'e1' as db,* from e1.exam union select 'e2' as db,* from e2.exam;           
      db          ekey        fn          ln          exam        score       timeEnter          
      ----------  ----------  ----------  ----------  ----------  ----------  -------------------
      e1          1           Bob         Anderson    1           75          2004-10-17 22:01:42
      e1          2           Bob         Anderson    2           82          2004-10-17 22:02:19
      e1          3           Bob         Anderson    3           92          2004-10-17 22:05:04
      e1          4           Bob         Anderson    4           95          2004-10-17 22:05:16
      e1          5           Tom         Stoppard    1           88          2004-10-17 22:05:24
      e1          6           Tom         Stoppard    2           90          2004-10-17 22:05:31
      e1          7           Tom         Stoppard    3           92          2004-10-17 22:05:40
      e1          8           Tom         Stoppard    4           95          2004-10-17 22:05:50
      e2          1           Sue         Carter      1           89          2004-10-17 22:03:10
      e2          2           Sue         Carter      2           100         2004-10-17 22:03:10
      e2          3           Sue         Carter      3           99          2004-10-17 22:05:57
      e2          4           Sue         Carter      4           95          2004-10-17 22:06:05
      sqlite> 
Below, continuing with the same attached setup, is an example of horizontal averages and horizontal maximum values.

     sqlite> .headers on
     sqlite> .mode column    
     sqlite> select db,ln as lastname,fn as first,
                        sum(score*(1-abs(sign(exam-1)))) as exam1,                                  
                        sum(score*(1-abs(sign(exam-2)))) as exam2,                                  
                        sum(score*(1-abs(sign(exam-3)))) as exam3,                                  
                        sum(score*(1-abs(sign(exam-4)))) as exam4,                                   
                        avg(score) as avg, max(score) as max
        from (select 'e1' as db,* from e1.exam union select 'e2' as db,* from e2.exam)    
        group by ln,fn,db ;


db          lastname    first       exam1       exam2       exam3       exam4       avg         max
----------  ----------  ----------  ----------  ----------  ----------  ----------  ----        ----
e1          Anderson    Bob         75          82          92          95          86          95
e2          Carter      Sue         89          100         99          95          95.75       100
e1          Stoppard    Tom         88          90          92          95          91.25       95

Try finding the delta's, or the differences between each exam score. For hints on this, reference the end of the following ( article ) in the LONGWINDED TIPS section.

Pivot Table "Spreadsheet Format" to Normalized Data

Consider the reverse: suppose you had a pivot table, or the data in spreadsheet like format, and you wanted a normalized table of exams. For this example the table nonormal is needed. This table is defined and created as follows:

     SQLite version 3.0.8
     Enter ".help" for instructions
     sqlite> attach database 'examdatabase' as e1;
     sqlite> attach database 'examdatabase2' as e2;
     sqlite> create table e1.nonormal as 
            select ln,fn,
                 sum(score*(1-abs(sign(exam-1)))) as exam1,
                 sum(score*(1-abs(sign(exam-2)))) as exam2, 
                 sum(score*(1-abs(sign(exam-3)))) as exam3, 
                 sum(score*(1-abs(sign(exam-4)))) as exam4 
       from (select 'e1' as db,* from e1.exam union select 'e2' as db,* from e2.exam)    
                 group by ln,fn;

      sqlite> .header on
      sqlite> .mode column
      sqlite> select * from e1.nonormal;
      ln          fn          exam1       exam2       exam3       exam4     
      ----------  ----------  ----------  ----------  ----------  ----------
      Anderson    Bob         75          82          92          95        
      Carter      Sue         89          100         99          95        
      Stoppard    Tom         88          90          92          95        
Above the nonormal table was created in the examdatabase, since "e1." was given before the name. Again, the objective here is to go back-wards and create a normalized table from the pivot table: a table that will list all exam scores in one field, and all the exam numbers in another, without having a separate field for each exam. In addition, the goal is to do all this in one select statement without looping through the data. First it is necessary to create a number table "enum", and it must have the field "e" from 1..N where N is the number of exams, which is 4 in this case.

      sqlite> CREATE TABLE enum (e int);
      sqlite> INSERT INTO "enum" VALUES(1);
      sqlite> INSERT INTO "enum" VALUES(2);
      sqlite> INSERT INTO "enum" VALUES(3);
      sqlite> INSERT INTO "enum" VALUES(4);
The coalesce function is used in an interesting way for this example.

      sqlite> .mode list
      sqlite> select n.ln,n.fn,

         1*(1-abs(sign(e.e-1)))+
         2*(1-abs(sign(e.e-2)))+
         3*(1-abs(sign(e.e-3)))+
         4*(1-abs(sign(e.e-4))),
 
        coalesce(0/(e.e-1),n.exam1)+     
        coalesce(0/(e.e-2),n.exam2)+     
        coalesce(0/(e.e-3),n.exam3)+     
        coalesce(0/(e.e-4),n.exam4)

        from enum as e,e1.nonormal as n;


    Anderson|Bob|1|75
    Carter|Sue|1|89
    Stoppard|Tom|1|88
    Anderson|Bob|2|82
    Carter|Sue|2|100
    Stoppard|Tom|2|90
    Anderson|Bob|3|92
    Carter|Sue|3|99
    Stoppard|Tom|3|92
    Anderson|Bob|4|95
    Carter|Sue|4|95
For more examples see the following article: Solving_Complex_SQL_Problems.txt

Max Min Problems

Assume you have the following table of names, ages, and salaries, find the age, name and salary, of youngest person making the over all highest salary. Or, first find the highest salary, then, from this group select the youngest person.

     create table salary (name varchar(3),age int, salary double);   
     insert into salary values ('dan',23,67);                        
     insert into salary values ('bob',45,94);                        
     insert into salary values ('tom',24,94);                        
     insert into salary values ('sue',23,45);                        
     insert into salary values ('joe',45,51);                        
     insert into salary values ('sam',22,51);                        
Or, once you have the data entered you will have the following;

      sqlite> .headers on
      sqlite> .mode column
      sqlite> select * from salary;
      name        age         salary    
      ----------  ----------  ----------
      dan         23          67        
      bob         45          94        
      tom         24          94        
      sue         23          45        
      joe         45          51        
      sam         22          51        
      sqlite> 
The following select will give you the youngest person making the top salary in the company.

      sqlite>  select 1000-max(salary*1000-age)%1000 from salary;

      1000-max(salary*1000-age)%1000
      ------------------------------
      24                            
This is the correct answer. The highest salary is 94 for bob and tom. Tom is the youngest at 24.

First, why the number 1000. Well, no one lives to be 1000, so we know that age will never be >= 1000. Therefore max(salary*1000-age) will clearly choose the highest salary independent of age, as long as salary is >= 1. In cases of a tie in salary, the youngest person will subtract the the least amount from the salary. So this value will return as the highest. Now, it's easy to remove the salary part from this number. Since salary is multiplied by 1000 it will disappear with mod 1000, because it it's a perfect factor of 1000.

To understand how this works it is helpful to break the statement into separate, smaller parts as follows:

      sqlite> select salary*1000-age,salary*1000,-age from salary;
      salary*1000-age  salary*1000  -age      
      ---------------  -----------  ----------
      66977            67000        -23       
      93955            94000        -45       
      93976            94000        -24       
      44977            45000        -23       
      50955            51000        -45       
      50978            51000        -22       
      sqlite> 
But what about the negative value for age? With the non-Knuth method of the mod function, '%' , a negative number -x will always return -x if abs(x) < abs(y).

     x % y is defined as follows:                           
                                                            
      x % y ==  x - INT(x/y)*y                              
                                                            
     and undefined for y == 0.  The language                
     C and Fortran use this method.                         
                                                            
     In contrast the Knuth method, also found in python,    
     and accepted in mathematics  defines this function      
     as follows:                                            
                                                            
       x mod y == x - floor(x/y),                           
                                                            
     and equal to x if y == 0                               
                                                            
     The difference between the two shows up with negative  
     values for x.                                          
Or, put another way, as long as -x != y, then, -x % y = -x. For example, assume x=4 and y=5, then, -4 % 5 will return a -4. Here are a few other examples. Again this is NOT the Kuth method for the mod function.
     -1 % 5 = -1
     -2 % 5 = -2
     -3 % 5 = -3
So what we are really doing is the following:

      1000 +  -1*(1000-age) = age
C and C++ API

Simple C program

The following is a simple C program, simplesqlite3.c that will open a database and execute a sql string

     #include <stdio.h>                                                            
     #include <stdlib.h>                                                       
     #include <sqlite3.h>                                                      
                                                                                     
                                                                                     
     static int callback(void *NotUsed, int argc, char **argv, char **azColName){    
       NotUsed=0;                                                                    
       int i;                                                                        
       for(i=0; i<argc; i++){                                                     
         printf("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL");              
       }                                                                             
       printf("\n");                                                                 
       return 0;                                                                     
     }                                                                               
                                                                                     
     int main(int argc, char **argv){                                                
       sqlite3 *db;                                                                  
       char *zErrMsg = 0;                                                            
       int rc;                                                                       
                                                                                     
       if( argc!=3 ){                                                                
         fprintf(stderr, "Usage: %s DATABASE SQL-STATEMENT\n", argv[0]);             
         exit(1);                                                                    
       }                                                                             
       rc = sqlite3_open(argv[1], &db);                                              
       if( rc ){                                                                     
         fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));           
         sqlite3_close(db);                                                          
         exit(1);                                                                    
       }                                                                             
       rc = sqlite3_exec(db, argv[2], callback, 0, &zErrMsg);                        
       if( rc!=SQLITE_OK ){                                                          
         fprintf(stderr, "SQL error: %s\n", zErrMsg);                                
       }                                                                             
       sqlite3_close(db);                                                            
       return 0;                                                                     
     }                                                                               
The command to compile and run the program is shown below. In particular note the "-Wl,-R/usr/local/lib" options, which will be needed if you installed sqlite3 source, since the path "/usr/local/lib" may not be listed in your "/etc/ld.so.conf" file.

     gcc -o simplesqlite3 simplesqlite3.c  -Wall -W -O2 -Wl,-R/usr/local/lib -lsqlite3
You either have to use the compile option above, or add the directory where the sqlite3 library "libsqlite3.so" is installed into the file "/etc/ld.so.conf" and then run the command "ldconfig" from the shell. I prefer to use the "-Wl,-R" option instead; but, there are the steps.

     $ locate libsqlite3.so
     /usr/local/lib/libsqlite3.so.0.8.6
     /usr/local/lib/libsqlite3.so.0
     /usr/local/lib/libsqlite3.so    <--- note directory is /usr/local/lib

     $ echo "/usr/local/lib" >> /etc/ld.so.conf
     $ ldconfig
After you have entered and compiled the program, it will run as follows:

     $ ./simplesqlite3 test.db "create table notes (t text)"        
                                                                    
     $ ./simplesqlite3 test.db "insert into  notes (t) values ('    
     > This is some random                                       
     > stuff to add'                                             
     >);"                                                        
                                                                    
     $ ./simplesqlite3 test.db "select * from notes"                
                                                                    
     t =                                                            
     This is some random                                            
     stuff to add                                                   
There are really only 3 important statements sqlite3_open() which takes the name of the database and a database pointer, sqlite3_exec() which executes the SQL commands in argv[2] but also list the callback function used to display the results, and sqlite3_close() which closes the database connection.

C++ program - Building a Class to do the Work

It is possible to build a class SQLITE3 , defined below that reads the returned data into a vector. Note, instead of using the sqlite3_exec() function sqlite3_get_table() is used instead copies the result of the SQL statement into the variable array of string result. Note this variable must be freed with sqlite3_free_table() after it has been used to copy the returned SQL headings and data into the vector vcol_head and vdata. So note that the first row is the heading.

  class SQLITE3 {
  private:
    sqlite3 *db;
    char *zErrMsg;
    char **result;
    int rc;
    int nrow,ncol;
    int db_open;
  
  public:
  
    std::vector<std::string> vcol_head;
    std::vector<std::string> vdata;
  
    SQLITE3 (std::string tablename="init.db"): zErrMsg(0), rc(0),db_open(0) {
      rc = sqlite3_open(tablename.c_str(), &db);
      if( rc ){
        fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
        sqlite3_close(db);
      }
      db_open=1;
    }
  
    int exe(std::string s_exe) {
        rc = sqlite3_get_table(
  			db,              /* An open database */
  			s_exe.c_str(),       /* SQL to be executed */
  			&result,       /* Result written to a char *[]  that this points to */
  			&nrow,             /* Number of result rows written here */
  			&ncol,          /* Number of result columns written here */
  			&zErrMsg          /* Error msg written here */
  			);
        if(vcol_head.size()>0) { vcol_head.clear();  }
        if(vdata.size()>0)     { vdata.clear(); }

  
       if( rc == SQLITE_OK ){
        for(int i=0; i < ncol; ++i)
  	vcol_head.push_back(result[i]);   /* First row heading */
        for(int i=0; i < ncol*nrow; ++i)
  	vdata.push_back(result[ncol+i]);
       }
       sqlite3_free_table(result);
        return rc;
    }
  
    ~SQLITE3(){
        sqlite3_close(db);
    } 
  };

The complete program can be found in the example download or viewed here: simplesqlite3cpp2.cc

With the class defined above, it can be used in main or in a function as follows:

    std::string s;
    SQLITE3 sql("database.db");

    
    sql.exe("create table notes (t text)");
    s="insert into notes (t) values ('sample data')";
    sql.exe(s);

    s="select t from notes";
    sql.exe(s);
The following, still assuming this code will be entered in main or a function, is an example of printing the data from a select. Note the headings section and the data sections.

  if( sql.vcol_head.size() > 0 )
    {
      std::cout << "Headings" << std::endl;
      copy(sql.vcol_head.begin(),
                sql.vcol_head.end(),
                std::ostream_iterator<std::string>(std::cout,"\t")); 

      std::cout << std::endl << std::endl;
      std::cout << "Data" << std::endl;
      copy(sql.vdata.begin(),
                sql.vdata.end(),
                 std::ostream_iterator<std::string>(std::cout,"\t")); 

      std::cout << std::endl;
    }

Defining SQLite User Functions

Aggregate Functions and Simple Functions

There are two types of functions. Aggregate functions and simple functions. Simple functions like sign(), which was created above can be used in any expression. Aggregate functions like "avg()" can only be used in the select statment. Some functions "min" and "max" can be defined as both. min() with 1 argument is an aggregate function, whereas, min() with an unlimited number of arguments is a simple function

Here is an example the illistrates the difference

      $ sqlite3
SQLite version 3.0.8
Enter ".help" for instructions
create table junk (a integer);

      $ sqlite3
      SQLite version 3.0.8                                
      Enter ".help" for instructions                      
      sqlite> create table junk (a integer);              
      sqlite> insert into junk (a) values (1);            
      sqlite> insert into junk (a) values (2);            
      sqlite> insert into junk (a) values (3);            
      sqlite> select * from junk;                         
      1                                                   
      2                                                   
      3                                                   

      sqlite> select * from junk where a=min(1,2,3,4,5);  
      1                                                   
      sqlite> select * from junk where a=min(1);          
      SQL error: misuse of aggregate function min()       
      sqlite>                                             
Note above that the min() function, with only one variable, is an aggregate function. Since it is only an aggregate function, it cannot be used after the where clause. An aggregate function can only be used after the select clause as follows:


      sqlite> select min(a) from junk
      1
But, if you add a second argument,then, you're calling the simple function. Note below that each row is compared to 2.3. Look closely because there is subtle but important difference here.


      sqlite> select min(a,2.3) from junk
      2
      2.3
      1
      
      sqlite> 
Creating a User Defined Sign Function - msign

Below is an example of the sign function. It is called msignFunc so as not to interfere with the permanent sign function that was created earlier.

void msignFunc(sqlite3_context *context, int argc, sqlite3_value **argv){
  assert( argc==1 );
  switch( sqlite3_value_type(argv[0]) ){
    case SQLITE_INTEGER: {
      long long int iVal = sqlite3_value_int64(argv[0]);
      iVal = ( iVal > 0) ? 1 : ( iVal < 0 ) ? -1 : 0;
      sqlite3_result_int64(context, iVal);
      break;
    }
    case SQLITE_NULL: {
      sqlite3_result_null(context);
      break;
    }
    default: {
      double rVal = sqlite3_value_double(argv[0]);
      rVal = ( rVal > 0) ? 1 : ( rVal < 0 ) ? -1 : 0;
      sqlite3_result_double(context, rVal);
      break;
    }
  }
}

As you can see below, this function gets inititiated as follows:

sqlite3_create_function(db, "msign", 1, SQLITE_UTF8, NULL, 
                 &msignFunc, NULL, NULL));
Note that "msign" is the name of the function in sqlite3. It is the name you would use in a select statement "select msign(3);". The 1 is the number of arguments. The msign function here only takes one argument. SQLITE_UTF8 is for the text representation. Then, skiping over NULL, &msignFunc is the name of the C function. The last two values, and they must be NULL for simple function - again, a simple function can be used in any part of the select where clause.

Aggregate Funcion

A good place to look for creating function is the "./src/func.c" file in the sqlite3 source. Suppose you would like to create a new sum function call S. It will create the aggregate sum of the rows.

The following data is used to explain this function.

    $ ./myfuncpp DATABASE "create table t(a integer, b integer, c integer)"  
    $ ./myfuncpp DATABASE "insert into t (a,b,c) values (1,-1,2)"
    $ ./myfuncpp DATABASE "insert into t (a,b,c) values (2,-2,4)"
    $ ./myfuncpp DATABASE "insert into t (a,b,c) values (3,-3,8)"
    $ ./myfuncpp DATABASE "insert into t (a,b,c) values (4,-4,16)"
    $ ./myfuncpp DATABASE "select * from t"
    a = 1 b = -1 c = 2 
    a = 2 b = -2 c = 4 
    a = 3 b = -3 c = 8 
    a = 4 b = -4 c = 16 
    a = 4 b = -4 c = 16 
Now for how the function S will create list of the sums. Unlike the standard aggregate sum() function, a list is returned.

     $ ./myfuncpp DATABASE "select S(a),S(b),S(c) from t"
     S(a) = (1,3,6,10,14) S(b) = (-1,-3,-6,-10,-14) S(c) = (2,6,14,30,46) 
Note above that a column which contains the values in the table (1,2,3,4,5) shows the cumulative sum (1,1+2=3,1+2+3=6,1+..) in a list. This is different from any function defined in "./src/func.c" since the data must be in a string.

To view this example and all other examples see the download. Since a list is returned, this example will use C++ std::stringstream, since this is fast and well suited for all types of data integer, double, and text.

Since S is an aggregate function, there are two functions "SStep" and "SFinalize". Aggregate functions always have a "Step" and a "Finalize". "Step" is called for each row, and after the last row the "Finalize" function is called.

A Both the Step and the Finalize can make use of a structure for holding the cumulated data collected from each row. For this function the structure SCtx is defined below. std::stringstream is global. No I would not advise putting an additional variable in SCtx "char *ss". You may be thinking that the additional variable "char *ss" could be dynamically increased with realloc, which will work. However, the problem is freeing the memory hanging off the structure. A bit of confusion here. As the sqlite documentation correctly points out the structure SCtx will be freed; but again, in my testing any additional memory allocated off members in the structure will not. On the otherhand, an array of std:stringstrings "BS" will have to be kept for when this function is called more than once in the same select "select S(a),S(b),...S(100th)" The overhead appears minimal.

     #define MAXSSC 100

     typedef struct SCtx SCtx;
     struct SCtx {
       double sum;     /* Sum of terms */
       int cnt;        /* Number of elements summed */
       int sscnt;      /* Keeps counts for ss */
     };

     std::stringstream ss[MAXSSC];
     int sscnt=0;
Below is the step function. p gets initialized the first time through SStep. On the firt pass, all the values in the SCtx structure will be zeroed. This is a feature of "sqlite3_aggregate_context". Since std::stringstream s0 is defined as a global variable, care will have to be taken to ensure that when S is called in the same select: "Select S(a),S(b)..." that S(a) does not use S(b)'s stringstream.

     static void SStep(sqlite3_context *context, int argc, sqlite3_value **argv){
       SCtx *p=NULL;                                                             
       int i;                                                                    
                                                                                 
       std::string d;
       if( argc<1 ) return;                                     
       p = (SCtx *) sqlite3_aggregate_context(context, sizeof(*p));
       if( p->cnt == 0)    /* When zero first time through */
         {                                                                       
           if ( sscnt  >= MAXSSC )                                               
             { fprintf(stderr,"MAXSSC needs to increase\n");                     
               exit(1);                                                          
             }                                                                   
           p->sscnt=sscnt;                          
           sscnt++;                                                              
           ss[p->sscnt].str("");
           ss[p->sscnt] << "(";
          d="";
         } else { 
            d=",";
            
         }  
                                                                                 
         p->sum += sqlite3_value_double(argv[0]);
         p->cnt++;
         ss[p->sscnt] << d <<  p->sum ;
     
                                                             
     /*                                                 
      *      If the simple function is not used this    
      *      comes into play.  
      */                       
         if (p->cnt == 1)   
           {                 
             for(i=1; i< argc; ++i) {  
               p->cnt++;         
               p->sum+=sqlite3_value_double(argv[i]);  
               ss[p->sscnt] <<  "," << p->sum ;  
               }    
                  
           }   
              
     }   

Above the line, p = (SCtx *) sqlite3_aggregate_context(context, sizeof(*p));, will initilize p->sum, p->cnt, and p->sscnt to zero on the first entry into this function. On each successive entry, the old values get passed back. Although the std:stringstream ss variable is global S(a) calle in the select uses ss[0], S(b) will use ss[1]... etc.

Also note the comment above "IF the simple function is not used this comes into play". Below that statment i walks through the arguement count. It is possible to have a function name 'S' in this case defined as both an aggregate function and a simple function. The distinction is made with the number of arguments in the calling function. This is set in sqlite3_create_function. For example, a name could be assigned to a simple function and an aggregate function. Normally this is setup so that the simple function takes two or more arguments max(1,2,3,4,5) and the aggregate function just takes one argument max(a). In the source "./src/func.c" , take a look at max.

Here is the Finalize function.

     static void SFinalize(sqlite3_context *context){                                
       SCtx *p=NULL;                                                                 
       char *buf=NULL;                                                               
       p = (SCtx *) sqlite3_aggregate_context(context, sizeof(*p));                  

       ss[p->sscnt] << ")"; 
       buf = (char *) malloc (sizeof(char)*(ss[p->sscnt].str().size()+1));           
       if (buf == NULL)                                                              
         fprintf(stderr,"malloc error in SNFinalize, buf\n");                        
                                                                                     

                                                                                     
       snprintf(buf,ss[p->sscnt].str().size(),"%s",ss[p->sscnt].str().c_str());    
       sqlite3_result_text(context,buf,ss[p->sscnt].str().size()+1,free );           
       sscnt--;    /* reclaim this stream */
                                                                                     
     }                                                                               
After all the rows in select have gone through the "SStep" function, the "SFinalize" function is called. The last value for the SCTx structure, is assigned to p in the following statement "p = (SCtx *) sqlite3_aggregate_context(context, sizeof(*p));". Note that p->sscnt is need for indexing the correct ss. The proper size memory is allocated using +1 in ss[p->sscnt].str().size()+1, to allow for the NUL character. sqlite3_result_text takes care of freeing the memory allocated for buf.

The user functions above "SStep" and "SFinalize" need to be added to the SQL language interpreter. This get done with the "sqlite3_create_function" as shown below.


  if (sqlite3_create_function(db, "S", 1, SQLITE_UTF8, NULL, NULL, &SStep,
			      &SFinalize) != 0)
    fprintf(stderr,"Problem with S using SStep and SFinalize\n");

Note the 1 for the 3rd argument. This aggregate function is used when one argument is passed. To have it both ways, to have 'S' defined as both an aggregate and a simple function, an SFunc would have to be created. That could handle 2 to N variables. Once this function is created, the additional "sqlite3_create_function" would be defined in main as follows:

... still in main
  if (sqlite3_create_function(db, "S", -1, SQLITE_UTF8, NULL, &SFunc, NULL,
			      NULL) != 0)
    fprintf(stderr,"Problem with S using SFunc -- simple function\n");
Here is an example SFunc function.

     static void SFunc(sqlite3_context *context, int argc, sqlite3_value **argv){             
       std::stringstream s;                                                              
       std::string d;                                                                    
       double sum=0;                                                                     
       char *buf=NULL;                                                                   
       int i;                                                                            
                                                                                         
       s.str("");                                                                        
                                                                                         
       s << "(";                                                                   
       d="";                                                                             
       for(i=0; i < argc; i++)                                                        
         {                                                                               
       switch( sqlite3_value_type(argv[i]) ){                                            
         case SQLITE_INTEGER: {                                                          
           sum+=(double) sqlite3_value_int64(argv[i]);                                   
           s << d << sum;                                                    
           d=",";                                                                        
           break;                                                                        
         }                                                                               
         case SQLITE_NULL: {                                                             
           s << d << "()";                                                   
           d=",";                                                                        
           break;                                                                        
         }                                                                               
         default: {                                                                      
           sum+=sqlite3_value_int64(argv[i]);                                            
           s << d <<  sum;                                                   
           d=",";                                                                        
           break;                                                                        
          }                                                                              
         }                                                                               
                                                                                         
         }                                                                               
                                                                                         
       s << ")";                                                                   
       buf = (char *) malloc (sizeof(char)*(s.str().size()+2));                          
       if (buf == NULL)                                                                  
         fprintf(stderr,"malloc error in SNFunc, buf\n");                                
       snprintf(buf,s.str().size()+1,"%s",s.str().c_str());                              
       sqlite3_result_text(context,buf,s.str().size()+1,free );                          
                                                                                         
     }                                                                                   
Now S works as both a simple function and an aggregate function. The simple function can go in any expression; but, the aggregate only works after the select. Hence, this goes back to the power of the sign function, which is a simple function.

     ./myfuncpp DATABASE 'select S(1,2,3,4)'
     S(1,2,3,4) = (1,3,6,10)
For a few more examples, take a look at myfuncpp.cc in the download. There are some interesting functions here. For instance, there is an I or index function that works as follows:

         $ ./myfuncpp DATABASE "select S(1,2,3)"
         S(1,2,3) = (1,3,6) 

         $ ./myfuncpp DATABASE "select I(S(1,2,3),0)"
         I(S(1,2,3),0) = 1 

         $ ./myfuncpp DATABASE "select I(S(1,2,3),1)"
         I(S(1,2,3),1) = 3 
Which takes the index in the list. The first index starts at zero.

Reading in Images (Blob data) - eatblob.c

First a demonstration of how the program eatblob.c works. This program is a C API that inserts binary (blob) data into a table.

The program can be run in two ways. Script commands can be redirected into this program. For example, if you first create the following script file "sqlcommands"

      $ cat sqlcommands

          create table blobtest (des varchar(80),b blob);
              insert into blobtest (des,b) 
                       values ('A test file: test.png',?);
              select * from  blobtest;
Note the "?" on the line "values ('A test file: test.png',?);". This serves as a place holder for where blob data in the SQL statement. Using the file above "sqlcommands" the program gets executed as follows:

      $ ./eatblob test3.db test.png <sqlcommands
The image file "test.png" will get read into the program. And will be inserted into the field b, since this is where the question mark is placed.

The program also works interactively. You can work with it in an interactive session as follows:

      $ ./eatblob test.db test.png
      eatblob:0> create table blobtest2 (des varchar(30), b blob);
      eatblob:0> insert into blobtest2 (des,b) values ('A second test: test.png',?);
      eatblob:1> insert into blobtest2 (des,b) values ('A third test: test.png',?); 
      eatblob:2> select * from blobtest2;
      A second test: test.png
      A third test: test.png
      eatblob:2> .q
      [chirico@third-fl-71 sqlite_examples]$ ls outdata.*
      outdata.0.png  outdata.1.png
The blob data is not shown, instead it is written to the file outdata.n.png where n is the record number. Above 2 records are written "outdata.0.png" and "outdata.1.png".

Examining the C code in eatblob.c

The program eatblob.c works by reading in all of the binary data from the filename given as the third argument to the command. The complete file is read into memory. One way to do this is to get the total file size first, then, allocate that amount of memory with malloc. That approach is not taken here. Instead, a more general approach is used. For instance, if you were to read data from a socket, you may not know beforehand how big the file will be. This general approach will take advantage of the realloc function. The function in the program addmem will give us a number. The number will be the new number of units to reallocate. We want to increase the amount of memory in a non-linear fashion to minimize the number of reallocations for large files.

          #define MULT_START     16384
          #define MULT_INCREMENT 2
          #define INIT_SIZE      1024

          long memindx = MULT_START;
          long memnext;
...

     1      long addmem(char **buf, long size)
     2      {
     3          memnext = (size > 0) ? size + memindx : INIT_SIZE;
     4          memindx = memindx * MULT_INCREMENT;
     5          char *tbuf = realloc(*buf, memnext);
     6          if (tbuf == NULL) {
     7                  fprintf(stderr, "Can't allocate memory in addmem\n");
     8                  return size;
     9          } else {
    10                  *buf = tbuf;
    11                  return memnext;
    12          }
    13       }

...
One line 3, the fist time this function is called, the variable size is the current number of bytes allocated. If no memory has been allocated (size is 0), then, the new size will be INIT_SIZE. For this program, INIT_SIZE is set to 1024 in the define statement. However, if size is greater than zero, then, the new size will be the initial size plus memindx, which starts at 1024.

    Call   memidx        Number Returned            size
      1     1024          1024 =     0 +   1024         0
      2     2048          3072 =  1024 + (1024*2)    1024
      3     4096          7168 =  3072 + (2048*2)    3072
      4     8192         15360 =  7168 + (4096*2)    7168
      5    16384         31744 = 15360 + (8192*2)   15360
   ...
And, as you can see from above the number returned increases exponentially. "memindx" is doubled each time this function is called. This doubled value gets added to the size.


     1      long addmem(char **buf, long size)
     2      {
     3          memnext = (size > 0) ? size + memindx : INIT_SIZE;
     4          memindx = memindx * MULT_INCREMENT;
     5          char *tbuf = realloc(*buf, memnext);
     6          if (tbuf == NULL) {
     7                  fprintf(stderr, "Can't allocate memory in addmem\n");
     8                  return size;
     9          } else {
    10                  *buf = tbuf;
    11                  return memnext;
    12          }
    13       }

...
So the function above gives us a number that we can pass to realloc. Note, realloc works as follows, if realloc is successful it will copy the contents pointed to by buf to a location of memory with the larger size memnext, and then free the up the old region of memory. This new region of memory will be assigned to tbuf. Since the old location, the location pointed to by *buf has been released, we need to assign the new value to *buf ( *buf = tbuf).

If realloc cannot get the new size memnext, then, *buf is left untouced and tbuf will be null

Note, in the above program buf is a pointer to a pointer, *buf is a pointer, and **buf is the first byte of data.

Perl and sqlite3

To use Perl with sqlite3 DBI and DBD::SQLite must be installed. To install the packages from CPAN, use the following commands.


   # perl -MCPAN -e shell
   cpan> install DBI
   cpan> install DBD::SQLite

The following program will create a database in enter records

   #!/usr/bin/perl                                                                           
                                                                                             
   use DBI;                                                                                  
                                                                                             
   $dbh = DBI->connect( "dbi:SQLite:data.dbl" ) || die "Cannot connect: $DBI::errstr";       
                                                                                             
   $dbh->do( "CREATE TABLE authors ( lastname, firstname )" );                               
   $dbh->do( "INSERT INTO authors VALUES ( 'Conway', 'Damian' ) " );                         
   $dbh->do( "INSERT INTO authors VALUES ( 'Booch', 'Grady' ) " );                           
   $dbh->do( "CREATE TABLE books ( title, author )" );                                       
   $dbh->do( "INSERT INTO books VALUES ( 'Object Oriented Perl',                             
                                             'Conway' ) " );                                 
   $dbh->do( "INSERT INTO books VALUES ( 'Object-Oriented Analysis and Design',              
                                             'Booch' ) ");                                   
   $dbh->do( "INSERT INTO books VALUES ( 'Object Solutions', 'Booch' ) " );                  
                                                                                             
                                                                                             
   $res = $dbh->selectall_arrayref( q( SELECT a.lastname, a.firstname, b.title               
                                           FROM books b, authors a                           
                                           WHERE b.title like '%Orient%'                     
                                       AND a.lastname = b.author ) );                        
                                                                                             
   foreach( @$res ) {                                                                        
    foreach $i (0..$#$_) {
       print "$_->[$i] "
       }
    print "\n";

   }                                                                                         
                                                                                             
   $dbh->disconnect;                                                                         

For a more elaborate perl example, one that defines functions, reference perlExample.pl in the download.

Simple Everyday Application

Keeping Notes in a Database

This simple bash script allows you to take notes. The notes consist of a line of text followed by an optional category without the additional typing.

 "sqlite3 <database> <sql statement>",
Instead, it is a simple one letter command.

     $ n 'Take a look at sqlite3 transactions - 
           http://www.sqlite.org/lang.html#transaction' 'sqlite3'
The above statement enters the text into a notes table under the category 'sqlite3'. Anytime a second field appears, it is considered the category. To extract records for the day, I enter "n -l", which is similiar to "ls -l", to "note list".

With just "n" help is listed on all the commmands.

     $ n
     This command is used to list notes in 
     a database.
 
     n <option>                                  
      -l list all notes                          
      -t list notes for today                    
      -c list categories                         
      -f <search string> seach for text          
      -e <cmd> execute command and add to notes  
      -d delete last entry                       
REFERENCES:

Source Code: Source code for this article.

SQLite Tutorial: Latest version of this tutorial.

Over 90 Linux Tips: See TIP 50 on working the libraries in C and C++. This tip details how to create dynamic and static libraries, as well make use of the -Wl,-R switch in gcc

Solving Complex SQL Problems: Increasing list of examples using the sign function.

www.sqlite.org: Home page for the SQLite project.

Lemon Parser Generator Tutorial: Tutorial on the parser used with sqlite.


c0Title: Mouse Button Up Or Down Status
c1Code:
--------------------------------------------------------------------------------

Here is how to check the mouse button 'up_or_down' status.

Declare Function GetKeyState Lib "user" (ByVal k%) As Integer 
Declare Function GetAsyncKeyState Lib "user" (ByVal k%) As Integer

This functions will tell you whether a key is up or down, and if it's "toggled". The high order bit tells you if the key is up or down. The low order bit tells you if it's toggled - each time the key is pressed and then released the low order bit will change.

The first function maintains the keyboard state when the last *window message* was received by the app and is the one you normally use. The second one corresponds to the real time keyboard state.

Now, why on earth do I talk keyboard when the question was about the mouse? - That's because the mouse buttons are considered as "virtual" keyboard keys. Some virtual key codes are:

VK_LBUTTON 1 
VK_RBUTTON 2 
VK_MBUTTON 4 
VK_TAB 9 
VK_ESCAPE 27

So to wait for the left mouse button to be first pressed and then released you could use:

Do While GetKeyState(VK_LBUTTON)>=0: DoEvents: Loop 

Do While GetKeyState(VK_LBUTTON)<0: DoEvents: Loop

Submitted By: Dan Bystrom


c0Title: Moving between controls with the enter key
c1Code:
If you want to have the focus move between the controls on a form when the enter key is pressed, you can with the SendKeys function. The basis of the tip is to detect when the Enter key is pressed, and then Send the Tab key to the program.

You can detect when the user presses enter from the KeyPress event procedure by checking to see if the KeyAscii parameter is the character code 13. Then you can move the focus to the next control in the TabIndex order with the code:

SendKeys "{tab}"
You can move the focus to the previous control with the code:

SendKeys "+{tab}"
This technique works with most kinds of controls. However, it does not work with command button controls, because command buttons do not receive the KeyPress event when you press enter.

Code

Sub Text1_KeyPress (KeyAscii As Integer) If KeyAscii = 13 Then
    SendKeys "{tab}"
    KeyAscii = 0
End If
End Sub
Tip by James Limm


c0Title: CryptoAPI Demo Version 1.7
c2File: cryptoapi_demo.zip
c1Code:
Author: Kenneth Ives (Featured Developer)  
Category: String Manipulation  
Type: Applications  
Difficulty: Advanced  


Version Compatibility:  Visual Basic 6    


More information: This is a detailed demonstration Microsoft's CryptoAPI. It shows you how to use passwords, hash algorithms, encrypting strings, and files. 

Upgraded to version 1.7, 09/10/10. Upgrades include: 

Fixed several minor bugs when trying to access the enhanced provider. 
Enhanced wrapper functionality that accesses CryptoAPI calls within advapi32.dll. 
Updated documentation in the source code and the associated text files. Please read them before writing me.


c0Title: MSD RadixSort (Stable, Recursive)
c1Code:
' MSD RadixSort.  No sort based on comparisons can be faster than O(N log N).  
' RadixSort makes no comparisons and can therefore achieve O(N) behavior.  To 
' do this, it examines keys one byte at a time, counting the number of keys 
' that have each possible byte value.  The counts are then used to build an 
' offset table specifying the sorted order of the keys.  It's easiest to work 
' backwards from the least significant digit of the keys (LSD Radix),
'  since order based on more significant digits is not disturbed by less 
' significant digits.  Unfortunately, the LSD approach requires padding short 
' keys if key length is variable, and guarantees that all digits will be 
' examined even if the first 3-4 digits contain all the information needed to 
' achieve sorted order.  Most significant digit (MSD) RadixSort takes a lot 
' more bookkeeping -- the list must repeatedly be split into sublists for each 
' value of the last digit processed -- but the pay-off is that only as many 
' digits will be examined as are needed.  As in QuickSort and MergeSort,
'  it's worthwhile to hand off the sublists to InsertionSort when they get 
' short enough.
'
' MSD RadixSort is stable and runs in linear O(N) time.  It is fairly memory 
' intensive, needing space for some extra counting arrays and stack space for 
' recursive calls, but still uses less memory than MergeSort.  This version is 
' set up for strings and would take some work to adapt to other data types.  
' Integers and longs could be handled by converting them to strings (and 
' limiting the arrays CNT and IND to the ten numerical digit values plus the 
' minus sign); this is probably worthwhile only if you have hundreds of 
' thousands of integers or longs to sort.  Doubles are more of a challenge,
'  requiring conversion to an array of bytes by type casting.  I have not 
' pursued this, since VBA on the Mac does not include the CopyMemory function 
' that enables type casting on Windows systems.  
'
' NOTE:  This version is set up to count byte values from 1 to 127.  If your 
' keys use (for instance) only lowercase or only uppercase alphabetic 
' characters or only numerical digits, you can trim the CNT and IND arrays and 
' your sorts will run correspondingly faster.
'
' Reference:  P. M. McIlroy, K. Bostic and M. D. McIlroy,
'  "Engineering  Radix Sort", Computing Systems 6(1):5-27 (1993).
'
' Speed:  MSDRadixSortS sorts 500,000 random strings in 28.3 sec; sorts 100186 
' library call numbers in 21.3 sec; sorts 25479 dictionary words in 1.8 sec 
' (random order), 1.5 sec (presorted) or 1.9 sec (reverse sorted).  Timed in 
' Excel 2001 on an 800 mhz PowerBook.
'
' Bottom line:  complex and best suited to strings, but there's nothing faster 
' for really long lists.

' Usage:  

Dim S1(L To R) As Strings
Dim B1(1 To nChars) As Byte
Dim P1(L To R) As Long

For I = L To R
    S1(I) = GetRandomString()
Next I

StrsToBytes S1, B1, P1    'a routine that stores the strings in 0 terminated 
                          ' byte arrays with
                    'P1() holding pointers to the start of each byte series.

MSDRadixSortS L, R, B1, P1


' CODE:

Sub MSDRadixSortS(N As Long)
    Dim CH() As Integer
    
    ReDim CH(1 To N)  
    'See below for type PILE (stack records)
    ReDim STACK(1 To 1000)
    StackPtr = 1
    With STACK(StackPtr)
        .L = 1
        .R = N
        .D = 0
    End With
    StackPtr = StackPtr + 1
    RadixS CH
End Sub

Sub RadixS(CH() As Integer)
    Dim L As Long
    Dim R As Long
    Dim D As Integer
    Dim I As Long
    Dim J As Long
    Dim TMP As Long
    Dim C As Integer
    Dim NextC As Integer
    Dim CNT(-1 To 127) As Long
    Dim IND(-1 To 127) As Long
    Dim NuCnt As Long
    Dim BigCnt As Long
    Dim OldSp As Long
    Dim BigSp As Long
    
    While StackPtr > 1
    'Pop a PILE off the stack.
        StackPtr = StackPtr - 1
    'Get left and right limits of sublist and depth of byte to examine
        With STACK(StackPtr)
            L = .L
            R = .R
            D = .D
        End With
    'Sublists of <= 24 keys will be finished by InsertionSort
        If R - L > 24 Then
        'Clear the count array.
            For I = -1 To 127
                CNT(I) = 0
            Next I
        'Get the byte at depth D in each key and count the number of each byte 
        ' value.
            For I = L To R
                C = CInt(B(P(I) + D))
                CH(I) = C
                CNT(C) = CNT(C) + 1
            Next I
        'We will add the counts to create sorted addresses in array IND().
            IND(-1) = L
        'At the same time we'll push the sublists for each byte value onto the 
        ' stack.
            OldSp = StackPtr
        'We'll track the biggest sublist and make sure it comes off the stack 
        ' last;
        'this ensures the stack will not get more than logarithmically deep.
            BigCnt = 0
        'Now we build the addresses out of the counts.
            For I = 0 To 127
                J = I - 1
                NuCnt = CNT(J)
                IND(I) = IND(J) + NuCnt
         'If there is a sublist of keys starting with the current byte value,
         '  stack it.
                If NuCnt > 1 Then
                    With STACK(StackPtr)
                        .L = IND(J)
                        .R = IND(I) - 1
                        .D = D + 1
                    End With
                    StackPtr = StackPtr + 1
             'Keep track of the largest count / sublist.
                    If NuCnt > BigCnt Then
                        BigCnt = NuCnt
                        BigSp = StackPtr
                    End If
                End If
            Next I
        'Swap the biggest sublist down into the stack so it comes off last.
            TMP = BigSp
            BigSp = OldSp
            OldSp = TMP
        'Now use the counts to move the pointers to their sorted positions 
        ' based on the
        'bytes examined so far; doing this in place this gets a bit ugly.
            For I = L To R
         'We will use the byte at I to find what address P(I) should be mapped 
         ' to.
                C = CH(I)
         'We use -1 to flag pointers already moved.
                CH(I) = -1
         'If C = -1 we skip the loop and increment I until we find a pointer 
         ' not already moved.
                Do While C >= 0
             'We go to IND(C) to get the destination address for P(I).
                    J = IND(C)
             'We swap the current pointer for the one at that address.
                    TMP = P(I)
                    P(I) = P(J)
                    P(J) = TMP
             'Now we determine where to map the pointer we just displaced.
             'We get the byte value from its key.
                    NextC = CH(J)
             'We flag it as moved.
                    CH(J) = -1
             'Once we've used each address we increment it unless we've hit R.
                    If J < R Then IND(C) = J + 1 Else Exit Do
             'We set C to the byte of the key of the displaced pointer; ready 
             ' to loop!
                    C = NextC
                Loop
         'If a series of displacements circles back to a pointer already moved,
         '  we 
         'increment I until we find a pointer not yet moved or until we end at 
         ' R.
            Next I
        Else
            DeepInsertS B, P, L, 1 + R - L, D
        End If
    Wend
End Sub

Type PILE
    L As Long
    R As Long
    D As Integer
End Type

Dim STACK() As PILE
Dim StackPtr As Integer

Sub DeepInsertS(B() As Byte, P() As Long, L As Long, N As Long, D As Integer)
    Dim LP As Long
    Dim RP As Long
    Dim TMP As Long
    Dim I As Long
    Dim J As Long
    
    For RP = L + 1 To L + N - 1
        TMP = P(RP)
        For LP = RP To L + 1 Step -1
            I = TMP + D
            J = P(LP - 1) + D
            Do While B(I) = B(J)
                If B(I) = 0 Or B(J) = 0 Then Exit Do
                I = I + 1
                J = J + 1
            Loop
            If CInt(B(I)) - CInt(B(J)) < 0 Then P(LP) = P(LP - 1) Else Exit For
        Next LP
        P(LP) = TMP
    Next RP
End Sub


David B. Ring


c0Title: Navigate Effectively with SHIFT+F2 and CTRL+SHIFT+F2
c1Code:
One of the confusing aspects of developing a large project with Visual Basic is that code snippets tend to be in any of a variety of places, and keeping track of what's where - not to mention navigating to a particular routine when you want to see it - is often difficult. However, a little-known keyboard shortcut can help. 
Simply highlight the property or procedure name in the code and press SHIFT+F2; you'll be transported as if by magic to the highlighted property or procedure. (If it's outside the project, you'll be taken to the Object Browser, where you can obtain further information about it.) 

You can navigate back to where you came from just as easily. This time hit CTRL+SHIFT+F2 and you're back at your original place. This can save hours and hours of scrolling and jumping from one module to another in a large project.


c0Title: NdxShellSort -Sort Indexed Arrays using ShellSort
c1Code:
' Indexed ShellSort of an array of any type
'
' Indexed Sorts are sort procedures that sort an index array
' instead of the main array. You can then list the items in
' sorted member by simply scanning the index, as in
'   For i = 1 To numEls: Print arr(ndx(i)): Next
'
' NUMELS is the index of the last item to be sorted, and is
' useful if the array is only partially filled.
'
' Works with any kind of array, except UDTs and fixed-length
' strings, and including objects if your are sorting on their
' default property. String are sorted in case-sensitive mode.
'
' You can write faster procedures if you modify the first two lines
' to account for a specific data type, eg.
' Sub NdxShellSortS(arr() As Single, ndx() As Long,
'  '   Optional numEls As Variant, Optional descending As Boolean)
'   Dim value As Single

Sub NdxShellSort(arr As Variant, ndx() As Long, Optional numEls As Variant, _
    Optional descending As Boolean)

    Dim value As Variant
    Dim index As Long, index2 As Long
    Dim firstItem As Long
    Dim distance As Long
    Dim tempNdx As Long

    ' account for optional arguments
    If IsMissing(numEls) Then numEls = UBound(arr)
    firstItem = LBound(arr)
    ' init index array if necessary
    If ndx(firstItem) = 0 And ndx(UBound(ndx)) = 0 Then
        For index = firstItem To UBound(ndx)
            ndx(index) = index
        Next
    End If
    
    ' find the best value for distance
    Do
        distance = distance * 3 + 1
    Loop Until distance > numEls
    
    Do
        distance = distance \ 3
        For index = distance + 1 To numEls
            tempNdx = ndx(index)
            value = arr(tempNdx)
            index2 = index
            Do While (arr(ndx(index2 - distance)) > value) Xor descending
                ndx(index2) = ndx(index2 - distance)
                index2 = index2 - distance
                If index2 <= distance Then Exit Do
            Loop
            ndx(index2) = tempNdx
        Next
    Loop Until distance = 1
End Sub



Francesco Balena


c0Title: Nix the X
c1Code:
Sometimes, you want to show a form that you don�t want users to be able to cancel by clicking on the X�it might not make sense for your app. The best VB solution is to cancel the unload in the form�s QueryUnload event. However, this allows users to do something wrong, for which you then have to handle and scold them. If you do nothing, it looks as if the form has a bug and won�t cancel. Add this routine to a standard BAS module:

 

Private Declare Function GetSystemMenu Lib "user32" _

               (ByVal hWnd As Long, ByVal bRevert As Long) As Long

Private Declare Function RemoveMenu Lib "user32" _

               (ByVal hMenu As Long, ByVal nPosition As Long, _

               ByVal wFlags As Long) As Long

 

Private Const MF_BYPOSITION = &H400&

 

Public Sub RemoveCancelMenuItem(frm As Form)

               Dim hSysMenu As Long

               'get the system menu for this form

               hSysMenu = GetSystemMenu(frm.hWnd, 0)

               'remove the close item

               Call RemoveMenu(hSysMenu, 6, MF_BYPOSITION)

               'remove the separator that was over the close item

               Call RemoveMenu(hSysMenu, 5, MF_BYPOSITION)

End Sub

 

     Then call the routine from any form as it loads:

 

               Private Sub Form_Load()

                              RemoveCancelMenuItem Me

               End Sub

 

     After this call, the Close menu item in the system menu and the option to cancel [X] will be disabled. Note that if you�re doing other things with the system menu, you might have to adjust the position number in the RemoveMenu call.

�Josh Frank, Parsippany, New Jersey


c0Title: Object Oriented International Functions
c2File: IntlObj.zip
c1Code:
Author: Huw Wilkins  
Category: Miscellaneous  
Type: Classes  
Difficulty: Intermediate  


Version Compatibility:  Visual Basic 5   Visual Basic 6  


More information: Provides an OO interface to the system international functions. Allows conversion of dates between over ninety locales plus localized string sorting, currency formatting and number formatting.


c0Title: Obtain Regional Decimal Character Without API
c1Code:
Use this function to read a number decimal symbol from regional
settings:

Sub Form_Load()
	Dim DecS As String
	DecS = ReadDecimalSymbol()
End Sub

Function ReadDecimalSymbol() As String
	ReadDecimalSymbol = Mid$(CStr(1.1), 2, 1) End Function

�Gianfranco Callino, Verona, Italy


c0Title: Open Your VB Projects With a Clean Slate
c1Code:
If you�re like me, you hate all the clutter of open form, class, and module windows when you open your VB projects in the Integrated Development Environment (IDE). Here�s a simple workaround to let you start your project with a clean workspace.
Edit the accompanying VB Workspace file for your project. It has the same name as your project file, but with a VBW file extension. Delete all the lines in this file and save it. Now make this file read-only by right-clicking on the file, choosing Properties, then selecting the read-only checkbox.
Whenever you save your project from then on, VB won�t update this file because it is read-only, and it won�t complain. Each time you open your project, your workspace will start fresh with no clutter. If for any reason you want to revert to the old way, simply change the read-only flag back.

�Richard Edwards, Belleville, Ontario, Canada


c0Title: Open/Close CD-ROM Door
c1Code:
Here is a quick tip on how to open and close a CD-ROM door.

Declare
Private Declare Function mciSendString Lib "winmm.dll" Alias "mciSendStringA" (ByVal lpstrCommand As String, ByVal lpstrReturnString As String, ByVal uReturnLength As Long, ByVal hwndCallback As Long) As Long
Sample Code
Sub OpenCDDoor()
  mciSendString "Set CDAudio Door Open Wait", 0&, 0&, 0&
End Sub

Sub CloseCDDoor()
  mciSendString "Set CDAudio Door Closed Wait", 0&, 0&, 0&
End Sub

Tip Submitted By: Malcolm Hubert


c0Title: Opening A File With Its Associated Program
c1Code:
There are many times when you want to open a file with the its associated program. For instance, you might want to display a Word DOC for or an Adobe PDF file. Normally, you would just run:

R = Shell("word.exe mydoc.doc")

But most of the time, you will not know where Word is installed. Simply writing

R = Shell("mydoc.doc")

won't work. The functionally of Shell in Visual Basic is very limited. Much greater functionality can be had by using the ShellExecute API call. The following code will open a file with its associated program:

Declare The Following

Private Declare Function ShellExecute Lib "shell32.dll" _

     Alias "ShellExecuteA" (ByVal hwnd As Long, ByVal _

     lpOperation As String, ByVal lpFile As String, _

     ByVal lpParameters As String, ByVal lpDirectory _

     As String, ByVal nShowCmd As Long) As Long

Code

ShellExecute hwnd:=Screen.ActiveForm.hwnd, _

     lpOperation:="Open", lpFile:="mydoc.doc", _

     lpParameters:=vbNullString, lpDirectory:="c:\myapp", _

     nShowCmd:=vbNormalFocus

 

Parameter
 Description
 
hwnd
 Identifies the parent window. This window receives any message boxes produced by an application (for example, for error reporting).
 
LpOperation
 Points to a null-terminated string specifying the operation to perform. This string can be "open" or "print." If this parameter is NULL, "open" is the default value.
 
LpFile
 Points to a null-terminated string specifying the file to open.
 
LpParameters
 Points to a null-terminated string specifying parameters passed to the application when the lpFile parameter specifies an executable file. If lpFile points to a string specifying a document file, this parameter is NULL.
 
LpDirectory
 Points to a null-terminated string specifying the default directory.
 
NShowCmd
 Specifies whether the application window is to be shown when the application is opened. This parameter can be one of the values described in the API ShowWindow().
 

This tip is reprinted from the VB Tips & Tricks Volume 1 book. 
Parts of this tip was submitted by: : David McCarter


c0Title: Operate on Array of Selected ListItems
c1Code:
The fast way to get multiple selected items from a ListBox control is to send it a LB_GETSELITEMS window message. Here�s a simple example that moves items from one ListBox to another ListBox. To test this example, place two ListBox controls (lstFrom and lstTo) and a Command button control (cmdMove) on a form, then copy this code into the form�s code editing page:

Option Explicit
Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long

Private Const LB_GETSELCOUNT = &H190 
Private Const LB_GETSELITEMS = &H191 

Private Sub Form_Load() 

	' Add some items into source list 	
	lstFrom.AddItem "Matthew So"
	lstFrom.AddItem "Join"
	lstFrom.AddItem "Hello"
	lstFrom.AddItem "Morning"
	lstFrom.AddItem "Apple"

End Sub


Private Sub cmdMove_Click()

	Dim nRet As Long
	Dim nSel() As Long
	Dim i As Long

	nRet = SendMessage(lstFrom.hWnd, LB_GETSELCOUNT, 0, ByVal 0&)
	Me.Caption = CStr(nRet)

	If nRet > 0 Then

		' Allocate enough memory for the array
		ReDim nSel(0 To nRet - 1)

		' Get an array of ListIndexes for the
		' selected items
		nRet = SendMessage(lstFrom.hWnd, _
		LB_GETSELITEMS, lRet, nSel(0))

		' Start from the end of list to avoid
		' index change of the source list
		For i = UBound(nSel) To LBound(nSel) Step -1

			' Copy item from source list to
			' destination list
			lstTo.AddItem lstFrom.List(nSel(i))

			' Remove selected item from source
			' list
			lstFrom.RemoveItem nSel(i)
		Next i
	End If
End Sub

The tricks here are to redimension the nSel array using the count of selected items returned by LB_GETSELCOUNT, and to move the selected items starting from the end of the origin ListBox. If you try to move items from the beginning of the ListBox, the ListItems are shifted downward and the saved array of item ListIndexes is no longer valid.

�Matthew So, Hong Kong


c0Title: ADO: Optimize Parametrized Queries With ADO Objects
c1Code:
When you write Insert statements, it can be difficult to accommodate the possible values end users might enter into a textbox. The most common task is replacing single quotes with double quotes. However, parameterized queries provide two benefits: You do not have to parse data entered by users�except for business rules; and SQL Server 7.0 immediately caches the SQL statement:

Dim cmd As ADODB.Command
Dim prm As ADODB.Parameter

Set cmd = New ADODB.Command
Set prm = New ADODB.Parameter

With cmd

       .ActiveConnection = CONNECT_STRING

       .CommandText = "INSERT INTO employees " & "(name) VALUES(?)"
       .CommandType = adCmdText

       Set prm = .CreateParameter(, adChar, adParamInput, 50, Me.txtName.Text)
       .Parameters.Append prm

       .Execute

End With

Set cmd = Nothing
Set prm = Nothing

�Christopher P. Madrid, Austin, Texas


c0Title: Optimizing String Operations
c1Code:
Play VB�s Strings by Francesco Balena

You can write incredibly fast routines if you have a better understanding of how VB manages its strings.


Strings play a key role in making Visual Basic programming so easy. You don�t have to declare their maximum length, and you don�t have to manually allocate and release memory when assigning a new value to them. 

All this power comes at a price, though. Strings are probably the most inefficient data type provided by VB. The usual string functions offer good performance in most cases, but when you need to create fast, string-reliant applications, you need alternate ways to process them.

First, always try to avoid string functions; deal directly with individual characters� numeric codes. On the free Registered level of The Development Exchange you�ll find the source code of a program that parses a text file, using two different algorithms to count its words, paragraphs and non-space characters. The first algorithm loads the whole file into a huge string and uses the Mid$ function to analyze each character; the second loads the file into a Byte array and iterates on its items. The difference is noticeable. The optimized version runs about fifteen times faster than the "pure-VB" one, if you compile it into native code with the "Remove Array Bounds Checks" option turned on. 

Next, use a Byte array to keep your data in ANSI format. This helps saves memory and usually speeds up execution, because you�re working with fast numeric operations that are compiled into CPU instructions, rather than calls to routines in the VBA library. You can convert a Byte array holding an ANSI string into a Unicode string using the StrConv function:

res$ = StrConv(byteArr(), vbUnicode)

This approach isn�t efficient, however, when you want to extract only a substring�a portion of the Byte array�and store it in a VB variable. You can easily load a text file into a Byte array, but how can you extract a paragraph or a word without creating a temporary string as big as the entire file (actually twice as big, being a Unicode string)? The most efficient "pure-VB" solution performs so poorly that it offsets the benefits of using the Byte array:

' extract the portion of the array that
' starts at byteArr(startIndex)
res$ = Space$(length)
For i = 1 To length
Mid$(res$, i, 1) = Chr$(byteArr(startIndex + i � 1)) Next

Instead, use the CopyMemory API function:

res$ = Space$(length)
CopyMemory ByVal res$, byteArr(startIndex), length

This works because the ByVal keyword temporarily converts the string to ANSI an instant before it receives the contents of the subarray. When the function returns, it converts the string back to Unicode. This technique runs five to ten times faster than the "pure VB" approach, or even faster (depending on the length of the string involved). You can also use CopyMemory to do the opposite�take data from a Unicode string and store it in ANSI format in a portion of a Byte array:

CopyMemory byteArr(startIndex), ByVal res$, length


USE POINTERS TO STRINGS

You can often achieve better performance by looking at your strings as byte streams stored somewhere in memory. The undocumented StrPtr function returns the address of a string�s first character; use the CopyMemory API function to move bytes around:

' replace a$ string with a substring in
' b$, starting with its third character
a$ = "1234567890"
b$ = "abcdefghijklmnopq"
' standard approach
a$ = Mid$(b$, 3, Len(a$))
' smart copy using CopyMemory
CopyMemory ByVal StrPtr(a$), ByVal StrPtr(b$) + 4, LenB(a$)

When dealing with strings in this way, the offset of the N-th character equals (N-1)*2, because VB Unicode strings require two bytes per character. Likewise, when calculating the amount of bytes to copy, you must double the number of characters or use the LenB function I�ve showed here.

And pay attention to pointer arithmetic. Whenever you manipulate memory addresses, you should use unsigned Long values. Unfortunately, VB only offers signed Long variables. In theory this difference might cause an overflow error when adding to a number under 80000000 hex (such as 7FFFFFFE + 2). But in practice this never occurs, because under Windows 95 the address 80000000 hex marks the boundary separating the memory area private to a process from the area shared by all processes (and used by system DLLs and memory mapped files). A given object in memory�private or shared�can�t span that boundary. So in practice you�ll never experience an overflow with your pointers. 

Under Windows NT memory partition works slightly differently, but it doesn�t matter in this case. Under NT the area from 7FFF0000 to 7FFFFFFF is unavailable to processes, so you can�t cause overflow if you directly access memory allocated to VB arrays or strings. 

Even if VB does move bytes from one string to another efficiently, it wastes time creating temporary strings, then releasing them. So try to avoid such temporary strings. Consider the standard "pure VB" approach to "rotate" the characters in a string: 

work$ = Mid$(work$, 2) + Left$(work$, 1)

The target variable doesn�t change length, yet VB has to create two temporary strings and concatenate them. Instead, I recommend speeding up execution by dealing with memory yourself:

addr = StrPtr(work)
' save first char
CopyMemory acode, ByVal addr, 2
' copy the bulk of chars
bytes = LenB(work) � 2
CopyMemory ByVal addr, ByVal addr + 2, bytes ' complete the rotation CopyMemory ByVal addr + bytes, acode, 2

In this way your statements will execute twice as fast as a VB statement based on Mid$ and Left$ functions. You can�t use this method for fixed-length strings, though.

 
PLAY WITH DESCRIPTORS

While you can do a lot of neat tricks using StrPtr, string processing comes into its own with VarPtr, another undocumented VB function (see Figure 1). The VarPtr function usually returns the address of a variable, but when used with string variables it returns the address of the string�s descriptor (a pointer to the first character of the string). I�m using the "descriptor" term somewhat arbitrarily, because descriptors are more often associated with structures. A stickler for language might call them BSTRs, but "descriptors" is more familiar to VB programmers (especially to old salts with QuickBasic experience).



Figure 1
Pointer Madness. You can get confused when working with strings at a low level. The StrPtr function returns the address of the first character, while the VarPtr function returns the address of the descriptor (which in turn contains the address of the first character). The LenB function returns the value of the double word that immediately precedes the first character. The Dim x As String statement reserves 4 bytes for the descriptor, but no memory is allocated for the string until the first assignment occurs.

When VB compiles your program, it hard-codes references to strings as references to descriptors. In a given procedure the x$ variable always corresponds to a given descriptor, whose contents, however, may vary. That�s because VB may reallocate the memory area holding the characters when it assigns a new value to the string. And since the value the StrPtr(x$) function returns may change during execution, don�t cache it in a variable for later reuse�unless no string operations will be executed. 

Playing with descriptors can often turbocharge string routines. Take the common task of swapping the contents of two string variables:

temp$ = a$
a$ = b$
b$ = temp$

Unfortunately, this simple code requires a lot of time-consuming operations. VB has to allocate memory for the temporary variable (and eventually release it), reallocate memory for a$ and b$ when they receive a string of different length, and copy the characters back and forth. Instead you should be able to just swap the contents of their descriptors, as QuickBasic�s SWAP command did. This got left out of VB, but you can do it yourself using VarPtr:

' save a$'s descriptor
saveAddr = StrPtr(a$)
' copy b$'s descriptor into a$'s
CopyMemory ByVal VarPtr(a$), ByVal VarPtr(b$), 4 ' original a$'s descriptor into b$'s CopyMemory ByVal VarPtr(b$), saveAddr, 4

This lets you do simple string swaps several times faster than the "pure VB" counterpart, especially with long strings of different lengths. I�ve found that swapping two strings of about a thousand character length ran a dozen times faster than using a temporary string variable.

Directly manipulating string descriptors helps even more striking when you�re using string arrays. A string array is just a table of descriptors and the actual data is stored elsewhere (see Figure 2). But you can insert and delete items in a string array without using a For-Next (see Listing 1). And you can do up to three orders of magnitudes faster than the standard VB algorithm! 



Figure 2
Strings Arrays Are Just A Block of Descriptors. While the actual string items are stored in non-contiguous memory locations, all the descriptors of a string array are stored in one block of memory, and can therefore be copied and moved around using the CopyMemory API function.

Private Declare Sub CopyMemory Lib "kernel32" Alias _
	"RtlMoveMemory" (dest As Any, source As Any, _
	ByVal numBytes As Long)

' insert a new item in a given position of a string array Sub InsertStringItem(strArr() As String, ByVal index As Long, _
        ByVal newItem As String)
	Dim lastItem As Long, saveAddr As Long
	lastItem = UBound(strArr)
	' save the descriptor of last item 
	' before it gets overwritten
	saveAddr = StrPtr(strArr(lastItem))
	' shift all the items in one operation
	CopyMemory ByVal VarPtr(strArr(index + 1)), ByVal _
		VarPtr(strArr(index)), (lastItem - index) * 4
	' overwrite the descriptor at strArr(index)
	' with the last item's descriptor
	CopyMemory ByVal VarPtr(strArr(index)), saveAddr,4
	' now we can safely replace it with the new string
	strArr(index) = newItem
End Sub

' delete an element of a string array, and shift all the ' subsequent items towards lower indexes Sub DeleteStringItem(strArr() As String, ByVal index As Long)
	Dim lastItem As Long, saveAddr As Long
	lastItem = UBound(strArr)
	' save the descriptor of strArr(index) 
	' before it gets overwritten
	saveAddr = StrPtr(strArr(index))
	' shift all the items in one operation
	CopyMemory ByVal VarPtr(strArr(index)), ByVal _
		VarPtr(strArr(index + 1)), (lastItem - index)*4
	' overwrite the descriptor of last array item
	' with the saved descriptor
	CopyMemory ByVal VarPtr(strArr(lastItem)), saveAddr, 4
	' now we can safely delete the last item
	strArr(lastItem) = vbNullString
End Sub

Listing 1
Low-Level Pointer Tricks Pay Off. You can insert and remove items in a string array by moving a block of descriptors. Depending on the size of your array and its individual items, these routines run from 100 to 1000 times faster than the standard VB technique based on For-Next loops! 

But sloppy handling of string descriptors provokes GPFs, so proceed with caution. Only move descriptors from one memory location to another if the total number of descriptors hasn�t changed at the end of the routine. Don�t destroy or overwrite a descriptor without first saving it or copying it elsewhere. You can overwrite a null descriptor with another descriptor, but then you must manually delete the source descriptor to avoid duplicate values (see Listing 2).

Declare Sub CopyMemory Lib "kernel32" Alias _
	"RtlMoveMemory" (dest As Any, source As Any, _
	ByVal numBytes As Long)
Declare Sub ZeroMemory Lib "kernel32" Alias _
	"RtlZeroMemory" (dest As Any, ByVal numBytes As Long)

Sub MoveStringArray(source() As String, dest() As String)
	Dim firstEl As Long, lastEl As Long, numBytes As Long
	firstEl = LBound(source)
	lastEl = UBound(source)
	numBytes = (lastEl - firstEl + 1) * 4
	' start with a fresh new array 
	'(it clears all its descriptors)
	ReDim dest(firstEl To lastEl) As String
	' copy all the descriptors from source() to dest()
	CopyMemory ByVal VarPtr(dest(firstEl)), _
		ByVal VarPtr(source(firstEl)), numBytes
	' manually clear all the descriptors in source()
	ZeroMemory ByVal VarPtr(source(firstEl)), numBytes End Sub

Listing 2
Move descriptors, not data. You can move the contents of one string array to another, using this routine. To do so you need the ZeroMemory API function to manually clear duplicate descriptors created by CopyMemory; a regular ReDim source() statement won�t work right because it would deallocate all the strings, leaving the dest() array full of "orphaned" descriptors.

VB provides safe string functions and decent performance, so often you won�t need the low level techniques I�ve discussed here. But if you do need faster string routines, you don�t have to write a DLL in C, or switch to an expensive Alpha computer. VB has the power if you have the skill.


c0Title: Optional Variant Arguments
c1Code:
Use the IsMissing function to detect whether or not optional Variant arguments have been provided in calling a procedure. IsMissing returns True if no value has been passed for the specified argument; otherwise, it returns False.

If IsMissing is used on a ParamArray argument, it always returns False. To detect an empty ParamArray, test to see if the array's upper bound is less than its lower bound.


c0Title: Pass Error Message Throughout Nested Components
c1Code:
When you use components in your VB projects, sometimes it�s
hard to determine which component has an error when the
application stops running. You might especially have this prob-lem
with nested components.
Here�s a structure for developing your nested components that
lets you obtain detailed error information from your application.
You can determine which component and which method has the
error without having to work through your code.
Create your component using this structure:
'/
' Component1
'
Public Sub GetName(ByRef strName As String)

	On Error GoTo EndOfSub
	Dim objComponent2 As New Component2

	'Create Component2
	Dim strErrMsg As String
	Dim lYourErrorNumber As Long

	strErrMsg = ""

	' Write your code to something else
	' Record any error to strErrMsg and set lYourErrorNumber
	' Get name from Component2
	Call objCompnent2.GetName(strName)

	If strErrMsg <> "" Then
		' Use "GetName::Component1" as Err.Source
		' here for any error that occurred inside this method
		Err.Raise vbObjectError + lYourErrorNumber, _
		"GetName::Component1", strErrMsg
	End If

EndOfSub:

	If Err.Number <> 0 Then
		' raise error to component's client program
		Err.Raise Err.Number, Err.Source, Err.Description
	End If

End Sub


'/
'/
' Component2
'
Public Sub GetName(ByRef strName As String)
	On Error GoTo EndOfSub

	Dim strErrMsg As String
	Dim lYourErrorNumber As Long

	strErrMsg = ""

	' Write your code to get name
	' Record any error to strErrMsg and set lYourErrorNumber
	If strErrMsg <> "" Then
		' Raise error
		Err.Raise vbObjectError + lYourErrorNumber, , strErrMsg
	End If

EndOfSub:
	If Err.Number <> 0 Then

		' Raise error to component's client program
		' Use "GetName::Component2" as Err.Source
		' here for any error ocurred inside this method
		Err.Raise Err.Number, "GetName::Component2", Err.Description
	End If
End Sub


'/
Use this code in your application program:
'/
' Client program
'
Sub DoMyJob()

	On Error GoTo EndOfSub

	Dim objComponent1 As New Component1

	'Create Component1
	Dim strMyName As String

	' Get name from Component1
	Call objComponent1.GetName(strMyName)

	' Write your code to do your job

EndOfSub:

	If Err.Number <> 0 Then

		' If any error occurred in the component1
		' or component2, it can be catched here,
		' including detail information about
		' error number, source and description
		Err.Raise Err.Number, Err.Source, Err.Description
	End If
End Sub

'/

�Peter Luo, Calgary, Alberta, Canada


c0Title: Pass the hidden Global object to an ActiveX DLL
c1Code:
Date: 12/25/2000
Versions: VB5 VB6 Level: Intermediate
Author: The VB2TheMax Team 

An ActiveX DLL doesn't have direct access to the environment of the calling EXE. For example, the App and Printer objects in the DLL don't correspond to the objects with the same name in the main application, therefore if you want to print something from the DLL using the same settings as the main app you must explicitly pass the Printer object from the main application. A similar reasoning applies to other global VB objects, such as the Forms collection. 

Instead of explicitly passing the Printer, Forms, and App objects from the main app to the DLL, you can just pass it the hidden Global VB object. This object is actually the parent of the above-mentioned objects (and others as well, such as the Clipboard and the Screen objects). This code invokes a method in the DLL that receives the reference to the global object of the main app: 

' in the main (client) application
Dim obj As New MyDll.Functions

' note that we need a pair of square brackets 
obj.SetGlobal [Global]

Here's is the code in the Functions class module of the MyDLL component that receives the reference to the Global object. Note that the argument can't be declared using a specific object type, because this object is private to the VB library and Visual Basic won't let you pass it across project boundaries. Interestingly, the global object is a vtable-only object, and therefore doesn't support IDispatch, so the receiving parameter must be declared using IUnknown: 

' in the Functions module of the MyDLL ActiveX DLL project 
Dim glb As VB.Global

Sub SetGlobal(glbIU As IUnknown)
    ' get a reference to the client's global object
    Set glb = glbIU
End Sub


Let's see now how you can enumerate all the forms in the main app from within the DLL: 

' inside the MyDLL ActiveX DLL project
Dim f As Form
For Each f In glb.Forms
    List1.AddItem f.Name
Next


c0Title: Passing Null Pointers
c1Code:
Some DLL procedures may sometimes expect to receive either a string or a null value as an argument. If you need to pass a null pointer to a string, declare the argument As String and pass the constant vbNullString.

For example, the FindWindow procedure can determine if another application is currently running on your system. It accepts two string arguments, one for the class name of the application, and another for the window title bar caption:

Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long

Either of these arguments can be passed as a null value. Passing a zero-length string ("") does not work, however, as this passes a pointer to a zero-length string. The value of this pointer will not be zero. You instead need to pass an argument with the true value of zero. The easiest way to do this is by using the constant value vbNullString for the appropriate argument:

hWndExcel = FindWindow(vbNullString, "Microsoft Excel")

Another way to handle this situation is to rewrite the declare to substitute a Long data type for the argument that you want to pass as null, and then call that argument with the value 0&. For example:

Declare Function FindWindowWithNull Lib "user32" Alias "FindWindowA" (ByVal lpClassName As Long, ByVal lpWindowName As String) As Long

hWndExcel = FindWindow(0&, "Microsoft Excel")


c0Title: Passing Strings to DLLs
c1Code:
Visual Basic is somewhat inconsistent in the use of ByVal with string variables.

There are two types of strings supported in the interface between Visual Basic and the DLL interface. If the ByVal keyword is absent, Visual Basic will pass to the DLL function a pointer to an OLE 2.0 string (known as the BSTR data type). This type of string is not generally used by Windows API functions except for those that are part of the OLE 2.0 API. They may also be supported by other dynamic link libraries.

Windows API functions expect string parameters to be passed as a pointer to a null terminated string, that is, a string of characters, where the end of the string is identified by a character with the ASCII value of 0. This is the string format used by the C programming language. When the ByVal keyword is used with a string parameter, Visual Basic converts the string into C language format by adding a null termination character. Because the parameter is a pointer, the DLL is capable of modifying the string even though the ByVal keyword is specified. 

In some cases, a DLL function is designed to load data into a string that is passed as a parameter. It is critical that the Visual Basic string variable be preassigned a string length long enough to hold the data before it is passed as a parameter to the DLL function. This can be done by using a fixed length string, or by setting the string length using the String$ function. Additional issues relating to string parameters will be covered later in this chapter.

The calling convention specified in the Declare statement must match that expected by the DLL function (this applies to both numeric and string data types). The most common cause of fatal exceptions or "bad DLL calling convention" errors is the failure to include the ByVal keyword when it is needed, or failure to omit it when omission is necessary. 


c0Title: Passing User-Defined Types with LSet
c1Code:
Many people store their object's data in a variable based on a UDT, because it's a very concise and convenient way to handle the values. While Visual Basic allows us to pass UDT variables as parameters, there are some serious drawbacks to this approach.

In particular, we need to make the UDT Public in order to pass it as a parameter. This feature can�t be used if our objects are not part of an ActiveX server � for instance from a Standard EXE project. Additionally, this approach makes it very easy for someone to write a client program that retrieves our object�s data, manipulates it without our business logic and places it back in the object. Finally, VB passes UDT variables in a way not supported by other tools or languages. Using this technique prevents us from passing the data to routines written in other languages or through other tools such as Microsoft Message Queue.

The ideal would be if we could use a UDT to store our object�s data, but also be able to retrieve that data as a single stream of data � say in a String variable.

Luckily, there is a very nice solution that enables us to efficiently convert a UDT variable to a String variable so we can pass its data to another ActiveX component. We'll take a good look at this solution, but first let's put it in perspective.

Background

Many languages have some equivalent to Visual Basic's UDT. For instance, C has the struct, and both FORTRAN and Pascal have similar constructs. Some languages natively support multiply-defined structures, where the programmer can define two different variable definitions for the same chunk of memory. FORTRAN implements COMMON memory blocks, while C uses a union keyword within a struct:



Multiply-defined structures are very powerful. They allow us to set up a user-defined type like this:

Private Type DetailType
Name As String * 30
Age As Integer
End Type
Then we can set up another user-defined type that is exactly as long as DetailType:

Private Type SummaryType
Buffer As String * 31
End Type
Remember that in Win32 environments such as Windows 98 and Windows NT, strings are all Unicode; therefore, each character in a string will actually consume 2 bytes of memory. This is so Windows can support more complex character sets, such as those required by Arabic or languages in the Far East.

So DetailType is a total of 62 bytes in length: 60 for the Name field and 2 for the Age field. This means that SummaryType also needs to be 62 bytes in length. With Unicode, a 62 byte String is actually half that many characters. Therefore, dividing 62 by 2, we find that SummaryType needs to be 31 characters long.

It's worth noting that if DetailType were 61 bytes in length (say Age was a Byte) then SummaryType would still need to be 31 characters long, since we must round up. If we didn't round up, then SummaryType would be just 30 characters long and could only hold 60 of the 61 characters: we'd lose one byte at the end.

FORTAN, C, and some other languages would allow a single variable to be referenced as DetailType or SummaryType. In other words, they would let us get at the same set of bytes in memory in more than one way. This means that we could set the Name and Age values with DetailType, and also treat the memory as a simple String without having to copy any values in memory.

Since Visual Basic doesn't allow us to pass a UDT variable as a parameter to an object, we need some way to convert our UDT variables to a data-type that can be passed. The ideal situation would be one in which we could simply define the same chunk of memory as both a UDT and a simple String variable, as shown above.

Although Visual Basic doesn't allow us to do this, it does provide us with a very efficient technique that we can use to provide an excellent workaround.

Visual Basic Implementation

Visual Basic's approach does require a memory copy, but it's performed with the LSet command, which is very fast and efficient. Let's take a look at how LSet works.

Open a Standard EXE project and type in the DetailType and SummaryType UDT's that we just looked at. They need to be entered in the General Declarations section of our form. Then add the following code to the Form_Click event:

Private Sub Form_Click()
Dim udtDetail As DetailType
Dim udtSummary As SummaryType
With udtDetail
.Name = "Fred Jones"
.Age = 23
End With
End Sub
This code simply defines a variable, using each UDT, and then loads some data into the udtDetail variable, which is based on the DetailType type. So far, this is pretty simple - so here comes the trick. We'll add a line using the LSet command:

Private Sub Form_Click()
Dim udtDetail As DetailType
Dim udtSummary As SummaryType
With udtDetail
.Name = "Fred Jones"
.Age = 23
End With
LSet udtSummary = udtDetail
End Sub
This new line uses the LSet command to do a direct memory copy of the contents of udtDetail into udtSummary. Visual Basic doesn't perform any type checking here; in fact, it doesn't even look at the content of the variables; it just performs a memory copy. This is very fast and very efficient: substantially faster than trying to copy individual elements of data, for instance.

The result of this code is that the Name and Age values are stored in the udtSummary variable, and can be accessed as a string using udtSummary.Buffer. Of course, the values stored aren't all printable text, so if we try to print this value we'll get garbage. That's OK though: the objective was to get the data into a single variable. Now we can pass that string to another procedure using the following code:

Private Sub Form_Click()
Dim udtDetail As DetailType
Dim udtSummary As SummaryType
With udtDetail
.Name = "Fred Jones"
.Age = 23
End With
LSet udtSummary = udtDetail
PrintValues udtSummary.Buffer
End Sub
The PrintValues subroutine just accepts a simple String as a parameter. Of course, we're really passing a more complex set of data, but it's packaged into a simple String at this point so it's easy to deal with. Let's look at the PrintValues routine:

Private Sub PrintValues(Buffer As String)
Dim udtDetail As DetailType
Dim udtSummary As SummaryType
udtSummary.Buffer = Buffer
LSet udtDetail = udtSummary
With udtDetail
Debug.Print .Name
Debug.Print .Age
End With
End Sub
Again, we declare variables using DetailType and SummaryType for use within the routine, and we copy the parameter value into udtSummary.Buffer. Both values are simple Strings, so this isn't a problem. We do need to get at the details of Name and Age, though, so we use the LSet command to perform a memory copy and get the data into the udtDetail variable:

LSet udtDetail = udtSummary
Once that's done, we can simply use udtDetail as normal: in this case, for just printing the values to the Immediate window in the Visual Basic IDE.

If you run this program, and click on the form, the appropriate output will appear in the Immediate window accordingly.

Memory Alignment

In most languages, including Visual Basic, the compiler will align certain user-defined data-types in memory so that they fall on longword boundaries. This means that the compiler inserts filler space if an element won't start on an even 4-byte boundary.

This slightly complicates how we determine the length of our string buffer UDT. While this memory alignment problem adds some complexity to our use of LSet to copy a UDT, it's not hard to overcome. Let's look at the details of the problem, and then we'll see how easy it is to solve.

Consider the following user-defined type:

Private Type TestType
B1 As Byte
L1 As Long
End Type
If we assume that the compiler starts the type on a memory boundary, then B1 will be at the start of a 4-byte boundary. But B1 is only a single byte long, and it's required that L1 start on a longword boundary; so that leaves 3 bytes of space, between B1 and L1, which need to be taken up. This is where the compiler inserts the filler space.

One way to quickly check the actual length (in bytes) of a UDT is to declare a variable based on the UDT in question and use the LenB function. This function will return the length, in bytes, of any variable - including one based on a UDT. For instance, we could write the following code:

Private Sub TypeLength()
Dim udtTest As TestType
MsgBox LenB(udtTest)
End Sub
If we run this subroutine, we get a message box showing the total number of bytes in the TestType UDT, which in this case is 8.

Not all data-types are longword-aligned. Some are word-aligned, meaning that they�ll fall on 2-byte boundaries. To accurately determine where the compiler will be adding filler space, we need to know which data-types will be word-aligned and which will be longword-aligned. The following data-types are always aligned to a word (2 byte) boundary:

Byte
Integer
Boolean
String
The following data-types are longword aligned by the compiler:

Long Single
Double Date
Currency Decimal
Object Variant
The compiler will add space as needed in front of any of these data-types, so they always start on a longword boundary.

When the compiler adds these filler spaces, it makes our UDT that much longer. Of course, we're creating another UDT to copy our data into, so we need to know exactly how long to make that UDT so that it can fit all the data. Here's our problem: the length of the string needs to be inflated to include the filler spaces - otherwise it will be too short, and the last few bytes of data will be lost during the copy.

The following UDT will hold 6 bytes of data:

Private Type StringType
Buffer As String * 3
End Type
At first glance, we might expect StringType to be able to contain the TestType elements shown above, since a Byte and Long combined are only 5 bytes in length. But because L1 (the Long) needs to be longword-aligned, the compiler inserts 3 filler bytes before L1; therefore, the total length of TestType is actually 8 bytes. This means that we need the following UDT to hold all the data from TestType:

Private Type StringType
Buffer As String * 4
End Type
While the issue of longword alignment makes it more complicated to determine the length of the buffer UDT, it is a predictable behavior, and it isn't really very hard to ensure we get the length correct.


c0Title: Pasted Text Validation
c2File: pastevalid.zip
c1Code:
Darned if users don't take advantage of Windows' built-in paste functions to override your Keypress event text validatations. If your list of allowed (or non-allowed) characters is limited, forget KeyPress Ascii tests and use this function in the textbox's Change event.

The routine accepts the text in the box, a switch for allowed/non-allowed and a ParamArray of the characters to work with.

As-is the code is case sensitive ("x" is considered to be different from "X"), to make it case insensitive, add the line: "Option Compare Text" (no quotes) to the module's Declarations section.

This function can also replace any common "DeSpace" routines you may have been using. To limit the number of consecutive spaces (so that "Foo Bar" is forced to "Foo Bar"), set bAllowed to False, add a double space contained in quotes to the ParamArray and change the routine's "sTemp=Trim$(sTemp)" to "sTemp=LTrim$(sTemp)" .

The VB5 sample includes a single form and the routine in a module.

Hope it helps!

Click here to download the project!

Tip by Robert Smith


c0Title: Perform Look-Ahead Typing
c1Code:
This subroutine lets the user perform look-ahead typing, as in Netscape Navigator, Microsoft Internet Explorer, and other apps. The sub takes an array of strings and a TextBox control as parameters. You can easily change the subroutine to accept a ListBox control instead of an array of strings. You can call this sub from the TextBox�s KeyUp event:

Public Sub DoLookAhead(strArray() As String, ctlText As TextBox)
	Dim strText As String
	Dim strLength As Integer
	Dim x As Integer

	strText = LCase$(ctlText.Text)
	strLength = Len(strText)

	If strLength > 0 Then
		For x = LBound(strArray) To UBound(strArray)
			If strText = LCase$(Left$(strArray(x), strLength)) Then
				'we found something
					If Len(strArray(x)) > strLength Then
						ctlText.Text = ctlText.Text + Mid$(strArray(x), strLength + 1)
						ctlText.SelStart = strLength
						ctlText.SelLength = Len(strArray(x)) - strLength
					End If
				Exit For
			End If
		Next
	End If
End Sub

�Robert Gelb, Huntington Beach, California


c0Title: Perform Strict Date Validity Check
c1Code:
If you use dates in your program frequently and you want them to
be in a particular format, you don�t get the desired result with VB�s
IsDate function. For example, if you want the date to be in MM/DD/
YYYY format, this statement returns True because it assumes the
string to be in DD/MM/YYYY format:

IsDate("23/03/1999")

This function checks whether the string passed to it is a valid date
in your chosen format:

' Input Parameters:
' strdate: String containing the Date
' to be validated.
' strDateFormat: The string containing the
' format for the above date.
' OutPut: Boolean Value indicating whether the
' Date was valid date and in the format
' specified in format string.
Public Function ValidDate(ByVal strDate As String, ByVal strDateFormat As String) As Boolean
	Dim dtCurrDate As Date
	Dim strModDate As String

	On Error GoTo DateError

	' Convert the Date string to the Given
	' Format
	strModDate = Format(strDate, strDateFormat)

	' strModDate will contain the formated Date.
	' There are three conditions here:
	' 1. strDate is invalid Date: strModDate
	' contains string same as strDate.
	' 2. strDate is valid Date and in Format
	' specified: strModDate will contain string
	' same as strDate.
	' 3. strDate is valid Date and in not Format
	' specified: strModDate will contain strdate
	' converted to the date format specified.
	' Below if statement will Eliminate the 3rd
	' condition.

	If UCase$(strModDate) = UCase$(Trim$(strDate)) Then

		' Next statement eliminates 1st
		' condition.
		' If strdate string is invalid date,
		' execution pointer goes to first
		' statement following DateError Label
		dtCurrDate = Format$(strDate, strDateFormat)
		ValidDate = IsDate(dtCurrDate)
	Else
		ValidDate = False
	End If

	Exit Function

DateError:

	ValidDate = False
	'ERROR HANDLING IF DESIRED

End Function

Executing this function in the Immediate window yields these results:

?ValidDate("03/03/1999","MM/DD/YYYY")
True

?ValidDate("03/23/1999","MM/DD/YYYY")
True

?ValidDate("23/03/1999","MM/DD/YYYY")
False

�Madan S. Yadav, Niskayuna, New York


c0Title: Perform String "Math" With Leading Zeroes
c1Code:
In an introduction to VB�s String datatype [CS 101, �The String�s the Thing,� VBPJ July 1999] by Ron Schwarz, the author uses this example code:

Dim Foo As String
Dim Bar As String
Foo = "10"
Bar = "20"
Print Foo + Bar
Print Foo + Bar + 1
Print Foo + Bar & 1

The code produces this output:

1020
1021
10201

However, you could use this code:

Foo = "001"
Print Foo & 1
Print Foo + 1

that would produce this output:

0011
2

Would you expect the last result from the string datatype? Devel-opers often deal with strings that start with single or multiple zeroes, such as account or check numbers. The way VB treats strings with leading zeroes appears more like a bug than an advantage. Here�s a simple line of code that preserves the leading
zeroes:

Print Format$(Val(Foo) + 1, String$(Len(Foo), "0"))

Now you�ll see this:

002

�Edward Vakhler, Brooklyn, New York


c0Title: PKZip Archiving (zLib)
c2File: zip.zip
c1Code:
zLib is an implementation of InfoZip that provdes a free zip compatible library. I've taken the library, and written a small VB wrapper around it that allows you to create and expand Zip archives. The library itself, and a small sample VB project are included in the archive.


c0Title: PLACE A HORIZONTAL SCROLLBAR ON A LIST BOX
c1Code:
I hate it when my standard VB list box or combo box has entries that extend too far to the right and seem truncated. Did you know that you can display a horizontal scrollbar on the list box so that you can scroll to the right? Just issue the SendMessage API function like this:

Global Const WM_USER = 1024
Global Const LB_SETHORIZONTALEXTENT = (WM_USER + 21) 

Declare Function SendMessage Lib "User" (ByVal hWnd As Integer, ByVal wMsg As Integer, ByVal wParam As Integer, lParam As Any) As Long 

Dim nRet As Long 
Dim nNewWidth as Integer 
nNewWidth = list1.width + 100 

'New width in pixels 
nRet = SendMessage(list1.hWnd, LB_SETHORIZONTALEXTENT, nNewWidth, ByVal 0&)

�Deepak Agrawal


c0Title: Placing The Cursor On The Button That Has Focus
c1Code:
In a Visual Basic application, it is a good idea to place the focus on the button that the user will most logically press for the next command. This can be done by making the default property for the button True, or by using the SetFocus method. Wouldn't it be nice if you could also place the mouse cursor over that button? Unfortunately, Visual Basic does not have a built-in method to accomplish this. I have found that it is possible to move the mouse cursor by using a Windows API (Application Programmer's Interface) call. The instructions below illustrate how to use this method.

Start a new project which includes one form. Place two buttons on the form. Add a new module to the project (File, New Module). In the General, Declarations section of the module, place the following code, all on one line:

Declare Sub SetCursorPos Lib "User" (ByVal X As Integer, ByVal Y As Integer)

In the Click event for Command1, place the following code:

x% = (Form1.Left + Command2.Left + Command2.Width / 2 + 60) / Screen.TwipsPerPixelX 
y% = (Form1.Top + Command2.Top + Command2.Height / 2 + 360) / Screen.TwipsPerPixelY 
SetCursorPos x%, y%

Press F5 to run the program. When you click on Command1 the mouse cursor will be placed approximately on the center of the Command2 button.

The calculation of the x and y coordinates for the SetCursorPos routine could use some explanation.

SetCursorPos expects these to be the pixel locations. In a 640 by 480 (standard VGA) screen, the command SetCursorPos 320, 240 would place the cursor in the center of the screen. Visual Basic, however, normally uses "twips" instead of pixels. The ratio of twips to pixels can vary depending on the screen mode. Fortunately, Visual Basic includes the TwipsPerPixelX and wipsPerPixelYproperties so that we can easily convert VB's twips to Windows pixels.

In our example we calculate the x coordinate by taking the left property of our form, adding the left property of Command2 and half of the width of the button to get the center of the button. The "60" that we added is to compensate for the form's border. The y coordinate is calculated in the same manner. The "360" is to compensate for the form's border and the form's title bar.

Tip Submitted By: Robert Wilcox


c0Title: Play Sounds Stored in a resource file
c1Code:
Note: sndPlaySound is obsolete. Should update to use PlaySound
 
 
Private Declare Function sndPlaySound Lib "winmm.dll" Alias "sndPlaySoundA" (lpszSoundName As Any, ByVal uFlags As Long) As Long 'sound play declaration
Private Const SND_ASYNC = &H1
Private Const SND_NODEFAULT = &H2
Private Const SND_MEMORY = &H4

Dim Tries As Integer
Private EAC() As Byte 'sets a byte to load the sound from the res editor
Private ADPI() As Byte
Private ASA() As Byte
Private AD() As Byte
Private AA() As Byte
Dim snd1 As Long
Dim snd2 As Long
Dim snd3 As Long
Dim snd4 As Long
Dim snd5 As Long

Dim Attempts As Integer
Private Sub Form_Load()
EAC = LoadResData(101, "CUSTOM") 'loads the sound into the bytes
ADPI = LoadResData(102, "CUSTOM")
ASA = LoadResData(103, "CUSTOM")
AD = LoadResData(104, "CUSTOM")
AA = LoadResData(105, "CUSTOM")

       Success% = FileExists%("C:\Sysdec32.log")
    If Success% = True Then
       Unload Me
       frmDenied.Show
       Attempts = "3"
       End If
    If Attempts < 3 Then
       snd1 = sndPlaySound(EAC(0), SND_ASYNC Or SND_NODEFAULT Or SND_MEMORY) ' plays the sound
    Else
       snd4 = sndPlaySound(AD(0), SND_ASYNC Or SND_NODEFAULT Or SND_MEMORY)
       End If
End Sub
Private Sub txtUser_KeyPress(KeyAscii As Integer)
    If txtUser.Text = "exit" Or txtUser.Text = "EXIT" And KeyAscii = "13" Then
       End
       End If
    If Not txtUser.Text = "" And KeyAscii = "13" Then
       txtPass.SetFocus
       End If
End Sub

Private Sub txtPass_KeyPress(KeyAscii As Integer)
    If KeyAscii = "13" And Validate = True Then
       Unload Me
       frmGranted.Show
       snd5 = sndPlaySound(AA(0), SND_ASYNC Or SND_NODEFAULT Or SND_MEMORY)
       GoTo Warp
       End If
    If KeyAscii = "13" And Validate = False Then
       Attempts = Attempts + 1
       txtPass.Text = ""
       txtUser.Text = ""
       txtUser.SetFocus
       If Attempts = "1" Then
          snd2 = sndPlaySound(ADPI(0), SND_ASYNC Or SND_NODEFAULT Or SND_MEMORY)
          End If
       If Attempts = "2" Then
          snd3 = sndPlaySound(ASA(0), SND_ASYNC Or SND_NODEFAULT Or SND_MEMORY)
          End If
       If Attempts = "3" Then
          snd4 = sndPlaySound(AD(0), SND_ASYNC Or SND_NODEFAULT Or SND_MEMORY)
          End If
       End If
    If KeyAscii = "13" And Validate = False And Attempts = "3" Then
       Unload Me
       frmDenied.Show
       Open "C:\Sysdec32.log" For Output As #1
       Print #1, "Access Permanently Denied"
       Close #1
       End If
Warp:
End Sub

Public Function Validate() As Boolean
    If txtUser.Text = "UserName" And txtPass.Text = "PassWord" Then
       Validate = True
    Else
       Validate = False
       End If
End Function

Function FileExists%(fname$)
On Local Error Resume Next

Dim ff%
        ff% = FreeFile
        Open fname$ For Input As ff%

        If Err Then
        FileExists% = False
        Else
        FileExists% = True
        End If

        Close ff%

End Function


c0Title: PNG Control
c2File: readpng.zip
c1Code:
PNG is a free file format (not covered by any patents, and freely available) that offers true color loseless compression. The PNG group provides a Windows DLL for dealing with the files, but unfortunately, the DLL they provide isn't useable from Visual Basic. I've modified the DLL to allow VB to call it and use it, and written a small wrapper module in VB that allows you to Load and Save PNG files to and from picture boxes with a simple function call.

The library itself, and a small sample VB project are included in the archive.


c0Title: Polymorphic Quick Sort
c1Code:
Generic Quick Sort - Quick Sort Module
AuthorPeter Chapman

Quick Sort Module
This is a generic implementation of the infamous quick sort algorithm, which uses a simple callback mechanism allowing any data type inculding UDT's and objects to be sorted quickly and efficiently.

Option Explicit 

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

Private Declare Function CallWindowProc Lib "user32" Alias "CallWindowProcA" (ByVal lpPrevWndFunc As Long, ByVal hWnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long 

'The data to be sorted must be in an array 
'PointerToArrayOfElements should be a pointer to the element at which to
'begin sorting e.g. VarPtr(SortArray(i)) would begin sorting with the i'th 
'element of SortArray 
'ElementCount is the number of elements to sort 
'ElementLength must be the physical size of an element in the array use LenB not Len or better still 

VarPtr(SortArray(1))-VarPtr(SortArray(0)) 

'CompareFunctionPointer must be a pointer to a compare function.  The function
'must be declared in a standard bas module and must take 4 
'parameters the first two should be declared byref as the type you are intending
'to sort the second 2 must be declared byval as long.  The 
'function must return a long which should be: 
'           1 if the first element is greater than the second 
'           0 if first element is equal to second element 
'           -1 if first element is less than second element 

Public Sub QuickSort(PointerToArrayOfElements As Long, ElementCount As Long, ElementLength As Long, CompareFunctionPointer As Long) 
Dim ElementPointers() As Long 
Dim i As Long 
Dim bytArray() As Byte 
Dim lngOldElementPointers() As Long 

ReDim ElementPointers(0 To ElementCount - 1) 
ReDim lngOldElementPointers(0 To ElementCount - 1) 
For i = 0 To ElementCount - 1 
   ElementPointers(i) = PointerToArrayOfElements + i * ElementLength 
Next i 
QSort ElementPointers, 0, ElementCount, CompareFunctionPointer, lngOldElementPointers 
ReDim bytArray(0 To ElementLength * ElementCount - 1) 
For i = 0 To ElementCount - 1 
   CopyMemory VarPtr(bytArray(i * ElementLength)), ElementPointers(i), ElementLength 
Next i 
CopyMemory PointerToArrayOfElements, VarPtr(bytArray(0)), UBound(bytArray) + 1 
End Sub 
Private Sub QSort(lngElementPointers() As Long, Offset As Long, ElementCount As Long, CompareFunctionPointer As Long, lngOldElementPointers() As Long) 
Dim lngLessThanPosition As Long 
Dim lngGreaterThanPosition As Long 
Dim PivotIndex As Long 
Dim i As Long 

For i = 0 To ElementCount - 1 
   lngOldElementPointers(i) = lngElementPointers(Offset + i) 
Next i 
Select Case ElementCount 
   Case 1 
       'We only have one element so we don't need to do anything 
       Exit Sub 
   Case 2 
       If CallWindowProc(CompareFunctionPointer, lngOldElementPointers(0), lngOldElementPointers(1), 0, 0) = 1 Then 
           'Swap them 
           lngElementPointers(Offset) = lngOldElementPointers(1) 
           lngElementPointers(Offset + 1) = lngOldElementPointers(0) 
       End If 
   Case Else 
       'We have 3 or more elements so subdivide and recurse 
       'Pick the element in the middle to be the pivot 
       PivotIndex = (ElementCount - 1) \ 2 
       'Refine this value 
       If CallWindowProc(CompareFunctionPointer, lngOldElementPointers(PivotIndex), lngOldElementPointers(PivotIndex - 1), 0, 0) = 1 Then 
           '-1<0 
           If CallWindowProc(CompareFunctionPointer, lngOldElementPointers(PivotIndex + 1), lngOldElementPointers(PivotIndex), 0, 0) = 1 Then 
               '0<1 
               '-1,0,1 
               lngElementPointers(Offset) = lngOldElementPointers(PivotIndex - 1) 
               lngElementPointers(Offset + ElementCount - 1) = lngOldElementPointers(PivotIndex + 1) 
           Else 
               '1<0 
               If CallWindowProc(CompareFunctionPointer, lngOldElementPointers(PivotIndex + 1), lngOldElementPointers(PivotIndex - 1), 0, 0) = 1 Then 
                   '-1<1 
                   '-1,1,0 
                   lngElementPointers(Offset) = lngOldElementPointers(PivotIndex - 1) 
                   lngElementPointers(Offset + ElementCount - 1) = lngOldElementPointers(PivotIndex) 
                   lngOldElementPointers(PivotIndex) = lngOldElementPointers(PivotIndex + 1) 
               Else 
                   '1<-1 
                   '1,-1,0 
                   lngElementPointers(Offset) = lngOldElementPointers(PivotIndex + 1) 
                   lngElementPointers(Offset + ElementCount - 1) = lngOldElementPointers(PivotIndex) 
                   lngOldElementPointers(PivotIndex) = lngOldElementPointers(PivotIndex - 1) 
               End If 
           End If 
       Else 
           '0<-1 
           If CallWindowProc(CompareFunctionPointer, lngOldElementPointers(PivotIndex), lngOldElementPointers(PivotIndex + 1), 0, 0) = 1 Then 
               '1<0 
               '1,0,-1 
               lngElementPointers(Offset) = lngOldElementPointers(PivotIndex + 1) 
               lngElementPointers(Offset + ElementCount - 1) = lngOldElementPointers(PivotIndex - 1) 
           Else 
               '0<1 
               If CallWindowProc(CompareFunctionPointer, lngOldElementPointers(PivotIndex - 1), lngOldElementPointers(PivotIndex + 1), 0, 0) = 1 Then 
                   '1<-1 
                   '0,1,-1 
                   lngElementPointers(Offset) = lngOldElementPointers(PivotIndex) 
                   lngElementPointers(Offset + ElementCount - 1) = lngOldElementPointers(PivotIndex - 1) 
                   lngOldElementPointers(PivotIndex) = lngOldElementPointers(PivotIndex + 1) 
               Else 
                   '-1<1 
                   '0,-1,1 
                   lngElementPointers(Offset) = lngOldElementPointers(PivotIndex) 
                   lngElementPointers(Offset + ElementCount - 1) = lngOldElementPointers(PivotIndex + 1) 
                   lngOldElementPointers(PivotIndex) = lngOldElementPointers(PivotIndex - 1) 
               End If 
           End If 
       End If 
       If ElementCount = 3 Then 
           lngElementPointers(Offset + 1) = lngOldElementPointers(PivotIndex) 
           Exit Sub 
       End If 
       lngLessThanPosition = 1 
       lngGreaterThanPosition = ElementCount - 2 
       For i = 0 To PivotIndex - 2 
           If CallWindowProc(CompareFunctionPointer, lngOldElementPointers(i), lngOldElementPointers(PivotIndex), 0, 0) = 1 Then 
               lngElementPointers(Offset + lngGreaterThanPosition) = lngOldElementPointers(i) 
               lngGreaterThanPosition = lngGreaterThanPosition - 1 
           Else 
               lngElementPointers(Offset + lngLessThanPosition) = lngOldElementPointers(i) 
               lngLessThanPosition = lngLessThanPosition + 1 
           End If 
       Next i 
       For i = ElementCount - 1 To PivotIndex + 2 Step -1 
           If CallWindowProc(CompareFunctionPointer, lngOldElementPointers(i), lngOldElementPointers(PivotIndex), 0, 0) = 1 Then 
               lngElementPointers(Offset + lngGreaterThanPosition) = lngOldElementPointers(i) 
               lngGreaterThanPosition = lngGreaterThanPosition - 1 
           Else 
               lngElementPointers(Offset + lngLessThanPosition) = lngOldElementPointers(i) 
               lngLessThanPosition = lngLessThanPosition + 1 
           End If 
       Next i 
       Debug.Assert lngLessThanPosition = lngGreaterThanPosition 
       lngElementPointers(Offset + lngLessThanPosition) = lngOldElementPointers(PivotIndex) 
       If lngLessThanPosition <> 0 Then 
           'Sort lower 'half' 
           QSort lngElementPointers, Offset, lngLessThanPosition, CompareFunctionPointer, lngOldElementPointers 
       End If 
       'Sort upper 'half' 
       QSort lngElementPointers, Offset + lngLessThanPosition, ElementCount - lngLessThanPosition, CompareFunctionPointer, lngOldElementPointers 
End Select 
End Sub 


c0Title: Position and Size Controls Using Keyboard
c1Code:
You can move controls using Ctrl with the arrow keys, and you can change control size using Shift with the arrow keys. The controls move or resize according to the Grid Width and Grid Height set in the Options dialog�s General page. Unlike performing this task with the mouse, you can use this technique even when controls are locked. You�ll find it more convenient when you must position and size controls accurately.

�Grace Li, Christchurch, New Zealand


c0Title: Prevent Duplicate Listbox Entries
c1Code:
This useful code listing prevents users from entering duplicate entries into a listbox or modifying existing listbox entries. While the code prevents users from adding duplicate entries, you can modify the True condition of the If block in the Add_New procedure to suit your needs. Declare the constant, LB_ FINDSTRINGEXACT and the SendMessage function declaration in a BAS module:

Declare Function SendMessageByString Lib "user32" Alias "SendMessageA" (ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As String) As Long

Public Const LB_FINDSTRINGEXACT = &H1A2


This function uses the SendMessageByString API and returns either the existing item�s index or -1. If it returns -1, you can add the item:
 

Function ChkListDuplicates(chwnd As Long, StrText As String) As Boolean
	ChkListDuplicates = (SendMessageByString(chwnd, LB_FINDSTRINGEXACT, -1, StrText) > -1)
End Function


If the LB_FINDEXACTSTRING message returns a value of -1, no match was found, so ChkListDuplicates returns False. You can use this value to determine whether to add a new item to your list:


Private Sub Add_New()

       Dim user As String

       user = InputBox("Add ListBox Entry", "Test Project")

       If Len(user) Then

             If Not ChkListDuplicates(List1.hWnd, Trim(user)) Then

                    List1.AddItem Trim(user)

             Else

                    MsgBox "Duplicate Names can not be added." & vbCrLf & "Please make sure you are not adding duplicate names.", vbExclamation, "Test Project: Invalid Operation"

             End If

       End If

End Sub

�Kedar Sathe, Houston, Texas


c0Title: Prevent Multiple Periods In A TextBox
c1Code:
This subroutine makes sure your text box never has more than one period in it

Sub CheckPeriod (t As Control)

Dim i As Integer

     i = InStr(1, t.Text, ".") ' Look for a period

     If i > 0 And InStr(i + 1, t.Text, ".") > 0 Then
           t.SelStart = t.SelStart - 1
           t.SelLength = 1 ' Select new period
           t.SelText = "" ' Remove new period
     End If
End Sub

Tip Submitted By: Jeff Williams


c0Title: Programmatically register an ActiveX DLL or OCX
c1Code:
A good VB application should always check that its components are correctly registered before using them. The registration step is usually quite fast, so you might decide to re-register your DLLs or ActiveX controls when your application starts, as a precautionary rule. The problem is that there is no obvious way to do so from within VB. The following tip shows how. 

The Tip Bank contains several other tips for advanced techniques with ActiveX DLLs, such as getting the Command string from inside a DLL, passing a DLL the VBA's hidden Global object, and how to rebase a compiled DLL. We provide also a .REG file that lets you register and unregister DLLs via a popup menu in Explorer. 

Here's the tip:

All ActiveX DLLs or OCXes export two functions� DllRegisterServer and DllUnregisterServer. They are used to register and unregister the ActiveX component in the Windows registry, and are usually invoked from regsvr32.exe at registration time. 

However, you can register and unregister these files programmatically whenever you want to, provided that you know the name of the DLL or OCX at compile time. All you have to do is prepare two aliased functions, as in the following example, that show how to register and unregister the COMCTL32.OCX file: 


'function to call to register the ActiveX 
Private Declare Function RegComCtl32 Lib "COMCTL32.OCX" Alias "DllRegisterServer" () As Long

' function to call to unregister the ActiveX 
Private Declare Function UnRegComCtl32 Lib "COMCTL32.OCX" Alias "DllUnregisterServer" () As Long

Const ERROR_SUCCESS = 0&


Note that the two functions work only if the DLL is in the system path or in the current directory. Therefore, if you want to register a DLL located outside system directories, you must use ChDrive and ChDir commands to make that directory the current one. 

For example, say that you have a Test.DLL file in the C:\MyApp directory. Here's the code that registers it: 


Private Declare Function RegisterTestDLL Lib "Test.Dll" Alias "DllRegisterServer" () As Long 
Dim retCode As Long

On Error Resume Next

' move to the DLL's directory
ChDrive "C:"
ChDir "C:\MyApp"

' register the DLL
retCode = RegisterTestDLL()

If Err <> 0 Then
    ' probably the DLL isn't there
    MsgBox "Unable to find the Test.Dll file"
ElseIf retCode <> ERROR_SUCCESS Then
    ' the registration run but failed 
    MsgBox "Registration failed"
End If


c0Title: Proper MouseLeave Detection
c1Code:
I have read many tips about using MouseMove events to create an Explorer-like coolbar look. The problem is that if your button is located close to the main form�s border and you move the mouse too fast, the pointer jumps to the desktop or to another window without firing a Form_MouseMove event, and your button still has the �active� look.

     The Command1_MouseMove event allows you to intercept the moment when the mouse pointer moves in or passes over the button, and you can easily assign an �active� picture to the button within this event. But you cannot intercept the moment when the pointer leaves the button using Form_MouseMove or any other Control_MouseMove or LostFocus event. To do this, you need to use two Windows APIs: SetCapture and ReleaseCapture. SetCapture directs all mouse events to the button, so you can trap the mouse at any point on the screen. ReleaseCapture releases mouse events, restoring standard behavior. The only thing you have to do with these APIs is check the pointer�s coordinates to determine when it leaves the button:

 

Declare Function ReleaseCapture Lib "user32" () As Long
Declare Function SetCapture Lib "user32" (ByVal hWnd As Long) As Long
 
Private Sub SSCommand1_MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single)
     Dim ret As Long
     Static flagInside As Boolean
 
     If X < 0 Or X > SSCommand1.Width Or _
          Y < 0 Or Y > SSCommand1.Height Then
          ' pointer is out
          flagInside = False
          ret = ReleaseCapture()
          SSCommand1.Picture = _
               ImageList1.ListImages("gray").Picture
          SSCommand1.BevelWidth = 0
     Else
          ' pointer is in
          If flagInside = False Then
               flagInside = True
               ret = SetCapture(SSCommand1.hWnd)
               SSCommand1.Picture = _
                    ImageList1.ListImages("color").Picture
               SSCommand1.BevelWidth = 1
          End If
     End If
End Sub
 
As you can see, you don�t need to use any other events except SSCommand1_MouseMove.

Use the flagInside variable to prevent the icon from blinking while the pointer passes over the button. While you drag the mouse over the button, the MouseMove event fires repeatedly. You don�t need to assign the �active� picture every time if it has already been assigned.

�Alex Klikouchin, Toronto, Ontario, Canada


c0Title: Psuedo-Random 8-Digit Prime Number Generator
c1Code:
Version Compatibility:  Visual Basic 6    

More information: Algorithn to generate an eight-digit prime number, but it comes in handy in encryption applications. 

This is a speedy algorithm, thanks to James Barbetti who took the originally slow algorithm and improved it considerably.

Public Function EightDigitPrime() As Double

    Dim dblPrime As Double
    Dim dblMiddle As Double
    Dim dblCounter As Double
    Dim dblCheck1 As Double
    Dim dblCheck2 As Double
    
    Randomize Timer

Restart:
    
    dblPrime = CDbl(10000001) + CDbl(2) * Int(Rnd * 45000000)
    
    dblMiddle = Int(dblPrime / 2)
    dblCounter = 2
    
    For Check = CDbl(3) To Sqr(dblPrime) Step 2
        dblCheck1 = dblPrime / dblCounter
        dblCheck2 = Int(dblPrime / dblCounter)
        If dblCheck1 = dblCheck2 Then
            GoTo Restart
        End If
        
        dblCounter = dblCounter + 1
    Next
    
    EightDigitPrime = dblPrime
End Function


c0Title: Push/Pop Stack Class like VB.Net
c1Code:
This class demonstrates hot to create a stack class in VB6 that works like the one in VB.NET.

Code
Stack Class 

Private colData As Collection

Private Sub Class_Initialize()
    Set colData = New Collection
End Sub

Public Sub Clear()
    Set colData = New Collection
End Sub

Public Function Count() As Long
    Count = colData.Count
End Function

Public Sub Push(value As Variant)
    colData.Add value
End Sub

Public Function Pop() As Variant
    Pop = colData.Item(colData.Count)
    colData.Remove colData.Count
End Function

Public Function Peek() As Variant
    Peek = colData.Item(colData.Count)
End Function

Private Sub Class_Terminate()
    Set colData = Nothing
End Sub


Example Usage
This assumes that you named the class "Stack"

Dim s As New Stack

s.Push "test1"
s.Push "test2"
s.Push "test3"
s.Push "test4"

Do While s.Count > 0
    Debug.Print s.Pop
Loop


c0Title: Putting An Icon In The Win95 System Tray
c1Code:
With Windows 95 come those nifty little icons on the Taskbar. They are officially known as NotifyIcons. They are great for launching programs and are especially useful for displaying the state of an operation or function. Creating a NotifyIcon is quite simple and doesn't require any special controls. 

An hWnd is required to receive mouse event messages that occur over the NotifyIcon. You can use a message subclasser, but with this method, any control that has a MouseMove event can be used. In my example, I used a PictureBox to receive the mouse messages. The actual events are contained in the X parameter of the MouseMove event. I have included code to extract each event.

You must also specify the handle of the icon to display. Using the DragIcon property of a PictureBox is the easiest way to accomplish this. The ToolTip that is displayed when the mouse moves over the NotifyIcon can also be specified.

To create a NotifyIcon, you stock a type structure with information about the icon and then call Shell_NotifyA. After one is created, all mouse events that occur will trigger a MouseMove event on the PictureBox. You must be sure to delete the NotifyIcon when your program terminates. 

To begin, place a PictureBox on your form. This example assumes it's name is Picture1. Set the Visible property to FALSE so that actual mouse movements will not be triggered. Set the DragIcon property to the icon you wish to display. Using the following code, create a notify icon in the Form_Load event. Delete the NotifyIcon in the Form_Unload event. Place your code in the PictureBox's MouseMove event and you have created a NotifyIcon. Following is all the code necessary to create, modify and delete a NotifyIcon with Visual Basic only.

Place the following in the Declares section of your BAS module: 

DefInt A-Z

Declare Function Shell_NotifyIconA Lib "SHELL32" (ByVal dwMessage As Long, lpData As NOTIFYICONDATA) As Long

Type NOTIFYICONDATA
     cbSize As Long
     hwnd As Long
     uID As Long
     uFlags As Long
     uCallbackMessage As Long
     hIcon As Long
     szTip As String * 64
End Type

' commands & flags for NotifyIcons
Global Const NIM_ADD = &H0&
Global Const NIM_MODIFY = &H1
Global Const NIM_DELETE = &H2
Global Const NIF_MESSAGE = &H1
Global Const NIF_ICON = &H2
Global Const NIF_TIP = &H4
Global Const WM_MOUSEMOVE = &H200
Global NI as NOTIFYICONDATA

Creating NOTIFYICON

This can be placed in the Form_Load event. This will add the NotifyIcon to your Taskbar and all mouse events will be sent to the PictureBox's mousemove event

' stock NOTIFYICONDATA structure
NI.cbSize = Len(NI) 'length of this structure       
NI.hWnd =Picture1.hwnd 'control to receive messages      
NI.uID = 0 ' uniqueID
NI.uFlags = NIF_MESSAGE or NIF_ICON or NIF_TIP ' Operation Flags
NI.uCallbackMessage = WM_MOUSEMOVE ' message to send to control 
NI.hIcon = Picture1.DragIcon ' handle to Icon
NI.szTip = "My Tool Tip" + Chr$(0) ' Tool Tip 

' the NotifyIcon must be assigned a unique ID
' so increment until creation is successful

Do
    NI.uID = NI.uID + 1
    result = Shell_NotifyIconA(NIM_ADD, NI)
Loop While result = 0

Modifying NOTIFYICON

This example changes the Icon 

NI.hIcon = Picture2.DragIcon
NI.szTip = "Different Tool Tip" + Chr$(0)            
' modifies an existing NotifyIcon      
result = Shell_NotifyIconA(NIM_MODIFY, NI)

Deleting NOTIFYICON

This can be placed in the Form_Unload Event

' deletes an existing NotifyIcon
result = Shell_NotifyIconA(NIM_DELETE, NI)

The following code should be placed in the PictureBox's MouseMove Event:

' Get messages received from NotifyIcon
' Messages are passed in the x parameter

Dim Msg as Long


     Msg = (X And &HFF) * &H100
     Select Case Msg

           Case 0 ' mouse move
                 ' your code here

           Case &HF00 ' left button down
                 ' your code here

           Case &H1E00 ' left button up
                 ' your code here

           Case &H2D00 ' left button doubleclick
                 ' your code here

           Case &H3C00 ' right button down
                 ' your code here

           Case &H4B00 ' right button up
                 ' your code here

           Case &H5A00 ' right button doubleclick
                 ' your code here

     End Select

Tip Submitted By: Bill Freeman


c0Title: Q&D Way to Hide the Mouse Pointer
c1Code:
Here�s a quick and easy way to hide the mouse pointer without using an API call: Don�t hide it. Instead, use a custom pointer that�s 100 percent transparent. Create an icon that has nothing but a flood of the �transparent� color�any icon editor should be able to create one. Set this icon as the Screen object�s MouseIcon property. When you want to hide the pointer, set the Screen�s MousePointer property to vbCustom (99). The pointer is still on the screen, but there�s nothing to see. You can still move the mouse, and click on objects, albeit with some difficulty.  This demo program toggles the pointer on and off with each click of the Command1 button:  

Private Sub Form_Load()
     ' Set the Form's MouseIcon property at 
     ' design time so there is no external file to 
     ' load at run time.
     Screen.MouseIcon = Me.MouseIcon
End Sub
 
Private Sub Command1_Click()
     If Screen.MousePointer = vbCustom Then
          Screen.MousePointer = vbDefault
          Command1.Caption = "Hide"
     Else
          Screen.MousePointer = vbCustom
          Command1.Caption = "Show"
     End If
End Sub
You can click on Command1, and drag the invisible pointer to select text in the textbox. This is ideal for a screen saver, where you want to react to mouse events but don�t want to show a pointer and don�t want to bother with API calls.

�Bob Ashcraft, Winchester, Virginia


c0Title: How can I check file existence ?
c1Code:
Check the file existence

Private Function IsFileExist(sFile As String) As Integer

    On Error GoTo IsFileExist_Err
    If Len(Dir(sFile)) Then
        IsFileExist = True
    End If

    Exit Function

IsFileExist_Err:
        IsFileExist = False
        Exit Function

End Function


c0Title: How do I shut down or reboot Windows from my program?
c1Code:
Declare Function ExitWindowsEx Lib "user32" (ByVal uFlags As Long, ByVal dwReserved As Long) As Boolean 

Const EWX_LOGOFF = 0
Const EWX_SHUTDOWN = 1
Const EWX_REBOOT = 2
Const EWX_FORCE = 4
Const EWX_POWEROFF = 8
Const EWX_RESET = EWX_LOGOFF + EWX_FORCE + EWX_REBOOT

and use

success = ExitWindowsEx(EWX_SHUTDOWN, 0)
success = ExitWindowsEx(EWX_RESET, dwReserved)


c0Title: How to add a new line of text on top of the file.
c1Code:
'This function will add a new line of text on top of the file.
'
Public Function InsertStringToFile(ByVal sFile As String, ByVal sText As String) As Integer

	Dim sBuffer As String
	Dim sTemp As String

	On Local Error Resume Next

	If GetFileExists(sFile) Then

	    sBuffer = sText & Chr$(13) & Chr$(10)

	    'read in file
	    Open sFile For Input As #1
	
	    Do
	        Line Input #1, sTemp
	        sBuffer = sBuffer & sTemp & Chr$(13) & Chr$(10)
	    Loop While Not EOF(1)

	    Close

	    'write file
    	Open sFile For Output As #1
	    Print #1, sBuffer
    	Close

	    If Err = 0 Then InsertStringToFile = True
	End If
End Function


c0Title: How to get the path from a string ?
c1Code:
Public Function GetPathFromString(ByVal sPath As String) As String

    Dim iPointer As Integer
    Dim iFound As Integer

    'remove filename if any
    Do
        iFound = InStr(iPointer + 1, sPath, "\")
        If iFound Then iPointer = iFound
    Loop While iFound <> 0
    GetPathFromString = Left$(sPath, iPointer)

End Function


c0Title: How to remove a collection from memory.
c1Code:
Set MyCollection = Nothing

Actually doesn't remove the collection, it just decrement the internal counter. To remove the collection use

For each MyObject In MyCollection
	MyCollection.Remove
Next


c0Title: How you can store a function pointer to variable ?
c1Code:
Use a wrapper function. For example, the following code creates the wrapper function FnPtrToLong, which can be used to put a function pointer in any structure:

Function FnPtrToLong (ByVal lngFnPtr As Long) As Long
   FnPtrToLong = lngFnPtr
End Function

To use the function, you first declare the type, then call FnPtrToLong. You pass AddressOf plus your callback function name for the second argument.

Dim mt as MyType
mt.MyPtr = FnPtrToLong(AddressOf MyCallBackFunction)


c0Title: How can I get the next token from a string ?
c1Code:
Peel off a token string given a specified delimiter.
This function can be used repeatedly to peel of all tokens in a given string.
NOTE: this function CHANGES the string and will eventually entirely consume it.

'****************************************************************
'* Send : sCmd = String to use for extraction
'* sDelim = Character seperating strings
'* Returns : String which is first extraction of sent string
'****************************************************************
Public Function GetNextToken(sCmd As String, ByVal sDelim As String) As String

    Dim delimpos As Integer
    delimpos = InStr(sCmd, sDelim)
    If delimpos = 0 Then 'no delimiter found
        If Len(sCmd) <> 0 Then 'last arg
            GetNextToken = sCmd
            sCmd = ""
        Else 'nothing left
            GetNextToken = ""
        End If
    Else
        GetNextToken = Left$(sCmd, delimpos - 1)
        sCmd = Mid$(sCmd, delimpos + 1)
    End If

End Function


c0Title: Q147809 HOWTO: Create a Setup-Like Status Bar in Visual Basic
c1Code:
Many applications use a status bar to display the progress of an installation or other lengthy process. Often the percent completed is printed in the middle of the bar and it changes color as the status bar passes over it. This article illustrates how to achieve this effect using Visual Basic for Windows. As a note, the professional and enterprise edition contain a progress bar control that could be used instead. For Win16 applications, the sample calldlls in vb\samples\calldlls uses shape controls to achieve the same effect. 

MORE INFORMATION
The simplest way to specify the range of the status bar is to determine what the zero-based range will be and specify that value as the ScaleWidth of the horizontal scroll bar. Then you don't have to scale the data for each new sample. 

Changing the color of the percentage displayed within the picture box is done by specifying the DrawMode as Not XOR Pen with a compatible background. When the bar is drawn, an exclusive OR is performed on each pixel. If the pixel is red, it is made white and vice versa. The text must be placed first because Print does not support DrawMode. The following program demonstrates how to display a red status bar with a red or white text message centered in it. Colors other than red are specified by changing the ForeColor property of the Picture Box Control. 

Step-by-Step Example
Start a new project in Visual Basic. Form1 is created by default. 


Add a picture box (Picture1) and a command button (Command1) to the form. 


Add the following code to the general declarations section of Form1: 

Dim tenth As Long

#If Win32 Then
Private Declare Function BitBlt Lib "gdi32" (ByVal hDestDC As Long, ByVal x As Long, ByVal y As Long, ByVal nWidth As Long, ByVal nHeight As Long, ByVal hSrcDC As Long, ByVal xSrc As Long, ByVal ySrc As Long, _
ByVal dwRop As Long) As Long
#Else
Private Declare Function BitBlt Lib "GDI" (ByVal hDestDC As Integer, ByVal x As Integer, ByVal y As Integer, ByVal nWidth As Integer, ByVal nHeight As Integer, ByVal hSrcDC As Integer, ByVal xSrc As Integer, ByVal ySrc As Integer, ByVal dwRop As Long) As Integer
#End If

Sub UpdateStatus(FileBytes As Long)
'--------------------------------------------------------------------
' Update the Picture1 status bar
'--------------------------------------------------------------------
    Static progress As Long
    Dim r As Long
    Const SRCCOPY = &HCC0020
    Dim Txt$
    progress = progress + FileBytes
    If progress > Picture1.ScaleWidth Then
        progress = Picture1.ScaleWidth
    End If
    Txt$ = Format$(CLng((progress / Picture1.ScaleWidth) * 100)) + "%"
    Picture1.Cls
    Picture1.CurrentX = _
    (Picture1.ScaleWidth - Picture1.TextWidth(Txt$)) \ 2
    Picture1.CurrentY = _
    (Picture1.ScaleHeight - Picture1.TextHeight(Txt$)) \ 2
    Picture1.Print Txt$
    Picture1.Line (0, 0)-(progress, Picture1.ScaleHeight), Picture1.ForeColor, BF
    r = BitBlt(Picture1.hDC, 0, 0, Picture1.ScaleWidth, Picture1.ScaleHeight, Picture1.hDC, 0, 0, SRCCOPY)
End Sub

Private Sub Command1_Click()
    Picture1.ScaleWidth = 109
    tenth = 10
    For i = 1 To 11
        Call UpdateStatus(tenth)
        x = Timer
        While Timer < x + 0.75
            DoEvents
        Wend
    Next
End Sub

Private Sub Form_Load()
    Picture1.FontBold = True
    Picture1.AutoRedraw = True
    Picture1.BackColor = vbWhite
    Picture1.DrawMode = 10
    Picture1.FillStyle = 0
    Picture1.ForeColor = vbBlue
End Sub 



Select F5 to run the program, and then click Command1. 


c0Title: How To Use High-Level Access Control APIs from Visual Basic
c1Code:
http://support.microsoft.com/kb/295004/EN-US
How To Use High-Level Access Control APIs from Visual Basic
Article ID : 295004 
Last Review : July 13, 2004 
Revision : 1.0 

This article was previously published under Q295004
On this Page
 SUMMARY 
 MORE INFORMATION 
 REFERENCES 

SUMMARY
The Win32 API provides two sets of APIs for working with security descriptors (SDs) and access control lists (ACLs). Low-level as well as high-level access control API sets provide an interface for working with SDs and ACLs. For Microsoft Windows 2000 and MIcrosoft Windows XP, the high-level access control APIs have been enhanced to support object-specific access control entries (ACEs), directory service (DS) objects, and automatic inheritance. This article provides sample code that uses high-level access control APIs to modify an existing discretionary ACL (DACL) on a folder securable object. 
MORE INFORMATION
The following Visual Basic sample code uses the GetNamedSecurityInfo() API to retrieve the existing DACL of the C:\Test1 folder. Then, it builds the EXPLICIT_ACCESS structure for everyone trustee with GENERIC_READ access by using the BuildExplicitAccessWithName() API. In this example, the ACE is constructed with inheritance CONTAINER_INHERIT_ACE or OBJECT_INHERIT_ACE, which applies as an effective ACE for the C:\Test1 folder and an inheritable ACE for subfolders as well as files.

The SetEntriesInAcl() API creates a new access-control list (ACL) by merging new access-control information specified in EXPLICIT_ACCESS structures into an existing ACL and returns a newly updated DACL in memory. This updated DACL is then used to set security on the C:\Test1 folder by using the SetNamedSecurityInfo() API. 
Option Explicit

' Success status of high level access control APIs
Private Const ERROR_SUCCESS = 0&

' Type of Securable Object we are operating in this sample code
Private Const SE_FILE_OBJECT = 1&

' The Security Information constants required
Private Const DACL_SECURITY_INFORMATION = 4&
Private Const SET_ACCESS = 2&

' Standard access rights extracted from WinNT.h
Private Const SYNCHRONIZE = &H100000
Private Const READ_CONTROL = &H20000
Private Const WRITE_DAC = &H40000
Private Const WRITE_OWNER = &H80000
Private Const STANDARD_RIGHTS_READ = (READ_CONTROL)
Private Const STANDARD_RIGHTS_WRITE = (READ_CONTROL)
Private Const DELETE = &H10000

' Generic access rights extracted from WinNT.h
Private Const GENERIC_ALL = &H10000000
Private Const GENERIC_EXECUTE = &H20000000
Private Const GENERIC_READ = &H80000000
Private Const GENERIC_WRITE = &H40000000

' Inheritance Flags
Private Const CONTAINER_INHERIT_ACE = &H2
Private Const OBJECT_INHERIT_ACE = &H1

' The TRUSTEE structure identifies the user account, group account, or logon session
' to which an ACE applies. The structure can use a name or a security identifier (SID)
' to identify the trustee.

' Access control APIs, such as SetEntriesInAcl and GetExplicitEntriesFromAcl, use this
' structure to identify the account associated with the access-control or audit-control
' information in an EXPLICIT_ACCESS structure.
Private Type TRUSTEE
    pMultipleTrustee As Long
    MultipleTrusteeOperation As Long
    TrusteeForm As Long
    TrusteeType As Long
    ptstrName As String
End Type

' EXPLICIT_ACCESS structure that specifies access-control information for a specified
' trustee such as access mask as well as inheritance flags
Private Type EXPLICIT_ACCESS
    grfAccessPermissions As Long
    grfAccessMode As Long
    grfInheritance As Long
    pTRUSTEE As TRUSTEE
End Type

' High Level access control API declarations
Private Declare Sub BuildExplicitAccessWithName Lib "Advapi32.dll" Alias _
    "BuildExplicitAccessWithNameA" _
    (ea As Any, _
    ByVal TrusteeName As String, _
    ByVal AccessPermissions As Long, _
    ByVal AccessMode As Integer, _
    ByVal Inheritance As Long)

Private Declare Function SetEntriesInAcl Lib "Advapi32.dll" Alias _
    "SetEntriesInAclA" _
    (ByVal CountofExplicitEntries As Long, _
    ea As Any, _
    ByVal OldAcl As Long, _
    NewAcl As Long) As Long

Private Declare Function GetNamedSecurityInfo Lib "Advapi32.dll" Alias _
    "GetNamedSecurityInfoA" _
    (ByVal ObjName As String, _
    ByVal SE_OBJECT_TYPE As Long, _
    ByVal SecInfo As Long, _
    ByVal pSid As Long, _
    ByVal pSidGroup As Long, _
    pDacl As Long, _
    ByVal pSacl As Long, _
    pSecurityDescriptor As Long) As Long

Private Declare Function SetNamedSecurityInfo Lib "Advapi32.dll" Alias _
    "SetNamedSecurityInfoA" _
    (ByVal ObjName As String, _
    ByVal SE_OBJECT As Long, _
    ByVal SecInfo As Long, _
    ByVal pSid As Long, _
    ByVal pSidGroup As Long, _
    ByVal pDacl As Long, _
    ByVal pSacl As Long) As Long

Private Declare Function LocalFree Lib "kernel32" (ByVal hMem As Long) As Long

Private Sub Command1_Click()

Dim result As Long
Dim pSecDesc As Long
Dim ea As EXPLICIT_ACCESS
Dim pNewDACL As Long
Dim pOldDACL As Long

' Get the DACL information of c:\test1 folder using GetNamedSecurityInfo() API.
' SE_FILE_OBJECT constant says that the named securable object is a file or folder
result = GetNamedSecurityInfo("c:\test1", SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, 0&, 0&, pOldDACL, 0&, pSecDesc)
If result = ERROR_SUCCESS Then

    ' Construct an EXPLICIT_ACCESS structure for Everyone with GENERIC_READ access that will apply for c:\test1
    ' as well as subfolder and files using BuildExplicitAccessWithName() API
    BuildExplicitAccessWithName ea, "Users", GENERIC_READ, SET_ACCESS, CONTAINER_INHERIT_ACE Or OBJECT_INHERIT_ACE

    ' Merge constructed EXPLICIT_ACCESS structure to the existing DACL and get an updated DACL in memory from
    ' SetEntriesInAcl() API
    result = SetEntriesInAcl(1, ea, pOldDACL, pNewDACL)
    If result = ERROR_SUCCESS Then
        MsgBox "SetEntriesInAcl succeeded"

        ' Call SetNamedSecurityInfo() API with the updated DACL in memory to change the DACL of c:\test1 folder
        result = SetNamedSecurityInfo("c:\test1", SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, 0&, 0&, pNewDACL, 0&)
        If result = ERROR_SUCCESS Then
            MsgBox "SetNamedSecurityInfo succeeded"
        Else
            MsgBox "SetNamedSecurityInfo failed with error code : " & result
        End If

        ' Free the memory allocated for the new DACL by the SetEntriesInAcl() API, using LocalFree() API
        LocalFree pNewDACL
    Else
        MsgBox "SetEntriesInAcl failed with error code : " & result
    End If

    ' Free the memory allocated for the security descriptor by the GetNamedSecurityInfo() API, using LocalFree() API
    LocalFree pSecDesc
Else
    MsgBox "GetNamedSecurityInfo failed with error code : " & result
End If
End Sub
				
REFERENCES
For additional information, click the article numbers below to view the articles in the Microsoft Knowledge Base: 
102102 How To Add an Access-Allowed ACE to a File 
316440 How To Use Low-Level Access Control APIs from Visual Basic 

--------------------------------------------------------------------------------

APPLIES TO
� Microsoft Win32 Application Programming Interface (API), when used with: 
  Microsoft Windows 2000 Standard Edition 
  Microsoft Windows XP Standard Edition 
 


c0Title: Quick and Easy Queue
c1Code:
Listboxes provide suitable functionality to act as a quick queue.
Create a listbox named ListMyQueue. Use this code to add to your Queue:

Public Sub Enqueue(StringToAdd As String)
	If Len(String_to_Add) > 0 Then
		ParentForm.ListMyQueue.AddItem StringToAdd
	End If
End Sub

Use this code to retrieve from your Queue:

Public Function Dequeue() As Variant
	If ParentForm.ListQueue.ListCount > 0 Then
		Dequeue = ParentForm.ListQueue.List(0)
		Parent_Form.ListQueue.RemoveItem (0)
	Else
		MsgBox "Queue is Empty"
	End If
End Function

�Eric Robuck, Lyon Station, Pennsylvania


c0Title: Quick Recordset Copy to Excel Workbook
c1Code:
One of the most common things VB programmers do with Excel is load data into an Excel worksheet from a Recordset object. The method I see used most often to do this is looping through each column and row of the recordset, placing the values individually into the corresponding cells on the Excel worksheet. However, a far faster and more efficient way takes advantage of the little-known CopyFromRecordset method of the Excel Range object.
All you need to use this method is an object reference to the top-left cell of the destination range. Then invoke the CopyFrom-Recordset method for this Range object, passing it the Recordset object you want to load into the worksheet.
Here�s a simple example, which requires that your project reference the Data Access Objects (DAO) and Excel object libraries:

Sub Main()
	Dim db As DAO.Database
	Dim rs As DAO.Recordset
	Dim xlApp As Excel.Application
	Dim xlBook As Excel.Workbook

	' Open the recordset.
	Set db = DBEngine.Workspaces(0).OpenDatabase("D:\db1.mdb")
	Set rs = db.OpenRecordset("SELECT * FROM MyTable")

	' Open the destination Excel workbook.
	Set xlApp = New Excel.Application
	Set xlBook = xlApp.Workbooks. _
	Open("D:\Book1.xls")

	' This is all it takes to copy the contents
	' of the recordset into the first worksheet
	' of Book1.xls.
	xlBook.Worksheets(1).Range("A1"). CopyFromRecordset rs

	' Clean up everything.
	xlBook.Save
	xlBook.Close False
	xlApp.Quit
	rs.Close
	db.Close
	Set xlBook = Nothing
	Set xlApp = Nothing
	Set rs = Nothing
	Set db = Nothing
End Sub

Excel 97 supports only plain vanilla DAO recordsets for this operation (not including ODBCDirect recordsets). However, Excel 2000 has added support for all flavors of DAO and ActiveX Data Objects (ADO) recordsets, making this a powerful tool for Office 2000 programming.

�Rob Bovey, Edmonds, Washington


c0Title: Quick Sort 2 (Unstable, Recursive)
c1Code:
Quick Sort 
--------------------------------------------------------------------------------

This is where things start getting complicated - and a lot faster. The main problem with the first 3 are that almost every value must be compared with almost every other, resulting in a lot of comparisons. If we break the sorts down into a number of smaller sorts and then combine them, we might speed things up.

Quick Sort
The quick sort works by gradually dividing each sort into 2 smaller sorts. The first thing we do is select a pivot value from our values. Using this, we swap elements over so that all the values less than the pivot are on the left and all the values greater than the pivot are on the right. The individual left and right subgroups are still unsorted within themselves, but in relation to each other the groups are correct. Once we have done this, we call the procedure recursively on the left group and the right group.

Example (pivot values underlined):

Code:
Array of data [2,8,4,9,3,5,7,1,0,6]


        [5,8,3,9,4,1,7,0,2,6]
                  |                               Recursive step
     [2,3,0,4,1] [5] [7,9,8,6]
          |              |                        Recursive step
    [1,0][2][4,3]    [6][7][8,9]
      |       |       |      |                    Recursive step
    [0,1]   [3,4]    [6]   [8,9]
      |       |       |      |
     [0,1,2,3,4]      [6,7,8,9]
          |               |
        [0,1,2,3,4,5,6,7,8,9]


Finish [0,1,2,3,4,5,6,7,8,9]


Yes I have chosen nice pivot values which create balanced divisions between the left and right, but that was really just so I could illustrate the workings of the function. My code uses 2 procedures, the inner recursive sort and the outer wrapper function.


VB: 
--------------------------------------------------------------------------------
Public Sub QuickSort(ByRef lngArray() As Long)
    Dim iLBound As Long
    Dim iUBound As Long
    Dim iTemp As Long
    Dim iOuter As Long
    Dim iMax As Long
    
    iLBound = LBound(lngArray)
    iUBound = UBound(lngArray)
    
    'Dont want to sort array with only 1 value
    If (iUBound - iLBound) Then
        
        'Move the largest value to the rightmost position, otherwise
        'we need to check that iLeftCur does not exceed the bounds of the
        'array on EVERY pass (time consuming)
        
        For iOuter = iLBound To iUBound
            If lngArray(iOuter) > lngArray(iMax) Then iMax = iOuter
        Next iOuter
        
        iTemp = lngArray(iMax)
        lngArray(iMax) = lngArray(iUBound)
        lngArray(iUBound) = iTemp
    
        'Start quicksorting
        InnerQuickSort lngArray, iLBound, iUBound
    End If
End Sub

Private Sub InnerQuickSort(ByRef lngArray() As Long, ByVal iLeftEnd As Long, ByVal iRightEnd As Long)
    Dim iLeftCur As Long
    Dim iRightCur As Long
    Dim iPivot As Long
    Dim iTemp As Long
    
    If iLeftEnd >= iRightEnd Then Exit Sub
    
    iLeftCur = iLeftEnd
    iRightCur = iRightEnd + 1
    iPivot = lngArray(iLeftEnd)
    
    'Arrange values so that < pivot are on the left and > pivot are on the right
    Do
        'Find >= value on left side
        Do
            iLeftCur = iLeftCur + 1
        Loop While lngArray(iLeftCur) < iPivot
        
        'Find <= value on right side
        Do
            iRightCur = iRightCur - 1
        Loop While lngArray(iRightCur) > iPivot
        
        'No more swapping to do
        If iLeftCur >= iRightCur Then Exit Do
        
        'Swap
        iTemp = lngArray(iLeftCur)
        lngArray(iLeftCur) = lngArray(iRightCur)
        lngArray(iRightCur) = iTemp
    Loop
    
    'Call quicksort recursively on left and right subarrays
    lngArray(iLeftEnd) = lngArray(iRightCur)
    lngArray(iRightCur) = iPivot
    
    InnerQuickSort lngArray, iLeftEnd, iRightCur - 1
    InnerQuickSort lngArray, iRightCur + 1, iRightEnd
End Sub


c0Title: Quick Sort UDT (non-recursive)
c1Code:
Public Type state_struct
   name As String
   abbreviation As String
   population2000 As Long
   population1990 As Long
End Type


Public Sub QuickSortStatesByName(states() As state_struct, Optional ByVal numEls As Variant, _
Optional ByVal descending As Variant)

    '-----------------------------------
    ' Quicksort of an array of state_structs by state name
    '-----------------------------------

    Dim sp As Integer
    Dim leftStk(32) As Long, rightStk(32) As Long
    Dim leftNdx As Long, rightNdx As Long
    Dim inverseOrder As Boolean
    Dim i As Long, j As Long
    Dim value As state_struct, temp As state_struct
   
    ' account for optional arguments
    If IsMissing(numEls) Then numEls = UBound(states)
    If IsMissing(descending) Then descending = False
    inverseOrder = (descending <> False)

    ' init pointers
    leftNdx = LBound(states)
    rightNdx = numEls
    ' init stack
    sp = 1
    leftStk(sp) = leftNdx
    rightStk(sp) = rightNdx

    Do
        If rightNdx > leftNdx Then
           
            value = states(rightNdx)
            i = leftNdx - 1
            j = rightNdx

            ' find the pivot item
            If descending Then
                Do
                    Do: i = i + 1: Loop Until states(i).name <= value.name
                    Do: j = j - 1: Loop Until j = leftNdx Or states(j).name >= value.name
                    temp = states(i)
                    states(i) = states(j)
                    states(j) = temp
                Loop Until j <= i
            Else
                Do
                    Do: i = i + 1: Loop Until states(i).name >= value.name
                    Do: j = j - 1: Loop Until j = leftNdx Or states(j).name <= value.name
                    temp = states(i)
                    states(i) = states(j)
                    states(j) = temp
                Loop Until j <= i
            End If

            ' swap found items
            temp = states(j)
            states(j) = states(i)
            states(i) = states(rightNdx)
            states(rightNdx) = temp

            ' push on the stack the pair of pointers
            ' that differ most
            sp = sp + 1
            If (i - leftNdx) > (rightNdx - i) Then
                leftStk(sp) = leftNdx
                rightStk(sp) = i - 1
                leftNdx = i + 1
            Else
                leftStk(sp) = i + 1
                rightStk(sp) = rightNdx
                rightNdx = i - 1
            End If
        Else
            ' pop a new pair of pointers off the stacks
            leftNdx = leftStk(sp)
            rightNdx = rightStk(sp)
            sp = sp - 1
            If sp = 0 Then Exit Do
        End If

    Loop

End Sub



c0Title: Quick Sort with Insertion Sort (Unstable, Recursive)
c1Code:
Sub QuickSort(A$(), Start As Long, Finish As Long)
    Dim PosOfSplitter As Long
    
    If Finish - Start <= 8 Then     'More efficient to do insertion sort for short list

        InsertionSort A$, Start, Finish

    ElseIf Finish > Start Then
'   If Finish > Start Then
        Partition A$(), Start, Finish, PosOfSplitter
        QuickSort A$(), Start, PosOfSplitter - 1
        QuickSort A$(), PosOfSplitter + 1, Finish
    End If
    
End Sub


Private Sub Partition(A$(), Start As Long, Finish As Long, LocOfSplitter As Long)
    Dim SplitPos As Long
    Dim NewStart As Long
    Dim I As Long
    Dim LeftPos As Long
    Dim Splitter$
    Dim Temp As String
    
    SplitPos = Start + Int((Finish - Start + 1) * Rnd)      'Random position helps avoid tremendously slow sorting for non-random lists

    Splitter$ = A$(SplitPos)

'   Swap A$(SplitPos), A$(Start)     'More efficient to do inline...
    Temp = A$(SplitPos): A$(SplitPos) = A$(Start): A$(Start) = Temp

    
    LeftPos = Start
    
    For I = Start + 1 To Finish
        If A$(I) < Splitter$ Then
            LeftPos = LeftPos + 1
            
'           Swap A$(LeftPos), A$(I)     'More efficient to do inline...
            Temp = A$(LeftPos): A$(LeftPos) = A$(I): A$(I) = Temp

         End If
    Next I
    
'   Swap A$(Start), A$(LeftPos)     'More efficient to do inline...
    Temp = A$(Start): A$(Start) = A$(LeftPos): A$(LeftPos) = Temp
    
    LocOfSplitter = LeftPos
    
End Sub


'Sub Swap(A$, B$)
'    Dim C$
'
'    C$ = A$: A$ = B$: B$ = C$
'End Sub


Private Sub InsertionSort(A$(), Start As Long, Finish As Long)
    
    Dim I As Long
    Dim J As Long
    Dim Temp As String

    For I = Start + 1 To Finish
        Temp = A$(I)
        For J = I - 1 To 1 Step -1
            If Temp >= A$(J) Then Exit For
            A$(J + 1) = A$(J)
        Next J
        A$(J + 1) = Temp$
    Next I
End Sub


c0Title: Quick Timer Control Replacement
c1Code:
Timer controls can be practical when you need to add a small delay in program execution. However, if you need the delay in a class module (instead of on a form), the actual control can be hard to get at. Instead, use these functions in a single code module:


Option Explicit

Private Declare Function SetTimer Lib "user32" (ByVal hWnd As Long, ByVal nIDEvent As Long, ByVal uElapse As Long, ByVal lpTimerFunc As Long) As Long
Private Declare Function KillTimer Lib "user32" (ByVal hWnd As Long, ByVal nIDEvent As Long) As Long

Private m_cb As Object

Public Function timerSet(lTime As Long, cb As Object) As Long
	Set m_cb = cb
	timerSet = SetTimer(0, 0, lTime, AddressOf timerProcOnce)
End Function

 

Private Sub timerProcOnce(ByVal lHwnd As Long, ByVal lMsg As Long, ByVal lTimerID As Long, ByVal lTime As Long)
	On Error Resume Next

	Call KillTimer(0, lTimerID)
	m_cb.cbTimer
End Sub


The class module then calls the function like this:

...
timerSet 10, Me
...


After 10 milliseconds, the code triggers the cbTimer method in the class module:

 
Public Sub cbTimer()
	' Do some stuff
End Sub

 
You can also use the function on forms instead of the intrinsic Timer control.

�Bo Larsson, Copenhagen, Denmark


c0Title: QuickCrypt Library API
c1Code:
The following sample demonstrates how to use the DES_EDE3 API to encrypt a 32-byte array of data in ECB mode of operation in Visual Basic.

Private Sub MyButton_Click()
    Dim context As SL_DES_EDE3_CONTEXT
    
    'The key is an array of SLC_DES_EDE3_DEFAULTKEYSIZE bytes
    Dim key(0 To SLC_DES_EDE3_DEFAULTKEYSIZE - 1) As Byte
    
    'The data is an array of bytes( in this case 4 DES-EDE3 blocks )
    Dim data(0 To 31) As Byte
    
    'Fill the key with some data
    For I = 0 To SLC_DES_EDE3_DEFAULTKEYSIZE - 1
        key(I) = SLC_DES_EDE3_DEFAULTKEYSIZE - 1 - I
    Next I
    
    'Fill the data bytes with some values
    For I = 0 To 31
        data(I) = I
    Next I
    
    'Initialize the context
    SL_DES_EDE3_Init context, SLC_ENCRYPT, key(0), SLC_DES_EDE3_DEFAULTKEYSIZE
    
    'Encrypt each block in part
    For I = 0 To 3
       'Encrypt the data passing first byte of  each block. This is in-place encrypting
       SL_DES_EDE3_ProcessBlock context, data(I * 8), data(I * 8)
    Next I
       
    'Show what we get
    
    'Convert Data To Hex
    Dim hex As String
    hex = "0123456789ABCDEF"
    Dim strData As String
    strData = Space(64)
    
    For I = 0 To 31
       Mid(strData, 2 * I + 1, 1) = Mid(hex, data(I) \ 16 + 1, 1)
       Mid(strData, 2 * (I + 1), 1) = Mid(hex, data(I) Mod 16 + 1, 1)
    Next I
       
    'Show it
    MsgBox strData
End Sub 
     



Note. To be able to access the API functionality from Visual Basic, create a .bas module which contains the declarations of all functions, constants and types for QuickCrypt Library. You have to include this module in your VB project and its content has to be the following:

            Public Const SLC_ENCRYPT As Long = 0
            Public Const SLC_DECRYPT As Long = 1

            Public Const SLC_DES_BLOCKSIZE As Long = 8
            Public Const SLC_DES_CONTEXTSIZE As Long = 168
            Public Const SLC_DES_DEFAULTKEYSIZE As Long = 8

            Public Const SLC_DES_EDE3_BLOCKSIZE As Long = SLC_DES_BLOCKSIZE
            Public Const SLC_DES_EDE3_CONTEXTSIZE As Long = 440
            Public Const SLC_DES_EDE3_DEFAULTKEYSIZE As Long = 24

            Public Const SLC_DES_EDE2_BLOCKSIZE As Long = SLC_DES_BLOCKSIZE
            Public Const SLC_DES_EDE2_CONTEXTSIZE As Long = 308
            Public Const SLC_DES_EDE2_DEFAULTKEYSIZE As Long = 16

            Public Const SLC_DESX_BLOCKSIZE As Long = SLC_DES_BLOCKSIZE
            Public Const SLC_DESX_CONTEXTSIZE As Long = (SLC_DES_CONTEXTSIZE + 24 + 28 + 8)
            Public Const SLC_DESX_DEFAULTKEYSIZE As Long = 24

            Public Const SLC_RIJNDAEL_BLOCKSIZE As Long = 16
            Public Const SLC_RIJNDAEL_CONTEXTSIZE As Long = 312
            Public Const SLC_RIJNDAEL_DEFAULTKEYSIZE As Long = 16

            Public Const SLC_AES_BLOCKSIZE As Long = SLC_RIJNDAEL_BLOCKSIZE
            Public Const SLC_AES_CONTEXTSIZE As Long = SLC_RIJNDAEL_CONTEXTSIZE
            Public Const SLC_AES_DEFAULTKEYSIZE As Long = SLC_RIJNDAEL_DEFAULTKEYSIZE

            Public Const SLC_BLOWFISH_BLOCKSIZE As Long = 8
            Public Const SLC_BLOWFISH_CONTEXTSIZE As Long = 4208
            Public Const SLC_BLOWFISH_DEFAULTKEYSIZE As Long = 56

            Public Const SLC_GOST_BLOCKSIZE As Long = 8
            Public Const SLC_GOST_CONTEXTSIZE As Long = 76
            Public Const SLC_GOST_DEFAULTKEYSIZE As Long = 32

            Public Type SL_DES_CONTEXT
                Value(0 To SLC_DES_CONTEXTSIZE - 1) As Byte
            End Type

            Public Type SL_DES_EDE3_CONTEXT
                Value(0 To SLC_DES_EDE3_CONTEXTSIZE - 1) As Byte
            End Type

            Public Type SL_DES_EDE2_CONTEXT
                Value(0 To SLC_DES_EDE2_CONTEXTSIZE - 1) As Byte
            End Type

            Public Type SL_DESX_CONTEXT
                Value(0 To SLC_DESX_CONTEXTSIZE - 1) As Byte
            End Type

            Public Type SL_RIJNDAEL_CONTEXT
                Value(0 To SLC_RIJNDAEL_CONTEXTSIZE - 1) As Byte
            End Type

            Public Type SL_AES_CONTEXT
                Value(0 To SLC_AES_CONTEXTSIZE - 1) As Byte
            End Type

            Public Type SL_BLOWFISH_CONTEXT
                Value(0 To SLC_BLOWFISH_CONTEXTSIZE - 1) As Byte
            End Type

            Public Type SL_GOST_CONTEXT
                Value(0 To SLC_GOST_CONTEXTSIZE - 1) As Byte
            End Type

            Public Declare Sub SL_DES_Init Lib "QuickCrypt.dll" Alias "#1" (pContext As SL_DES_CONTEXT, ByVal nDir As Long, pKey As Any, ByVal nKeyKen As Long)
            Public Declare Sub SL_DES_ProcessBlock Lib "QuickCrypt.dll" Alias "#2" (pContext As SL_DES_CONTEXT, pDestBlock As Any, pSrcBlock As Any)
            Public Declare Function SL_DES_CheckKeyParityBits Lib "QuickCrypt.dll" Alias "#3" (pKey As Any) As Long
            Public Declare Sub SL_DES_CorrectKeyParityBits Lib "QuickCrypt.dll" Alias "#4" (pKey As Any)

            Public Declare Sub SL_DES_EDE3_Init Lib "QuickCrypt.dll" Alias "#11" (pContext As SL_DES_EDE3_CONTEXT, ByVal nDir As Long, pKey As Any, ByVal nKeyKen As Long)
            Public Declare Sub SL_DES_EDE3_ProcessBlock Lib "QuickCrypt.dll" Alias "#12" (pContext As SL_DES_EDE3_CONTEXT, pDestBlock As Any, pSrcBlock As Any)

            Public Declare Sub SL_DES_EDE2_Init Lib "QuickCrypt.dll" Alias "#21" (pContext As SL_DES_EDE2_CONTEXT, ByVal nDir As Long, pKey As Any, ByVal nKeyKen As Long)
            Public Declare Sub SL_DES_EDE2_ProcessBlock Lib "QuickCrypt.dll" Alias "#22" (pContext As SL_DES_EDE2_CONTEXT, pDestBlock As Any, pSrcBlock As Any)

            Public Declare Sub SL_DESX_Init Lib "QuickCrypt.dll" Alias "#31" (pContext As SL_DESX_CONTEXT, ByVal nDir As Long, pKey As Any, ByVal nKeyKen As Long)
            Public Declare Sub SL_DESX_ProcessBlock Lib "QuickCrypt.dll" Alias "#32" (pContext As SL_DESX_CONTEXT, pDestBlock As Any, pSrcBlock As Any)

            Public Declare Sub SL_RIJNDAEL_Init Lib "QuickCrypt.dll" Alias "#41" (pContext As SL_RIJNDAEL_CONTEXT, ByVal nDir As Long, pKey As Any, ByVal nKeyKen As Long)
            Public Declare Sub SL_RIJNDAEL_ProcessBlock Lib "QuickCrypt.dll" Alias "#42" (pContext As SL_RIJNDAEL_CONTEXT, pDestBlock As Any, pSrcBlock As Any)

            Public Declare Sub SL_AES_Init Lib "QuickCrypt.dll" Alias "#41" (pContext As SL_AES_CONTEXT, ByVal nDir As Long, pKey As Any, ByVal nKeyKen As Long)
            Public Declare Sub SL_AES_ProcessBlock Lib "QuickCrypt.dll" Alias "#42" (pContext As SL_AES_CONTEXT, pDestBlock As Any, pSrcBlock As Any)

            Public Declare Sub SL_BLOWFISH_Init Lib "QuickCrypt.dll" Alias "#51" (pContext As SL_BLOWFISH_CONTEXT, ByVal nDir As Long, pKey As Any, ByVal nKeyKen As Long)
            Public Declare Sub SL_BLOWFISH_ProcessBlock Lib "QuickCrypt.dll" Alias "#52" (pContext As SL_BLOWFISH_CONTEXT, pDestBlock As Any, pSrcBlock As Any)

            Public Declare Sub SL_GOST_Init Lib "QuickCrypt.dll" Alias "#61" (pContext As SL_GOST_CONTEXT, ByVal nDir As Long, pKey As Any, ByVal nKeyKen As Long)
            Public Declare Sub SL_GOST_ProcessBlock Lib "QuickCrypt.dll" Alias "#62" (pContext As SL_GOST_CONTEXT, pDestBlock As Any, pSrcBlock As Any)


            Public Declare Function SL_CBC_Init Lib "QuickCrypt.dll" Alias "#71" (pContext As Any, ByVal nDir As Long, pIV As Any, ByVal bPadded As Boolean) As Long
            Public Declare Sub SL_CBC_ProcessBlock Lib "QuickCrypt.dll" Alias "#72" (pContext As Any, pDestBlock As Any, pSrcBlock As Any)
            Public Declare Function SL_CBC_ProcessLastBlock Lib "QuickCrypt.dll" Alias "#73" (pContext As Any, pDestBlock As Any, pSrcBlock As Any, ByVal nSize As Long) As Long
            Public Declare Function SL_CBC_Process Lib "QuickCrypt.dll" Alias "#74" (pContext As Any, pDest As Any, pSrc As Any, ByVal nSize As Long) As Long

            Public Declare Function SL_CFB_Init Lib "QuickCrypt.dll" Alias "#81" (pContext As Any, ByVal nDir As Long, pIV As Any, ByVal fs As Long) As Long
            Public Declare Sub SL_CFB_Process Lib "QuickCrypt.dll" Alias "#82" (pContext As Any, pDest As Any, pSrc As Any, ByVal nSize As Long)

            Public Declare Function SL_OFB_Init Lib "QuickCrypt.dll" Alias "#91" (pContext As Any, ByVal nDir As Long, pIV As Any, ByVal fs As Long) As Long
            Public Declare Sub SL_OFB_Process Lib "QuickCrypt.dll" Alias "#92" (pContext As Any, pDest As Any, pSrc As Any, ByVal nSize As Long)

            Public Declare Function SL_CTR_Init Lib "QuickCrypt.dll" Alias "#101" (pContext As Any, ByVal nDir As Long, pIV As Any) As Long
            Public Declare Sub SL_CTR_Process Lib "QuickCrypt.dll" Alias "#102" (pContext As Any, pDest As Any, pSrc As Any, ByVal nSize As Long)
            Public Declare Sub SL_CTR_Seek Lib "QuickCrypt.dll" Alias "#103" (pContext As Any, ByVal nPos As Long) 
     





Note. See SlavaSoft QuickCrypt Library help file for more examples. It includes samples for:

  a.. DES, TripleDES (DES-EDE2, DES_EDE3), DESX, Rijndael(AES), Blowfish and GOST encryption algorithms in ECB, CBC, CFB, OFB and CTR modes of operation.


c0Title: QuickLZ manual, version 1.10
c2File: quicklz.ZIP
c1Code:
Files

\quick.c
Source code of the QuickLZ library.

\Visual C++ 2005 demo
Contains the source code of the executables that can be downloaded from the website - quick32.exe and quick64.exe.

\Simple ANSI C demo
Contains a very simple demonstration of compressing and decompressing a file. The source code of this demo is included at the end of this manual in the chapter "Example of use".

\Visual Basic 6.0 demo simple
Sample project for Visual Basic 6.0 using the string type as input and output. Some speed is lost because strings need to be converted to and from Unicode.

\Visual Basic 6.0 demo fast
Sample project for Visual Basic 6.0 which is using byte arrays as input and output. Both demo projects are using the quick32.dll file which is compiled for x86 and using the __stdcall calling convention.

Functions

This part is not valid for the Visual Basic demo projects because they include wrapper functions for a very easy to use interface which hardly needs any documentation.

unsigned int qlz_compress(const void *source, char *destination, unsigned int size);
This function will compress the source and store the result in the destination. The size in bytes of the source must be given in the sizeparameter. If the function fails, the return value is 0 else the size of the compressed data is returned.

qlz_compress expects that the destination buffer is exactly size + 36000 bytes or larger and will crash otherwise.

unsigned int qlz_decompress(const char *source, void *destination);
This function will decompress the source and store the result in the destination. If decompression fails the return value is 0 else the return value tells the size of the decompressed data.

unsigned int qlz_size_decompressed(char *source);
This function takes data compressed with QuickLZ as argument and returns its uncompressed size. The function can be used for allocating the correct amount of memory before decompression.

unsigned int qlz_size_source(char *source);
This function takes the first 32 bytes of data compressed with QuickLZ as argument (these 32 bytes contain a header) and returns the size of the entire compressed data.

The function is usefull when handling files that are too large to fit into memory and need to be compressed into several smaller chunks that are stored concecutive in a single destination file. When decompressing, the function tells how many bytes needs to be read from the file in order to have read the compressed chunk entirely into memory before calling qlz_decompress on it. The Visual C++ 2005 demo project uses this method.

Compatibility

Two architecture types are supported which can be specified with the #define x86x64 flag in the quick.c file:

x86x64 defined: Generates code which is higly optimized for the x86 and x64 architectures but is not compatible with other architectures. It assumes that the processor supports unaligned memory access, is little endian and that sizeof(unsigned int) == 4. Examples of supported processors are 80386, 80486, Xeon, Athlon64, Pentium 4, Pentium 4 EMT64, Opteron, Sempron, Centrino, Celeron.

x86x64 commented out: Does not use unaligned memory access, is independant of endianess and likewise assumes sizeof(unsigned int) == 4. Supported architectures are Alpha, MIPS, IA64 (Itanium), SPARC, POWER and the like. x86 and x64 are also supported in this mode but do not run at optimal speed.

Data compressed on one architecture with one setting of the x86x64 flag can be decompressed on any other architecture with any setting of the x86x64 flag - no problems can occur exchanging compressed data across architectures.

QuickLZ 1.10 is fully backwards compatible with QuickLZ 1.0 for both data format and programming API.

Notes

All functions are thread safe
  
The compiled library is around 16 Kbyte in size and no system calls to memory allocation or other system interaction is done.
  
Due to performance QuickLZ performs very little error checking, so passing corrupted data to qlz_decompress() can cause an exception. The only error checking done on the source is testing if the header is OK and if the last dword of the source is correct (which will detect many cases where the source is incomplete, for example due to incomplete file reads, etc.).
  
The length of input data is limited only by the system or by 4 Gbyte, whichever is smallest.
Example of use

compress.c
#include <stdio.h>
#include <stdlib.h>
#include "quick.c"

int main(int argc, char* argv[])
{
     FILE *ifile, *ofile;
     char *src, *dst;
     unsigned int len;

      ifile = fopen(argv[1], "rb");
      ofile = fopen(argv[2], "wb");

      // allocate source buffer and read file
      fseek(ifile, 0, SEEK_END);
      len = ftell(ifile);
      fseek(ifile, 0, SEEK_SET);
      src = (char*) malloc(len);
      fread(src, 1, len, ifile);

      // Allocate destination buffer. It is very important that you
      // allocate exactly len + 36000 bytes or more!
      dst = (char*) malloc(len + 36000);

      // compress and write result
      len = qlz_compress(src, dst, len);
      fwrite(dst, len, 1, ofile);
      fclose(ofile);
}

decompress.c
#include <stdio.h>
#include <stdlib.h>
#include "quick.c"

int main(int argc, char* argv[])
{
     FILE *ifile, *ofile;
     char *src, *dst;
     unsigned int len;

     ifile = fopen(argv[1], "rb");
     ofile = fopen(argv[2], "wb");

     // allocate source buffer
     fseek(ifile, 0, SEEK_END);
     len = ftell(ifile);
     fseek(ifile, 0, SEEK_SET);
     src = (char*) malloc(len);

     // read file and allocate destination buffer
     fread(src, 1, len, ifile);
     len = qlz_size_decompressed(src);
     dst = (char*) malloc(len);

     // decompress and write result
     len = qlz_decompress(src, dst);
     fwrite(dst, len, 1, ofile);
     fclose(ofile);
}


c0Title: Radix (or Count or Histogram) Sort (Not good for strings)
c1Code:
Radix (or Count or Histogram) Sort
This a completely new sort, pointed out to me by OnErr0r. It is quite different to the other sorts, but its a very very cool algorithm. What we do is allocate a series of 'buckets' into which we put values. We sort them into buckets by checking the value of a specific digit in the number. We start at the rightmost digit and repeat the sort through to the leftmost digit. By the end, the array is fully sorted.

Example:

Code:
Array of data [5,530,64,203,99,331,4,692,362,34]

Radix with right-most digit
Bucket [ 0 ][ 1 ][ 2 ][ 3 ][ 4 ][ 5 ][ 6 ][ 7 ][ 8 ][ 9 ]
        530  331  692  203  64   5                   99
                  362       4
                            34

Array now [530,331,692,362,203,64,4,34,5,99]

Radix with middle digit
Bucket [ 0 ][ 1 ][ 2 ][ 3 ][ 4 ][ 5 ][ 6 ][ 7 ][ 8 ][ 9 ]
        203            530            362            692
        4              331            64             99
        5              34

Array now [203,4,5,530,331,34,362,64,692,99]

Radix with left-most digit
Bucket [ 0 ][ 1 ][ 2 ][ 3 ][ 4 ][ 5 ][ 6 ][ 7 ][ 8 ][ 9 ]
        4         203  331       530  692
        5              362
        34
        64
        99

Array now [4,5,34,64,99,203,331,362,530,692]

Finish [4,5,34,64,99,203,331,362,530,692]


That was a demonstration. The actual code will not work with decimal digits but with groups of 2 hexadecimal digits (8 bits). This requires 256 buckets labelled 0 to FF (255). We do as many sorts as needed, which will be no more than 4 for Long values. The actual radix sort comprises 3 steps - first, count the elements going into each bucket, then work out where the buckets should be in the array, then finally move the values into their buckets.


VB: 
--------------------------------------------------------------------------------
Public Sub RadixSort(ByRef lngArray() As Long)
    Dim arrTemp() As Long
    Dim iLBound As Long
    Dim iUBound As Long
    Dim iMax As Long
    Dim iSorts As Long
    Dim iLoop As Long

    iLBound = LBound(lngArray)
    iUBound = UBound(lngArray)
    
    'Create swap array
    ReDim arrTemp(iLBound To iUBound)

    iMax = &H80000000
    'Find largest
    For iLoop = iLBound To iUBound
        If lngArray(iLoop) > iMax Then iMax = lngArray(iLoop)
    Next iLoop
    
    'Calculate how many sorts are needed
    Do While iMax
        iSorts = iSorts + 1
        iMax = iMax \ 256
    Loop
    
    iMax = 1
    
    'Do the sorts
    For iLoop = 1 To iSorts
        
        If iLoop And 1 Then
            'Odd sort -> src to dest
            InnerRadixSort lngArray, arrTemp, iLBound, iUBound, iMax
        Else
            'Even sort -> dest to src
            InnerRadixSort arrTemp, lngArray, iLBound, iUBound, iMax
        End If
        
        'Next sort factor
        iMax = iMax * 256
    Next iLoop
    
    'If odd number of sorts we need to swap the arrays
    If (iSorts And 1) Then
        For iLoop = iLBound To iUBound
            lngArray(iLoop) = arrTemp(iLoop)
        Next iLoop
    End If
End Sub

Private Sub InnerRadixSort(ByRef lngSrc() As Long, ByRef lngDest() As Long, ByVal iLBound As Long, ByVal iUBound As Long, ByVal iDivisor As Long)
    Dim arrCounts(255) As Long
    Dim arrOffsets(255) As Long
    Dim iBucket As Long
    Dim iLoop As Long
    
    'Count the items for each bucket
    For iLoop = iLBound To iUBound
        iBucket = (lngSrc(iLoop) \ iDivisor) And 255
        arrCounts(iBucket) = arrCounts(iBucket) + 1
    Next iLoop
    
    'Generate offsets
    For iLoop = 1 To 255
        arrOffsets(iLoop) = arrOffsets(iLoop - 1) + arrCounts(iLoop - 1) + iLBound
    Next iLoop
        
    'Fill the buckets
    For iLoop = iLBound To iUBound
        iBucket = (lngSrc(iLoop) \ iDivisor) And 255
        lngDest(arrOffsets(iBucket)) = lngSrc(iLoop)
        arrOffsets(iBucket) = arrOffsets(iBucket) + 1
    Next iLoop
End Sub

--------------------------------------------------------------------------------


c0Title: RC4 Encryption in VB
c1Code:
Author: Andreas J�nsson  

Version Compatibility:  Visual Basic 5   Visual Basic 6    

More information: RC4 is a stream cipher designed by Rivest for RSA Security. Here is a RC4-encryption function for VB. Just copy and paste into your project. Enjoy!

 
Public Function RC4(ByVal Expression As String, ByVal Password As String) As String
	On Error Resume Next

	Dim RB(0 To 255) As Integer, X As Long, Y As Long, Z As Long, Key() As Byte, ByteArray() As Byte, Temp As Byte

	If Len(Password) = 0 Then
	    Exit Function
	End If

	If Len(Expression) = 0 Then
	    Exit Function
	End If

	If Len(Password) > 256 Then
	    Key() = StrConv(Left$(Password, 256), vbFromUnicode)
	Else
	    Key() = StrConv(Password, vbFromUnicode)
	End If

	For X = 0 To 255
	    RB(X) = X
	Next X

	X = 0
	Y = 0
	Z = 0

	For X = 0 To 255
	    Y = (Y + RB(X) + Key(X Mod Len(Password))) Mod 256
	    Temp = RB(X)
	    RB(X) = RB(Y)
	    RB(Y) = Temp
	Next X

	X = 0
	Y = 0
	Z = 0

	ByteArray() = StrConv(Expression, vbFromUnicode)

	For X = 0 To Len(Expression)
	    Y = (Y + 1) Mod 256
	    Z = (Z + RB(Y)) Mod 256
	    Temp = RB(Y)
	    RB(Y) = RB(Z)
	    RB(Z) = Temp
	    ByteArray(X) = ByteArray(X) Xor (RB((RB(Y) + RB(Z)) Mod 256))
	Next X

	RC4 = StrConv(ByteArray, vbUnicode)

End Function


c0Title: RC4 Stream Cipher (with file handling)
c1Code:
Compatibility:VB 3.0, VB 4.0 (16-bit), VB 4.0 (32-bit), VB 5.0, VB 6.0

This code offers you a strong encryption with RC4. I've tested it a lot and it's the right implementation of the RC4 cipher. 'You can use this code in your commercial code because it's not patented! 'I know there is another code that deals with RC4 but my code has nothing to do with this code! 'More infos: sci.crypt


'**************************************
' Name: (Update) RC4 Stream Cipher (with
'     file handling )
' Description:This code offers you a str
'     ong encryption with RC4. I've tested it 
'     a lot and it's the right implementation 
'     of the RC4 cipher.
'You can use this code in your commercia
'     l code because it's not patented!
'I know there is another code that deals
'     with RC4 but my code has nothing to do w
'     ith this code! 
'More infos: sci.crypt
' By: Sebastian
'
' Inputs:Create the form and simply sele
'     ct a file to en(de)crypt!
'Notice that you use the same function f
'     or encryption and decryption
'
' Returns:After you press the Button you
'     should get the en(de)crypted file!
'
' Assumes:'Assumes:Create a form with:
'
'txtpwd (txtbox)
'txtSave (txtbox)
'txtPattern (Combobox)
'filList (FileListBox)
'DirList (DirListBox)
'drvList (DrvlistBox)
'Command1 (Command Button ; Caption=Encr
'     ypt)
'Command2 (Command Button ; Caption=Decr
'     ypt)
'
' Side Effects:If you encrypt different 
'     textes with the same password, someone c
'     ould be able to decrypt your code. (This
'     is quiet normal for a stream cipher!)

If YOU ENCRYPT LARGE FILES PLEASE USE THE EnDeCryptSingle ROUTINE INSTEAD OF THE EnDeCrypt ROUTINE OR SPLIT THE INPUT IN SMALLER PIECES!

'
'This code is copyrighted and has' limited warranties.Please see http://w
'     ww.Planet-Source-Code.com/vb/scripts/Sho
'     wCode.asp?txtCodeId=1736&lngWId=1'for details.'**************************************

Option Explicit
Dim s(0 To 255) As Integer 'S-Box
Dim kep(0 To 255) As Integer
Dim i As Integer, j As Integer
'For the file actions
Dim path As String


Public Sub RC4ini(Pwd As String)
    Dim temp As Integer, a As Integer, b As Integer
    'Save Password in Byte-Array
    b = 0


    For a = 0 To 255
        b = b + 1


        If b > Len(Pwd) Then
            b = 1
        End If
        kep(a) = Asc(Mid$(Pwd, b, 1))
    Next a
    'INI S-Box


    For a = 0 To 255
        s(a) = a
    Next a
    b = 0


    For a = 0 To 255
        b = (b + s(a) + kep(a)) Mod 256
        ' Swap( S(i),S(j) )
        temp = s(a)
        s(a) = s(b)
        s(b) = temp
    Next a
End Sub
'Only use this routine for short texts


Public Function EnDeCrypt(plaintxt As Variant) As Variant
    Dim temp As Integer, a As Long, i As Integer, j As Integer, k As Integer
    Dim cipherby As Byte, cipher As Variant


    For a = 1 To Len(plaintxt)
        i = (i + 1) Mod 256
        j = (j + s(i)) Mod 256
        ' Swap( S(i),S(j) )
        temp = s(i)
        s(i) = s(j)
        s(j) = temp
        'Generate Keybyte k
        k = s((s(i) + s(j)) Mod 256)
        'Plaintextbyte xor Keybyte
        cipherby = Asc(Mid$(plaintxt, a, 1)) Xor k
        cipher = cipher & Chr(cipherby)
    Next a
    EnDeCrypt = cipher
End Function
'Use this routine for really huge files


Public Function EnDeCryptSingle(plainbyte As Byte) As Byte
    Dim temp As Integer, k As Integer
    Dim cipherby As Byte
    i = (i + 1) Mod 256
    j = (j + s(i)) Mod 256
    ' Swap( S(i),S(j) )
    temp = s(i)
    s(i) = s(j)
    s(j) = temp
    'Generate Keybyte k
    k = s((s(i) + s(j)) Mod 256)
    'Plaintextbyte xor Keybyte
    cipherby = plainbyte Xor k
    EnDeCryptSingle = cipherby
End Function

'************This section handles the fi
'     le actions*****************


Private Sub DirList_Change()
    filList.path = Dirlist.path
End Sub


Private Sub drvList_Change()
    On Error Goto DriveHandler
    Dirlist.path = drvList.Drive
    Exit Sub
    DriveHandler:
    drvList.Drive = Dirlist.path
    Exit Sub
End Sub


Private Sub filList_Click()
    txtSave.Text = filList.List(filList.ListIndex) End Sub


Private Sub Form_Load()
    txtPatter.AddItem "*.*", 0
    txtPatter.AddItem "*.txt", 1
    filList.Pattern = txtPatter.Text
End Sub


Private Sub txtPatter_Change()
    filList.Pattern = txtPatter.Text
End Sub


Private Sub txtPatter_Click()
    filList.Pattern = txtPatter.Text
End Sub
'************* Encrypten Routine *******
'     ***********


Private Sub Command1_Click()
    Dim inbyte As Byte
    Dim z As Long
    'Set the Set-Box Counter zero
    i = 0: j = 0
    'Ini the S-Boxes only once for a hole fi
    '     le


    If txtpwd.Text = "" Then
        MsgBox "You need To enter a password For encrypten or decrypten"
        Exit Sub
    Else
        RC4ini (txtpwd.Text)
    End If
    'Disable the Mousepointer
    MousePointer = vbHourglass
    path = Dirlist.path + "\" + txtSave
    Open path For Binary As 1
    Open path + ".enc" For Binary As 2


    For z = 1 To LOF(1)
        Get #1, , inbyte
        Put #2, , EnDeCryptSingle(inbyte)
    Next z
    Close 1
    Close 2
    'Enable the Mousepointer
    MousePointer = vbDefault
End Sub
'*********** Decryptenroutine **********
'     *


Private Sub Command2_Click()
    Dim inbyte As Byte
    Dim z As Long
    'Set the Set-Box counter zero
    i = 0: j = 0
    'Ini the S-Boxes only once for a hole fi
    '     le


    If txtpwd.Text = "" Then
        MsgBox "You need To enter a password For encrypten or decrypten"
        Exit Sub
    Else
        RC4ini (txtpwd.Text)
    End If
    'Disable the Mousepointer
    MousePointer = vbHourglass
    path = Dirlist.path + "\" + txtSave
    Open path For Binary As 1
    path = Left$(path, Len(path) - 4)
    Open path For Binary As 2


    For z = 1 To LOF(1)
        Get #1, , inbyte
        Put #2, , EnDeCryptSingle(inbyte)
    Next
    Close 1
    Close 2
    'Enable the Mousepointer
    MousePointer = vbDefault
End Sub


c0Title: React to Font-Changed Events
c1Code:
Use WithEvents to perform an action when you change any font properties of a specific control or form. Make sure you set the OLE Automation reference in the References dialog:

 
' Declaration section
Private WithEvents fntAny As StdFont
 
Private Sub fntAny_FontChanged(ByVal _
     PropertyName As String)
     Select Case PropertyName
          Case "Name"
               ' Perform specific action
          Case "Size"
               ' Perform specific action
          Case "Italic"
               ' Perform specific action
          Case "Bold"
               ' Perform specific action
          Case "Underline"
               ' Perform specific action
          '...
          '...
          ' Similarly, you can extend the
          ' functionality for each font property.
     End Select
End Sub
 
You only have to assign any form or control�s Font reference to fntAny. For example, if you want to trap the changes in the form�s font attributes, add this code to the Form_Load event:

 
Set fntAny = Me.Font
' If a control, then Control.Font
�Badari Syam Mysore, Scotch Plains, New Jersey


c0Title: Read and Write INI File Parameters
c1Code:
Option Explicit
Public gINIFile As String   'this global contains the name of the INI file assigned to this application

'API Function calls
Private Declare Function GetPrivateProfileString Lib "kernel32" Alias "GetPrivateProfileStringA" (ByVal lpApplicationName As String, ByVal lpKeyName As Any, ByVal lpDefault As String, ByVal lpReturnedString As String, ByVal nSize As Long, ByVal lpFileName As String) As Long
Private Declare Function WritePrivateProfileString Lib "kernel32" Alias "WritePrivateProfileStringA" (ByVal lpApplicationName As String, ByVal lpKeyName As Any, ByVal lpString As Any, ByVal lpFileName As String) As Long


'This public method is used to retrieve a Key value from the INI file)
Public Function GetINIString(strINIFile As String, strSection As String, strKey As String, _
                                strDefault As String) As String
    
    Dim strTemp As String * 256     'set string max length to 256 chars
    Dim intLength As Integer
    strTemp = ""
    strTemp = Space$(256)           'initialize string with spaces
    intLength = GetPrivateProfileString(strSection, strKey, strDefault, strTemp, 255, strINIFile)
    GetINIString = Left$(strTemp, intLength)
                                
    
End Function

'This public method is used to write a Key value to the INI file
Public Sub WriteINIString(strINIFile As String, strSection As String, strKey As String, strvalue As String)
    Dim indx As Integer
    Dim strTemp As String
    
    strTemp = strvalue
    
    'a key value must not contain either a carriage return or a line feed, therefore check for these in
    'the passed string, and substitute a " " if you find one.  This is purely precautionary.
    For indx = 1 To Len(strvalue)
        If Mid$(strvalue, indx, 1) = vbCr Or Mid$(strvalue, indx, 1) = vbLf Then
            Mid$(strvalue, indx) = " "
        End If
    Next indx
    
    indx = WritePrivateProfileString(strSection, strKey, strTemp, strINIFile)
End Sub


c0Title: Read MP3 File Headers
c1Code:
Ever wanted to create a database of your MP3 files but didn't know how to get them? With this handy code snippet, you can do just that. The function reads summary information stored in an MP3 file: 


Code: 
Option Explicit 

Public Type MP3Info 
    Tag As String * 3 
    Songname As String * 30 
    Artist As String * 30 
    Album As String * 30 
    Year As String * 4 
    Comment As String * 30 
    Genre As String * 1 
End Type 

Function MP3GetInfo(strFileName As String) As MP3Info 

Dim sTemp As String, iFreeFile As Integer 
     
    On Error GoTo ErrFailed 
    iFreeFile = FreeFile 
    Open strFileName For Binary As iFreeFile 
     
    With MP3GetInfo 
        Get #1, FileLen(strFileName) - 127, .Tag 
        If Not .Tag = "TAG" Then 
            Debug.Print "No tag for " & strFileName 
        Else 
            Get #1, , .Songname 
            Get #1, , .Artist 
            Get #1, , .Album 
            Get #1, , .Year 
            Get #1, , .Comment 
            Get #1, , .Genre 
        End If 
    End With 
    Close iFreeFile 
     
    Exit Function 
     
ErrFailed: 
    Debug.Print "Error in MP3GetInfo: " & Err.Description 
    Close iFreeFile 
End Function


c0Title: Read the Serial Number of a Disk
c1Code:
The new Microsoft Scripting Runtime library includes a FileSystemObject hierarchy containing several objects that let you obtain information about your drives, folders, and files. For example, you can retrieve the serial number of a disk using this code:

' Get the serial number of drive C:

Dim fso As New Scripting.FileSystemObject
Dim dr As Scripting.Drive

' Get a reference to the Drive object
Set dr = fso.GetDrive("C")
Print Hex$(dr.SerialNumber)


You can also easily check whether a drive has enough free space on it, using the Drive object�s FreeSpace property:


Print "Drive C: has " & dr.FreeSpace " bytes free."
 

For more information, look on VB�s Help file under �Dictionary� and �FileSystemObject.�

�Francesco Balena, Bari, Italy


c0Title: Reading And Writing INI Files
c1Code:
Compatible With - Visual Basic 3.0
Applies To - INI Files
Most programs need to keep some sort of settings information. This is commonly used for setting up file paths, registration numbers, the form�s last location, users� names and tons of varying but specific information. You could keep this in your own file format, but Windows has given us a simpler way to do this by using initialization (INI) files.

Not only are INI files easy to use, but they are fast! Even though Microsoft wants us to use the registration database for this type of information in Visual Basic 4.0 and Visual Basic 5.0, INI files are still faster and the only way to keep this type of informaion local to your program. This way, if the Registration Database gets wiped out or corrupted, your program will still work.

Using INI files is not built into Visual Basic 3.0 and below. To further complicate matters, Microsoft has not done a good job of documenting INI file use for Visual Basic programmers.

In this tip, we will go over the major functions you can do with an INI file. 

Declare The Following
Declare Function WritePrivateProfileString Lib "Kernel" _
     (ByVal AppName As String, ByVal KeyName As String, _
     ByVal NewString As String, ByVal filename As String) _
     As Integer
Declare Function GetPrivateProfileString Lib "Kernel" _
     (ByVal AppName As String, ByVal KeyName As String, _
     ByVal default As String, ByVal ReturnedString As _
     String, ByVal MAXSIZE As Integer, ByVal filename As _
     String) As Integer
Declare Function GetProfileInt Lib "Kernel" (ByVal _
     lpAppName As String, ByVal lpKeyName As String, _
     ByVal nDefault As String) As Integer
Declare Function GetProfileString Lib "Kernel" (ByVal _
     lpAppName As String, ByVal lpKeyName As String, _
     ByVal lpDefault As String, ByVal lpReturnedString _
     As String, ByVal nSize As Integer) As Integer
Declare Function WriteProfileString Lib "Kernel" _
     (ByVal lpAppName As String, ByVal lpKeyName As _
     String, ByVal lpString As String) As Integer
Declare Function GetPrivateProfileInt Lib "Kernel" _
     (ByVal lpAppName As String, ByVal lpKeyName As _
     String, ByVal nDefault As Integer, ByVal lpFileName _
     As String) As Integer
You will notice here that there are basically two different types of INI files, private and non-private. Private profile calls should be used for your program�s INI file while the non-private profile calls should be used for the WIN.INI file that�s found in the C:\WINDOWS directory..

Parameter Values
Here is some terminology you will need to remember when dealing with INI files.

 

AppName - Heading of the section in an INI file. 
KeyName - Key within a heading. 
Value - Value either being set or retrieved. 
Reading An INI File
For most purposes, you will be reading either strings or integers from INI files. Well, for that matter, everything could be strings and you can convert them to integers.

Reading Integer Values
Function iReadINI (sAppName As String, sKeyName As _
           String, sFilename As String, iDefault As Integer) _
           As Integer
 
     iReadINI = GetPrivateProfileInt(sAppName, _
           ByVal sKeyName, iDefault, sFilename)
End Function
Usage
Dim iMyValue As Integer
iMyValue = GetPrivateProfileInt("VBTT", "Download", _
     App.Path & "\VBTT.INI", 0)
One key thing to remember, here when writing or reading INI files, is that if you don�t specifically specify a path where for the INI file. Windows, by default, puts it in the C:\WINDOWS directory. In most cases, you would want to keep your INI file in the same directory as your application. It makes it easier to remove all traces of your program, if needed.

Reading String Values
Function sReadINI (sAppName As String, sKeyName As _
           String, sFilename As String) As String
     Dim sReturn As String
     sReturn = String(255, Chr(0))
     sReadINI = Left(sReturn, GetPrivateProfileString(sAppName, ByVal sKeyName, "", sReturn, Len(sReturn), sFilename))
End Function
Since this API call returns a string, we need to first create a string large enough to hold the return value. Since most of the time, that length is unknown, it�s better to create a large string. That�s why this code uses the maximum of 255 characters.

You can change the code to return a default value, but, in my experience, I have not found a use for it. If there is no value, then it�s never been set.

Usage
Dim sMyValue As String
sMyValue = sReadINI("VBTT", "Download Directory", _
     App.Path & "\VBTT.INI")
Note: One thing to remember about when reading reading INI files: if the INI file does not exist you will get an empty string, when reading strings, or the default value, when reading integers.

Writing To An INI File
Writing to an INI file is just as easy. I always use the WriteProfileString, because any value can be sent, including integers.

Code
Sub WriteINI (sAppName As String, sKeyName As String, _
           sNewString As String, sFilename As String)
     Dim R As Integer
 
     R = WritePrivateProfileString(sAppName, sKeyName, _
           sNewString, sFilename)
End Sub
Usage
WriteINI "VBTT", "Download Directory", _
     "c:\Vbtt\download", App.Path & "\VBTT.INI"
Removing KeyNames
Now for some tricks that are very hard to find. How about if you want to remove a Key or AppName? Well, it�s straight-forward too, just not well documented.

Declare The Following
Declare Function RemovePrivateProfileString Lib _
     "Kernel" Alias "WritePrivateProfileString" (ByVal _
     AppName As String, ByVal KeyName As Any, ByVal _
     NewString As Any, ByVal filename As String) As Integer
Here we are using the Alias feature of an API call. We are just renaming the WritePrivateProfileString call to a new name of RemovePrivateProfileString. This way, we can pass it a key "As Any". We are doing this because the value that we will use is not a string, but we want to keep the "As String" in the original WritePrivateProfileString call for type checking.

Removing KeyNames
Sub RemoveKeyName (sAppName As String, sKey As String, _
           sFileName As String)
     Dim R As Integer
 
     R = RemovePrivateProfileString(sAppName, sKey, 0&, _
           sFileName)
End Sub
Usage
RemoveKeyName "VBTT", "Download Directory", _
     App.Path & "\VBTT.INI"
The trick here is the 0&, in the place the value would normally be used, will erase the key from the INI file.

Removing AppName Section
Sub RemoveAppNameSection (sAppName As String, _
           sFileName As String)
     Dim R As Integer
 
     R = RemovePrivateProfileString(sAppName, 0&, 0&, _
           sFileName)
End Sub
Usage
RemoveAppNameSection "VBTT", App.Path & "\VBTT.INI"
There you have the most commonly used API calls for reading and writing INI files.

This tip is reprinted from the VB Tips & Tricks Volume 1 book. 
This tip was submitted by: David McCarter


c0Title: Recursively enable or disable controls in frames
c1Code:
This sub calls itself to recursively enable or disable controls in nested frames. This is much easier then going through by hand if you have several nested frames with controls in. For example:

EnableFrame fraMyFrame, True
This code will enable all controls in fraMyFrame.

Procedure

Public Sub EnableFrame(InFrame As Frame, ByVal Flag As Boolean) 'Recursive routine to 'set the enable property for all controls inside 'nested frames to true or false Dim Contrl As Control

On Error Resume Next 'not all controls have a container property InFrame.Enabled = Flag For Each Contrl In InFrame.Parent.Controls If Contrl.Container.Name = InFrame.Name Then If (TypeOf Contrl Is Frame) And Not (Contrl.Name = InFrame.Name) Then EnableFrame Contrl, Flag 'repeat for nested frame Else Contrl.Enabled = Flag End If End If Next End Sub Tip by John Percival


c0Title: Remove Selected Items From A ListView Control
c1Code:
In my projects, I use the ListView control (part of the comctl32.ocx) as a souped-up list box. Here is how I remove all the selected items:

Code

Public Sub RemoveListViewSelectedItems(lvControl As ListView)

	Dim plngCounter As Long

	Do Until ListViewSelected(lvControl) = False
		For plngCounter = 1 To lvControl.ListItems.Count
			If lvControl.ListItems.Item(plngCounter).Selected Then
				lvControl.ListItems.Remove (plngCounter)
				Exit For
			End If
		Next
	Loop

End Sub


Example


RemoveListViewSelectedItems lstvSomeData


Tips Submitted By: David McCarter


c0Title: Remove the default context menu for text boxes
c1Code:
This example shows you how to remove the default context menu for text boxes, and replace it with your own one if you want. It uses subclassing to intercept the WM_RBUTTONUP message.

Declarations

Copy the following code into the declarations section of a module.

Private Declare Function GetWindowLong Lib "user32" Alias "GetWindowLongA" (ByVal hWnd As Long, ByVal nIndex As Long) As Long Private Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hWnd As Long, ByVal nIndex As _ Long, ByVal dwNewLong As Long) As Long Private Declare Function CallWindowProc Lib "user32" Alias _ "CallWindowProcA" (ByVal lpPrevWndFunc As Long, ByVal hWnd _ As Long, ByVal Msg As Long, ByVal wParam As Long, _ ByVal lParam As Long) As Long Private Const GWL_WNDPROC = (-4) Private Const WM_RBUTTONUP = &H205 Private Const WM_NCDESTROY = &H82


Code


Copy the following code into your a module.

Public Function WndProc(ByVal hWnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long On Error Resume Next Dim txtcontrol As Object Set txtcontrol = GetControlFromhWnd(hWnd) If TypeOf txtcontrol Is TextBox Then If Msg <> WM_RBUTTONUP Then WndProc = CallWindowProc(CLng(txtcontrol.Tag), hWnd, Msg, wParam, lParam) Else 'Put custom menu here 'eg.
'form1.PopUpMenu form1.mnupopup

WndProc = 0
End If
If Msg = WM_NCDESTROY Then UnHookWindow (txtcontrol) Else 'oops WndProc = 0 End If End Function

Public Function GetControlFromhWnd(hWnd As Long) As Object
'Parameters: hWnd - Window Handle to object
'Returns: Object reference to cotrol with the hwnd 'Note this will not return forms

Dim frm As Form
Dim ctl As Control

For Each frm In Forms
For Each ctl In frm.Controls
If ctl.hWnd = hWnd Then
Set GetControlFromhWnd = ctl
Exit Function
End If
Next
Next
Beep
End Function

Public Sub HookWindow(hookobject As TextBox) If hookobject.Tag <> "" Then Exit Sub

'remember old window handler
hookobject.Tag = CStr(GetWindowLong(hookobject.hWnd, GWL_WNDPROC))

'install old window handler
Call SetWindowLong(hookobject.hWnd, GWL_WNDPROC, AddressOf WndProc) End Sub

Public Sub UnHookWindow(hookobject As TextBox) 'if no old window handler remembered - nothing to replace current one with If hookobject.Tag = "" Then Exit Sub

'replace ole window handler
Call SetWindowLong(hookobject.hWnd, GWL_WNDPROC, CLng(hookobject.Tag))

hookobject.Tag = ""
End Sub
Use

Use this code by passing a text box to the HookWindow procedure. To turn it off, use the UnHookWindow procedure. Make sure that you unhook all hooked windows at the end of the program. This involves putting the UnHookWindow code in all the places where the code can end. Most importantly, include the UnHookWindow code in the form's unload event on which the text box is placed. Also, never end the program by pressing the stop button in the VB IDE.

Notes:

  a.. Don't change the Tag property of the text box while it is hooked, as it uses this to store the old window handler. 
  b.. Sub classing: approach with caution. This example will probably crash VB if there is an error. Make sure that you save your work before running, and run with full compile if possible to reduce errors. We take no responsibility for any loss in data. 

Tip by John Percival


c0Title: Remove Unused Controls From Projects
c1Code:
If you usually load many controls into your VB project during development, you often have controls loaded that aren�t used any more by the time the project�s finished. If the controls were added to the project, they are stored in the VBP file regardless of whether they�re used. Here�s a quick way to clean up your VBP files so only the controls really being used are stored in your project: Open your VB project. Open your component window under the Project | Components menu or right-click on your toolbox and select Components. Hold down the Shift key and click on OK. All components not being used will be unclicked and removed from your project. VB6 developers: You must not eliminate references to controls you create at run time with the Controls.Add method.

�Mike Ellis, Charlotte, North Carolina


c0Title: Remove Unwanted System Menu Options
c1Code:
You�ve probably wanted to limit the normal operations of a form, such as resizing it, preventing it from being minimized or maximized, or allowing it to be closed only when you say so. The trick is to remove the control menu that corresponds to the functionality you want to limit. For example, when you remove the Size menu from the Control menu, the user won�t be able to resize your form. The same goes for Minimize, Maximize, and Move. Add this code to a BAS module:

 
' declarations necessary to work with the Control 
' Box menus
Private Declare Function RemoveMenu Lib _
     "User32" (ByVal hMenu As Long, _
     ByVal nPosition As Long, ByVal wFlags _
     As Long) As Long
Private Declare Function GetSystemMenu Lib _
     "User32" (ByVal hWnd As Long, ByVal revert _
     As Long) As Long
Private Declare Function GetWindowLong Lib _
     "User32" Alias "GetWindowLongA" (ByVal hWnd _
     As Long, ByVal lIndex As Long) As Long
Private Declare Function SetWindowLong Lib _
     "User32" Alias "SetWindowLongA" (ByVal hWnd _
     As Long, ByVal lIndex As Long, _
     ByVal dwNewLong As Long) As Long
 
Private Const MF_BYPOSITION = &H400
Private Const MAXIMIXE_BUTTON = &HFFFEFFFF
Private Const MINIMIZE_BUTTON = &HFFFDFFFF
Private Const GWL_STYLE = (-16)
 
' enumeration used when calling VBRemoveMenu
Public Enum RemoveMenuEnum
     rmMove = 1
     rmSize = 2
     rmMinimize = 3
     rmMaximize = 4
     rmClose = 6
End Enum
 
Public Sub VBRemoveMenu(ByVal TargetForm _
     As Form, ByVal MenuToRemove As RemoveMenuEnum)
     ' VBRemoveMenu
     ' this routine removes the specified menu item 
     ' from the control menu and the corresponding 
     ' functionality from the form
     '
     ' Parameters
     '     TargetForm - the form to perform the 
     '     operation on
     '     MenuToRemove - Enum specifying which menu 
     '     to remove
     Dim hSysMenu As Long
     Dim lStyle As Long
     hSysMenu = GetSystemMenu(TargetForm.hWnd, 0&)
     RemoveMenu hSysMenu, MenuToRemove, _
          MF_BYPOSITION
 
     Select Case MenuToRemove
          Case rmClose
               ' when removing the Close menu, also 
               ' remove the separator over it
               RemoveMenu hSysMenu, MenuToRemove - 1, _
                    MF_BYPOSITION
          Case rmMinimize, rmMaximize
               ' get the current window style
               lStyle = GetWindowLong( _
                    TargetForm.hWnd, GWL_STYLE)
 
               If MenuToRemove = rmMaximize Then
                    ' turn off bits for Maximize arrow 
                    ' button
                    lStyle = lStyle And MAXIMIXE_BUTTON
               Else
                    ' turn off bits for Minimize arrow 
                    ' button
                    lStyle = lStyle And MINIMIZE_BUTTON
               End If
 
               ' set the new window style
               SetWindowLong TargetForm.hWnd, _
                    GWL_STYLE, lStyle
     End Select
End Sub
 
     Then, when you load your form, call VBRemoveMenu with one of the menu enumerations:

     VBRemoveMenu Me, rmMaximize

Not only will the menu be removed from the Control menu, but the corresponding functionality of the form will be removed as well. Because the menus are removed by ordinal position, remove them in descending order if you want to remove more than one. For example: 

Private Sub Form_Load()
     Call VBRemoveMenu(Me, rmClose)
     Call VBRemoveMenu(Me, rmMaximize)
End Sub
 
�Earl Damron, Louisville, Kentucky


c0Title: Removing A Form's Title Bar
c1Code:
Compatible with: Visual Basic 3, Visual Basic 4.0 16-bit 
Applies to: Form 
  

--------------------------------------------------------------------------------
 

There might be times where you want to remove the title bar from a Form. The only way to do this is by using API calls. This routine will remove the title bar and also put it back. You might find this useful for creating dialog box type Forms.

Declare The Following

Declare Function GetWindowWord% Lib "User" (ByVal hWnd%, _
     ByVal nIndex%)
Declare Function SetWindowWord% Lib "User" (ByVal hWnd%, _
     ByVal nIndex%, ByVal wNewWord%)
Declare Function GetWindowLong& Lib "User" (ByVal hWnd%, _
     ByVal nIndex%)
Declare Function SetWindowLong& Lib "User" (ByVal hWnd%, _
     ByVal nIndex%, ByVal dwNewLong&)
Const GWW_ID = (-12)
Const GWL_STYLE = (-16)
Const WS_DLGFRAME = &H400000
Const WS_SYSMENU = &H80000
Const WS_MINIMIZEBOX = &H20000
Const WS_MAXIMIZEBOX = &H10000
Code

Sub ShowTitleBar (frmIn As Form, bShow As Integer)
     Static iOldMenu As Integer
     Static lSavedStyle As Long
     Dim lNewStyle As Long
     Dim R As Long
 
     If bShow Then
           'get the current style attributes
           lNewStyle = GetWindowLong&(frmIn.hWnd, GWL_STYLE)
 
           'set only the attributes that were removed earlier
           lNewStyle = lNewStyle Or lSavedStyle
 
           're-establish the menu
           If iOldMenu <> 0 Then
                 R = SetWindowWord%(frmIn.hWnd, GWW_ID, iOldMenu)
           End If
 
           'set the new style
           R = SetWindowLong&(frmIn.hWnd, GWL_STYLE, lNewStyle)
 
           'force Visual Basic to update the Form
           frmIn.Move frmIn.Left
           frmIn.Refresh
     Else
           'get the current style attributes
           lNewStyle = GetWindowLong&(frmIn.hWnd, GWL_STYLE)
 
           'determine whether the Form has a dialog frame,
           'a ControlBox, a minimize button, or a maximize
           'button and save this info for later use
           lSavedStyle = 0
           lSavedStyle = lSavedStyle Or (lNewStyle And _
                 WS_DLGFRAME)
           lSavedStyle = lSavedStyle Or (lNewStyle And _
                 WS_SYSMENU)
           lSavedStyle = lSavedStyle Or (lNewStyle And _
                 WS_MINIMIZEBOX)
           lSavedStyle = lSavedStyle Or (lNewStyle And _
                 WS_MAXIMIZEBOX)
 
           'remove the attributes for a dialog frame, a
           'ControlBox, a minimize button and a maximize button
           lNewStyle = lNewStyle And Not WS_DLGFRAME
           lNewStyle = lNewStyle And Not WS_SYSMENU
           lNewStyle = lNewStyle And Not WS_MINIMIZEBOX
           lNewStyle = lNewStyle And Not WS_MAXIMIZEBOX
 
           'is there a menu associated with this Form?
           iOldMenu = GetWindowWord%(frmIn.hWnd, GWW_ID)
           If iOldMenu <> 0 Then
                 'yes-zero it the menu handle
                 R = SetWindowWord%(frmIn.hWnd, GWW_ID, 0)
           End If
 
           'set the new style
           R = SetWindowLong&(frmIn.hWnd, GWL_STYLE, lNewStyle)
 
           'force Visual Basic to update the Form and get rid of the
           'title bar
           frmIn.Move frmIn.Left
           frmIn.Refresh
     End If
End Sub
Usage

To turn off the title bar, use:

ShowTitleBar Me, False

To turn it back on, use:

ShowTitleBar Me, True

This tip is reprinted from the VB Tips & Tricks Volume 1 book. 
Parts of this tip was submitted by: Bruce Guthals


c0Title: Rename short Citect page names using LFNs
c1Code:
'Renames the ctg files to their full page name
'does not handle the case where multiple files start with the same first 8 characters but any system using 8.3 file names should not
'have this case anyway
 

'first commandline argument is the project directory
'ie to fixup the page names in project bob the usage will be like
'ToLFN "C:\Program Files\Citect\CitectSCADA\User\Example"
 
 
 
'''''''''''''''''''''''''''''''''''''''''''
'Get the Command line arg
Dim sPath
'sPath = "C:\Program Files\Citect\CitectSCADA\User\Example"
sPath = WScript.Arguments.Item(0)
 
'Make ADO connection to project directory
Dim DBConn
Set DBConn = CreateObject("ADODB.Connection")
DBConn.Open "Driver={Microsoft dBASE Driver (*.dbf)};" & _
 "DriverID=277;" & _
 "Dbq=" & sPath
 
'open record set for pages.dbf
Dim rsPages
Set rsPages = CreateObject("ADODB.Recordset")
rsPages.open "Select * from Pages.DBF", DBConn, , , adCmdText
 

CheckForLPN
 

'check for pages with long page names
Function CheckForLPN
  rsPages.MoveFirst
    Do While Not rsPages.EOF
      If Len(rsPages.Fields("Name").Value)>8 Then
        'WScript.Echo(rsPages.Fields("Name").Value & " is > 8 characters")
        RenameCTGFile rsPages.Fields("Name").Value
      End If
      rsPages.MoveNext
    Loop
End Function
 
'If shortfilename exists and longfilename doesnot then rename short to long (not sure if case of filenames is of concern?)
Function RenameCTGFile(sName)
  Dim fso, f
  Set fso = CreateObject("Scripting.FileSystemObject")
  If fso.FileExists(sPath & "\" & Left(sName,8) & ".ctg") AND (FALSE = fso.FileExists(sPath & "\" & sName & ".ctg")) Then
    WScript.Echo(sName & " is stuffed up")
    Set f = fso.GetFile(sPath & "\" & Left(sName,8) & ".ctg")
    f.Move(sPath & "\" & sName & ".ctg")
  End If
End Function


c0Title: Resize that Form!
c1Code:
Need to resize that Form? Can't afford one of the commercial resize controls?

Don't worry, this could be your lucky day. This handy code snippet demonstrates how to add resizing capabilities to your Form in an instant.

The code consists of two main parts, the first in the Form_Load event, the second in Form_Resize.

The Form_Load code runs the first time your Form is loaded. It cycles through all your controls, storing individual dimensions in the Tag property of each. It also stores the height and width of the entire Form in two variables.

Now we have all our original dimensions stored, safe and sound.

The Form_Resize code runs whenever the Form is resized. It cycles through each control and resizes appropriately, depending on the new dimensions. In addition to changing the position, width and height, it also alters Font Size.

Just copy and paste the below code behind any Form. It is well commented and works brilliantly!

Note: This code will not work with the few, rare controls that don�t have Top, Left, Width, Height or Tag properties. The most common example of this is the Line control.

Code

Private lngFormWidth As Long
Private lngFormHeight As Long

Private Sub Form_Load()
    
    Dim Ctl As Control
    
    'Store form dimensions in variables
     
    lngFormWidth = ScaleWidth
    lngFormHeight = ScaleHeight
        
    'Store initial control dimensions
    'in the Tag property - with error
    'handling for those controls which
    'do not have properties such as Top
    '(eg, Line control)
    
    On Error Resume Next
    
    For Each Ctl In Me
        Ctl.Tag = Ctl.Left & " " & Ctl.Top & " " & _
            Ctl.Width & " " & Ctl.Height & " "
            Ctl.Tag = Ctl.Tag & Ctl.FontSize & " "
    Next Ctl
    
    On Error GoTo 0

End Sub

Private Sub Form_Resize()

    Dim D(4) As Double
    Dim i As Long

    Dim TempPoz As Long
    Dim StartPoz As Long

    Dim Ctl As Control
    Dim TempVisible As Boolean

    Dim ScaleX As Double
    Dim ScaleY As Double

        'Calculate the scale
        
    ScaleX = ScaleWidth / lngFormWidth
    ScaleY = ScaleHeight / lngFormHeight

    On Error Resume Next

    'Cycle through each control
        
    For Each Ctl In Me
        TempVisible = Ctl.Visible
        Ctl.Visible = False
        StartPoz = 1
            
        'Read data from the Tag property
             
        For i = 0 To 4
            TempPoz = InStr(StartPoz, Ctl.Tag, " ", _
                vbTextCompare)
                     
            If TempPoz > 0 Then
                D(i) = Mid(Ctl.Tag, StartPoz, _
                    TempPoz - StartPoz)
                StartPoz = TempPoz + 1
            Else
                D(i) = 0
            End If
                      
            'Move the control based on data
            'in the Tag property and the form
            'scale...
            
            Ctl.Move D(0) * ScaleX, D(1) * ScaleY, _
                D(2) * ScaleX, D(3) * ScaleY
            Ctl.Width = D(2) * ScaleX
            Ctl.Height = D(3) * ScaleY
            
            'Change font size
            
            If ScaleX < ScaleY Then
                   Ctl.FontSize = D(4) * ScaleX
            Else
                   Ctl.FontSize = D(4) * ScaleY
            End If
            
        Next i
        
        Ctl.Visible = TempVisible
        
    Next Ctl
    
    On Error GoTo 0
    
End Sub
Tip by Karl Moore


c0Title: Resize Forms Automatically
c1Code:
This class scales all the objects on a form as the user resizes it.

The main methods are Load and Resize. Call the Load method from the Form_Load event. This loads the size and position of each control on the form and saves it in a collection in the array. Call the Resize method from the Form_Resize event. This resizes all the controls in proportion to the form. It also scales the FontSize of each control (if applicable).

It is also possible to add and delete controls from the form at runtime. If you do that, just call the Add method so the control is scaled as the other controls are. Call the Remove method to when deleting a control. In the sample code, press Command1 to add a textbox, and Command2 to remove it.

Note: This code will not work with the few, rare controls that don�t have Top, Left, Width, Height or Tag properties. The most common example of this is the Line control.

For this example, create a form with a few controls on it, including Command1 and Command2.

Class code:

Option Explicit

Private Enum enuControlPosition
    cpcontrol = 1
    cpleft
    cptop
    cpwidth
    cpheight
    cpfontsize
End Enum

Private mControlPositions As Collection
Private mThisForm
Private mFormWidthOriginal As Double
Private mFormHeightOriginal As Double

Public Sub Load(ThisForm As Form)
    Dim Ctl As Control
    Dim pCtl As String      'holds object pointer
   
    Dim Index As Long
    Dim ControlPosition(1 To 6) As Variant
   
    Set mThisForm = ThisForm
   
    'Store form dimensions in variables
    mFormWidthOriginal = mThisForm.ScaleWidth
    mFormHeightOriginal = mThisForm.ScaleHeight

    On Error Resume Next    'Ignore properties that don't exist
   
    For Each Ctl In mThisForm
       
        Set ControlPosition(cpcontrol) = Ctl
        ControlPosition(cpleft) = Ctl.Left
        ControlPosition(cptop) = Ctl.Top
        ControlPosition(cpwidth) = Ctl.Width
        ControlPosition(cpheight) = Ctl.Height
        ControlPosition(cpfontsize) = Ctl.FontSize
       
        pCtl = ObjPtr(Ctl)
        mControlPositions.Add ControlPosition, Key:=pCtl

    Next Ctl
   
End Sub

Public Sub Resize()

    Dim VisibleOriginal As Boolean
    Dim ControlPosition As Variant
    Dim Ctl As Control
    Dim pCtl As Long

    Dim ScaleX As Double
    Dim ScaleY As Double
   
    'Calculate the scale
    ScaleX = mThisForm.ScaleWidth / mFormWidthOriginal
    ScaleY = mThisForm.ScaleHeight / mFormHeightOriginal

    On Error Resume Next

    'Cycle through each control
    For Each ControlPosition In mControlPositions
       
        Set Ctl = ControlPosition(cpcontrol)
       
        VisibleOriginal = Ctl.Visible
        Ctl.Visible = False
          
        Ctl.Move ControlPosition(cpleft) * ScaleX, _
                 ControlPosition(cptop) * ScaleY, _
                 ControlPosition(cpwidth) * ScaleX, _
                 ControlPosition(cpheight) * ScaleY
       
        If ScaleX < ScaleY Then
               Ctl.FontSize = ControlPosition(cpfontsize) * ScaleX
        Else
               Ctl.FontSize = ControlPosition(cpfontsize) * ScaleY
        End If
           
        Ctl.Visible = VisibleOriginal  'Restore previous visibility
       
    Next ControlPosition
   
End Sub

'Add position of new control to the scaling collection 'Size and position are assumed to be at original scale 'Object will be automatically redrawn at current scale Public Sub Add(Ctl As Control)

    Dim ControlPosition(1 To 6) As Variant
    Dim pCtl As String      'holds object pointer

    On Error Resume Next    'Ignore properties that don't exist
   
    Set ControlPosition(cpcontrol) = Ctl
    ControlPosition(cpleft) = Ctl.Left
    ControlPosition(cptop) = Ctl.Top
    ControlPosition(cpwidth) = Ctl.Width
    ControlPosition(cpheight) = Ctl.Height
    ControlPosition(cpfontsize) = Ctl.FontSize
   
    pCtl = ObjPtr(Ctl)
    mControlPositions.Add ControlPosition, Key:=pCtl
    Resize  'Rescale object to current form scale
   
End Sub

Public Sub Remove(Ctl As Control)
   
    Dim pCtl As String      'holds object pointer

    pCtl = ObjPtr(Ctl)
    mControlPositions.Remove pCtl
   
End Sub


Private Sub Class_Initialize()
    Set mControlPositions = New Collection End Sub

Private Sub Class_Terminate()
    Set mControlPositions = Nothing
End Sub


Form code:

Dim FormSizer As clsFormSizer

Dim WithEvents txtnewtextbox As VB.TextBox

Private Sub Form_Load()
    Set FormSizer = New clsFormSizer
    
    FormSizer.Load Me
End Sub

Private Sub Form_Resize()
    FormSizer.Resize
End Sub

Private Sub Command1_Click()
    Set txtnewtextbox = Controls.Add("VB.TextBox", "txtNewTextBox", Me)
    txtnewtextbox.Move 500, 500, 1000, 1000
    txtnewtextbox.Visible = True
    
    FormSizer.Add txtnewtextbox
End Sub

Private Sub Command2_Click()
    FormSizer.Remove txtnewtextbox
    Controls.Remove txtnewtextbox
    Set txtnewtextbox = Nothing
End Sub

Private Sub txtnewtextbox_Change()
    MsgBox "new textbox change event"
End Sub




c0Title: Restart / Exit Windows
c1Code:
Listed below is a subroutine that will quit windows in three different ways if needed. Passing 1 to it will reboot the computer, passing 2 will restart Windows, and passing 3 will exit Windows and return to DOS.

Declare

Declare Function ExitWindows Lib "User" (ByVal RestartCode As Long,ByVal DOSReturnCode As Integer) As Integer

Code

Sub ExitWin (ByVal nExitOption As Integer) Dim n As Integer 

n = MsgBox("Do you really want to exit Windows?", 36, "Exiting") 

     If n = 7 Then Exit Sub 'User chose NO 

     Select Case nExitOption 
           Case 1 
                 n = ExitWindows(67, 0) 'reboot the computer 
           Case 2 
                 n = ExitWindows(66, 0) 'restart Windows 
           Case 3 
                 n = ExitWindows(0, 0) 'exit Windows 
     End Select 

End Sub

Tip By: Brian Simper


c0Title: Restore Errant Focus to RichTextBox
c1Code:
If a RichTextBox control has the focus in a Multiple Document Interface (MDI) child form, it doesn�t properly regain the focus after your application loses and regains focus. To fix this, you must subclass the MDI form and watch for the WM_ACTIVATE event. Set a Public variable equal to the window handle of the RichTextBox in its GotFocus event, and set that variable to zero in its LostFocus event. Use the SetFocus API function to force the focus to the RichTextBox:

 
'Module Code:
Private Declare Function CallWindowProc Lib _
     "user32" Alias "CallWindowProcA" (ByVal _
     lpPrevWndFunc As Long, ByVal hWnd As Long, _
     ByVal Msg As Long, ByVal wParam As Long, _
     ByVal lParam As Long) As Long
Private Declare Function SetFocusAPI Lib _
     "user32" Alias "SetFocus" (ByVal hWnd As _
     Long) As Long
Public Declare Function SetWindowLong Lib _
     "user32" Alias "SetWindowLongA" (ByVal hWnd _
     As Long, ByVal nIndex As Long, ByVal _
     dwNewLong As Long) As Long
Private Declare Sub CopyMem Lib "kernel32" Alias _
     "RtlMoveMemory" (Destination As Any, Source _
     As Any, ByVal Length As Long)
 
Public Const WM_ACTIVATE = &H6
Public Const WA_INACTIVE = 0
Public Const GWL_WNDPROC = (-4)
Public origWndProc As Long
Public lFocusHandle As Long
 
Public Function AppWndProc(ByVal hWnd As Long, _
     ByVal Msg As Long, ByVal wParam As Long, _
     ByVal lParam As Long) As Long
     Select Case Msg
          Case WM_ACTIVATE
               If WordLo(wParam) <> WA_INACTIVE Then
                    If lFocusHandle Then SetFocusAPI _
                         lFocusHandle
               End If
     End Select
     AppWndProc = CallWindowProc(origWndProc, _
          hWnd, Msg, wParam, lParam)
End Sub
 
Private Function WordLo(LongIn As Long) As Integer
     Call CopyMem(WordLo, ByVal VarPtr(LongIn), 2)
End Function
 
'MDI Form code:
Private Sub MDIForm_Load()
     origWndProc = SetWindowLong(Me.hWnd, _
          GWL_WNDPROC, AddressOf AppWndProc)
End Sub
 
Private Sub MDIForm_Unload(Cancel As Integer)
     SetWindowLong Me.hWnd, GWL_WNDPROC, _
          origWndProc
End Sub
 
'MDIChild Form code:
Private Sub RichTextBox1_GotFocus()
     lFocusHandle = RichTextBox1.hWnd
End Sub
 
Private Sub RichTextBox1_LostFocus()
     lFocusHandle = 0
End Sub
�Matt Hart, Tulsa, Oklahoma


c0Title: Restrict Control Sizing
c1Code:
A few ActiveX controls, such as the Common Dialog Control, don�t allow themselves to be resized at design time because they�re typically invisible at run time. In these situations, you don�t want users to resize the ActiveX controls you create. You can also save some work for users by correctly sizing an ActiveX control when-ever they instantiate it on a form. Add this code to the User-Control_ Paint event to size your control correctly:

Private Sub UserControl_Paint()
	UserControl.Width = <width>
	UserControl.Height = <height>
End Sub


The UserControl_Resize event is another option for controls that are visible�and potentially painting frequently�at run time. But code around the inevitable recursion if you choose this other
route:


Private Sub UserControl_Resize()
	Static Busy As Boolean
	' Restrict size to desired dimensions

	If Not Busy Then
		Busy = True
		UserControl.Width = <width>
		UserControl.Height = <height>
		Busy = False
	End If
End Sub

�Narasimhan Padmanabhan, Bellevue, Washington


c0Title: Retrieve Additional File Properties
c1Code:
Several new properties associated with files were not available when the original VB file I/O statements and functions were designed. To easily access these new properties�DateLast-Accessed, Type, DateCreated, DateLastModified, Path, ShortPath, ShortName, and ParentFolder�you need to set a project refer-ence to the Microsoft Scripting Runtime. Set the reference by selecting Project from the VB main menu, then select References and check the Microsoft Scripting Runtime item. With the refer-ence established, you can add this code to access and display the new file properties:

Dim objFSO As New FileSystemObject
Dim objFileDetails as File

' Identify the file for which you want
' to display properties
Set objFileDetails = objFSO.GetFile("C:\config.sys")

' Move file properties associated with
' the above selected file into labels
' on your property form
lblFileType=objFileDetails.Type
lblDateCreated=objFileDetails.DateCreated
lblDateModified=objFileDetails.DateLastModified
lblDateAccessed=objFileDetails.DateLastAccessed

�January Smith, Houston, Texas


c0Title: Retrieve File Description
c1Code:
This routine takes a passed filename as an argument and generates
a description for it. It returns the same string as Windows Explorer
does when it has been set to Details view.

For example, if you pass the file c:\windows\win.com to the
routine, it returns the string �MS-DOS Application.� For files it can�t describe, the routine returns a generic message of �{filetype} File.�

If the file passed doesn�t exist, it returns �Unknown File,� but you
can change this easily. This code is especially useful for telling
beginning users what type of file they�re dealing with:

Private Declare Function SHGetFileInfo Lib "shell32.dll" Alias "SHGetFileInfoA" (ByVal pszPath As String, ByVal dwFileAttributes As Long, psfi As SHFILEINFO, ByVal cbFileInfo As Long, ByVal uFlags As Long) As Long

Private Const SHGFI_TYPENAME = &H400
Private Const MAX_PATH = 260

Private Type SHFILEINFO
	hIcon As Long
	iIcon As Long
	dwAttributes As Long
	szDisplayName As String * MAX_PATH
	szTypeName As String * 80
End Type

Public Function GetFileType(lpStrFile As String) As String
	Dim sfi As SHFILEINFO

	' Make API Call to fill structure with
	' information
	If SHGetFileInfo(lpStrFile, 0, sfi, Len(sfi), SHGFI_TYPENAME) Then
		' Return filetype string
		GetFileType = Left$(sfi.szTypeName, InStr(sfi.szTypeName, vbNullChar) - 1)
	Else
		' If failed then return "Unknown File"
		GetFileType = "Unknown File"
	End If
End Function

�Adam Lanzafame, Adelaide, South Australia, Australia


c0Title: Retrieve File Version Information
c1Code:
VB4 32, VB5, VB6

Level: Intermediate

Retrieve File Version Information

Win32 file images can contain a file version resource that stores
product and version information about the file. The version num-ber
is actually four 16-bit values typically displayed using dot
notation (such as 4.0.9.4566). You can use this information when
determining whether one file is newer or older than another.
This code implements the GetVersionInfo procedure in a stan-dard
BAS module. Pass the name of a file to GetVersionInfo, and a
dot-formatted string of the version number returns, if available, or
�N/A� returns if the file does not contain a version resource:

Private Type VS_FIXEDFILEINFO
dwSignature As Long
dwStrucVersion As Long
dwFileVersionMSl As Integer
dwFileVersionMSh As Integer
dwFileVersionLSl As Integer
dwFileVersionLSh As Integer
dwProductVersionMSl As Integer
dwProductVersionMSh As Integer
dwProductVersionLSl As Integer
dwProductVersionLSh As Integer
dwFileFlagsMask As Long
dwFileFlags As Long
dwFileOS As Long
dwFileType As Long
dwFileSubtype As Long
dwFileDateMS As Long
dwFileDateLS As Long
End Type

Private Declare Function GetFileVersionInfo Lib "Version.dll" Alias "GetFileVersionInfoA" (ByVal lptstrFilename As String, ByVal dwHandle As Long, ByVal dwLen As Long, lpData As Any) As Long

Private Declare Function GetFileVersionInfoSize Lib "Version.dll" Alias "GetFileVersionInfoSizeA" (ByVal lptstrFilename As String, lpdwHandle As Long) As Long

Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (dest As Any, src As Long, ByVal length As Long)

Private Declare Function VerQueryValue Lib "Version.dll" Alias "VerQueryValueA" (pBlock As Any, ByVal lpSubBlock As String, lplpBuffer As Any, puLen As Long) As Long

Public Function GetVersionInfo(ByVal sFile As String) As String

	Dim lDummy As Long
	Dim sBuffer() As Byte
	Dim lBufferLen As Long, lVerPointer As Long
	Dim lVerBufferLen As Long
	Dim udtVerBuffer As VS_FIXEDFILEINFO

	' Default return value
	GetVersionInfo = "N/A"

	' Attempt to retrieve version resource
	lBufferLen = GetFileVersionInfoSize(sFile, lDummy)

	If lBufferLen > 0 Then
		ReDim sBuffer(lBufferLen)
		If GetFileVersionInfo(sFile, 0&, lBufferLen, sBuffer(0)) <> 0 Then
			If VerQueryValue(sBuffer(0), "\", lVerPointer, lVerBufferLen) <> 0 Then
				CopyMemory udtVerBuffer, ByVal lVerPointer, Len(udtVerBuffer)

				With udtVerBuffer
					GetVersionInfo = .dwFileVersionMSh & "." & .dwFileVersionMSl & "." & .dwFileVersionLSh & "." & .dwFileVersionLSl
				End With
			End If
		End If
	End If
End Function

�James D. Murray, Huntington Beach, California


c0Title: Return Empty Arrays Too
c1Code:
With VB6 came the ability to return arrays from functions. Returning
an uninitialized array is a problem because there is no easy way�
other than error-trapping�to find whether an array has been
dimensioned. Also, ReDim myArr(-1) does not work. You can use the
Split function to return an empty array�one with no elements and
no data�and an LBound of 0 and an UBound of -1. This practice
simplifies code for looping through the returned array:


Private Function Foo(args...) As String()
	Dim myArr() As String

	' Initialize array dimensions as 0 to -1
	myArr = Split("")

	If Condition Then
		ReDim myArr(n)
		' further processing...
	End If

	Foo = myArr
End Function


Here, no additional checking is required to use Foo. But omitting
the call to Split can lead to a �Subscript out of range� error in a
routine that attempts to use Foo�s return; when you use Split to
establish an empty array, this loop is simply skipped over:

Dim i As Integer
Dim retArr() As String
retArr = Foo

For i = LBound(retArr) To _
	UBound(retArr)
	' Does not execute as LBound > UBound.
Next i

�Anand Likhite, Orlando, Florida


c0Title: Return Fractional Part of a Number
c1Code:
No native VB function returns a fractional part of a decimal number. However, by subtracting the whole portion, obtained with Fix, from the original value, you can derive the fractional portion easily. Return the absolute value of this calculation to remove unwanted negatives:

Public Function Frac(ByVal Value As Double) As Double
	Frac = Abs(Value - Fix(Value))
End Function

�William Powell Jr., Lanham, Maryland


c0Title: Reusable Splash / About Screen
c1Code:
In VB4 you can create a single Splash Screen or About Screen that works with any program and automatically keeps itself up to date using the new properties of the App Object. Create your form and make an attractive arrangement of label controls on it. Then add code such as this to the Load Event:

Label1.Caption = App.ProductName
Label2.Caption = App.FileDescription
Label3.Caption = "Version " & App.Major & "." _ & App.Minor & "." & App.Revision _ & " (" & Format$(FileDateTime(App.Path & "\" & _ App.EXEName & ".EXE"), "Short Date") & " - " & _ (FileLen(App.Path & "\" & App.EXEName _ & ".EXE")) & " bytes)"
Label4.Caption = App.LegalCopyright
Label5.Caption = App.CompanyName
Label6.Caption = App.Title
Label7.Caption = App.LegalTrademarks
Label8.Caption = App.Comments
For graphics you can use your company�s logo or put a blank PictureBox on the form, create the graphic as a separate file, and add this code to the Load Event:

Picture1.Picture = LoadPicture(App.Path & "\logo.bmp")

There are two cautions, however: � Until the first time you compile your code, there will be no EXE file, so the FileLen and FileDateTime functions will fail. To avoid this problem, create a dummy file with the project name EXE. If any of the referenced App properties are not set the form will load fine in the development environment but will generate the run time error "Resource with identifier �Version� not found" when compiled and run as an EXE (the error will say "Version" no matter which property is not set). App properties are set from the not-too-obvious location of the Options button of the Make EXE menu selection of the File menu. Once you set the properties they will be saved with the project and do not need to be set each time you make the EXE file.

�Daniel R. Nolte


c0Title: Reversing a String
c1Code:
This tip demonstrates how to reverse the position of the characters in a string. For example, the string "String" will be converted to "gnirtS".

Function Code

Public Function reversestring(revstr As String) As String
	'revstr: String to reverse
	'Returns: The reverse string

	Dim doreverse As Long

	reversestring = ""

	For doreverse = Len(revstr) To 1 Step -1
		reversestring = reversestring & Mid$(revstr, doreverse, 1)
	Next

End Function


Use


Dim strResult As String
strResult = reversestring("String")
MsgBox strResult


Tip by John Percival


c0Title: RIGHT-JUSTIFY MENU ITEMS
c1Code:
Menu items you add to your VB applications will be left justified by default. If you wish to have one or more of your menus rightjustified, you can change it at run time. The trick is to add a backspace character to the caption of your menu. Because the backspace character is a Chr$(8), it cannot be entered in design mode. In the Form_Load() or other initialization routine, add this code:

Sub Form_Load ()
    mnuHelp.Caption = Chr$(8) & mnuHelp.Caption
End Sub

This code right-justifies the mnuHelp menu item. Right- justified menu items are not part of the GUI design standard set forth by Microsoft, however, so you should avoid them unless you have a good reason for using that style.

�Paul D. Sherriff/Visual Basic Power Guides



c0Title: Running Programs At StartUp
c1Code:
If you have a program that you want to run automatically when Windows starts, instead of making a shortcut in the StartUp Menu, you can make the following Registration Database entries: 
Run Programs During Windows StartUp

Find the key:

HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Run

Add a new String Value. Give the value any name, usually the name of the program. Then double-click on it and type in the path, file name and any command line parameters. For example:

Value Name = Notepad

Value Data = c:\windows\notepad.exe

Run Programs When Loading A User

Do the same as listed above, but use the key:

HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run 

Tip Submitted By: Yatir Halevi 


c0Title: Save and load the contents of a TreeView to and from a file
c1Code:
How do you save and load the contents of a TreeView to and from a file? I've been asked this question several times and I've also seen it pop up a few times on the VB-World Q and A forums so I thought it would be useful here in the tips library.

This code makes use of the Contents property of a PropertyBag object. This property was introduced in VB6 so VB5 users can't use these routines I'm afraid.

What it does

The code consists of two public Sub routines called SaveTree and LoadTree. The SaveTree procedure takes 3 arguments:

  1.. The file name (including the path) to where you want the file to be saved. The TreeView control of which contents you want to save. 
  2.. The root node: The node you want the save to begin with. You can pass any valid node and the procedure will save the contents from that node and below. 
  3.. The SaveTree procedure calls a private Sub called FillPropBag which is doing the actual job of filling the PropertyBag that is saved. It does this in a recursive manner. 
The LoadTree procedure takes two arguments: the file name you want to load and The TreeView that you want to populate.

Copy the following code into a BAS module or even better add the code to a Class module and change the code so the FileName and TreeView arguments are properties instead.

Have Fun!
Joacim Andersson (joacim@programmer.net)

'-------------------------------
Private Sub FillPropBag(pb As PropertyBag, tvw As MSComctlLib.TreeView, Root As MSComctlLib.Node)

	Static nCount As Long
	Dim nde As MSComctlLib.Node
	Dim nIndex As Long

	If pb Is Nothing Then
	Set pb = New PropertyBag
	nCount = 0
	pb.WriteProperty "NumberOfNodes", tvw.Nodes.Count, 0
	End If
	On Error Resume Next
	pb.WriteProperty "Text" & nCount, Root.Text
	nIndex = Root.Parent.Index
	If nCount = 0 Then
	nIndex = 0
	End If
	pb.WriteProperty "Parent" & nCount, nIndex, 0
	pb.WriteProperty "Key" & nCount, Root.Key, ""
	pb.WriteProperty "Index" & nCount, Root.Index, 0
	pb.WriteProperty "Image" & nCount, Root.Image, vbEmpty
	pb.WriteProperty "SelectedImage" & nCount, _
          Root.SelectedImage, vbEmpty
	Set nde = Root.Child
	If nde Is Nothing Then
	Set nde = Root.Next
	If nde Is Nothing Then
		Exit Sub
	End If
	End If
	nCount = nCount + 1
	FillPropBag pb, tvw, nde
End Sub

Public Sub SaveTree(sFilename As String, tvw As MSComctlLib.TreeView, Root As MSComctlLib.Node)
  
  Dim pb As PropertyBag
  Dim bArr() As Byte
  Dim iFile As Integer

  FillPropBag pb, tvw, Root
  iFile = FreeFile
  Open sFilename For Binary Access Write As #iFile
  bArr = pb.Contents
  Put #iFile, , bArr
  Close #iFile
End Sub

Public Sub LoadTree(sFilename As String, tvw As MSComctlLib.TreeView)
  
  Dim pb As PropertyBag
  Dim bArr() As Byte
  Dim iFile As Integer
  Dim nde As MSComctlLib.Node
  Dim nCount As Long
  Dim nNumOfNodes As Long
  Dim nIndex As Long
  Dim sText$, sKey$
  Dim nParent As Long
  Dim vImage, vSelectedImage

  iFile = FreeFile
  Open sFilename For Binary Access Read As #iFile
  ReDim bArr(LOF(iFile)) As Byte
  Get #iFile, , bArr
  Close #iFile
  Set pb = New PropertyBag
  pb.Contents = bArr
  tvw.Nodes.Clear
  nNumOfNodes = pb.ReadProperty("NumberOfNodes", 0)
  If nNumOfNodes = 0 Then
    Exit Sub
  End If
  Do While nCount < nNumOfNodes
    nParent = pb.ReadProperty("Parent" & nCount, 0)
    nIndex = pb.ReadProperty("Index" & nCount, 0)
    sKey = pb.ReadProperty("Key" & nCount, "")
    sText = pb.ReadProperty("Text" & nCount, "")
    vImage = pb.ReadProperty("Image" & nCount, vbEmpty)
    vSelectedImage = pb.ReadProperty("SelectedImage" _
      & nCount, vbEmpty)
    If nParent Then
      tvw.Nodes.Add tvw.Nodes(nParent), tvwChild, sKey, _
        sText, vImage, vSelectedImage
    Else
      tvw.Nodes.Add , , sKey, sText, vImage, vSelectedImage
    End If
    nCount = nCount + 1
  Loop
End Sub

Tip by Joacim Andersson


c0Title: Save Expensive Heap Allocations
c1Code:
Fixed-size arrays in local variables use a stack-allocated descriptor as expected, but all the data for an array is allocated on the heap. However, fixed-size arrays embedded in structures are fully stack-allocated. This means that you can save yourself expensive heap allocations by defining a (Private) type with a single fixed-size array element and using a UDT-typed variable in place of the local fixed-size array. You can optimize the number of allocations you need to load a standard module or create a class instance using the same technique at module-level.

�Matt Curland, Redmond, Washington


c0Title: Save Forms� Size and Location at Run Time
c1Code:
You�ve noticed how some apps display forms and toolboxes in the same location and size as when you last closed them. Here�s some simple code that gives your VB app the same effect by using the Registry. First, fill in an appropriate Tag property for your form at design time�something like Main Application Form or Color Tool Box. Then keep a global string constant called ApplicationName that holds the title for your application. It�s used here to distinguish the Registry key, but it can also be used for error messages. Place this line in a module:

 
Public Const ApplicationName = "My Application Name"


Finally, place this code in a module:


Public Sub SaveFormDisplaySettings(frm As Form)

       If frm.Tag = "" Then Exit Sub

       SaveSetting ApplicationName, frm.Tag & " Display Settings", "Top", Str(frm.Top)
       SaveSetting ApplicationName, frm.Tag & " Display Settings", "Left", Str(frm.Left)

       SaveSetting ApplicationName, frm.Tag & " Display Settings", "Height", Str(frm.Height)

       SaveSetting ApplicationName, frm.Tag & " Display Settings", "Width", Str(frm.Width)

End Sub

 

Public Sub LoadFormDisplaySettings(frm As Form)

       Dim FormSettings As Variant
       Dim intSettings As Integer

       If frm.Tag = "" Then Exit Sub

       FormSettings = GetAllSettings(ApplicationName, frm.Tag & " Display Settings")

       If IsEmpty(FormSettings) Then Exit Sub

       For intSettings = LBound(FormSettings, 1) To UBound(FormSettings, 1)

       Select Case FormSettings(intSettings, 0)

            Case "Left"
			frm.Left = Val(FormSettings(intSettings, 1))
             Case "Top"
                    frm.Top = Val(FormSettings(intSettings, 1))
             Case "Height"
                    frm.Height = Val(FormSettings(intSettings, 1))
             Case "Width"
                    frm.Width = Val(FormSettings(intSettings, 1))
       End Select

       Next intSettings

End Sub

 

Add this line to the Form_Load events of the forms you want to save:

 

Call LoadFormDisplaySettings(Me)

 

Add this line to the Form_Unload events:

 

Call SaveFormDisplaySettings(Me)

 

     Note one side effect: These Registry settings remain in the Registry even after the application has been uninstalled. They�re stored at or below HKEY_CURRENT_USER\Software\VB and VBA Program Settings\My Application Name\.

�Kevin Fizz, Reading, Pennsylvania


c0Title: Screensaver Class
c1Code:
This sample code consists of three files. A form for the screen saver itself, that just displays a big "X" in the example. A class and module that provide the necessary support for all of the screen saver specific functionality. This is a very complete framework, it supports the basic screen saver functionality, as well as support for the standard password dialog under Win9x, and it displays a dummy settings message box, which can easily be replaced with a more complex settings screen. It even supports the small preview window in the display properties dialog.

      I've used this to create a new mail display, and have been quite pleased with the results.

      4/20/2000: Added some logic to block SC_SCREENSAVE messages, to prevent problems on NT. 
        
      Code: 
     ' ---------------------------------------------------------------------
' Begin: From frmScreenSaver

'This is the screen saver itself.  Because all of the screen saver code ' is in mod/clsScreenSaver, this is just concerned with what to draw on ' the screen while it's displayed.  A couple of notes however.  First, ' this form should have the following properties set:
'
'  BorderStyle = 0 - None
'  BackColor = (Black)
'  KeyPreview = True
'
' Also, you're responsible for tracking the mouse cursor in any child ' controls.  Ideally, just put the following in any of the child's ' _MouseMove events:
'
'     PostMessage Me.hwnd, WM_MOUSEMOVE, 0, 0
'
'  From there the framework will close the screensaver if necessary '
'  Finally, make sure and set the start up to Sub Main()

Option Explicit

Private Sub Form_Paint()
    'Just draw a stupid 'X' on the form to verify it's size and
    ' and position both full screen and in the display
    ' properties dialog.

    Line (0, 0)-(Width, Height), vbWhite
    Line (Width, 0)-(0, Height), vbWhite

End Sub

' End: From frmScreenSaver
' ---------------------------------------------------------------------

' ---------------------------------------------------------------------
' Begin: Module modScreenSaver

Option Explicit

'Mutex name to prevent multiple versions Public Const MUTEX_NAME = "Scott's Screen Saver Mutex"

'ID of timer to dismiss password window after 30 seconds Public Const TIMER_ID = 10

'Windows API
Type POINTAPI
    X As Long
    Y As Long
End Type

Type RECT
    Left As Long
    Top As Long
    Right As Long
    Bottom As Long
End Type

Type OSVERSIONINFO
    dwOSVersionInfoSize As Long
    dwMajorVersion As Long
    dwMinorVersion As Long
    dwBuildNumber As Long
    dwPlatformId As Long
    szCSDVersion As String * 128
End Type

Declare Function GetCursorPos Lib "user32" (lpPoint As POINTAPI) As Long Declare Function SetWindowPos Lib "user32" (ByVal hwnd As Long, ByVal _
        hWndInsertAfter As Long, ByVal X As Long, ByVal Y As Long, _
        ByVal cx As Long, ByVal cy As Long, ByVal wFlags As Long) As _
        Long
Declare Function PostMessage Lib "user32" Alias "PostMessageA" (ByVal _
        hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal _
        lParam As Long) As Long
Declare Function ShowCursor Lib "user32" (ByVal bShow As Long) As Long Declare Function GetTickCount Lib "kernel32" () As Long Declare Function CallWindowProc Lib "user32" Alias "CallWindowProcA" _
        (ByVal lpPrevWndFunc As Long, ByVal hwnd As Long, ByVal Msg As _
        Long, ByVal wParam As Long, ByVal lParam As Long) As Long Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" _
        (ByVal hwnd As Long, ByVal nIndex As Long, ByVal dwNewLong As _
        Long) As Long
Declare Function GetWindowLong Lib "user32" Alias "GetWindowLongA" _
        (ByVal hwnd As Long, ByVal nIndex As Long) As Long Declare Function SetParent Lib "user32" (ByVal hWndChild As Long, ByVal _
        hWndNewParent As Long) As Long
Declare Function GetWindowRect Lib "user32" (ByVal hwnd As Long, lpRect _
        As RECT) As Long
Declare Function CreateMutex Lib "kernel32" Alias "CreateMutexA" _
        (lpMutexAttributes As Any, ByVal bInitialOwner As Long, ByVal _
        lpName As String) As Long
Declare Function ReleaseMutex Lib "kernel32" (ByVal hMutex As Long) As _
        Long
Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As _
        Long
Declare Function PwdChangePassword Lib "mpr" Alias "PwdChangePasswordA" _
        (ByVal lpcRegkeyname As String, ByVal hwnd As Long, ByVal _
        uiReserved1 As Long, ByVal uiReserved2 As Long) As Long Declare Function VerifyScreenSavePwd Lib "password.cpl" (ByVal hwnd As _
        Long) As Long
Declare Function GetVersionEx Lib "kernel32" Alias "GetVersionExA" _
        (lpVersionInformation As OSVERSIONINFO) As Long Declare Function SystemParametersInfo Lib "user32" Alias _
        "SystemParametersInfoA" (ByVal uAction As Long, ByVal uParam _
        As Long, lpvParam As Long, ByVal fuWinIni As Long) As Long Declare Function SetTimer Lib "user32" (ByVal hwnd As Long, ByVal _
        nIDEvent As Long, ByVal uElapse As Long, ByVal lpTimerFunc As _
        Long) As Long
Declare Function KillTimer Lib "user32" (ByVal hwnd As Long, ByVal _
        nIDEvent As Long) As Long
Declare Sub keybd_event Lib "user32" (ByVal bVk As Byte, ByVal bScan As _
        Byte, ByVal dwFlags As Long, ByVal dwExtraInfo As Long)

Public Const KEYEVENTF_KEYUP = &H2
Public Const VK_ESCAPE = &H1B
Public Const WM_TIMER = &H113
Public Const SPI_SCREENSAVERRUNNING = 97 Public Const VER_PLATFORM_WIN32_NT = 2 Public Const WS_CHILD = &H40000000 Public Const GWL_WNDPROC = (-4) Public Const WM_MOUSEMOVE = &H200 Public Const WM_CLOSE = &H10 Public Const HWND_TOPMOST = -1 Public Const SWP_SHOWWINDOW = &H40 Public Const GWL_STYLE = (-16) Public Const ERROR_ALREADY_EXISTS = 183& Public Const WM_ACTIVATE = &H6 Public Const WM_ACTIVATEAPP = &H1C Public Const WM_SYSCOMMAND = &H112 Public Const SC_SCREENSAVE = &HF140&


Private m_ScreenSaver As clsScreenSaver 'Screen saver class, mostly  ' used to catch the form's events

Private m_hWnd As Long 'hWnd of main form Private m_lpPrevProc As Long 'Previous window procedure Private m_hMutex As Long 'Handle to mutex to prevent multiple instances

Sub Main()
    
    DoEvents 'Gets rid of stray windows events
    
    Dim rt As RECT
    Dim hwnd As Long
    
    Select Case LCase(Mid(Command$, 1, 2))
        Case "/a" 'Password
            
            'Trigger the change password dialog
            PwdChangePassword "SCRSAVE", Val(Mid(Command$, 3)), 0&, 0&
            
        Case "", "/s" 'Show screen saver
            
            'Create a mutex to prevent two instances of the screen
            ' saver starting.  This is used vs App.PrevInstance
            ' because I want to allow a preview window and the
            ' screen saver to co-exist
            m_hMutex = CreateMutex(ByVal 0, True, MUTEX_NAME & _
                                   "[Saver]")
                                   
            'Did the mutex already exist?
            If Err.LastDllError = ERROR_ALREADY_EXISTS Then
                ReleaseMutex m_hMutex
                CloseHandle m_hMutex
                Exit Sub
            End If
            If m_hMutex = 0 Then
                Exit Sub
            End If
            
            'Create the screensaver class and point it to our form
            Set m_ScreenSaver = New clsScreenSaver
            Set m_ScreenSaver.Form = frmScreenSaver
            
            Load frmScreenSaver
            
            'Display the form full-screen and topmost
            SetWindowPos frmScreenSaver.hwnd, HWND_TOPMOST, 0, 0, _
                         Screen.Width / Screen.TwipsPerPixelX, _
                         Screen.Height / Screen.TwipsPerPixelY, _
                         SWP_SHOWWINDOW
            
        Case "/p" 'Tiny preview window the display properties
            
            'Create the screensaver class and let it know
            ' that it's in a preview window
            Set m_ScreenSaver = New clsScreenSaver
            Set m_ScreenSaver.Form = frmScreenSaver
            m_ScreenSaver.IsPreview = True
            
            'Make the main form a child of the display properties
            SetWindowLong frmScreenSaver.hwnd, GWL_STYLE, _
                          GetWindowLong(frmScreenSaver.hwnd, GWL_STYLE) _
                          Or WS_CHILD
            SetParent frmScreenSaver.hwnd, Val(Mid(Command$, 3))
            
            'Get the size of the little display window
            GetWindowRect Val(Mid(Command$, 3)), rt
            
            Load frmScreenSaver
            'Show the window, not topmost, and the size specified
            SetWindowPos frmScreenSaver.hwnd, 0, 0, 0, rt.Right - _
                         rt.Left, rt.Bottom - rt.Top, SWP_SHOWWINDOW
            
        Case "/c" 'Settings
            
            'Use a mutex to prevent multiple display settings
            ' dialogs
            m_hMutex = CreateMutex(ByVal 0, True, MUTEX_NAME & _
                       "[Setting]")
            If Err.LastDllError = ERROR_ALREADY_EXISTS Then
                ReleaseMutex m_hMutex
                CloseHandle m_hMutex
                Exit Sub
            End If
            If m_hMutex = 0 Then
                Exit Sub
            End If
            
            ' I was lazy, you'll want to replace this with your
            '  own form
            MsgBox "No settings", vbInformation, "Sorry"
            
            ReleaseMutex m_hMutex
            CloseHandle m_hMutex
    End Select
    
End Sub

Public Sub Hook(hwnd As Long)
    'Procedure to subclass the main form
    
    m_hWnd = hwnd
    m_lpPrevProc = SetWindowLong(m_hWnd, GWL_WNDPROC, AddressOf WndProc)
    
End Sub

Public Sub Unhook()
    'Cease subclassing of the main form
    
    SetWindowLong m_hWnd, GWL_WNDPROC, m_lpPrevProc
    
    ReleaseMutex m_hMutex
    CloseHandle m_hMutex
    
End Sub

Public Function WndProc(ByVal hwnd As Long, ByVal uMsg As Long, ByVal _
       wParam As Long, ByVal lParam As Long) As Long
    ' This is the subclassed window procedure, basically, look for
    '  loss of focus, and the timer to dismiss the password dialog

    Dim bBlockMsg As Boolean 'Block this message from being sent    

    Select Case uMsg
        Case WM_SYSCOMMAND
            If wParam = SC_SCREENSAVE Then
                'Stop WinNT from trying to re-enable the screen saver
                ' and disabling it if the password is on
                bBlockMsg = True
            End If
        Case WM_ACTIVATE, WM_ACTIVATEAPP
            If wParam = 0 Then
                PostMessage m_hWnd, WM_MOUSEMOVE, 0, 0
            End If
        Case WM_TIMER
            If m_ScreenSaver.IsPasswordVisible Then
                If GetTickCount - m_ScreenSaver.PasswordShown > 30000 _
                      Then
                    keybd_event VK_ESCAPE, 0, 0, 0
                    keybd_event VK_ESCAPE, 0, KEYEVENTF_KEYUP, 0
                End If
            End If
    End Select
    
    'Pass the message to VB's window procedure if it's not blocked
    If Not bBlockMsg Then
        WndProc = CallWindowProc(m_lpPrevProc, hwnd, uMsg, _
                  wParam, lParam)
    End If
    
End Function

' End: Module modScreenSaver
' ---------------------------------------------------------------------

' ---------------------------------------------------------------------
' Begin: Class Module clsScreenSaver

Option Explicit

Private WithEvents m_Form As Form 'The form that this class ' is watching

Private m_bPreview As Boolean 'Are we in the display properies?

Private m_ptLast As POINTAPI 'The last seen mouse position Private m_bLast As Boolean 'Is the last seen mouse position valid Private m_nTickLoad As Long 'Time that the form loaded Private m_bPasswordVisible As Boolean 'Pwd dialog displayed?
Private m_nPasswordShown As Long 'Time pwd dialog shown

Public Property Get PasswordShown() As Long
    PasswordShown = m_nPasswordShown
End Property

Public Property Get IsPasswordVisible() As Boolean
    IsPasswordVisible = m_bPasswordVisible End Property

Public Property Set Form(newForm As Form)
    Set m_Form = newForm
End Property

Public Property Let IsPreview(bPreview As Boolean)
    m_bPreview = bPreview
End Property

Public Property Get IsPreview() As Boolean
    IsPreview = m_bPreview
End Property

Private Sub Class_Initialize()
    
    'Just make note of the display time, that way we can ignore
    ' mouse and keyboard events for 1/2 a second, so the stray
    ' events don't trigger the unload
    
    m_nTickLoad = GetTickCount
    
End Sub

Private Sub m_Form_KeyUp(KeyCode As Integer, Shift As Integer)
    
    'Key press, call the generic unload procedure
    TriggerUnload
    
End Sub

Private Sub m_Form_Load()
    
    If Not m_bPreview Then
        Hook m_Form.hwnd ' Subclass this form to watch for
                        ' the timer and focus events
        
        ShowCursor False 'Hide the mouse cursor
        Dim nTemp As Long
        ' Let windows know we're running, primarily to
        '  prevent the system keys
        SystemParametersInfo SPI_SCREENSAVERRUNNING, 1, nTemp, 0
    End If
    
End Sub

Private Sub m_Form_MouseMove(Button As Integer, Shift As Integer, X As _
        Single, Y As Single)
    
    If Not m_bLast Then
        'If the last mouse cursor position hasn't been used yet,
        ' save the current position
        GetCursorPos m_ptLast
        m_bLast = True
        Exit Sub
    End If
    
    'Compare the last with the current, if they differ, trigger
    ' the generic unload procedure
    Dim pt As POINTAPI
    GetCursorPos pt
    If pt.X <> m_ptLast.X Or pt.Y <> m_ptLast.Y Then
        TriggerUnload
        m_ptLast.X = pt.X
        m_ptLast.Y = pt.Y
    End If
    
End Sub

Private Sub m_Form_MouseUp(Button As Integer, Shift As Integer, X As _
        Single, Y As Single)
    
    'Mouse button pressed, trigger the generic unload procedure
    TriggerUnload
    
End Sub

Private Sub m_Form_QueryUnload(Cancel As Integer, UnloadMode As Integer)
    
    'Make sure we don't re-enter ourselves
    Static bInSub As Boolean
    
    If bInSub Then
        Exit Sub
    End If
    bInSub = True
    
    'This just displays the password dialog if the screen saver
    ' isn't being run in the display properties dialog.
    If Not m_bPreview Then
        If Not VerifyPassword Then
            Cancel = True
        Else
            Exit Sub
        End If
    End If
    
    ' Make note of the time incase the password was wrong
    m_nTickLoad = GetTickCount
    bInSub = False
    
End Sub

Private Sub m_Form_Unload(Cancel As Integer)
    'Undo what we did in Form_Load
    
    If Not m_bPreview Then
        Unhook
        
        ShowCursor True
        Dim nTemp As Long
        SystemParametersInfo SPI_SCREENSAVERRUNNING, False, nTemp, 0
    End If
    
End Sub

Private Sub TriggerUnload()
    
    ' Check to see if we should unload because of user actions,
    '  if so, then check to see if it's been atleast 1/2 a
    '  second since we started.
    
    If m_bPreview Then
        Exit Sub
    End If
    
    If GetTickCount - m_nTickLoad > 500 Then
        'Just use postmessage to avoid the problems with
        ' forcing and unload this very instant
        PostMessage m_Form.hwnd, WM_CLOSE, 0, 0
    End If
    
End Sub

Public Function VerifyPassword() As Boolean
    'Display and verify the users password
    
    'Check to see if this is NT.  If it is, let the os handle
    ' the password
    Dim osv As OSVERSIONINFO
    osv.dwOSVersionInfoSize = Len(osv)
    GetVersionEx osv
    
    If osv.dwPlatformId = VER_PLATFORM_WIN32_NT Then
        VerifyPassword = True
        Exit Function
    End If
    
    ShowCursor True 'The cursor should be visible for this
    m_bPasswordVisible = True
    m_nPasswordShown = GetTickCount
    SetTimer m_Form.hwnd, TIMER_ID, 1000, 0 'Used to dismiss
      'the password dialog after 30 seconds
    
    'Call the DLL function and return it's result
    VerifyPassword = (VerifyScreenSavePwd(m_Form.hwnd) <> 0)
    
    'Kill the timer, and hide the cursor (if the password is
    ' right, the cursor will be redisplayed when the form is
    ' killed
    KillTimer m_Form.hwnd, TIMER_ID
    m_bPasswordVisible = False
    ShowCursor False
    
End Function


c0Title: Scrub Out Unused Constants
c1Code:
Low-level parsing in VB doesn�t come up every day. This routine demonstrates how to search a VB module for dead module-level constants with only a few lines of code. Reading the entire contents of a file into a string variable is key to this approach:

 

Private Sub FindDeadConstants(ByVal PathAndFile As String)

               Dim FileHandle As Integer

               Dim FileContents As String

               Dim PositionOfDeclaration As Long

               Dim StartOfConstantName As Long

               Dim EndOfConstantName As Long

               Dim ConstantName As String

 

               'open the file and read the contents 

               'into a string variable:

               FileHandle = FreeFile

               Open PathAndFile For Binary Access _

                              Read As FileHandle

               FileContents = _

                              Input$(LOF(FileHandle), _

                              FileHandle)

               Close FileHandle

 

               'loop through all the module-level 

               'constants:

               Do

                              PositionOfDeclaration = _

                                             InStr(PositionOfDeclaration _

                                             + 1, FileContents, _

                                             "Private Const ")

                              If PositionOfDeclaration > 0 Then

                                             'we've found a constant:

                                             StartOfConstantName = _

                                                            PositionOfDeclaration _

                                                            + Len("Private Const ")

                                             EndOfConstantName = _

                                                            InStr( _

                                                            StartOfConstantName, _

                                                            FileContents, " ")

                                             ConstantName = _

                                                            Mid$(FileContents, _

                                                            StartOfConstantName, _

                                                                           EndOfConstantName _

                                                                           - StartOfConstantName)

                                             'if the constant is not 

                                             'referenced beyond its 

                                             'declaration, then it's dead:

                                             If InStr(EndOfConstantName, _

                                                            FileContents, _

                                                            ConstantName) = 0 Then

                                                            lstDeadConstants.AddItem _

                                                                           ConstantName

                                             End If

                              End If

               Loop Until PositionOfDeclaration = 0

End Sub

�Dave Doknjas, Surrey, British Columbia, Canada


c0Title: Searching a list/combo box for a given string
c1Code:
List Boxes:

To find first occurance:

iRes=sendmessage(lst1.hwnd,LB_FINDSTRING,-1,szSearchString)

To find an exact match:

iRes=sendmessage(lst1.hwnd,LB_FINDSTRINGEXACT,-1,szSearchString)

Combo Boxes:

To find first occurance:

iRes=sendmessage(lst1.hwnd,CB_FINDSTRING,-1,szSearchString)

To find an exact match:

iRes=sendmessage(lst1.hwnd,CB_FINDSTRINGEXACT,-1,szSearchString)

Using CB_SELECTSTRING/LB_SELECTSTRING to select an item in a combo/list box. This is used because, the setting of a listindex causes a click event, which is a *BUG* according to the knowledge base articles, so if u don't want the code in the click event to be executed to this:

iRes=sendmessage(lst1.hwnd,CB_SELECTSTRING,-1,szSelectString) 
iRes=sendmessage(lst1.hwnd,LB_SELECTSTRING,-1,szSelectString)


c0Title: Selecting all text on focus
c1Code:
When working with data entry controls, the current value in the control often needs to be selected when the control received focus. This allows the user to immediately begin typing over any previous value. Here's a quick subroutine to do just that:

Code

Public Sub SelectAll(Editctr As Control)
    With Editctl
        .SelStart = 0
        .SelLength = Len(Editctr)
    End With
End Sub
Use

If the GotFocus event of an input control, put the following code:

Call SelectAll(controlname)
For example, if the input control was called txtHello, the code in its GotFocus event would be:

Call SelectAll(txtHello)
Tip by James Limm


c0Title: Selection Sort
c1Code:
This sort works on a simple procedure - take an array position and then look through the remaining items to see which one fits. This item then swaps places with the one which is already there. Do this for every position in the array and by the time we've run out of positions, the array is fully sorted.

Example:

Code:
Array of data [6,7,9,3,2,5]

[6,7,9,3,2,5]
           ^ Which element goes here?
[6,7,5,3,2,9]
         ^ Which element goes here?
[6,2,5,3,7,9]
       ^ Which element goes here?
[3,2,5,6,7,9]
     ^ Which element goes here?
[3,2,5,6,7,9]
   ^ Which element goes here?

Finish [2,3,5,6,7,9]


Again nested loops are in order, one for each iteration, and one to find the largest remaining value:


VB: 
--------------------------------------------------------------------------------
Public Sub SelectionSort(ByRef lngArray() As Long)
    Dim iOuter As Long
    Dim iInner As Long
    Dim iLBound As Long
    Dim iUBound As Long
    Dim iTemp As Long
    Dim iMax As Long
    
    iLBound = LBound(lngArray)
    iUBound = UBound(lngArray)
    
    For iOuter = iUBound To iLBound + 1 Step -1
        
        iMax = 0
        
        'Find the largest value in the subarray
        For iInner = iLBound To iOuter
            If lngArray(iInner) > lngArray(iMax) Then iMax = iInner
        Next iInner
        
        'Swap with last slot of the subarray
        iTemp = lngArray(iMax)
        lngArray(iMax) = lngArray(iOuter)
        lngArray(iOuter) = iTemp
        
    Next iOuter
End Sub

--------------------------------------------------------------------------------


c0Title: Send a Click Message
c1Code:
Recently, I turned to Windows messaging to manipulate certain dialogs by simulating button clicks programmatically. I looked through my API references and found only the WM_LBUTTONDOWN and WM_LBUTTONUP messages. I couldn�t get them to work until I found, on the MSDN Web site, a message that�s not documented in the API text that comes with VB�BM_CLICK =  &HF5. You set lParam and wParam both to zero to use this message. It works perfectly when it�s sent directly to the button. SendMessage is a synchronous call. If the button you want to click might take some time to process its work, and you�d rather make an asynchronous click, use PostMessage instead. This sample shows how to use the BM_CLICK message. Paste this code into a new form, with two command buttons, one option button, and one checkbox:

 

Option Explicit

Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long

Private Declare Function PostMessage Lib "user32" Alias "PostMessageA" (ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Long) _
       As Long

Private Const BM_CLICK = &HF5

Private Sub Check1_Click()
       Debug.Print "  Check1_Click"
End Sub

Private Sub Command1_Click()
       Debug.Print "  Command1_Click"
End Sub

Private Sub Command2_Click()
       Debug.Print "Entering Command2_Click"
       Call SendMessage(Check1.hWnd, BM_CLICK, 0, ByVal 0&)
       Call SendMessage(Option1.hWnd, BM_CLICK, 0, ByVal 0&)
       Call SendMessage(Command1.hWnd, BM_CLICK, 0, ByVal 0&)
       Debug.Print "Exiting Command2_Click"
End Sub

Private Sub Option1_Click()

       Debug.Print "  Option1_Click"

End Sub

 

The BM_CLICK message works on any button-class control. This includes option buttons and checkboxes.

�Marc Boorshtein, Framingham, Massachusetts


c0Title: Send Email w/o MAPI
c1Code:
Have you ever wanted to send mail to someone without Microsoft MAPI control sending your real information with it? Well then simply handle the message yourself.
If your using a Proxy or firewall this code may not work for you.
It's simple to build an SMTP message just call this function passing in 4 parameters: 

  psTo  - This is the email address of the recipient.
  psFrom - This is your email address.
  psSubject - Subject for the message.
  psBody - The message text.


Controls Needed

     
Control Type Control Name 
Form Form1 
WinSock Control Socket 
Textbox txtHost 
Textbox txtPort 
Textbox txtTo 
Textbox txtFrom 
Textbox txtSubject 
      Textbox txtBody 
      CommandButton cmdSend 

Place the following on Form1:

Option Explicit
Dim ConnectedState As Boolean

Private Sub cmdSend_Click()
    Dim lbSendWorked As Boolean
    
    On Error Resume Next
    Socket.Close
    Socket.Connect txtHost, txtPort
            
    Do While ConnectedState = False
        DoEvents
    Loop
                
    lbSendWorked = SendMail(txtTo, txtFrom, txtSubject, txtBody)
    
    Socket.Close
End Sub

Private Sub Socket_Connect()
    ConnectedState = True
End Sub

Function SendMail(psTo As String, psFrom As String, psSubject As String, _
                     psBody As String) As Boolean
    Dim lsMessage As String
    Dim lsSep As String
        
    lsMessage = "MAIL FROM: <" & psFrom & ">" & vbCrLf _
        & "RCPT TO: <" & psTo & ">" & vbCrLf _
        & "DATA" & vbCrLf _
        & "DATE: " & Format$(Now, "dd mmm yy ttttt") & vbCrLf _
        & "FROM: " & psFrom & vbCrLf _
        & "TO: " & psTo & vbCrLf _
        & "SUBJECT: " & psSubject & vbCrLf & vbCrLf _
        & psBody & vbCrLf & "." & vbCrLf
               
    Socket.SendData (lsMessage)
End Function




c0Title: Send NT System Broadcast
c1Code:
This function will allow you to send a broadcast message across a Windows NT Network.
Private Const NERR_Success As Long = 0&
Private Declare Function NetMessageBufferSend Lib "NETAPI32.DLL" _ (yServer As Any, yToName As Byte, yFromName As Any, yMsg As Byte, _  ByVal lSize As Long) As Long


Public Function Sendmsg(strTo As String, strFrom As String, _
                    strMessage As String) As Boolean
    Dim bytTo() As Byte
    Dim bytFrom() As Byte
    Dim bytMsg() As Byte
    
    bytTo = strTo & vbNullChar
    bytName = strFrom & vbNullChar
    bytMsg = strMessage & vbNullChar

    Sendmsg = (NetMessageBufferSend(ByVal 0&, yToName(0), _
              ByVal 0&, yMsg(0), UBound(yMsg)) = NERR_Success) End Function



c0Title: Set A Thread's Process Priority
c1Code:
The Problem:   Background programs Hog the CPU

When a computer is shared between programs that server users, such as IIS or VB forms based programs, and programs that have no user interface, such as background computational programs, the background programs can hog the CPU and reduce the response time to the user.


--------------------------------------------------------------------------------

Solution: Have the Background Programs Lower Their Process Priority
Using the Windows 32-bit API a program cal lower its priority.  It still runs instead of the Idle Process but higher priority programs, such as IIS run faster.

The solution is coded as a module which you'll see below.  There's also a ZIP file to download here.  I've made it a ZIP so that it won't be stopped by any code that's protecting your system from scripts.

There isn't much to using the routines.  In your application make this call early on:


        Dim iPri As Long
        iPri = ProcessPrioritySet(Priority:=ppBelowNormal)

If you want to find out the priority of your process as a numeric value use:

        Dim iPri As Long
        iPri = ProcessPriorityGet()

To get a name for the process priority use:

        Dim sPriority As String
        spriority = ProcessPriorityName (ProcessPriorityGet())

Here's the module:

Option Explicit

' (c) Copyright 2003 Andrew Novick. 
' You may use this code in your projects, including projects
' that you sell so long as there is substantial additional
' content. All other rights including rights to publication
' are reserved.
 

' Win32 API declarations

Private Declare Function GetCurrentProcess _
                                                    Lib "kernel32" () As Long
Private Declare Function GetCurrentProcessId Lib "kernel32" () As Long
Private Declare Function GetCurrentThread Lib "kernel32" () As Long
Private Declare Function GetCurrentThreadId Lib "kernel32" () As Long
Private Declare Function SetThreadPriority Lib "kernel32" _
                                                       (ByVal hThread As Long, ByVal nPriority As Long) As Long
Private Declare Function GetThreadPriority Lib "kernel32" (ByVal hThread As Long) As Long

Private Const THREAD_BASE_PRIORITY_LOWRT As Long = 15 ' value that gets a thread to LowRealtime-1
Private Const THREAD_BASE_PRIORITY_MAX As Long = 2 ' maximum thread base priority boost
Private Const THREAD_BASE_PRIORITY_MIN As Long = -2 ' minimum thread base priority boost
Private Const THREAD_BASE_PRIORITY_IDLE As Long = -15 ' value that gets a thread to idle

Public Enum ThreadPriority
    THREAD_PRIORITY_LOWEST = -2
    THREAD_PRIORITY_BELOW_NORMAL = -1
    THREAD_PRIORITY_NORMAL = 0
    THREAD_PRIORITY_HIGHEST = 2
    THREAD_PRIORITY_ABOVE_NORMAL = 1
    THREAD_PRIORITY_TIME_CRITICAL = 15 ' THREAD_BASE_PRIORITY_LOWRT
    THREAD_PRIORITY_IDLE = -15 'THREAD_BASE_PRIORITY_IDLE
End Enum

Private Declare Function GetWindowThreadProcessId Lib "user32" (ByVal hWnd As Long, ByVal lpdwProcessId As Long) As Long
Private Declare Function OpenProcess Lib "kernel32" (ByVal dwDesiredAccess As Long, _ 
                                                  ByVal bInheritHandle As Long, ByVal dwProcessID As Long) As Long
Private Declare Function SetPriorityClass Lib "kernel32" (ByVal hProcess As Long, ByVal dwPriorityClass As Long) As Long
Private Declare Function GetPriorityClass Lib "kernel32" (ByVal hProcess As Long) As Long
Private Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long

' Used by the OpenProcess API call
Private Const PROCESS_QUERY_INFORMATION As Long = &H400
Private Const PROCESS_SET_INFORMATION As Long = &H200

' Used by SetPriorityClass
Private Const NORMAL_PRIORITY_CLASS = &H20
Private Const BELOW_NORMAL_PRIORITY_CLASS = 16384
Private Const ABOVE_NORMAL_PRIORITY_CLASS = 32768
Private Const IDLE_PRIORITY_CLASS = &H40
Private Const HIGH_PRIORITY_CLASS = &H80
Private Const REALTIME_PRIORITY_CLASS = &H100

Public Enum ProcessPriorities
    ppidle = IDLE_PRIORITY_CLASS
    ppbelownormal = BELOW_NORMAL_PRIORITY_CLASS
    ppAboveNormal = ABOVE_NORMAL_PRIORITY_CLASS
    ppNormal = NORMAL_PRIORITY_CLASS
    ppHigh = HIGH_PRIORITY_CLASS
    ppRealtime = REALTIME_PRIORITY_CLASS
End Enum

Public Function ProcessPriorityName(ByVal Priority As ProcessPriorities) As String

Dim sName As String

Select Case Priority

    Case ppidle
        sName = "Idle"

    Case ppbelownormal
        sName = "Below Normal"

    Case ppNormal
        sName = "Normal"

    Case ppAboveNormal
        sName = "Above Normal"

    Case ppHigh
        sName = "High"

    Case ppRealtime
        sName = "Realtime"

    Case Else
        sName = "Unknown:" & CStr(Priority)

End Select

ProcessPriorityName = sName

End Function

Public Function ProcessPriorityGet(Optional ByVal ProcessID As Long, Optional ByVal hWnd As Long) As Long

    ' Gets the process priority identified by an Id, a hWnd

    '  or if not identified, then the current process

    Dim hProc As Long
    Const fdwAccess As Long = PROCESS_QUERY_INFORMATION

    ' If not passed a PID, then find value from hWnd.

    If ProcessID = 0 Then
        If hWnd <> 0 Then
            Call GetWindowThreadProcessId(hWnd, ProcessID)
        Else
            ProcessID = GetCurrentProcessId()
        End If
    End If

    '   Need to open process with simple query rights,

    ' get the current setting, and close handle.
    hProc = OpenProcess(fdwAccess, 0&, ProcessID)
    ProcessPriorityGet = GetPriorityClass(hProc)

    Call CloseHandle(hProc)

End Function

Public Function ProcessPrioritySet( _
                    Optional ByVal ProcessID As Long, _
                    Optional ByVal hWnd As Long, _
                    Optional ByVal Priority As ProcessPriorities = NORMAL_PRIORITY_CLASS _
                    ) As Long

    Dim hProc As Long
    Const fdwAccess1 As Long = PROCESS_QUERY_INFORMATION Or PROCESS_SET_INFORMATION
    Const fdwAccess2 As Long = PROCESS_QUERY_INFORMATION

    ' If not passed a PID, then find value from hWnd.

    If ProcessID = 0 Then
        If hWnd <> 0 Then
            Call GetWindowThreadProcessId(hWnd, ProcessID)
        Else
            ProcessID = GetCurrentProcessId()
        End If
    End If

    ' Need to open process with setinfo rights.
    hProc = OpenProcess(fdwAccess1, 0&, ProcessID)

    If hProc Then
        ' Attempt to set new priority.
        Call SetPriorityClass(hProc, Priority)
    Else
        ' Weren't allowed to setinfo, so just open to
        ' enable return of current priority setting.
        hProc = OpenProcess(fdwAccess2, 0&, ProcessID)
    End If

    ' Get current/new setting.
    ProcessPrioritySet = GetPriorityClass(hProc)
    ' Clean up.
    Call CloseHandle(hProc)

End Function

Public Function ProcessThreadPrioritySet( _
                                Optional ByVal Priority As ThreadPriority = THREAD_PRIORITY_NORMAL _
                                ) As ThreadPriority

    Dim hThread As Long
    Dim rc As Long

    ' Set's the priority of the current thread

    hThread = GetCurrentThread()

    ' Need to open process with setinfo rights.

    rc = SetThreadPriority(hThread, Priority)
    ProcessThreadPrioritySet = GetThreadPriority(hThread)

End Function


