Wednesday, October 08, 2008
ICommand::Cancel is a dummy
Tuesday, October 07, 2008
Reading BLOBs
There are two important changes to the code since last post:
- Columns are indexed by their ordinal.
- Columns are bound using a two-pass approach - On the first pass the non-BLOB columns are bound using the same accessor handle. The second pass binds the BLOB columns allocating an accessor handle for each column.
Most of the complexity you see on the code (the difference between binding and column indexes) stems from the fact that columns are being moved around in order to make binding simpler. When life was simpler and there were no BLOBs, there was a direct relation between column and binding entries and there was no need to map between the two. Now that we are moving binding entries around, we must make sure that a mapping exists between both arrays (see the GetBindingIndex method).
Next post will handle writing data to the BLOB.
Sample files: EditRow3.zip, oledbcli2.zip
Wednesday, October 01, 2008
CStdDialogImpl and the OK button
Derive your dialog class (say CMyDialog) from
CStdDialogImpl<CMyDialog, SHIDIF_SIZEDLGFULLSCREEN | SHIDIF_SIPDOWN>
On the OnInitDialog handler, call:
SHDoneButton(m_hWnd, SHDB_HIDE);
ModifyStyle(0, WS_NONAVDONEBUTTON, SWP_NOSIZE);
Make sure you change all the calls to CStdDialogImpl<CMyDialog> methods with the new base (you can use a typedef for that).
Tuesday, September 30, 2008
Let's talk about BLOBs
A BLOB is a different beast because it can store a very large amount of data. In SQL Compact there are only two types of BLOBs: the image (variable length byte array) and the ntext types (variable length character array). These can be really large and the maximum theoretical size of a BLOB field is 4 GB in SQL Compact (the same size limit for the whole database).Contrary to smaller data types, you may not be able to store the whole BLOB data in the device memory, so we need a different approach to handling these types.
The OLE DB standard allows two different ways to bind BLOB data to your accessor: by reference or by value. SQL Compact does not allow by reference binding, so we are limited to binding these by value? What are the differences between both types of data binding?
- When binding by reference, the OLE DB provider allocates memory for each bound BLOB field and fills it with data. A pointer to this data is stored in the accessor buffer along with its status and size. Column data is accessed through an extra pointer indirection and our code must make sure that the BLOB data buffer is released when the data is not needed anymore.
- When binding by value, we can either specify (force) a fixed column size or bind through a storage interface. The first approach allows your BLOB data to be manipulated just like any other column but forces you to put a limit on how much data you will handle per BLOB column and this defeats the whole purpose of using them. Alternatively (and most commonly) your code will request that the provider creates a storage object to handle the BLOB data (when reading - when writing data your code must do this). When data is read ftom the table, the provider stores a pointer to either an ISequentialStream or an ILockBytes interface and then your code can read the data through these. When writing, your code must create one of these objects per BLOB column.
The first change we must make to the rowset code is to add more accessor handles. The first accessor handle will bind the non-BLOB columns and these can be read and written to just like we are doing right now. For each BLOB column the rowset must allocate an additional accessor handle and make a note of it.
When data is read, the non-BLOB columns are all read, but none of the BLOB columns are. When your client code requests data from a BLOB column, the rowset must know that this is a special case, retrieve the colsumn's accessor handle and call IRowset::GetData on that handle. On the data buffer, the OLE DB provider will write a pointer to one of the above-mentioned storage objects (it is chosen by the rowset code when the data binding is created) and your client code must read through it and release it when no longer needed.
When you need to write data back to the table, your client code will have to create one storage object per BLOB column, store the respective pointers in the data buffer and engage in a multi-step update (new row insertion or existing row update). This means that your code will store the non-BLOB columns first and then store the BLOB columns one at a time, but to be able to achieve this the rowset must be in delayed update mode. You specify this mode by setting the DBPROP_IRowsetUpdate property to VARIANT_TRUE and the rowset now exposes the IRowsetUpdate interface. When this interface is exposed, you can call IRowsetChange::SetData on the allocated accessor handles without having to worry about having the row in an inconsisten state (due to table constraints, for instace) while you are updating data. When your code is done setting data through all the accessors, just call IRowsetUpdate::Update to update all the changes at once.
This will be easier to understand through a sample, which I will publish on my next post.
Monday, September 29, 2008
Using the OLE DB Client code
Now that you know this, should you use this code? If you want to use it to learn about OLE DB and the SQL Compact native provider, please go right ahead and use it (and change it, and tweak it and experiment with it). As to using it in a commercial application, I would say "not right now" for two reasons:
- The code has not been extensively tested. Although I plan to publish a bug-free (ah ah ah!) library, I cannot take resposibility for any use you make of it. You will be on your own.
- This code will change. Don't start writing your own code based on this library expecting that the library interfaces will not change. They will, and your code will fail to compile.
Wednesday, September 24, 2008
Moving to a DLL
Monday, September 22, 2008
Updating data
Wednesday, September 17, 2008
Bookmarks
- The bookmark value is valid only for the particular rowset that generated it. You should not store this value for later use after the rowset closes because the bookmark values will be rendered invalid. Even if you open the same rowset again, there is no guarantee that the bookmark values will be the same. (Ok, now that I wrote this disclaimer I can tell you that apparently on base table cursors, SQL Compact does seem to reuse the same bookmark values. Nevertheless don't reuse them, please.)
- There is no way to know a bookmark's value before loading the row data to memory. This means that if you are using bookmarks to navigate in your rowset, your code must visit the row before knowing its bookmark value, just like in a book.
Monday, September 15, 2008
Sample Databases
Database Schema Rowsets
Saturday, September 13, 2008
Moving around
- What value would you use to read the previous row? If you think -1 you are wrong (if you keep cRows = 1) and the reason is simple: you would go back one row from the current reading position (after the current row) and put it before the current row, so you would effectively re-read the current row! To move back one row (with cRows = 1) you must set lRowsOffset to -2.
- Can you really scroll back? If you look at all the available SQL Compact BOLs you will see that DBPROP_CANSCROLLBACKWARDS is read-only and VARIANT_FALSE, meaning that lRowsOffset cannot be negative. (I'm still trying to figure out if this is true for 3.0 and 3.5 becase elsewhere these BOLs say that you can have a negative offset.)
Friday, September 12, 2008
Date, time and money
- The numeric format of the value: None, Number or Currency. The first format returns the number as it is internally formatted, with trailing zeores and a decimal point (locale-insensitive). The second format returns the value as a number in the given locale and the third does the same but as a currency.
- The locale identifier (see above).
Thursday, September 11, 2008
Showing rows
Sunday, September 07, 2008
Counting rows
Thursday, September 04, 2008
SQL Compact's OLE DB limitations
Rowset limitations
SQL Compact's OLE DB provider returns only one row handle through IRowset::GetNextRows. This is not a dramatic limitation, but do keep it in mind.
Binding limitations
You cannot bind columns by reference, only by value. What does this mean? It means that you can only create bindings that have enough allocation for the bound data sizes. So how do we handle BLOBs? On desktop providers (like SQL Server's and Access) you can bind a BLOB column through a pointer to the provider memory. The provider allocates enough storage for the BLOB data and passes back a pointer that the consumer application later releases. This option is not available with SQL Compact, so a different approach is provided: structured storage. Instead of returning a pointer to the BLOB data, SQL Compact's provider returns a pointer to either an ISequentialStream or ILockBytes interface (the client application decides which). The major difference between the two is that the first provides sequential access while the second provides random access to the BLOB data. This is a bit of a pain, but gets worse.
Accessor limitations
Only one storage object can be open at a time (see DBPROP_MULTIPLESTORAGEOBJECTS). So what happens if you have more than one BLOB column to bind to at the same time? You can actually bind more than one BLOB column but you cannot get and set data to more than one BLOB column at the time: you have to get each storage object, use it and dispose of it. This means that you cannot read the whole row at once...
The trick is to create more than one accesor handle per row, binding all the non-BLOB columns with the first handle and creating an extra accessor handle per BLOB column. Reading and writing row data now becomes a multi-step process, and for that reason you must make sure that you use delayed update mode (use DBPROP_IRowsetUpdate).
Confusing? Things will become clearer with some code...
Wednesday, September 03, 2008
Opening a base table cursor
IOpenRowset
This interface has only one method: OpenRowset. Set the second parameter to the requested table name (set the DBID structure with eKind = DBKIND_NAME and pwszName pointing to a string containing the UNICODE table name) and the third to the optional index name (set to NULL if you don't want to use any index order). Set the fourth parameter to the IID_IRowset constant so that upon success you get an IRowset pointer in the last parameter. The remaining two parameters are used to specify the requested cursor properties. After successfully calling this method, you get an IRowset interface pointer.
IRowset
The IRowset interface is essentially used to navigate through the data rows (RestartPosition and GetNextRows) and to read data from them (GetData). This is done independently of the data structure of the table (or query result). To map between the data row structure and the consumer's memory we need another interface.
IAccessor
To retrieve data from the row, one or more accessors must be created through the IAccessor interface (retrieved via QueryInterface on the IRowset). Each accessor maps a set of columns in the data row to memory addresses in the consumer memory through CreateAccessor. Each column mapping is represented by an entry in the DBBINDING array that the cosumer passes as the third parameter. This array may either be filled with application-provided bindings (if you just want to bind a subset of the columns), or with data from the provider itself. To get this type of information, you typically request an IColumnsInfo interface pointer and call the GetColumnInfo method to retrieve all the column information for the table. The process of mapping between the two is not linear and requires some thought and attention to the provider's peculiarities. But we will see all of this on the next post.
Sunday, August 31, 2008
CDataSource and CSession
From now on things will start to get a bit more interesting and useful: besides creating or opening a database using OLE DB, we want to access and edit both data and structure. We can do this either through SQL commands or through the exposed interfaces. My next challenge will be to tackle base table cursors and SQL commands. These require the introduction of two other objects that are central to OLE DB programming: the rowset and the accessor. While a rowset represents a set of rows that you can navigate on, an accessor defines how the individual columns are accessed and mapped to your application data. Rowsets are not only used to handle cursor data (both from a table and from a SQL query) but they are also internally used by the OLE DB provider to report other data to the consumer (like schema information). Accessors are required to map data between your application's memory and the provider's data, for three specific purposes:
- Table or query data;
- SQL command parameters;
- Index column data.
Here we will meet some very interesting challenges, like handling the BLOB peculiarities of SQL Compact, but this will be an interesting ride. I promise.
Friday, August 29, 2008
Connecting to a SQL Compact database via OLE DB
Both the Data Source and the Session objects are COM objects and thus can be exposed thrugh a number of different interfaces (see the "CoType" definitions for both objects). Some of the interfaces are marked as mandatory while others as optional. SQL Compact implements a subset of these interfaces:
DataSource
Interestingly, the mandatory IPersist interface is not implemented in SQL Compact.
Session
Now, we can start writing high-level code to connect to a SQL Compact database. On my next post I will publish a minimalistic approach to both these objects.
Wednesday, August 27, 2008
Detecting the installed SQL Compact OLE DB providers
A word of caution is required here: this method detects only the installed OLE DB providers, not the SQL Compact engines. From version 3.0 onwards, Microsoft split the engine interfaces into two different stacks (managed and native) that are separately installable. This means that you can install the managed SQL Compact 3.5 SP1 stack without installing the native one. This methods detects only the installed OLE DB stacks.
The process of detecting the installed OLE DB providers is quite simple: just try to instantiate the IDBInitialize interface using the engine's CLSID:
bool IsInstalled(const CLSID& clsid)
{
CComPtr<IDBInitialize> spInitialize;
HRESULT hr;
hr = CoCreateInstance(clsid, NULL, CLSCTX_INPROC_SERVER, IID_IDBInitialize, (void**)&spInitialize);
return SUCCEEDED(hr);
}
You simply use this code like this:
if(IsInstalled(CLSID_SQLSERVERCE_3_5)) ...
In the revised sample, this test is used to filter out the nonexistent OLE DB providers from the engine combo box.
Monday, August 25, 2008
CDbException
These errors tend to be very descriptive about what cause them, but there are some exceptions. For instance, try to create a SQL CE 2.0 or 3.0 database without a password and specify that you want to encrypt it. Database creation will fail with a very terse "Errors occurred." message...
When such an error occurs, you should also look at the "basic error information" (ERRORINFO structure) for the native error code. You must look this value up on the provider's published error messages where you should get more detailed info about the error.
Thursday, August 21, 2008
Handling OLE DB Errors
The first thing you need to do is call the GetErrorInfo API function. If you call any other OLE DB method the error information will be reset and lost. The GetErrorInfo function returns an IErrorInfo interface pointer that does not contain any valuable information. Instead, you must use it to call QueryInterface and request an IErrorRecords interface pointer. This interface implements an error collection (typically contais one error only) where each error object is exposed as an IErrorInfo interface. Confused? It gets a bit better...
Getting the underlying error information requires some work because it is split between the IErrorRecords and each contained IErrorInfo. For instance, to retrieve the error parameters (the six parameter values you might have seen in a SqlCeError object), you must call the IErrorRecords::GetErrorParameters method. It takes as parameters the requested zero-based error index and a pointer to a DISPPARAMS structure. You can also get some basic error information by calling IErrorRecords::GetBasicErrorInfo with an ERRORINFO structure pointer as the second argument. For each IErrorInfo in the error collection, you can get information such as description, the source (reports the database engine and version) and the interface GUID that generated the error.
Oh, and when you are done make sure everything is cleaned up. Yes, we need another abstraction to manage error reporting. Hopefully this will be the theme for the next post and from then on we can move on to opening a database connection, executing SQL statements and more.
Monday, August 18, 2008
CDbProp and CDbPropSet
CDbProp
This class is a very simple wrapper around DBPROP that provides only safe initialization and disposal. It also knos how to make copies of itself. You will see this code in action when a property is added to a property set.
CDbPropSet
Besides wrapping the DBPROPSET structure, this class adds a few methods to ease the addition of properties to a property set. For instance, setting the file name is now simply:
propSetProvider.AddProperty(DBPROP_INIT_DATASOURCE, szFileName);
This makes for less code and especially for more readable code. The AddProperty method is overloaded for booleans, integers and strings (the most common property types) and simply sets all the DBPROP structure values according to the data type (see how the vVariant value is set), and then adds this structure to the existing property set. Property values are set through an instance of CDbProp class because it clears the variant data when it goes out of scope. Also it knows how to make a copy of itself into a DBPROP pointer (the CopyTo method).
Property set memory is managed through the CoTaskMemRealloc and CoTaskMemFree functions. Why didn't I use the new and delete operators? Some OLE DB interface methods will return this type of data back to the client and the client needs to correctly delete it. The provider allocates all its data using the CoTaskMem functions, so our code needs to comply in order to avoid memory leaks. The downside of this design is that we lose the delete [] semantics and must implement it ourselves (see CDbPropSet::Clear)...
The second version of the sample code can be downloaded from here.
Tuesday, August 12, 2008
Creating SDF files from OLE DB
When Microsoft upgraded to SQL CE 3.0, some of the property ID constants were changed while retaining their names. This meant that the same property ID would have different values in 2.0 and 3.0. To overcome this, the extension header file undefines the conflicting properties and redefines them with new names that refer to the version:
#undef DBPROP_SSCE_COL_ROWGUID
#undef DBPROP_SSCE_MAXBUFFERSIZE
#undef DBPROP_SSCE_DBPASSWORD
#undef DBPROP_SSCE_ENCRYPTDATABASE
#undef DBPROP_SSCE_TEMPFILE_DIRECTORY
These are redefined as:
#define DBPROP_SSCE3_COL_ROWGUID 0x1F9L
#define DBPROP_SSCE3_MAXBUFFERSIZE 0x1FAL
#define DBPROP_SSCE3_DBPASSWORD 0x1FBL
#define DBPROP_SSCE3_ENCRYPTDATABASE 0x1FCL
#define DBPROP_SSCE3_TEMPFILE_DIRECTORY 0x1FEL
And:
#define DBPROP_SSCE2_COL_ROWGUID 0x69
#define DBPROP_SSCE2_MAXBUFFERSIZE 0x70
#define DBPROP_SSCE2_DBPASSWORD 0x71
#define DBPROP_SSCE2_ENCRYPTDATABASE 0x72
#define DBPROP_SSCE2_TEMPFILE_DIRECTORY 0x73
The aplication itself is a very simple WTL 8.0 dialog with just a few simple properties:
- File name
- Password (optional)
- Locale (optional)
- Encryption (behavior depends on engine version)
- Engine version
A DBPROPSET array contains an array of DBPROP arrays. Each DBPROP structure contains the value of a single OLE DB property (like file name, locale, encryption mode...) and these are grouped in property sets. Each property set has its own unique ID, like DBPROPSET_DBINIT (generic OLE DB database initialization properties) and DBPROPSET_SSCE_DBINIT (SQL CE-specific initialization properties). This scheme only works correctly if you put each property in its own specific set and you must read the documentation to learn which goes where. In our case, we have:
DBPROPSET_DBINIT:
- DBPROP_INIT_DATASOURCE - database file name
- DBPROP_INIT_LCID - database locale ID
- DBPROP_SSCE_ENCRYPTIONMODE - database encryption mode (for 3.5 only)
- DBPROP_SSCE2_ENCRYPTDATABASE - database encryption status (for 2.0 only)
- DBPROP_SSCE3_ENCRYPTDATABASE - database encryption status (for 3.0 only - deprecated in 3.5)
- DBPROP_SSCE2_DBPASSWORD - database password (for 2.0 only)
- DBPROP_SSCE3_DBPASSWORD - database password (for 3.0 / 3.5 only)
At the end, don't forget to cleanup all the resources you have allocated. This includes the input properties and any output data. Also, don't forget to release the used OLE DB interfaces!
Before you make this code work on a Windows Mobile device, you must install at least one of the SQL Compact engines. For versions 3.0 and 3.5 make sure you also install the replication cab file because this is how the OLE DB provider is installed. Needless to say, you can have all three engines installed on the same device.
What's next? First, we need a better abstraction for handling all the property values. Second, we need a better way of retrieving error information whenever one pops up. I will start the OLE DB client library by implementing these two abstractions.
Friday, August 08, 2008
SQL Compact 3.5 SP1 and OLE DB
What can we do now? Using the OLE DB interfaces directly is a big pain because these are very low-level abstractions. We need something that elevates the abstraction to something of the ATL OLE DB Consumer Templates level. And this is just what I'm proposing to do (yes, I'm crazy).
The Consumer Templates are a very interesting and informative piece of code. The usual ATL templated classes are there and the classes model very closely all sorts of consumer objects that you may use. But they have one very interesting drawback. As I found out, you have to tweak the code under some very specific circumstances, when you don't know the exact outcome of a command execution, for example. These are not insurmountable and reflect the purpose of the class library: use a class template instantiation that is customized for your exact needs. I will use some of the Consumer Templates' concepts (not the code) in this new library which will be prmarily targeted to embedded and mobile devices. As a side effect, it should also be able to work in a desktop application.
Given this scope, the primary target of this new library is to develop applications for all versions of SQL Compact (SQL CE), namely 3.5, 3.0 and 2.0. Instead of writing SQL Compact-specific code only, this will be built in two layers: a generic OLE DB layer and a SQL Compact-specific layer built on top of the OLE DB layer.
So what do you need to get started? First of all you need the SQL Compact header file. For SQL Compact 3.5 you need the sqlce_oledb.h, while for SQL Compact 3.0 and 3.1 you need ssceoledb.h. These files contain both the OLE DB Interface, structure and constant declarations as well as the SQL Compact-specific GUIDs and constants. This means that, without tweaking, you musy have a header file for each version of SQL Compact. Fortunately this is not required and we may use just one header file to compile an application that will consume all versions of the database engine - a very nice feature indeed.
On my next post I will provide an add-on header file that will allow you to use sqlce_oledb.h to target all SQL Compact versions.
Tuesday, August 05, 2008
ClearType on a memory DC
The touch list uses a ClearType-rendered font (see the latest Touch List sample) painted to a memory DC. I also used a ClearType font to paint the text on the memory bitmap containing the detailed information about the main list item. Unfortunately the font was not being rendered in ClearType mode. Why was this? Maybe a Windows CE issue?
After quickly recompiling the code to target a Windows Mobile 6.0 device, I got exactly the same result. So I turned to the rendering code looking for a bug, but found none. Instead I found an interesting difference between the touch list rendering and the bitmap rendering.
If you look at the touch list samples, you will see that the memory DC is created from a CPaintDC. On the bitmap rendering code, I was using a memory DC created from a CClientDC (got the same result by using NULL as the HDC value).
So apparently you can only render ClearType fonts to a memory DC when this is created as compatible with the DC you get on BeginPaint... Has anyone else experienced this?
Monday, August 04, 2008
Windows Mobile API Usage Tool
Sunday, August 03, 2008
The Touch List - II
Code Changes
The first challenge I had to face was to decouple the "kinetics" from the list and implement it in a more general WTL base class. This was a project requirement for one of the data windows where a bitmap is displayed and the same auto scrolling behavior was sought.
The second challenge was to implement some sort of scroll bar into the window. I could use the native Windows scroll bars but I decided to use something nicer and possibly "cooler".
All the "touch" code was put into one single header file pompously named atltouch.h (see the sample code here). This header file contains the following classes:
- CScrollBarData - Contains scroll bar data (either horizontal or vertical) and associated calculations.
- CTouchWindow
- The "touch" base class, where you will find all generic "kinetics" functions. - CTouchListItem - Base class for touch list items. Implement one for your items (see sample code).
- CTouchList
- The touch list class template.
Finger Use
Most of the current devices are not ready for finger input, only for stylus input. Sure you can use your finger but I have found that on some devices, when you put your index finger to the screen, the application window receives the expected WM_LBUTTONDOWN, but may also receive a lot of WM_MOUSEMOVE messages... Why is this? Your finger is way larger than the expected stylus tip so you are effectively touching more than one screen point. What I have experienced is that most devices will produce the mouse move messages and your list will start scrolling just by touching it with your finger. This is less likely to happen when using the stylus.
The solution for this was to implement a "sensitivity" factor that will ignore movements within a given range (see the SetSensitivity method). If you use this with any value larger than 1 (in either direction) your list will become less sensitive, but you will have to adjust this value to your device...
Scroll Bars
Scroll bars have an unusual implementation, but I hope you find them useful (if not, suggestions
are mostly welcome). To use the vertical scroll bar, just drag your finger (or stylus) up or down and the list will scroll accordingly.
On Windows Mobile 5 and 6 devices, these are implemented as a transparent layer on top of your list so you can use the full screen size to display data.
Friday, July 25, 2008
How to draw gradient buttons

A few years ago, I wrote an MFC PocketPC Numeric Key Pad control (a dialog, actually) to help with numeric input on the Pocket PC. Recently I had to convert this code to WTL for a Windows CE 5.0 application and I decided to try and give it a modern look (see picture). This was something I was keen on doing because it was a bit of a mystery to me how that effect was achieved.
It's no big deal after all. Each button is painted with two gradients of gray and the digits are painted with a ClearType-rendered bold Tahoma font. The code to create the button effect (what do you call it: chiseled?) is very simple:
TRIVERTEX vertex[4] = { 0 };
GRADIENT_RECT grRect[2] = { 0 };
int nHeight2 = m_rc.Height() / 2;
This declares the variables we need to draw the button, where m_rc contains the button rectangle coordinates. Now we need to give the gradient renderer the information on how to render the button. Each button is split into two gradient-filled rectangles that must be described by their top left and bottom right points and color information. The top rectangle is:
vertex[0].x = m_rc.left;
vertex[0].y = m_rc.top;
vertex[0].Red = 0x0000;
vertex[0].Green = 0x0000;
vertex[0].Blue = 0xb000;
vertex[1].x = m_rc.right;
vertex[1].y = m_rc.top + nHeight2;
vertex[1].Red = 0x0000;
vertex[1].Green = 0x0000;
vertex[1].Blue = 0x7000;
vertex[1].Alpha = 0x0000;
Note how the color components are encoded. Now, we describe the bottom rectangle:
vertex[2].x = m_rc.left;
vertex[2].y = m_rc.top + nHeight2;
vertex[2].Red = 0x0000;
vertex[2].Green = 0x0000;
vertex[2].Blue = 0x2000;
vertex[2].Alpha = 0x0000;
vertex[3].x = m_rc.right;
vertex[3].y = m_rc.bottom;
vertex[3].Red = 0x0000;
vertex[3].Green = 0x0000;
vertex[3].Blue = 0x7000;
vertex[3].Alpha = 0x0000;
Finally, we must group these together for the API call:
grRect[0].UpperLeft = 0;
grRect[0].LowerRight = 1;
grRect[1].UpperLeft = 2;
grRect[1].LowerRight = 3;
GradientFill(dc, vertex, 4, (PVOID)grRect, 2, GRADIENT_FILL_RECT_V);
You may want to try different colors and shades to fit your taste.
Monday, July 21, 2008
Wednesday, July 02, 2008
Installing WTL Helper in VS 2008
- Install the original WTL Helper package;
- Download WtlHelper9.dll and copy it to the same install directory;
- Download WtlHelper9.reg and import it into your own registry using Regedit (you may have to edit the path);
- Open a command line (use Admin rights under Vista), go to the install directory and run the regsvr32 WtlHelper9.dll command;
- Start VS 2008 - the WTL helper must be there.
Enjoy!
Wednesday, June 25, 2008
The Touch List
So what does a "touch" list have to do? I would say it has to:
- Scroll smoothly;
- Follow your finger when you drag, scrolling the list;
- Automatically scroll the list when you finish dragging it.
Let's look at each one of these requirements.
Smooth Scrolling
The native Windows CE list controls don't scroll very smoothly. In fact, they usually scroll taking the item height as the scrolling quantum. On some ocasions, you will even see a full list refresh when you request a new page. This is not an acceptable behavior for a touch list.
A touch list scrolls one pixel at a time if needed be, clipping the items that are partially visible.
Drag Scrolling
This feature is also not implemented in none of the native list controls. If you click an item and drag the stylus, the list will not scroll to follow it. The underlying item will be selected, or some other custom behavior will occur, but the list will not scroll.
A touch list follows your finger or stylus and scrolls to keep the selected item in sync.
Autoscrolling
This is the coolest feature of a touch list that makes it behave like a flywheel: you nudge it and it keeps turning until the spindle friction dissipates all energy and the wheel stops. I have yet to see if the standard implementations support some kind of list "inertia" where successive quick drags accelerate the scrolling speed. Of course, this is not implemented in any of the native lists.
My Proposal
After considering all of the above, I wrote a sample implementation of a touch list (download here). The sample displays your contacts list (the FileAs property) and allows you to scroll the list up and down. It follows your finger and autoscrolls if you drag it quickly.
The implementation is far from complete and you will see lots of things changing in the next few days as I implement item states, mutiple columns, horizontal scrolling and some other cool features. Ah, this is WTL 8.0, of course!
Friday, June 06, 2008
Animating Child View Transitions - The article
Tuesday, May 27, 2008
An alternative to the SHMENUBAR resource
Most WM applications I have seen so far use a popup menu attached to each of the menu bar buttons. If this is your case, you can forget about the SHMENUBAR resource and use the menu editor. Create a menu with two items and their popups on the resource editor. If you are using WTL, go to your CMainFrame::OnCreate method and instead of the simple
CreateSimpleCEMenuBar();
write:
CreateSimpleCEMenuBar(IDR_MAINFRAME, SHCMBF_HMENU);
The IDR_MAINFRAME is your main menu ID. And that's all you need.
If you are using straight API calls, when calling the SHCreateMenuBar API, make sure that the dwFlags member of the SHMENUBARINFO contains the SHCMBF_HMENU flag and that nToolbarId contains your menu ID.
Monday, May 26, 2008
The tamed tree view
In this latest version of the sample, I extended the use of the CSelectionBar control (some call it the header bar control) in order to select which animation to perform (using the left menu). Now the code also supports switching views without any animation (select 'None').
This control will be updated to allow userd to display arbitrary windows instead of a standard popup menu and also will allow the inclusion of other controls in the toolbar (like an edit control or a combobox).
Thursday, May 22, 2008
The misbehaved tree view
This problem became more apparent when I decided to adapt a very old piece of code to WTL 8.0: the selection tool bar (what you see on top of the PPC File Explorer). Being a toolbar, it must coexist with the child view within the same frame, so the scrolling code had to be adapted.
I will post the solution for this problem when I find it. Most likely I will have to host the tree in another window...
Monday, May 19, 2008
Animating Child View Transitions - II
class CMainFrame :
public CFrameWindowImpl<CMainFrame>,
public CUpdateUI<CMainFrame>,
public CChildViewAnimate<CMainFrame>,
public CMessageFilter,
public CIdleHandler
Next, change the main frame's PreTranslateMessage method to this:
if(CFrameWindowImpl<CMainFrame>::PreTranslateMessage(pMsg))
return TRUE;
return ChildPreTranslateMessage(pMsg);
The call to ChildPreTranslateMessage ensures that the active child view class has a chance to do some custom message translation work. To enable this, you must derive all you child view classes from CChildView, like this:
class CSlideFormView :
public CDialogImpl<CSlideFormView>,
public CChildView<CSlideFormView>
If you need to do custom message translation, you must override the PreTranslateMessage:
virtual BOOL PreTranslateMessage(MSG* pMsg)
{
if(!::IsWindow(m_hWnd))
return FALSE;
return CWindow::IsDialogMessage(pMsg);
}
Note the IsWindow test: it is there to avoid having this method called when the child window is already destroyed.
Friday, May 09, 2008
ATL ASSERT on exit
To handle this situation, I changed the ATLASSERT macro definition the following way:
extern void AssertBreakpoint();
#define ATLASSERT(expr) { if(!(expr)) AssertBreakpoint(); \
_ASSERTE(expr); }
When the assertion is triggered, the code first calls the AssertBreakpoint function that is there just for you to place a debugger breakpoint. I implemented it with the following rocket-science code:
void AssertBreakpoint() { int a = 3; }
Now you can run the application and you will be sure that you know where the assertion fired. Why? When the debugger stops on your breakpoint you can immediately check the stack and look at the offending code. In my case the assertion was being fired in the CSlideFormView::PreTranslateMessage method. The form view HWND was already destroyed when the
return CWindow::IsDialogMessage(pMsg);
line was executed and this function validates m_hWnd in debug mode.
So if you find yourself in such a situation, use this technique because by defining your own version of ATLASSERT you essentially "infect" all the ATL and WTL code with your custom implementation. All your ATLASSERT are belong to me.
Thursday, May 08, 2008
Animating Child View Transitions
Instead of writing a dull imitation of the existing tabbed interface, I looked for ways to make a WTL SDI application dynamically change the frame's child view and there was no shortage of ideas, especially on CodeProject: Switch Views in a WTL SDI Application. The code works very well, but there is no provision for animating the child view transition. So I decided to do a little research into this issue and produced an early prototype.
The sample application flips between two views (Form and List) by horizontally scrolling the changing views. The idea is that the form view is on the left and the list view is on the right so when you switch views they animate intuitively: the form hides and the list shows by scrolling to the left and the reverse happens by scrolling to the right. This concept can be extended to vertical scrolling so the user can associate the process of navigating through the application with a map.
There were some challenges I had to overcome to implement this. To make animations smoother, I thought about rendering both views (the old that is going out and the new that is coming in) in a big bitmap and then just paint the bitmap in the frame's client area. Interestingly, there is no documented way to render a hidden window to a memory DC so there was no way to render the new view in the bitmap. You can overcome this if you have full control of the new window's paint cycle, but if you use Windows controls like the list view you are out of luck. I even tried forcing a WM_PAINT message with the memory HDC on the WPARAM, but the list would not render the header... So now I only render to a bitmap the old view and scroll in the new view using MoveWindow.
Another issue I had to take care of is the scrolling speed. The speed of ScrollDC increases as the area to scroll diminishes, so you get a scrolling procedure that gets faster and faster. To make it work at the same apparent speed, I had to time the first scroll and then use the system timer to compensate for faster execution times when the scrolled bitmap gets smaller.
I still need to implement the vertical scrolling and to encapsulate all of the code in a mixin class for the WTL main frame. Stay tuned.
Thursday, May 01, 2008
SQL Compact Command Parameter Sizes
Coming up: I'm starting to write the native alternative to the ATL OLE DB Consumer Templates for devices. This is going to be a lot of fun...
Thursday, April 24, 2008
MFC: The new Phoenix?
Jessica Liu posted on the Visual C++ Team Blog about the new MFC samples for everything from Office 2007 applications to Visual Studio-like user interfaces. So, is native code dead?
Nope.
Windows XP SP3
Friday, April 11, 2008
Off to Seattle!
Friday, April 04, 2008
WTL Helper and VS 2008
Crino ROCKS!
Wednesday, April 02, 2008
Smartphone keyboard handling with GAPI
I found a solution for this problem that solves most of the issues above, but also uncovers some other issues. The solution is to call ImmAssociateContext() on your main window passing NULL as the HIMC parameter. This will remove the input context association from the window, and you get the keyboard back. But...
... now you have to handle the keyboard mappings yourself. That's the price you pay. On some devices this means having a mixed keyboard (numbers and alpha keys) so you must internally implement a keyboard map in order to make it work as the user expects. To make matters worse, the keyboard map is device dependent so you are in for a bit of work. Finally, I have found that on some devices you don't get all the keys, especially the special keys for mail and contacts. These devices seem to have these keys wired to launch the matching applications and not even GAPI seems to release them...