Results 1 to 10 of 10

Thread: Mijn eigen navigator tool

  1. #1
    John Kuiper
    Join Date
    Apr 2007
    Location
    Almere
    Posts
    8,627

    Mijn eigen navigator tool

    Ik ben voor mijzelf een kleine eigen navigator te maken. Zo heb ik een class genaamd TMynavigator. Daarin bewaar ik de min, max en current van een ander object o.i.d. Ik koppel mijn actionlist aan mijn class. In TMynavigator heb ik een aantal procedures gekoppeld aan de actionlist:

    Delphi Code:
    1. procedure TMyNavigator.SetActionlist(aActionlist: TActionlist);
    2. begin
    3.   fActionList := aActionlist;
    4.   fActionList.ActionByName('acTerug').OnUpdate := @acTerugUpdate;
    5.   fActionList.ActionByName('acTerug').OnExecute:= @acTerugExecute;
    6.   fActionList.ActionByName('acVerder').OnUpdate := @acVerderUpdate;
    7.   fActionList.ActionByName('acVerder').OnExecute:= @acVerderExecute;
    8. end;
    Dat werkt prima. Vooral de update procedure doet wat hij moet doen. Maar als ik nu op een action de OnExecute aan klik en een uitgebreide procedure uit wil voeren, werkt dat niet meer. Dan maakt de actionlist toch gebruik van de reeds aangemaakte procedures in TMynavigation gelinkt aan een action.

    Ik heb in de bijlage een voorbeeldje ingezet. Daar wil ik label1 voorzien van de inhoud van tmynavigator.current.

    Als ik de event onexecute loskoppel in TMynavigator en een aparte procedure aanmaak om deze aan te roepen lukt het wel. Maar het mooiste zal zijn om de code in TMynavigator automatisch laten uit te voeren en dan de event van de actionlist in de form.

    Hoe krijg ik het voor elkaar om zowel mijn eigen event op mijn form alsmede de event gekoppeld aan de actionlist om het nodige doen wat ik vraag?
    Attached Files Attached Files
    Delphi is great. Lazarus is more powerfull

  2. #2
    John Kuiper
    Join Date
    Apr 2007
    Location
    Almere
    Posts
    8,627
    Ik laat het idee deels varen. De onExecute koppel ik los als dat nodig is om meerdere acties te kunnen ondersteunen.
    Delphi is great. Lazarus is more powerfull

  3. #3
    Silly member NGLN's Avatar
    Join Date
    Aug 2004
    Location
    Werkendam
    Posts
    5,082
    Zonder jouw code bekeken te hebben, maar om je wel even een richting in te sturen: in het algemeen houdt je volledige controle over de uitvoering van een event, én stel je deze beschikbaar aan de buitenwereld, door er een laag tussen te zetten. Zie de VCL met zijn doorgaans protected DoDinges method die behalve het aanroepen van de door de gebruiker ingestelde event-procedure tevens zelf nog iets kan uitvoeren, vooraf dan wel achteraf, en daarmee dus nog volledige controle behoudt.

    In jouw geval betekent dit dat je nieuwe eventproperties dient toe te voegen aan jouw class.

    Wat is jouw doel? Over wat voor objecten heb je het?
    (Sender as TNLDUser).Signature := 'Groeten van Albert';

  4. #4
    John Kuiper
    Join Date
    Apr 2007
    Location
    Almere
    Posts
    8,627
    Mijn doel is mijn buttons laten reageren zonder in elk scherm de events te programmeren. Ik ben al heel ver door de OnUpdate event te koppelen zodat ze gericht opties uitvoeren. In die class staat een aantal properties, die de 'cursor' bijhouden van het gekoppeld object. Daardoor kan ik op de juiste manier gegevens vanuit een objectlist tonen op scherm, zonder dat die 'uit zijn voegen loopt' (een AV dus).
    Ik had gehoopt dat als ik iets uitvoer op een button gekoppeld aan een actionlist eerst de event vanuit mijn class oproept en daarna degene die ik op mijn form heb gemaakt als extra configuratie.
    Ik weet dat ik aparte events kan koppelen naar mijn form toe. Maar dat is een extra handeling.
    Delphi is great. Lazarus is more powerfull

  5. #5
    Silly member NGLN's Avatar
    Join Date
    Aug 2004
    Location
    Werkendam
    Posts
    5,082
    Ik had gehoopt dat als ik iets uitvoer op een button gekoppeld aan een actionlist eerst de event vanuit mijn class oproept en daarna degene die ik op mijn form heb gemaakt als extra configuratie.
    Dat gaat helaas niet, want je hebt de eventproperty opzettelijk overschreven. Een oplossing kan zijn om de origineel ingestelde waarde te bewaren en die eventhandler vanuit jouw nieuwe eventhandler aan te roepen. Dat kan bijvoorbeeld als volgt:
    Delphi Code:
    1. private
    2.     fOrigineelTerugOnExecute: TNotifyEvent;
    3.     fOrigineelVerderOnExecute: TNotifyEvent;
    4.  
    5. ...
    6.  
    7. procedure TMyNavigator.SetActionlist(aActionlist: TActionlist);
    8. begin
    9.   fActionList := aActionlist;
    10.   fOrigineelTerugOnExecute := fActionList.ActionByName('acTerug').OnExecute;
    11.   fOrigineelVerderOnExecute := fActionList.ActionByName('acVerder').OnExecute;
    12.   fActionList.ActionByName('acTerug').OnUpdate := @acTerugUpdate;
    13.   fActionList.ActionByName('acTerug').OnExecute := @acTerugExecute;
    14.   fActionList.ActionByName('acVerder').OnUpdate := @acVerderUpdate;
    15.   fActionList.ActionByName('acVerder').OnExecute := @acVerderExecute;
    16. end;
    17.  
    18. procedure TMyNavigator.acTerugExecute(Sender: TObject);
    19. begin
    20.   fCurrent := fCurrent - 1;
    21.   if Assigned(fOrigineelTerugOnExecute) then
    22.     fOrigineelTerugOnExecute(Sender);
    23. end;
    24.  
    25. procedure TMyNavigator.acVerderExecute(Sender: TObject);
    26. begin
    27.   fCurrent := fCurrent + 1;
    28.   if Assigned(fOrigineelVerderOnExecute) then
    29.     fOrigineelVerderOnExecute(Sender);
    30. end;
    Maar wil je dit echt robuust krijgen, dan komt er nog wel iets meer bij kijken. Allereerst moet deze interventie ooit weer netjes ongedaan gemaakt worden.

    Bovendien is het onhandig (en niet zo netjes) om de naamgeving van jouw actions te laten afhangen van enkele hardcoded strings in een compleet andere unit. Al ben je zelf nóg zo gedisciplineerd om blijf je deze naamgeving tot in den eeuwigheid hanteren, het is niet onderhoudsvriendelijk, legt onnodige striktheid op aan het ontwerp: kortom het dient geen abstractie.

    Hoe je de actions dan wél kan identificeren? Tja, dat kan bijvoorbeeld door niet de ActionList te koppelen, maar de actions zelf, als volgt (let op: Delphi-code, ik heb geen Lazarus):
    Delphi Code:
    1. type
    2.   TMyNavigator = class(TObject)
    3.   private
    4.     FCurrent: Integer;
    5.     FMaxCount: Integer;
    6.     FMinCount: Integer;
    7.     FNextAction: TAction;
    8.     FPrevAction: TAction;
    9.     FSaveNextActionOnExecute: TNotifyEvent;
    10.     FSaveNextActionOnUpdate: TNotifyEvent;
    11.     FSavePrevActionOnExecute: TNotifyEvent;
    12.     FSavePrevActionOnUpdate: TNotifyEvent;
    13.     procedure SetCurrent(Value: Integer);
    14.     procedure SetMaxCount(Value: Integer);
    15.     procedure SetMinCount(Value: Integer);
    16.     procedure SetNextAction(Value: TAction);
    17.     procedure SetPrevAction(Value: TAction);
    18.   protected
    19.     procedure NextActionExecuted(Sender: TObject);
    20.     procedure NextActionUpdated(Sender: TObject);
    21.     procedure PrevActionExecuted(Sender: TObject);
    22.     procedure PrevActionUpdated(Sender: TObject);
    23.   public
    24.     destructor Destroy; override;
    25.     property Current: Integer read FCurrent write SetCurrent;
    26.     property MaxCount: Integer read FMaxCount write SetMaxCount;
    27.     property MinCount: Integer read FMinCount write SetMinCount;
    28.     property NextAction: TAction read FNextAction write SetNextAction;
    29.     property PrevAction: TAction read FPrevAction write SetPrevAction;
    30.   end;
    31.  
    32. ...
    33.  
    34. { TMyNavigator }
    35.  
    36. destructor TMyNavigator.Destroy;
    37. begin
    38.   NextAction := nil;
    39.   PrevAction := nil;
    40.   inherited Destroy;
    41. end;
    42.  
    43. procedure TMyNavigator.NextActionExecuted(Sender: TObject);
    44. begin
    45.   Current := Current + 1;
    46.   if Assigned(FSaveNextActionOnExecute) then
    47.     FSaveNextActionOnExecute(Sender);
    48. end;
    49.  
    50. procedure TMyNavigator.NextActionUpdated(Sender: TObject);
    51. begin
    52.   FNextAction.Enabled := FCurrent < FMaxCount;
    53. end;
    54.  
    55. procedure TMyNavigator.PrevActionExecuted(Sender: TObject);
    56. begin
    57.   Current := Current - 1;
    58.   if Assigned(FSavePrevActionOnExecute) then
    59.     FSavePrevActionOnExecute(Sender);
    60. end;
    61.  
    62. procedure TMyNavigator.PrevActionUpdated(Sender: TObject);
    63. begin
    64.   FPrevAction.Enabled := FCurrent > FMinCount;
    65. end;
    66.  
    67. procedure TMyNavigator.SetCurrent(Value: Integer);
    68. begin
    69.   if FCurrent <> Value then
    70.     FCurrent := Max(FMinCount, Min(Value, FMaxCount));
    71. end;
    72.  
    73. procedure TMyNavigator.SetMaxCount(Value: Integer);
    74. begin
    75.   if FMaxCount <> Value then
    76.   begin
    77.     FMaxCount := Value;
    78.     FCurrent := Min(FCurrent, FMaxCount);
    79.   end;
    80. end;
    81.  
    82. procedure TMyNavigator.SetMinCount(Value: Integer);
    83. begin
    84.   if FMinCount <> Value then
    85.   begin
    86.     FMinCount := Value;
    87.     FCurrent := Max(FMinCount, FCurrent);
    88.   end;
    89. end;
    90.  
    91. procedure TMyNavigator.SetNextAction(Value: TAction);
    92. begin
    93.   if FNextAction <> Value then
    94.   begin
    95.     if Assigned(FNextAction) then
    96.     begin
    97.       FNextAction.OnExecute := FSaveNextActionOnExecute;
    98.       FNextAction.OnUpdate := FSaveNextActionOnUpdate;
    99.     end;
    100.     FNextAction := Value;
    101.     if Assigned(FNextAction) then
    102.     begin
    103.       FSaveNextActionOnExecute := FNextAction.OnExecute;
    104.       FSaveNextActionOnUpdate := FNextAction.OnUpdate;
    105.       FNextAction.OnExecute := NextActionExecuted;
    106.       FNextAction.OnUpdate := NextActionUpdated;
    107.     end;
    108.   end;
    109. end;
    110.  
    111. procedure TMyNavigator.SetPrevAction(Value: TAction);
    112. begin
    113.   if FPrevAction <> Value then
    114.   begin
    115.     if Assigned(FPrevAction) then
    116.     begin
    117.       FPrevAction.OnExecute := FSavePrevActionOnExecute;
    118.       FPrevAction.OnUpdate := FSavePrevActionOnUpdate;
    119.     end;
    120.     FPrevAction := Value;
    121.     if Assigned(FPrevAction) then
    122.     begin
    123.       FSavePrevActionOnExecute := FPrevAction.OnExecute;
    124.       FSavePrevActionOnUpdate := FPrevAction.OnUpdate;
    125.       FPrevAction.OnExecute := PrevActionExecuted;
    126.       FPrevAction.OnUpdate := PrevActionUpdated;
    127.     end;
    128.   end;
    129. end;
    Met als gebruik:
    Delphi Code:
    1. procedure TForm2.FormCreate(Sender: TObject);
    2. begin
    3.   FMyNavigator := TMyNavigator.Create;
    4.   FMyNavigator.MinCount := 0;
    5.   FMyNavigator.MaxCount := 9;
    6.   FMyNavigator.NextAction := acVerder;
    7.   FMyNavigator.PrevAction := acTerug;
    8. end;
    Mijn doel is mijn buttons laten reageren zonder in elk scherm de events te programmeren. [...] In de class staat een aantal properties die de 'cursor' bijhouden van een gekoppeld object.
    Je hebt dus op meerdere vensters een aantal buttons waarmee je door een ObjectList navigeert. En je wilt dat al die buttons hetzelfde doen en hetzelfde reageren, maar dan steeds werkend op andere data. Het is begrijpelijk dat je een always-done-never-care-solution wilt. Zeker bij repeterende zaken denkt een programmeur al snel dat het anders moet kunnen. Echter vraag ik mij af of dit dan de handigste manier is.

    In Delphi kun je alle aangemaakte Actions ook op andere Forms gebruiken. Plaats daartoe jouw éne ActionList op een DataModule, en use zijn unit in elk form waarin je zijn actions design-time wilt gebruiken. Het lijkt mij dat Lazarus ook iets dergelijks biedt.

    Daardoor kan ik op de juiste manier gegevens vanuit een objectlist tonen op scherm, zonder dat die 'uit zijn voegen loopt' (een AV dus).
    Het veronderstelde syllogisme lijkt mij onjuist. Ik denk zelfs dat wanneer je jouw "navigator" (of wellicht beter "Counter" of gewoon "Cursor") data-aware maakt dat e.e.a. alleen maar minder foutgevoelig wordt.
    (Sender as TNLDUser).Signature := 'Groeten van Albert';

  6. #6
    Is dit niet iets wat je normaal gewoon via inheritance doet?

  7. #7
    Silly member NGLN's Avatar
    Join Date
    Aug 2004
    Location
    Werkendam
    Posts
    5,082
    Bedoel je (Visual) FormInheritance? Dat is geloof ik niet voor iedereen weggelegd.

    Het gaat om de design-time actions. John wil eigenlijk een soort van het omgekeerde van een TActionLink bereiken: in plaats van dat de action vertelt hoe het gekoppelde object zich dient te gedragen, moet het object de action aanvullen, en dat zonder gevolgen voor de reeds gekoppelde clients.

    Een prachtige alternatieve oplossing is om je eigen Action-klasse maken, maar daar heeft niet iedereen ervaring mee en die moet je installeren in de IDE. Maakt het er ook niet makkelijker op.
    Last edited by NGLN; 23-Mar-20 at 17:29.
    (Sender as TNLDUser).Signature := 'Groeten van Albert';

  8. #8
    Bedoel je (Visual) FormInheritance? Dat is geloof ik niet voor iedereen weggelegd.
    Yep.

    Snap nog steeds niet helemaal (of helemaal niet) het idee van John, maar met jouw reactie schreeuwde het voor mijn gevoel om een forminheritance.

  9. #9
    Silly member NGLN's Avatar
    Join Date
    Aug 2004
    Location
    Werkendam
    Posts
    5,082
    Ja, FormInheritance is absoluut een mooie oplossing hiervoor: je ontwerpt één form met daarop de button-bar, de actionlist, en de code betreffende de ObjectList. Elk ander venster stamt van dat Form af. In overriden eventhandlers kun je de inherited naar wens al dan niet aanroepen.
    (Sender as TNLDUser).Signature := 'Groeten van Albert';

  10. #10
    Klopt. Wel met als nadeel dat áls je wat wilt veranderen in het baseform, dat het dan een hoop gedoe is om het in de afgeleiden weer goed te krijgen.
    Maar je kan ook inheritance doen zonder het visual gedeelte. Aparte DFM, wel gedeelde code. Geen ervaring mee overigens.

    Of je maakt een eigen component dat je in één keer kan kopppelen aan datgene dat genavigeerd moet worden. Een standaard TDBNavigator hoef ten slot van rekening ook niet knopje voor knopje aan je datasource te koppelen.
    maar als het zo'n specifiek ding is, dan zou ik eerder gebruik maken van interfaces, of van een link class die vaste methods introduceert.
    Last edited by GolezTrol; 24-Mar-20 at 08:37.
    1+1=b

Thread Information

Users Browsing this Thread

There are currently 1 users browsing this thread. (0 members and 1 guests)

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •