NLDelphi logo

Apada
Start Forum Nieuws Artikelen Links E mail Statistieken
NLDelphi

Ga Terug   NLDelphi > Tips voor je collega's > Artikelen & tips

Antwoord
 
Onderwerpopties Zoek in onderwerp Stem op Onderwerp Weergavemodus
Oud 24-Jul-10, 21:48   #1
BobbaFet
Senior Member
 
Geregistreerd op: Apr 2003
Locatie: Netherlands
Berichten: 287
Laatst gebruikte bestanden in het TMainMenu

Ik vond dit toch wel een heel leuk en handig stukje code dat jullie gebruikers misschien wel op prijs gaan stellen. Het is code om de laatste (tot een maximum van 10) opgeslagen bestanden in het TMainMenu van je applicatie weer te geven á la Delphi 7's (weet niet hoe het geregeld is in andere Delphi's, heb heel kort 3 en 5 gebruikt maar gebruik nu al jaren Delphi 7) reopen menu item. Voor degenen die nog niet zo heel erg bekend zijn met het dynamisch aanmaken van objecten heb ik er ook een beetje uitleg bij gedaan van hoe dat werkt. Hopelijk hebben jullie er wat aan.

Allereerst zal ik de twee kleintjes posten die erbij horen:

De functie ExtractCaption:
Delphi Code:
  1. function ExtractCaption(S: string): string;
  2. begin
  3.     while Pos('\', S) <> 0 do
  4.         Delete(S, 1, Pos('\', S));
  5.     Delete(S, Pos('.ksb', S), 4);  // Gebruik hier natuurlijk je eigen extensie
  6.     Result := S;
  7. end;

Deze functie haalt alles weg dat niet de bestandsnaam is. Dus het pad en de extensie en geeft deze in een string formaat terug. Deze wordt later gebruikt als de Caption van de MenuItems die worden toegevoegd aan het MainMenu danwel submenu.

Het onClick-event voor het dynamisch aangemaakte menuitem.
Delphi Code:
  1. procedure TForm1.DynamicMenuItemClick(Sender: TObject);
  2. begin
  3.     if Sender.ClassType = TMenuItem then
  4.     begin
  5.         if (Sender as TMenuItem).Tag = 8 then
  6.             ListBox1.Items.LoadFromFile((Sender as TMenuItem).Hint);
  7.     end;
  8. end;

Zoals je kunt zien wordt er hier op twee dingen gecontrolleerd, namelijk of de Sender ook daadwerkelijk een TMenuItem is en of de Tag 8 is. Tag gebruik ik zelf vaak als secondaire check die moet valideren dat het ook daadwerkelijk de bedoeling is dat de Sender deze procedure gebruikt. Je kunt natuurlijk ook het objectnaam gebruiken, maar dit vind ik zelf makkelijker en is net zo effectief. Bij het dynamisch aanmaken van objecten moet je de events zelf toekennen en ze dus ook zelf in je type-lijst zetten. Klopt de hele mikmak, dan wordt het bestand geladen in ListBox1.

En dan nu de grote procedure die het allemaal laat werken:
Delphi Code:
  1. procedure TForm1.Update10LastUsedFiles(thisFile: string);
  2. var LastUsedFiles: TStringList;
  3.     R: TRegistry;
  4.     i: integer;
  5.     M: TMenuItem;
  6. begin
  7.     // Adds 10 menuitems that automatically load from the Open submenu
  8.     LastUsedFiles := TStringList.Create;
  9.     LastUsedFiles.Clear;
  10.  
  11.     // Delete all old files from the submenu
  12.     if Load1.Count > 1 then
  13.     begin
  14.         for i := Load1.Count - 1 downto 1 do
  15.             Load1.Items[i].Free;
  16.     end;
  17.  
  18.     // Get 10 current last used files from the registry
  19.     R := TRegistry.Create;
  20.     R.RootKey := HKEY_CURRENT_USER;
  21.     if R.KeyExists('Software\<Bedrijf>\<Applicatie>\Files') then
  22.     begin
  23.         R.OpenKey('Software\<Bedrijf>\<Applicatie>\Files', False);
  24.         if R.ValueExists('0') then
  25.         begin
  26.             LastUsedFiles.Add(R.ReadString('0'));
  27.             R.DeleteValue('0');
  28.         end;
  29.  
  30.         if R.ValueExists('1') then
  31.         begin
  32.             LastUsedFiles.Add(R.ReadString('1'));
  33.             R.DeleteValue('1');
  34.         end;
  35.  
  36.         if R.ValueExists('2') then
  37.         begin
  38.             LastUsedFiles.Add(R.ReadString('2'));
  39.             R.DeleteValue('2');
  40.         end;
  41.  
  42.         if R.ValueExists('3') then
  43.         begin
  44.             LastUsedFiles.Add(R.ReadString('3'));
  45.             R.DeleteValue('3');
  46.         end;
  47.  
  48.         if R.ValueExists('4') then
  49.         begin
  50.             LastUsedFiles.Add(R.ReadString('4'));
  51.             R.DeleteValue('4');
  52.         end;
  53.  
  54.         if R.ValueExists('5') then
  55.         begin
  56.             LastUsedFiles.Add(R.ReadString('5'));
  57.             R.DeleteValue('5');
  58.         end;
  59.  
  60.         if R.ValueExists('6') then
  61.         begin
  62.             LastUsedFiles.Add(R.ReadString('6'));
  63.             R.DeleteValue('6');
  64.         end;
  65.  
  66.         if R.ValueExists('7') then
  67.         begin
  68.             LastUsedFiles.Add(R.ReadString('7'));
  69.             R.DeleteValue('7');
  70.         end;
  71.  
  72.         if R.ValueExists('8') then
  73.         begin
  74.             LastUsedFiles.Add(R.ReadString('8'));
  75.             R.DeleteValue('8');
  76.         end;
  77.  
  78.         if R.ValueExists('9') then
  79.         begin
  80.             LastUsedFiles.Add(R.ReadString('9'));
  81.             R.DeleteValue('9');
  82.         end;
  83.  
  84.         R.CloseKey;
  85.     end;
  86.     R.Free;
  87.  
  88.     // Prevent empty item being added to the list
  89.     if thisFile <> '' then
  90.     begin
  91.         // Update file list in order of last accessed
  92.         LastUsedFiles.Insert(0, thisFile);
  93.  
  94.         // Prevent duplicates of added file being present in the list
  95.         if LastUsedFiles.Count > 1 then
  96.         begin
  97.             for i := LastUsedFiles.Count - 1 downto 1 do
  98.             begin
  99.                 if LastUsedFiles.Strings[i] = thisFile then
  100.                     LastUsedFiles.Delete(i);
  101.             end;
  102.         end;
  103.     end;
  104.  
  105.     // Save new file list to the registry
  106.     R := TRegistry.Create;
  107.     R.RootKey := HKEY_CURRENT_USER;
  108.     R.OpenKey('Software\CWCon\Bot-O-Matic\Files', True);
  109.  
  110.     for i := 0 to LastUsedFiles.Count - 1 do
  111.     begin
  112.         R.WriteString(IntToStr(i), LastUsedFiles.Strings[i]);
  113.  
  114.         if i = 9 then
  115.             Break;
  116.     end;
  117.  
  118.     R.CloseKey;
  119.     R.Free;
  120.  
  121.     // Create new submenu items
  122.     for i := 0 to LastUsedFiles.Count - 1 do
  123.     begin
  124.         M := TMenuItem.Create(Load1);
  125.         M.Name := 'DMI' + IntToStr(i);
  126.         M.Hint := LastUsedFiles.Strings[i];
  127.         M.Caption := FormatFloat('00: "' + ExtractCaption(LastUsedFiles.Strings[i]) + '"', i + 1);
  128.         M.OnClick := DynamicMenuItemClick;
  129.         M.Tag := 8;
  130.         Load1.Add(M);
  131.  
  132.         if i = 9 then
  133.             Break;
  134.     end;
  135.  
  136.     LastUsedFiles.Free;
  137. end;

Even belangrijk:
Hou bij het lezen van deze code dat Load1 een TMenuItem is. Load1 is zelf leeg en doet verder helemaal niets. Een soort van placeholder zeg maar.

Ik zal deze procedure sectie voor sectie bespreken.

Een verse start:
Delphi Code:
  1. procedure TForm1.Update10LastUsedFiles(thisFile: string);
  2. var LastUsedFiles: TStringList;
  3.     R: TRegistry;
  4.     i: integer;
  5.     M: TMenuItem;
  6. begin
  7.     // Adds 10 files that automatically load from the Open submenu
  8.     LastUsedFiles := TStringList.Create;
  9.     LastUsedFiles.Clear;
  10.  
  11.     // Delete all old files from the submenu
  12.     if Load1.Count > 1 then
  13.     begin
  14.         for i := Load1.Count - 1 downto 1 do
  15.             Load1.Items[i].Free;
  16.     end;

Zoals je ziet begin ik hier met het aanmaken van een stringlist. Deze gebruik ik om de bestandnamen tijdelijk in op te slaan. Verder zie je dat ik alle oude menuitems van Free zodat je geen duplicaten krijgt iedere keer dat de code wordt uitgevoerd.

Delphi Code:
  1. // Get 10 current last used files from the registry
  2.     R := TRegistry.Create;
  3.     R.RootKey := HKEY_CURRENT_USER;
  4.     if R.KeyExists('Software\<Bedrijf>\<Applicatie>\Files') then
  5.     begin
  6.         R.OpenKey('Software\<Bedrijf>\<Applicatie>\Files', False);
  7.         if R.ValueExists('0') then
  8.         begin
  9.             LastUsedFiles.Add(R.ReadString('0'));
  10.             R.DeleteValue('0');
  11.         end;
  12.  
  13.         if R.ValueExists('1') then
  14.         begin
  15.             LastUsedFiles.Add(R.ReadString('1'));
  16.             R.DeleteValue('1');
  17.         end;
  18.  
  19.         if R.ValueExists('2') then
  20.         begin
  21.             LastUsedFiles.Add(R.ReadString('2'));
  22.             R.DeleteValue('2');
  23.         end;
  24.  
  25.         if R.ValueExists('3') then
  26.         begin
  27.             LastUsedFiles.Add(R.ReadString('3'));
  28.             R.DeleteValue('3');
  29.         end;
  30.  
  31.         if R.ValueExists('4') then
  32.         begin
  33.             LastUsedFiles.Add(R.ReadString('4'));
  34.             R.DeleteValue('4');
  35.         end;
  36.  
  37.         if R.ValueExists('5') then
  38.         begin
  39.             LastUsedFiles.Add(R.ReadString('5'));
  40.             R.DeleteValue('5');
  41.         end;
  42.  
  43.         if R.ValueExists('6') then
  44.         begin
  45.             LastUsedFiles.Add(R.ReadString('6'));
  46.             R.DeleteValue('6');
  47.         end;
  48.  
  49.         if R.ValueExists('7') then
  50.         begin
  51.             LastUsedFiles.Add(R.ReadString('7'));
  52.             R.DeleteValue('7');
  53.         end;
  54.  
  55.         if R.ValueExists('8') then
  56.         begin
  57.             LastUsedFiles.Add(R.ReadString('8'));
  58.             R.DeleteValue('8');
  59.         end;
  60.  
  61.         if R.ValueExists('9') then
  62.         begin
  63.             LastUsedFiles.Add(R.ReadString('9'));
  64.             R.DeleteValue('9');
  65.         end;
  66.  
  67.         R.CloseKey;
  68.     end;
  69.     R.Free;

Een vrij voor zichzelf sprekend stukje. Het leest de waarden uit de Registry en zet deze in de StringList die we in de vorige sectie hebben aangemaakt. Vervolgens verwijderd hij de waarden uit de Registry. Waarom? Ik vind het zelf gewoon netjes om het op deze manier te doen, je hoeft ze niet te verwijderen maar ik doe het lekker toch omdat ik ze verderop toch weer opsla.

Iets dat je misschien opvalt is dat ik CanCreate op false heb staan hier. De reden hiervoor is dat als het programma het moet aanmaken dan staat er toch niets in om uit te lezen en kan hij programma dat net zo goed niet doen. Ik vind dat vallen onder het opslaan van de gegevens dus het createn van de Key doen we daar ook wel.

Delphi Code:
  1. // Prevent empty item being added to the list
  2.     if thisFile <> '' then
  3.     begin
  4.         // Update file list in order of last accessed
  5.         LastUsedFiles.Insert(0, thisFile);
  6.  
  7.         // Prevent duplicates of added file being present in the list
  8.         if LastUsedFiles.Count > 1 then
  9.         begin
  10.             for i := LastUsedFiles.Count - 1 downto 1 do
  11.             begin
  12.                 if LastUsedFiles.Strings[i] = thisFile then
  13.                     LastUsedFiles.Delete(i);
  14.             end;
  15.         end;
  16.     end;

Dit is een belangrijke sectie, want deze zorgt niet alleen het updaten maar zorgt er ook voor dat je deze procedure kunt gebruiken tijdens FormCreate om het menu aan te maken door lege strings te negeren. Door het invoegen van een nieuwe bestandsnaam thisFile op index 0 in de stringlist verzeker je jezelf ervan dat verderop in de code als de menuitems worden aangemaakt dat de meest recente altijd als eerste wordt aangemaakt en toegevoegd (en daarom dus bovenaan staat) en de minst recente als laatste (en daarom onderaan staat).

Het gedeelte onder "// Prevent duplicates of added file being present in the list" zorgt ervoor dat mocht het bestand al een keer in de lijst voorkomen dat deze dus niet twee keer zal voorkomen door de entry te verwijderen uit de lijst. Normaal gesproken omdat je de gehele lijst doorloopt en je weet dat een bestandnaam (hier dus het volledige pad en extensie) maximaal 2 keer kan voorkomen vanwege de inrichting van je code zou je hem kunnen breaken nadat je het duplicaat hebt verwijdert maar aangezien we het hier hebben over een maximale lijst van 11 items acht ik dat niet nodig.

Delphi Code:
  1. // Save new file list to the registry
  2.     R := TRegistry.Create;
  3.     R.RootKey := HKEY_CURRENT_USER;
  4.     R.OpenKey('Software\<Bedrijf>\<Applicatie>\Files', True);
  5.  
  6.     for i := 0 to LastUsedFiles.Count - 1 do
  7.     begin
  8.         R.WriteString(IntToStr(i), LastUsedFiles.Strings[i]);
  9.  
  10.         if i = 9 then
  11.             Break;
  12.     end;
  13.  
  14.     R.CloseKey;
  15.     R.Free;

Hier slaan we de tot 10 laatst gebruikte bestanden weer op in het Registry. In tegenstelling tot het laden van de gegevens in de TStringList (genaamd LastUsedFiles) staat CanCreate hier op True: als je geen plek hebt gecreëert kun je daar ook niet opslaan. Een simpel loopje door de stringlist van 0 naar X helpt ons hierbij niet alleen bij het de juiste string opslaan op de juiste positie maar ook bij de naamgeving.

De "if i = 9 then Break;". Dit stukje staat erin omdat de stringlist 11 unieke bestandsnamen kan bevatten: 10 van het laden plus 1 die zojuist is opgeslagen. Vandaar de loop wordt beëindigd zodra nummer 10 (met indexgetal 9) is afgehandeld.

Delphi Code:
  1. // Create new submenu items
  2.     for i := 0 to LastUsedFiles.Count - 1 do
  3.     begin
  4.         M := TMenuItem.Create(Load1);
  5.         M.Name := 'DMI' + IntToStr(i);
  6.         M.Hint := LastUsedFiles.Strings[i];
  7.         M.Caption := FormatFloat('00: "' + ExtractCaption(LastUsedFiles.Strings[i]) + '"', i + 1);
  8.         M.OnClick := DynamicMenuItemClick;
  9.         M.Tag := 8;
  10.         Load1.Add(M);
  11.  
  12.         if i = 9 then
  13.             Break;
  14.     end;
  15.  
  16.     LastUsedFiles.Free;
  17. end;

En dan nu het móment supreme! Het aanmaken van de menuitems! Hier loop ik dus door de LastUsedFiles stringlist heen om de menuitems in de juiste volgorde aan te maken. Eerst maken we het menuitem aan op dezelfde wijze als dat we de StringList LastUsedFiles aanmaakte alleen moeten we hier ook een owner meegeven. Ik geef dan altijd als owner de parent mee, in dit geval het menuitem waarvan het item dat we nu aanmaken een submenuitem is.

"M.Name". Componenten hebben namen, ook als ze dynamisch worden aangemaakt. Ik geef hier dus de opening DMI (Dynamisch Menu Item) mee plus het indexgetal om ze te separeren. In de hint geef ik de volledige bestandsnaam mee. Lekker simpele manier om in dit geval de data waar het om gaat mee te geven. De caption heb ik een heel gegoochel van gemaakt (met name omdat ik dat leuk vind) waardoor de kale bestandsnaam (zonder pad en extensie) wordt voor gegaan door een nummer in de trant van 01, 02, tot 10. Met onClick geef ik het event mee dat moet afgaan op het moment dat er wordt geklikt op het menuitem (vandaar ook de wijze waarop die geprogrammeerd is, een en dezelfde procedure kan door tot 10 verschillende menuitems worden aangeroepen en het zal telkens een ander bestand moeten laden).

Dan komt tag, Tag heeft geen echte functie geloof ik maar kun je gebruiken als identifier. Ik gebruik em hier om aan te geven dat dit menuitem de onclick event procedure mag gebruiken. Je kan dat ook via de componentnaam doen (dankzij de vaste prefix) en dan iets van:

Delphi Code:
  1. if Copy((Sender as TMenuItem).Name, 1, 3) = 'DMI' then

Is goed mogelijk. Maar tag gebruiken is gewoon makkelijker, vind ik. Daarna voegen we ons menuitem toe aan het het submenu middels de Add en klaar is Kees. Hier wederom de "if i = 9 then Break;" code om te zorgen dat er niet meer items dan 10 items worden gecreëerd. Je zou je kunnen afvragen waarom verwijderde je potentiele 11 niet gewoon. Simpel, ik bedenk het me nu net pas dat dat ook kan. Hahaha.

Hoe gebruik je dit dan uiteindelijk:

Ik gebruik zelf de opslag procedure van mijn programma en het FormCreate-event.

Via SaveDialog1 een bestand aan de lijst toevoegen.
Delphi Code:
  1. if SaveDialog1.Execute then
  2.     begin
  3.         ListBox1.Items.SaveToFile(SaveDialog1.FileName);
  4.         Update10LastUsedFiles(SaveDialog1.FileName);
  5.     end;

Via FormCreate de menuitems aanmaken:
Delphi Code:
  1. Update10LastUsedFiles(''); // File adding is prevented due to empty string

Groeten,

BobbaFet.

PS: Mocht je je afvragen waarom de comments in het Engels zijn, ik heb er een hekel aan twee talen door elkaar heen te lezen.
__________________
Iedereen heeft recht op mijn mening!
"You're not thinking, you're merely being logical!"

Laatst aangepast door BobbaFet : 24-Jul-10 om 21:54
BobbaFet is offline   Met citaat antwoorden
Oud 25-Jul-10, 14:19   #2
GolezTrol
Moderateur
 
GolezTrol's Avatar
 
Geregistreerd op: Oct 2002
Berichten: 12.760
Handige functionaliteit, zo'n MRU (Most Recently Used)-lijstje!

Ik heb wel het idee dat je code misschien wat omslachtig in elkaar zit. Waarom al die losse ifjes?
Code:
        if R.ValueExists('0') then
        begin
            LastUsedFiles.Add(R.ReadString('0'));
            R.DeleteValue('0');
        end
Dat kan natuurlijk in een for-loop. Met IntToStr(i) kun je zo '0' tot '9' invullen, of eventueel zelfs verder. Je kunt je code aanpassen tot een theoretisch oneindige lijst. Natuurlijk moet daar een kunstmatig maximum op, want het is helemaal niet handig om een immer groeiende MRU-lijst te hebben, maar het is zonde dat die beperking zo hard in code zit.

Verder zou ik er zelf voor kiezen om een class te maken voor deze functionaliteit. Door er bijvoorbeeld een component of een singleton van te maken, blijft deze class in het geheugen. Dat stelt je dan tevens in staat om een lijst van menu-items bij te houden, waardoor je niet meer hoeft te gaan graven naar menu-items met een bepaalde tag (met alle risico's van dien).

Bovendien kun je dan ook opties maken voor het wel of niet strippen van het pad en de extensie. Daarvoor kun je je class uitbreiden met properties.
__________________
Klik hier!
GolezTrol is nu online   Met citaat antwoorden
Oud 25-Jul-10, 20:15   #3
GolezTrol
Moderateur
 
GolezTrol's Avatar
 
Geregistreerd op: Oct 2002
Berichten: 12.760
Het tikt best lekker met Zomergasten op de achtergrond, dus even een stukje code ter illustatie.

Een component dus. Met properties kun je instellen of je de lijst direct op wilt slaan, of liever pad als het component vrijgegeven wordt. Doorgaans is dat bij het afsluiten van het mailform, of de datamodule waar je zo'n component op zet, en dus bij het afsluiten van de applicatie.
Delphi Code:
  1. TBigCustomMRU = class(TComponent)
  2.   private
  3.     FMRUList: TStringList;
  4.     FSaveOnDestroy: Boolean;
  5.     FSaveOnChange: Boolean;
  6.     FMaxItems: Integer;
  7.   protected
  8.     property SaveOnChange: Boolean
  9.              read FSaveOnChange write FSaveOnChange
  10.              default True;
  11.     property SaveOnDestroy: Boolean
  12.              read FSaveOnDestroy write FSaveOnDestroy
  13.              default False;
  14.     property MaxItems: Integer
  15.              read FMaxItems write FMaxItems
  16.              default 10;
Het component staat je toe om files toe te voegen aan de lijst, en om de lijst leeg te maken. Ook kun je de lijst opslaan, al gaat dit automatisch. Deze procedure roep je in principe niet rechtstreeks aan, al zie ik ook geen reden om dat onmogelijk te maken.
Delphi Code:
  1. public
  2.     procedure Add(const AFile: string); virtual;
  3.     procedure Clear; virtual;
  4.     procedure Save; virtual;
Het toevoegen van een file zet de file bovenaan de lijst. Zo houd je de lijst op volgorde van openen.
Delphi Code:
  1. procedure TBigCustomMRU.Add(const AFile: string);
  2. var
  3.   n: Integer;
  4. begin
  5.   inherited;
  6.  
  7.   // Put the new file on top of the list, except when it's already there.
  8.   n := FMRUList.IndexOf(AFile);
  9.   if n > 0 then
  10.     FMRUList.Delete(n);
  11.   if n <> 0 then
  12.     FMRUList.Insert(0, AFile);
  13.  
  14.   Changed;
  15. end;
  16.  
  17. procedure TBigCustomMRU.Clear;
  18. begin
  19.   // Clears the MRU list.
  20.   FMRUList.Clear;
  21.  
  22.   Changed;
  23. end;
Het kan natuurlijk zijn dat je een menu gebruikt, maar misschien heb je ook wel een 3rd party menucomponent, of wil je je MRU lijstje op een andere manier tonen. Daarvoor bedacht ik dat het misschien handig zou zijn om een apart component te hebben om de MRUlijst aan het menu te koppelen.
Delphi Code:
  1. TBigCustomMRUController = class(TComponent)
  2.   private
  3.     FOnExecute: TBigMRUFileExecuteEvent;
  4.     FOnGetCaption: TBigMRUGetCaptionEvent;
  5.   protected
  6.     procedure Refresh(Subject: TBigCustomMRU); virtual;
  7.     procedure Execute(const AFile: string); virtual;
  8.  
  9.     function GetCaption(FileName: string): string; virtual;
  10.  
  11.     property OnExecute: TBigMRUFileExecuteEvent
  12.              read FOnExecute write FOnExecute;
  13.     property OnGetCaption: TBigMRUGetCaptionEvent
  14.              read FOnGetCaption write FOnGetCaption;
  15.   end;
Deze algemene implementatie is een base class voor een specifieke implementatie die aan een menu gekoppeld zit. Deze basisimplementatie implementeert alleen een Refresh procedure die aangeroepen wordt als de MRUlijst wijzigt. Zodoende kun je het menu opnieuw opbouwen.
De Execute procedure roept het OnExecute event aan. In dat event kun je als programmeur het bestand openen.
Delphi Code:
  1. procedure TBigCustomMRUController.Execute(const AFile: string);
  2. begin
  3.   // Execute the OnExecute event to which the application can respond by opening
  4.   // the given file.
  5.   if Assigned(FOnExecute) then
  6.     FOnExecute(Self, AFile);
  7. end;
De GetCaption functie genereert de caption voor het menu-item. Ook dit is in principe niet afhankelijk van het menu en zit dus in de base class. De functie geeft in principe gewoon de filename terug, maar met een event kun je dat beďnvloeden.
Delphi Code:
  1. function TBigCustomMRUController.GetCaption(FileName: string): string;
  2. begin
  3.   // Return a caption for a filename. The default is the filename itself, but then
  4.   // application can modify it in the OnGetCaption event.
  5.   Result := FileName;
  6.  
  7.   if Assigned(FOnGetCaption) then
  8.     FOnGetCaption(Self, FileName, Result);
  9. end;
De controller zit gekoppeld aan het MRU component. Dat is in principe gewoon een property, al zit er wel wat extra code aan om dat ook in de IDE gewoon te kunnen gebruiken:
Delphi Code:
  1. private
  2.     FController: TBigCustomMRUController;
  3.     procedure SetController(const Value: TBigCustomMRUController);
  4.   protected
  5.     property Controller: TBigCustomMRUController
  6.              read FController write SetController;
  7.  
  8.     procedure Changed;
  9.     procedure Notification(AComponent: TComponent;
  10.       Operation: TOperation); override;
SetController zorgt ervoor dat de oude controller goed losgekoppeld wordt en de nieuwe gekoppeld wordt. Met 'koppelen' bedoel ik dat de het MRUControl automatisch een seintje, een notification, krijg als de controller wordt vrijgegeven. Dat is nodig om te voorkomen dat je een Access Violation krijgt als je een gelinkte controller verwijdert in de IDE, of als bij het afsluiten van het form de controller per ongeluk eerder vrij wordt gegeven. Daarbij zal deze method direct de nieuwe controller refreshen.
Delphi Code:
  1. procedure TBigCustomMRU.SetController(
  2.   const Value: TBigCustomMRUController);
  3. begin
  4.   // If the controller is changed, unlink the old controller (if any) and
  5.   // connect the new one
  6.   if FController <> Value then
  7.   begin
  8.     if Assigned(FController) then
  9.       FController.RemoveFreeNotification(Self);
  10.  
  11.     FController := Value;
  12.  
  13.     if Assigned(FController) then
  14.     begin
  15.       FController.FreeNotification(Self);
  16.       FController.Refresh(Self);
  17.     end;
  18.   end;
  19. end;
De Notification procedure is overerfd van TComponent. Deze procedure wordt aangeroepen als de controller wordt vrijgegeven. Je ziet hier ook code voor een storage, omdat ik voor het opslaan ook wil kunnen kiezen voor de registry, een database of gewoon niets. Dat wordt dus op dezelfde manier geďmplementeerd: met een los component.
Delphi Code:
  1. procedure TBigCustomMRU.Notification(AComponent: TComponent;
  2.   Operation: TOperation);
  3. begin
  4.   inherited;
  5.  
  6.   // Check if any of the linked components is freed. If so, break the connection.
  7.   if Operation = opRemove then
  8.   begin
  9.     if AComponent = FController then
  10.       SetController(nil);
  11.     if AComponent = FStorage then
  12.       SetStorage(nil);
  13.   end;
  14. end;
Na elke wijziging in de lijst roep je Changed aan om de gelinkte controls op de hoogte te stellen:
Delphi Code:
  1. procedure TBigCustomMRU.Changed;
  2. begin
  3.   // Save the MRU list
  4.   if FSaveOnChange then
  5.     Save;
  6.  
  7.   // Refresh the MRU controller
  8.   if Assigned(FController) then
  9.     FController.Refresh(Self);
  10. end;
De afgeleide Menu-controller implementeert op een soortgelijke manier de koppeling met een parent menuitem waaronder de menuitems worden gehangen.
Delphi Code:
  1. type
  2.   TBigMenuMRUController = class(TBigCustomMRUController)
  3.   private
  4.     FMenuList: TStringList;
  5.     FParentMenuItem: TMenuItem;
  6.     procedure SetParentMenuItem(const Value: TMenuItem);
  7.   protected
  8.     procedure Refresh(Subject: TBigCustomMRU); override;
  9.     procedure Clear;
  10.     procedure ItemClick(Sender: TObject);
  11.     procedure Notification(AComponent: TComponent;
  12.       Operation: TOperation); override;
  13.   public
  14.     constructor Create(AOwner: TComponent); override;
  15.     destructor Destroy; override;
  16.   published
  17.     property ParentMenuItem: TMenuItem
  18.              read FParentMenuItem write SetParentMenuItem;
  19.   end;
Deze implementeert een eenvoudige lijst met een stringlist. Hierin staat de filename en het gelinkte menuitem. De Refresh procedure implementeert het vullen van het menu:
Delphi Code:
  1. procedure TBigMenuMRUController.Refresh(Subject: TBigCustomMRU);
  2. var
  3.   i: Integer;
  4.   Item: TMenuItem;
  5.   FileName: String;
  6. begin
  7.   inherited;
  8.  
  9.   Clear;
  10.  
  11.   if Assigned(FParentMenuItem) then
  12.   begin
  13.     for i := 0 to Subject.MRUList.Count - 1 do
  14.     begin
  15.       FileName := Subject.MRUList[i];
  16.  
  17.       Item := TMenuItem.Create(nil);
  18.       Item.OnClick := ItemClick;
  19.       Item.Caption := GetCaption(FileName);
  20.  
  21.       FParentMenuItem.Add(Item);
  22.  
  23.       FMenuList.AddObject(FileName, Item);
  24.     end;
  25.   end;
  26.  
  27. end;
Een click op een menuitem zorgt dat het menuitem wordt opgezocht in de lijst van de controller. De bijbehorende filename wordt vervolgens uitgevoerd via de Execute method in de base class (die het OnExecute event afvuurt).
Delphi Code:
  1. procedure TBigMenuMRUController.ItemClick(Sender: TObject);
  2. var
  3.   n: Integer;
  4. begin
  5.   n := FMenuList.IndexOfObject(Sender);
  6.   if n = -1 then
  7.     raise Exception.Create('Clicked item not found');
  8.  
  9.   Execute(FMenuList[n]);
  10. end;


Het nadeel van deze manier, is dat je een stukje meer moet tikken voor je de componenten af hebt.
Het voordeel is dat je deze code heel makkelijk kunt hergebruiken. Je kunt eenvoudigweg de componenten installeren en op je form of datamodule zetten. Je kunt in de IDE de componenten koppelen en een menuitem kiezen waaronder je je MRU menu wilt zetten.

Het enige wat je echt moet coden in je applicatie, is de code in het OnExecute event. Die moet namelijk de file openen.

Optioneel kun je nog het OnGetCaption event koppelen om bijvoorbeeld alleen de filename terug te geven als caption. Overigens zijn daar handige functies voor. Je hoeft dus niet zelf op zoek naar backslashes:
Delphi Code:
  1. procedure TForm1.BigMenuMRUController1GetCaption(Sender: TObject;
  2.   const AFile: String; var ACaption: String);
  3. begin
  4.   ACaption := ChangeFileExt(ExtractFileName(AFile), '.');
  5. end;

De files heb ik toegevoegd, al zijn ze nog niet helemaal af. Ik heb bijvoorbeeld nog geen storage object geďmplementeerd om de MRU lijst daadwerkelijk in de registry op te slaan.

Je kunt de componenten natuurlijk vanuit code instantiëren, maar je kunt ze ook installeren in een package.
Bijgesloten Bestanden
Bestandstype: pas BigMRU.pas (6,2 KB, 10x gelezen)
Bestandstype: pas BigMenuMRUController.pas (3,1 KB, 9x gelezen)
__________________
Klik hier!
GolezTrol is nu online   Met citaat antwoorden
Oud 26-Jul-10, 11:51   #4
WhatJac3
Senior Member
 
Geregistreerd op: Mar 2003
Berichten: 608
Goede functionaliteit, vaak ook zelf over na zitten denken. Sla het op in .ini ipv registery and ik denk dat ik het in mijn code ga implementeren....
WhatJac3 is offline   Met citaat antwoorden
Oud 26-Jul-10, 12:10   #5
jkuiper
John Kuiper
 
Geregistreerd op: Apr 2007
Locatie: Almere
Berichten: 3.430
Hou daar rekening mee dat het niet in een directory wordt geplaatst waar je programma staat. Dat vind Vista en W7 niet leuk. In dat opzicht is de registry makkelijker.
__________________
Je bent nooit te oud om te leren, maar afleren des te moeilijker
jkuiper is offline   Met citaat antwoorden
Oud 26-Jul-10, 12:26   #6
GolezTrol
Moderateur
 
GolezTrol's Avatar
 
Geregistreerd op: Oct 2002
Berichten: 12.760
Het voordeel van mijn opzet is dat je heel makkelijk een IniStorage kunt maken. Je kunt er overigens voor kiezen om een lijst op te slaan in de registry of de inifile, maar wellicht is het veel makkelijker om gewoon de CommaText van de stringlist met files op te slaan in een enkele string in de registry of de inifile.

De IniFile storage kan, zoals John Kuiper zegt, zo gebouwd worden dat die bijvoorbeeld de AppData map gebruikt om de file op te slaan.
__________________
Klik hier!
GolezTrol is nu online   Met citaat antwoorden
Oud 26-Jul-10, 12:50   #7
Henske
TCrap.Create(Self)
 
Henske's Avatar
 
Geregistreerd op: Nov 2003
Locatie: Venray - NL
Berichten: 4.379
Citaat:
Origineel gepost door jkuiper Bekijk Bericht
In dat opzicht is de registry makkelijker.
... en heeft ook weer nadelen.

We hebben ooit eens iemand hier gehad die alle gegevens in de registry opsloeg. Werkte mooi totdat er iets aangepast moest gaan worden. De gebruikers hadden geen rechten om regedit te draaien en zo de settings aan te passen. Dit moest door een admin gedaan worden. Om dit te bewerkstelligen moest er een persoon met admin-rechten het veld in, moest van de gebruiker de rechten aangepast worden dat er wel regedit gedraaid kon worden, registry aanpassen en vervolgens rechten weer terug zetten.

Al met al wel iets om over na te denken indien je gebruik gaat maken van de registry.
__________________
De beste manier om te leren is door fouten te maken.
80 procent van alle leugens die jij en ik vertellen blijft onopgemerkt
Henske is offline   Met citaat antwoorden
Oud 26-Jul-10, 13:57   #8
Vos
Simple Member
 
Vos's Avatar
 
Geregistreerd op: Apr 2002
Locatie: Amersfoort
Berichten: 3.749
Dat is niet echt een probleem van het registry maar meer slecht ontwerp. Gebruikers kunnen gewoon bij HKEY_CURRENT_USER, en waarom moeten ze iets via regedit aanpassen?
Vos is offline   Met citaat antwoorden
Oud 02-Aug-10, 15:14   #9
WhatJac3
Senior Member
 
Geregistreerd op: Mar 2003
Berichten: 608
Ik heb de code van GolezTrol een beetje aangepast. Alhoewel ik wel steeds een foutmelding krijg bij het afsluiten van mijn applicatie. De fout doet zich voor in :
TBigMenuMRUController.SetParentMenuItem, maar goed die ben ik nog even aan het onderzoeken. Ik heb er ini Load en Save opties aan toegevoegd. Hieronder de code (wijzigingen) die ik heb geschreven om het te laten werken:

Code:
  TBigCustomMRUStorage = class(TComponent)
  protected
    procedure Save(Subject: TBigCustomMRU); virtual; abstract;
    procedure Load(Subject: TBigCustomMRU); virtual; abstract;
  end;

  TBigCustomMRUStorageINI = class(TBigCustomMRUStorage)
  protected
    procedure Save(Subject: TBigCustomMRU); override;
    procedure Load(Subject: TBigCustomMRU); override;
  end;
Wijzigingen aan TBigCustomMRU:

Code:
TBigCustomMRU = class(TComponent)
  private
    FLoading: Boolean;
  public
    procedure Load; virtual;

constructor TBigCustomMRU.Create(AOwner: TComponent);
begin
  inherited;
  FLoading := False;


procedure TBigCustomMRU.Load;
begin
  // Silently ignore if no storage is linked.
  FLoading := True;
  if Assigned(FStorage) then
    FStorage.Load(Self);
  FLoading := False;
end;

procedure TBigCustomMRU.Save;
begin
  // Silently ignore if no storage is linked.
  if not FLoading then
    if Assigned(FStorage) then
      FStorage.Save(Self);
end;
De INI load/save Class:

Code:
const cIniFile = 'recentfiles.ini';

{ TBigCustomMRUStorageINI }

procedure TBigCustomMRUStorageINI.Load(Subject: TBigCustomMRU);
var
  FFile : String;
  ini: TIniFile;
  tmp: string;
  I, J: Integer;
begin
  FFile := ExtractFilePath(ParamStr(0)) + cIniFile;
  ini := TIniFile.Create(FFile);
  try
    J := ini.ReadInteger('Recent Files', 'Count', 0);

    if J > 0 then
    begin
      Subject.Clear;
      for I := J downto 1 do
      begin
        tmp := ini.ReadString('Files', 'File' + IntToStr(I), '');
        if FileExists(tmp) then                       //Update, bestand moet bestaan (staat niet in attachment)
          Subject.Add(tmp);
      end;
    end;
  finally
    ini.Free;
  end;
end;

procedure TBigCustomMRUStorageINI.Save(Subject: TBigCustomMRU);
var
  ini: TIniFile;
  I: integer;
  FFile: String;
begin
  FFile := ExtractFilePath(ParamStr(0)) + cIniFile;
  DeleteFile(FFile);
  ini := TIniFile.Create(FFile);
  try
    ini.WriteInteger('Recent Files', 'Count', Subject.FMRUList.Count);
    for I := 0 to Subject.FMRUList.Count - 1 do
      ini.WriteString('Files', 'File' + IntToStr(I+1),
                        Subject.FMRUList.Strings[I]);
  finally
    ini.Free;
  end;
end;
De volgende code gebruik ik om alles te initializeren:

Code:
  FMRUListController: TBigMenuMRUController;
  FMRUList : TBigMRU;
  FMRUStorageINI : TBigCustomMRUStorageINI;

---

  FMRUListController := TBigMenuMRUController.Create(nil);
  FMRUListController.ParentMenuItem := Form1.Recent1;
  FMRUList := TBigMRU.Create(nil);
  FMRUList.Controller := FMRUListController;
  FMRUStorageINI := TBigCustomMRUStorageINI.Create(nil);
  FMRUList.Storage := FMRUStorageINI;
  FMRUList.Load;
Bijgesloten Bestanden
Bestandstype: pas BigMRU.pas (7,8 KB, 6x gelezen)

Laatst aangepast door WhatJac3 : 02-Aug-10 om 16:26 Reden: update, fileexists (staat niet in attachment)
WhatJac3 is offline   Met citaat antwoorden
Antwoord

Bookmarks

Tags
dynamisch , files , laatst gebruikt , mainmenu , menuitem


Momenteel bekijken: 1 (0 leden en 1 gasten en/of zoekmachine bots) actieve gebruikers dit onderwerp
 
Onderwerpopties Zoek in onderwerp
Zoek in onderwerp:

Geavanceerd zoeken
Weergavemodus Stem op dit onderwerp:
Stem op dit onderwerp::

Berichting Regels
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is Aan
Smilies zijn Aan
[IMG] code is Aan
HTML code is Uit

Forumnavigatie


Alle tijden zijn GMT +1. De tijd is nu 01:04.


Forum software: vBulletin, versie 3.8.4
Copyright ©2000 - 2010, Jelsoft Enterprises Ltd.
Copyright ©2008, NLDelphi.com (Dutch Delphi programming)