【问题标题】:Delphi OpenDialog filename display problemDelphi OpenDialog文件名显示问题
【发布时间】:2021-03-08 22:55:55
【问题描述】:

在新表单上放置一个 OpenDialog 组件,并将此代码放在 OnCreate 中

opendialog1.filename:='This is a long filename.txt';
opendialog1.execute;

当应用程序运行时,对话框出现,文件名显示在 opendialog 中,但文件名突出显示并向右滚动(即使有足够的空间显示完整的文件名)。

它显示的只是突出显示的“ng filename.txt”。

有没有一种方法可以“取消突出显示”文件名并将文本滚动回左侧,以便显示全名“This is a long filename.txt”?

如果我可以在 OpenDialog 显示后模拟/按下 home 键,问题就会得到解决,但以下选项似乎都不起作用。

keybd_event(VK_HOME, 0, 0, 0);
keybd_event(VK_HOME, 0, KEYEVENTF_KEYUP, 0);

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));

PostMessage(GetParent(OpenDialog1.Handle), WM_KEYDOWN, VK_HOME, 0);
PostMessage(GetParent(OpenDialog1.Handle), WM_KEYUP, VK_HOME, 0);

如果我将这些 sn-ps 代码放在 openDialog1.execute 之前,它似乎确实可以在我的 PC 上运行,但这是一个坏主意,因为对话框尚未打开,因此可能不会收到按键消息。

在 opendialog1.execute 调用之后尝试所有这些方法似乎没有任何作用。

【问题讨论】:

  • 这是一个众所周知的问题。
  • 有什么众所周知的修复方法吗?
  • 我不知道。不过,不能说我有任何搜索。
  • 为什么要设置文件名?用户不应该选择要打开的文件吗!?
  • 它表示上次加载的文件。应用程序启动时随机加载一堆文件中的一个。显示文件名让用户知道哪些文件是自动加载的。

标签: delphi topendialog


【解决方案1】:

建议的解决方案

keybd_event(VK_HOME, 0, 0, 0);
keybd_event(VK_HOME, 0, KEYEVENTF_KEYUP, 0);

安全的,不管你什么时候执行它。

如果我在应用程序中按 Ctrl+O 并在等待对话框显示时切换到其他应用程序怎么办?

然后这个其他应用程序将收到 HOME 键。那么任何事情都可能发生。另一个应用程序可能会显示一个控制患者 IV 药物流速的轨迹栏,并且该 HOME 键可能会将轨迹栏设置为 0 cc/min。

更有可能:您丢失了资源管理器窗口中的选择(可能包含一千张图像)、文档中的插入符号位置(位于非常特定的位置)、树视图中的选定节点等。或者您的媒体播放器重新开始当前曲目。

是的,很多人(比如我自己)确实以这种方式多项任务!


这是一个可能的(安全的)解决方案。不过,我并不是说它是最优雅的。

方法如下:

  1. 我使用OnSelectionChange 事件来获得在创建对话窗口后运行一些代码的机会。但是,我必须手动确保仅在第一次触发此事件时才运行“文件名修复”。我还确保仅在(默认)文件名非空时运行此代码。

  2. 第一次触发事件时,我在其中找到了编辑框。我“知道”它是正确的控件,因为 (1) 它是一个编辑框,并且 (2) 它的文本等于(默认)文件名(不为空)。

  3. 然后我专门指示此编辑框将其插入符号移动到第一个字符,然后(重新)选择每个字符。

完整代码:

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;

之前:

之后:

更新

根据要求,我制作了一个易于重复使用的单元和函数来应用此修复。

这是完整的单元:

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.

要在您自己的单元 X 中使用此单元和功能,只需将单元 (OpenDialogUpgrader) 添加到您的 X 单元的实现部分 uses 子句并更改您的标准

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;

进入

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;

当然,如果你的应用程序在 20 个不同的单元中有 73 个打开的对话框,你只需要这个 OpenDialogUpgrader 单元的一个副本,但是你需要将它添加到你的 20 个单元中的每一个的实现部分 uses 子句中单位。你需要在每个TOpenDialog.Execute之前调用FixOpenDialog

【讨论】:

  • 简单地把keybd_event(VK_HOME, 0, 0, 0); keybd_event(VK_HOME, 0, KEYEVENTF_KEYUP, 0);在当前的 OpenDialog1.OpenDialog1FolderChange 事件中似乎无需新类即可解决问题。我在这里没有看到任何可能的问题吗?它可以解决我的应用程序中的问题。
  • @Some1Else:这不安全。如果我在我的应用程序中按 Ctrl+O 并在等待对话框显示时切换到另一个应用程序怎么办?然后这个其他应用程序将收到 HOME 键。那么任何事情都可能发生。另一个应用程序可能正在显示一个控制患者 IV 药物流速的轨迹栏,并且该 HOME 键可能会将轨迹栏设置为 0 cc/min。 (更有可能:您会丢失资源管理器窗口中的选择、文档中的插入符号位置、树视图中的选定节点等)
  • 如果我使用 SetForegroundWindow(OpenDialog1.handle);就在keyb_event之前?或者,或者,为什么不 SendMessage(OpenDialog1.Handle, WM_KEYDOWN, VK_HOME, 0);工作?这将确保按键直接进入 opendialog 而不是另一个窗口或应用程序。
  • (1) SetForegroundWindow 将不起作用。如果其他应用程序具有键盘焦点,Windows 不会让您的应用程序窃取焦点。 (2) SendMessage(OpenDialog1.Handle, ...) 不起作用,因为这会将消息发送到打开的对话框窗口本身,而不是编辑框窗口。回想一下,对话框中的每个控件本身就是一个窗口。您需要将消息发送到文件名编辑框窗口。
  • 好的,最后的希望。有没有办法不必创建新类?更简单的单个函数找到 opendialog 句柄,然后是编辑句柄,然后向编辑发送消息。我可以在 OpenDialog1.OnSelectionChange 事件过程中自行包含代码。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-10-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-07-14
  • 1970-01-01
  • 2012-08-08
相关资源
最近更新 更多