Put an OpenDialog component on a new form and this code in OnCreate
opendialog1.filename:='This is a long filename.txt';
opendialog1.execute;
When the app runs the dialog appears with the filename displayed in the opendialog, but the filename is highlighted and scrolled to the right (even though there is plenty of room to show the full filename).
All it shows is "ng filename.txt" that has been highlighted.
Is there a way I can "unhighlight" the filename and get the text scrolled back to the left so it shows the full name "This is a long filename.txt"?
The problem would be fixed if I could simulate/push a press of the home key into the OpenDialog once it is shown, but none of the following options seem to work.
keybd_event(VK_HOME, 0, 0, 0);
keybd_event(VK_HOME, 0, KEYEVENTF_KEYUP, 0);
or
input.Itype := INPUT_KEYBOARD;
input.ki.wVk := 47;
input.ki.wScan := 47;
input.ki.dwFlags := 0;
input.ki.time := 0;
input.ki.dwExtraInfo := 0;
SendInput(1, input, sizeof(input));
input.ki.dwFlags := KEYEVENTF_KEYUP;
SendInput(1, input, sizeof(input));
or
PostMessage(GetParent(OpenDialog1.Handle), WM_KEYDOWN, VK_HOME, 0);
PostMessage(GetParent(OpenDialog1.Handle), WM_KEYUP, VK_HOME, 0);
If I put those snippets of code before openDialog1.execute it does seem to work on my PC, but is a bad idea as the dialog is not open yet and so may not receive the key press message(s).
Trying all of those methods after the opendialog1.execute call don't seem to do anything.
The suggested solution
keybd_event(VK_HOME, 0, 0, 0);
keybd_event(VK_HOME, 0, KEYEVENTF_KEYUP, 0);
is not safe, no matter when you execute it.
What if I press Ctrl+O in the app and switch to a different app while waiting for the dialog to show?
Then this other app will receive the HOME key. Then anything can happen. The other app might be displaying a track bar controlling the rate of flow of a patient's IV drugs, and that HOME key might set the trackbar to 0 cc/min.
More likely: you lose the selection in an Explorer window (possibly containing a thousand images), the caret pos in a document (located at a very particular place), the selected node in a tree view, etc. Or your media player restarts the current track.
And yes, many people (such as myself) really do multitask in that way!
This is a possible (safe) solution. I don't claim that it is the most elegant one, though.
The approach is as follows:
I use the OnSelectionChange
event to get a chance to run some code after the dialog window has been created. However, I must manually make sure that I only run my "file name fix" the very first time this event is fired. I also make sure only to run this code if the (default) file name is non-empty.
The first time the event is fired, I find the edit box inside it. I "know" it is the right control because (1) it is an edit box and (2) its text is equal to the (default) file name (which is not empty).
I then specifically instruct this edit box to move its caret to the first character, and then to (re)select every character.
Full code:
type
TOpenDialogFileNameEditData = class
FileName: string;
Handle: HWND;
end;
function EnumChildProc(h: HWND; lp: LPARAM): BOOL; stdcall;
var
WndClass, WndTxt: array[0..1024] of Char;
begin
Result := True;
FillChar(WndClass, SizeOf(WndClass), 0);
FillChar(WndTxt, SizeOf(WndTxt), 0);
if GetClassName(h, WndClass, Length(WndClass)) <> 0 then
begin
if SameText(WndClass, 'Edit') then
begin
if GetWindowText(h, WndTxt, Length(WndTxt)) <> 0 then
begin
if WndTxt = TOpenDialogFileNameEditData(lp).FileName then
begin
TOpenDialogFileNameEditData(lp).Handle := h;
Exit(False);
end;
end;
end;
end;
end;
procedure TForm1.FODE(Sender: TObject);
begin
if Sender is TOpenDialog then
begin
var OpenDialog := TOpenDialog(Sender);
if OpenDialog.Tag <> 0 then
Exit;
OpenDialog.Tag := 1;
var Data := TOpenDialogFileNameEditData.Create;
try
Data.FileName := ExtractFileName(OpenDialog.FileName);
if Data.FileName.IsEmpty then
Exit;
if OpenDialog.Handle <> 0 then
begin
EnumChildWindows(OpenDialog.Handle, @EnumChildProc, NativeInt(Data));
if Data.Handle <> 0 then
begin
SendMessage(Data.Handle, EM_SETSEL, 0, 0); // set caret at first char
SendMessage(Data.Handle, EM_SETSEL, 0, -1); // (re)select all
end;
end;
finally
Data.Free;
end;
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
with TOpenDialog.Create(nil) do
try
FileName := 'This is the story of a horse that met a cat that met a dog the day before.txt';
OnSelectionChange := FODE;
Execute
finally
Free;
end;
end;
Before:
After:
Upon request, I have made an easily reusable unit and function to apply this fix.
Here is the complete unit:
unit OpenDialogUpgrader;
interface
uses
Windows, Messages, SysUtils, Types, Dialogs;
procedure FixOpenDialog(AOpenDialog: TOpenDialog);
implementation
type
TOpenDialogFileNameEditData = class
FileName: string;
Handle: HWND;
class procedure DialogSelectionChange(Sender: TObject);
end;
procedure FixOpenDialog(AOpenDialog: TOpenDialog);
begin
AOpenDialog.Tag := 0;
AOpenDialog.OnSelectionChange := TOpenDialogFileNameEditData.DialogSelectionChange;
end;
{ TOpenDialogFileNameEditData }
function EnumChildProc(h: HWND; lp: LPARAM): BOOL; stdcall;
var
WndClass, WndTxt: array[0..1024] of Char;
begin
Result := True;
FillChar(WndClass, SizeOf(WndClass), 0);
FillChar(WndTxt, SizeOf(WndTxt), 0);
if GetClassName(h, WndClass, Length(WndClass)) <> 0 then
begin
if SameText(WndClass, 'Edit') then
begin
if GetWindowText(h, WndTxt, Length(WndTxt)) <> 0 then
begin
if WndTxt = TOpenDialogFileNameEditData(lp).FileName then
begin
TOpenDialogFileNameEditData(lp).Handle := h;
Exit(False);
end;
end;
end;
end;
end;
class procedure TOpenDialogFileNameEditData.DialogSelectionChange(Sender: TObject);
begin
if Sender is TOpenDialog then
begin
var OpenDialog := TOpenDialog(Sender);
if OpenDialog.Tag <> 0 then
Exit;
OpenDialog.Tag := 1;
var Data := TOpenDialogFileNameEditData.Create;
try
Data.FileName := ExtractFileName(OpenDialog.FileName);
if Data.FileName.IsEmpty then
Exit;
if OpenDialog.Handle <> 0 then
begin
EnumChildWindows(OpenDialog.Handle, @EnumChildProc, NativeInt(Data));
if Data.Handle <> 0 then
begin
SendMessage(Data.Handle, EM_SETSEL, 0, 0); // set caret at first char
SendMessage(Data.Handle, EM_SETSEL, 0, -1); // (re)select all
end;
end;
finally
Data.Free;
end;
end;
end;
end.
To use this unit and function in your own unit X
, just add the unit (OpenDialogUpgrader
) to your X
unit's implementation section uses
clause and change your standard
var OpenDialog := TOpenDialog.Create(nil);
try
OpenDialog.FileName := 'This is the story of a horse that met a cat that met a dog the day before.txt';
OpenDialog.Execute;
finally
OpenDialog.Free;
end;
into
var OpenDialog := TOpenDialog.Create(nil);
try
OpenDialog.FileName := 'This is the story of a horse that met a cat that met a dog the day before.txt';
FixOpenDialog(OpenDialog); // <-- just call this prior to Execute
OpenDialog.Execute;
finally
OpenDialog.Free;
end;
Of course, if your application has 73 open dialogs in 20 different units, you only need a single copy of this OpenDialogUpgrader
unit, but you need to add it to the implementation section uses
clause in each of your 20 units. And you need to call FixOpenDialog
before each TOpenDialog.Execute
.