如何在Delphi 2009中创建最近使用的文件列表?

3
我有一个TActionManager和一个TActionMainMenuBar,并且我知道如何为主菜单栏中的每个MRU文件添加一个TActionClientItem。但是我是否需要为列表中的每个MRU文件创建单独的操作?还是有一种方法可以创建一个操作,然后基于点击的哪个MRU文件传递标签或其他内容到操作的OnExecute事件中?
Delphi的帮助文档说:“有关MRU列表、示例代码以及查找列表中操作的方法,请参阅在线帮助中的FindItemByAction和FindItemByCaption。”但我在这些主题中找不到任何有用的信息,当然也没有示例代码。我真的很想避免使用第三方组件来完成此操作。

1
请继续观察,Demos\DelphiWin32\VCLWin32\ActionBands\MRU\ABMRU.dpr。 - Free Consulting
我在发布问题后发现了这个,但在编译和运行演示后,它似乎有点小bug,所以我不太确定它是否可靠。 - croceldon
2个回答

4
我使用以下代码,但你可能需要对其进行一些修改。唯一明显缺失的是 IAbbreviatedFileName,它基本上封装了 Windows API 函数 PathCompactPath。你需要一种方式来缩写非常长的文件名,这是我首选的选择。很抱歉给你带来如此庞大的代码,但也许有人会在其中找到有用的东西!
type
  TFileAwareMenuItem = class(TMenuItem)
  private
    FFileName: string;
  public
    property FileName: string read FFileName write FFilename;
  end;

  TMRU = class
  private
    FParent: array of TMenuItem;
    FMenuItemStart: array of TMenuItem;
    FMenuItemFinish: array of TMenuItem;
    FMenuCount: Integer;
    FRegistryKey: string;
    FOwner: TCustomForm;
    FMRUFileNames: TStringList;
    FAction: TAction;
    function GetCount: Integer;
    function GetItem(Index: Integer): string;
    procedure SetAction(Value: TAction);
    procedure Read;
    procedure Write;
    procedure UpdateMenu;
  public
    constructor Create(const RegistrySubKey: string; const Owner: TCustomForm);
    destructor Destroy; override;
    procedure RegisterBoundingMenuItems(Start, Finish: TMenuItem);
    procedure Add(const FileName: string);
    procedure Delete(ItemNum: Integer);
    property Count: Integer read GetCount;
    property Action: TAction read FAction write SetAction;
    property Items[Index: Integer]: string read GetItem; default;
  end;

const
  MRUSize=9;
  AppRegistryKey='??put your apps registry key here??';

var
  Registry: TRegistry;

constructor TMRU.Create(const RegistrySubKey: string; const Owner: TCustomForm);
begin
  inherited Create;
  FRegistryKey := Format('%s\%s', [AppRegistryKey, RegistrySubKey]);
  FOwner := Owner;
  FMRUFileNames := TStringList.Create;
  Read;
end;

destructor TMRU.Destroy;
begin
  Write;
  FreeAndNil(FMRUFileNames);
  inherited;
end;

procedure TMRU.RegisterBoundingMenuItems(Start, Finish: TMenuItem);
begin
  inc(FMenuCount);
  SetLength(FParent, FMenuCount);
  SetLength(FMenuItemStart, FMenuCount);
  SetLength(FMenuItemFinish, FMenuCount);

  FMenuItemStart[FMenuCount-1] := Start;
  FMenuItemFinish[FMenuCount-1] := Finish;
  Assert(Start.Parent=Finish.Parent);
  FParent[FMenuCount-1] := Start.Parent;

  UpdateMenu;
end;

procedure TMRU.UpdateMenu;
var
  Intf: IAbbreviatedFileName;
  i, j: Integer;
  FileName: string;
  NewMenuItem: TFileAwareMenuItem;
begin
  Intf := FOwner as IAbbreviatedFileName;
  for i := 0 to FMenuCount-1 do begin
    j := FMenuItemStart[i].MenuIndex+1;
    while j<FMenuItemFinish[i].MenuIndex do begin
      FParent[i][j].Free;
    end;
    for j := 0 to Count-1 do begin
      NewMenuItem := TFileAwareMenuItem.Create(FMenuItemStart[i].Owner);
      NewMenuItem.Action := Action;
      NewMenuItem.FileName := FMRUFileNames[j];
      FileName := ReplaceString(Intf.AbbreviatedFileName(NewMenuItem.FileName, False), '&', '&&');
      NewMenuItem.Caption := Format('&%d. %s', [j+1, FileName]);
      FParent[i].Insert(FMenuItemFinish[i].MenuIndex, NewMenuItem);
    end;
    FMenuItemStart[i].Visible := (Count>0) and (FMenuItemStart[i].MenuIndex>0);
    FMenuItemFinish[i].Visible := (FMenuItemFinish[i].MenuIndex<FParent[i].Count-1);
  end;
end;

procedure TMRU.Read;
var
  i: Integer;
  s: string;
begin
  if Registry.OpenKey(HKEY_CURRENT_USER, FRegistryKey) then begin
    FMRUFileNames.Clear;
    for i := 0 to MRUSize-1 do begin
      s := Registry.ReadString(IntToStr(i+1), '');
      if s<>'' then begin
        FMRUFileNames.Add(s);
      end;
    end;
    UpdateMenu;
    Registry.CloseKey;
  end;
end;

procedure TMRU.Write;
var
  i: Integer;
  ValueName: string;
begin
  if Registry.OpenKey(HKEY_CURRENT_USER, FRegistryKey, KEY_ALL_ACCESS, True) then begin
    Registry.WriteInteger('Size', MRUSize);
    for i := 0 to MRUSize-1 do begin
      ValueName := IntToStr(i+1);
      if i<Count then begin
        Registry.WriteString(ValueName, FMRUFileNames.Strings[i]);
      end else begin
        if Registry.ValueExists(ValueName) then begin
          Registry.DeleteValue(ValueName);
        end;
      end;
    end;
    Registry.CloseKey;
  end;
end;

function TMRU.GetCount: Integer;
begin
  Result := Min(FMRUFileNames.Count, MRUSize);
end;

function TMRU.GetItem(Index: Integer): string;
begin
  Result := FMRUFileNames[Index];
end;

procedure TMRU.SetAction(Value: TAction);
begin
  if Value<>FAction then begin
    FAction := Value;
    UpdateMenu;
  end;
end;

procedure TMRU.Add(const FileName: string);
var
  i, Index: Integer;
begin
  Index := -1;
  for i := 0 to FMRUFileNames.Count-1 do begin
    if FileNamesEqual(FileName, FMRUFileNames[i]) then begin
      Index := i;
      break;
    end;
  end;

  if Index<>-1 then begin
    FMRUFileNames.Move(Index, 0);
  end else begin
    FMRUFileNames.Insert(0, FileName);
    if FMRUFileNames.Count>MRUSize then begin
      FMRUFileNames.Delete(FMRUFileNames.Count-1);
    end;
  end;

  UpdateMenu;
  Write;
end;

procedure TMRU.Delete(ItemNum: Integer);
begin
  FMRUFileNames.Delete(ItemNum);
  UpdateMenu;
end;

initialization
  Registry := TRegistry.Create;
  if not Registry.KeyExists(AppRegistryKey) then begin
    Registry.CreateKey(AppRegistryKey);
  end;

finalization
  FreeAndNil(Registry);

嗨,我在查看上面的代码,想知道如何将上面的代码实现为在ActionMainMenuBar上创建重新打开菜单?我需要它从ini文件中读取设置,而不是注册表,这可能是我可以解决的,但我无法想出如何实现它。任何帮助都将是好的。 - colin
我不知道ActionMainMenuBar是什么。无论如何,你自己试试,如果卡住了就问一个问题。 - David Heffernan

3
您将为每个菜单项拥有一个单独的TAction,以便它们可以具有不同的Caption值。但您不必拥有单独的OnExecute事件处理程序。事件处理程序将在其Sender参数中接收到对该操作的引用。使用发送者的Tag属性引用存储文件名的列表。(不要使用Caption属性来发现要打开哪个文件;这会限制您执行添加加速器或缩写冗长路径等好事的能力。)
文档也假定您会这样做。FindItemByAction返回给定操作所附加的第一个项目。如果您将单个操作附加到所有MRU菜单项,则将无法使用该函数告诉您选择了哪个菜单。另一方面,菜单项不会包含比相关操作更多的信息,因此我认为没有理由寻找菜单项。直接使用操作中的信息即可。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接