Microsoft describes the user's control option for the list view under the CDN_INCLUDEITEM message. This allows you to accept or reject each list view item as it is added to the control by the system. Some visitors to the Borland news forums have asked for more control over the list view. In general, controls in the Open Dialog are accessed by their control identifier, from the system's dialog template. These identifiers are clearly described in the SDK and MSDN documents. (These were recently combined into one huge document, here. I must warn you that the location of the Common Dialog reference pages on MSDN changes about once a year. You may have to search for them.) If your BCB project doesn't automatically include the right Windows header, the control identifiers are in dlgs.h and winuser.h .
The Explorer-Style list view of filenames (and directories) in TOpenDialog is a special case of the controls included in the template. I suspect that if you called for an old-style Open Dialog, the control identifier Microsoft assigns to the list view, lst1, might refer to a simple list view. However, in the Explorer-Style Open Dialog (which is for all practical purposes, the only one in use today; The old-style Win 3.x dialog would look too old-fashioned) the lst1 identifier does not address the actual list view the user sees.
Just for the sake of completeness, I present the following means to set input focus to the list view. This would work for the "Select All" project below. It's not very elegant, but it also doesn't use a FindWindow or a Callback function. Note that this code won't work in OnShow. That's too soon. For the sake of simplicity, I didn't cast the Sender parameter, but hard-coded the property OpenDialog1->Handle. This code relies on the fact that the "Drives" or "Look in" combobox comes immediately before the list view in the dialog's tab order.
void __fastcall TForm1::OpenDialog1SelectionChange(TObject *Sender)
{
static int onceonly=true;
static HWND parenthandle;
if(onceonly)
{
parenthandle=GetParent(OpenDialog1->Handle);
::SetFocus(GetDlgItem(parenthandle,cmb2));
::SendMessage(parenthandle,WM_NEXTDLGCTL,false,false);
onceonly=false;
}
}
But this isn't good enough for bulletproof messaging of the list view. The first task is to obtain the handle of the list view window. I first did this with an EnumWindows call, but "dvd8n" came up with a more concise search in a Borland newsgroup,
hWndControl = FindWindowEx (GetParent (Handle), NULL, "SHELLDLL_DefView",NULL); hWndControl = FindWindowEx(hWndControl, NULL, "SysListView32", NULL); FileListHWnd = hWndControl;where Handle is the inherited VCL handle to TOpenDialog. This does assume that you have an Explorer-Style dialog, because that produces the (one suspects, Shell Extension COM-created window) that David finds first. I noticed that the lst2 control identifier is used for the same window. This produces the more concise code:
parentwindow = GetParent(Handle); shellextwindow = GetDlgItem(parentwindow,lst2); filelistview = FindWindowEx(shellextwindow, NULL, "SysListView32", NULL);Paul DiLascia, in MSDN Magazine (January, 2002) "OpenDlg Fixes Preview Problems" provides an even more concise way to do this. His article (written in terms of MFC, not BCB classes) shows a number of useful techniques for manipulating the listview, and otherwise modifying BCB's TOpenDialog. Microsoft links often change, but try this. His code uses a "magic number", the Dialog ID of the listview window within the SysListView32 window. Translating his code to BCB, you could use instead:
parentwindow = GetParent(Handle); filelistview = GetDlgItem(GetDlgItem(parentwindow,lst2),1);Note that anytime you manipulate the controls in an Open Dialog, you have to "wait" until they have been created (and in the case of the list view, populated.) Microsoft tells you to use a dialog Hook Procedure to watch for the CDN_INITDONE message. But since the VCL hooks the Open Dialog for you, you can use the VCL events. I found that OnShow is late enough for repositioning the basic controls, but too soon for assuming that the list view has been populated. Chandrabose describes a message to watch for, but there's a kluge available for BCB users. When the dialog is absolutely complete and displayed for the first time, an OnSelectionChange event occurs. You can make changes in the list view at this time. They may produce a flicker, however, because the contents of the list view have already been painted.
David's request was to implement a pushbutton actuating the keyboard shortcut for "Select All" in a dialog with Multi-Select enabled. You can type <Cntrl><A> while the list view is focused, but we want to do it in code. I'm not displaying the code for adding a pushbutton that might say "Select All" here; That's elsewhere on this site. Combining the code we have so far:
parenthandle=GetParent(Handle);
::SetFocus(GetDlgItem(parenthandle,cmb2));
::SendMessage(parenthandle,WM_NEXTDLGCTL,false,false);
keybd_event(VK_CONTROL,0,0,0);
keybd_event('A',0,0,0);
keybd_event('A',0,KEYEVENTF_KEYUP,0);
keybd_event(VK_CONTROL,0,KEYEVENTF_KEYUP,0);
If you don't like the keybd_event code, David wrote a nice snippet of code that loops through the list view items and selects everything but directories. Windows validates selected filenames, so if you end up with hundreds of files selected, either the selection process or the overhead after pressing "OK" may take a perceptible amount of time. As you may have noticed when the dialog first appears, the Open File Dialog is very slow to execute.
Now let's suppose you want to sort the list of files by size. The following code is based on a visual TOpenDialog, rather than a complete, derived class. There are examples of derived components elsewhere on this site. Note that the list view sort function is a Windows CALLBACK. That means it cannot be a member function, if you do derive a new component. The import of this is that it can't refer to member variables in the function. As you can see, I pass the handle to the listview with the LVM_SORTITEMS message (I used the windows macro form instead of ::SendMessage). This results in that handle being passed with each sort function call, which is how the CALLBACK knows which Open Dialog list view is being sorted. I was surprised to see that while the sort is in progress, it's possible to message the same list view to receive the information needed to perform each comparison. You need to press the "Details" toolbutton on the dialog to see that the files have been sorted into order by file size:
In the header,(partial quote, to show non-member CALLBACK)
public: // User declarations __fastcall TForm1(TComponent* Owner); }; int CALLBACK CompareFunc(LPARAM lParam1, LPARAM lParam2, LPARAM lvhandle); // ---- extern PACKAGE TForm1 *Form1; // ---- #endifThe main unit:
#include <vcl.h>
#pragma hdrstop
#include "commctrl.h"
#include <stdlib.h>
#include "Unit1.h"
// ---
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
#define TMAX 200
// Not a member function
int CALLBACK CompareFunc(LPARAM lParam1, LPARAM lParam2, LPARAM lvhandle)
{
static char * cp; // for parsing size string "nnKB"
static int item1index, item2index;
static LV_FINDINFO lvfi;
static LV_ITEM lvitem1, lvitem2;
static char text1[TMAX],text2[TMAX]; // File names
static char item1text[TMAX],item2text[TMAX]; // sub item text
static int size1,size2;
static int retval;
lvfi.flags=LVFI_PARAM;
// This 32-bit value was assigned by Windows when the listview
// was created. For extra credit, determine what it represents,
// and how it could be more directly useful. (I don't know.)
lvfi.lParam=lParam1;
item1index=ListView_FindItem((HWND)lvhandle,-1,&lvfi);
// This code does not actually use the filename, but if you need
// information not already in the listview to sort it, you'll
// need the filenames. Get filename 1 in next statement.
ListView_GetItemText((HWND) lvhandle,
item1index,0,text1,TMAX);
// Prepare to get a listview subitem for sort item number one
lvitem1.mask=LVIF_TEXT;
lvitem1.iItem=item1index;
lvitem1.iSubItem=1; // 0=filename 1=size 2=typename 3=datetime
lvitem1.stateMask=NULL;
lvitem1.pszText=item1text;
lvitem1.cchTextMax=TMAX;
::SendMessage((HWND)lvhandle,LVM_GETITEM,0,(LPARAM)&lvitem1);
cp=strstr(item1text,"KB");
if(cp)
*cp='\0';
// By happy coincidence, this keeps Directories first, since the
// field is blank for them.
size1=atoi(item1text);
// Repeat for sort item two
lvfi.lParam=lParam2;
item2index=ListView_FindItem((HWND)lvhandle,-1,&lvfi);
ListView_GetItemText((HWND) lvhandle,
item2index,2,text2,TMAX);
lvitem2.mask=LVIF_TEXT;
lvitem2.iItem=item2index;
lvitem2.iSubItem=1; // 0=filename 1=size 2=typename 3=datetime
lvitem2.stateMask=NULL;
lvitem2.pszText=item2text;
lvitem2.cchTextMax=TMAX;
::SendMessage((HWND)lvhandle,LVM_GETITEM,0,(LPARAM)&lvitem2);
cp=strstr(item2text,"KB");
if(cp)
*cp='\0';
size2=atoi(item2text);
retval=size1-size2;
if(!retval)
return 0;
return retval/abs(retval); // need signof()
}
// ---
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
OpenDialog1->Execute();
Application->Terminate();
}
// ---
void __fastcall TForm1::OpenDialog1SelectionChange(TObject *Sender)
{
static int onceonly=true;
static HWND parentwindow=NULL;
static HWND filelistview=NULL;
// Use first OnSelectionChange (when dialog appears) as a
// substitute for completion of listview population message
if(onceonly)
{
onceonly=false;
parentwindow = FindWindowEx (GetParent (OpenDialog1->Handle), NULL,
"SHELLDLL_DefView",NULL);
filelistview = FindWindowEx(parentwindow, NULL, "SysListView32", NULL);
ListView_SortItems(filelistview,CompareFunc,filelistview);
}
}
// --
This is not the last word, because it only sorts the list view once. You might need to put the same code in OnDirectoryChange, or detect whether the same Open Dialog is re-used later in your program. For an example of controlling the items that are placed in the list view, see Chandrabose, or for a more BCB/VCL oriented example, Damon Chandler .
|