|
|
||||||||
|
|||||||||
![]() |
|
|||||||
![]() |
|
|
Onderwerpopties | Zoek in onderwerp | Stem op Onderwerp | Weergavemodus |
|
|
#1 |
|
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:
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:
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:
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:
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:
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:
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:
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:
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:
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:
Via FormCreate de menuitems aanmaken: Delphi Code:
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 |
|
|
|
|
|
#2 |
|
Moderateur
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
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! |
|
|
|
|
|
#3 |
|
Moderateur
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:
Delphi Code:
Delphi Code:
Delphi Code:
De Execute procedure roept het OnExecute event aan. In dat event kun je als programmeur het bestand openen. Delphi Code:
Delphi Code:
Delphi Code:
Delphi Code:
Delphi Code:
Delphi Code:
Delphi Code:
Delphi Code:
Delphi Code:
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:
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.
__________________
Klik hier! |
|
|
|
|
|
#4 |
|
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....
|
|
|
|
|
|
#5 |
|
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
|
|
|
|
|
|
#6 |
|
Moderateur
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! |
|
|
|
|
|
#7 |
|
TCrap.Create(Self)
Geregistreerd op: Nov 2003
Locatie: Venray - NL
Berichten: 4.379
|
... 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 |
|
|
|
|
|
#8 |
|
Simple Member
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?
|
|
|
|
|
|
#9 |
|
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;
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;
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;
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; Laatst aangepast door WhatJac3 : 02-Aug-10 om 16:26 Reden: update, fileexists (staat niet in attachment) |
|
|
|
![]() |
| 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 |
| Weergavemodus | Stem op dit onderwerp: |
|
|