DLL i forma niemodalna - jak poinformować aplikację o jej zamknięciu?

0

Przerzucam w D7 część kodu do dll. Trafił mi się taki oto przypadek braku wiedzy:
W dll chcę mieć formę dostępną dla użytkownika w ten sposób, że jest ona zawsze na wierzchu ale pozwala na interakcję z główną aplikacją. Klasyczna forma niemodalna StayOnTop.
Umówmy się, że forma zawiera tylko jeden Button którego rolą jest zamknąć okno.
W "Delphi 6 Vademecum profesjonalisty" jest odpowiedni przykład jak taką formę oprogramować. Delikatnie go zmieniłem i cytuję poniżej.
Tworzę i eksportuję z dll funkcję tworzącą formę oraz procedurę ją zwalniającą jak w przykładzie:

unit DLLfrm;

interface
uses ...

type 
 TDLLForm = class(TForm)
   ButtonZamknij   :TButton;
procedure ButtonZamknijClick(Sender: TObject);
 end;

function ShowCalendar(AHandle: THandle): LongInt; stdcall;
procedure CloseCalendar(var AFormRef:Longint): stdcall;

var InnerRef:LongInt;

implementation
{$R *.DFM}

function ShowCalendar(AHandle:THandle): LongInt;
var  DLLForm:TDLLForm;
begin
  Application.Handle := AHandle;
  DLLForm := TDLLForm.Create(Application);
  Result := LongInt(DLLForm);
  InnerRef := Result;
  DLLForm.Show;
end;

procedure CloseCalendar(var AFormRef:LongInt);
begin
  if AFormRef > 0 then TDLLForm(AFormRef).Release;
  AFormRef := 0;   {<- jak tą wiadomosć przekazać do głównej aplikacji?}
end;

procedure TDLLForm.ButtonZamknijClick(Sender: TObject);
begin
  CloseCalendar(InnerRef);
end;

W aplikacji matce wywołuję:

if Referencja = 0 then {zabezpieczam się przed dwukrotnym wywołaniem okna}
Referencja := ShowCalendar(Application.Handle);

Jeśli chcę zamknąć formę buttonem, który znajduje się w aplikacji a nie na formie z dll to jak rozumiem wywołuję CloseCalendar(Referencja) i sprawa jest jasna.

A teraz pytanie:
Jeśli jednak chcę zamknąć okno buttonem, który jest częścią tego okna to w jaki sposób mogę przesłać informację do aplikacji matki, że okna już nie ma. Chciałbym uaktualnić wartość zmiennej Referencja na zero.

Pomożecie? :)

3

Do formy potomnej przekazujesz referencję do metody z formy głownej i wywołujesz ją przy zamknięciu lub zwolnieniu formy potomnej .
Czyli tzw. callback

forma gówna

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    procedure form2CloseNotify(Sender: TObject);
  public
  end;

procedure TForm1.Button1Click(Sender: TObject);
var
  f: Tform2;
begin
  f := Tform2.Create(self);
  f.onMyCloseAction := self.form2CloseNotify;
  f.Show;
end;

procedure TForm1.form2CloseNotify(Sender: TObject);
begin
  showmessage('Form2 closed');
end;

forma potomna

type
  TForm2 = class(TForm)
    procedure FormCreate(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
  private

  public
    onMyCloseAction: Tnotifyevent;
  end;

implementation

{$R *.dfm}

procedure TForm2.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  action:=caFree;
  if assigned(self.onMyCloseAction) then
    self.onMyCloseAction(self);
end;

procedure TForm2.FormCreate(Sender: TObject);
begin
  self.onMyCloseAction := nil;
end;
0

@grzegorz_so:
Dzięki za zainteresowanie i przepraszam za małą wiedzę ale nie bardzo widzę jak to rozwiązanie może działać w moim przypadku.
Forma potomna jest w dll i tam jest tworzona. Ty tworzysz formę potomną w pierwotnej, która z założenia nie musi znać typu formy potomnej więc Tform2 jest poza zasięgiem jak i onMyCloseAction więc nie mogę tu przypisać form2CloseNotify.

1

Mniej miałbyś problemów, gdybyś zamiast zwracania licznika referencji, zwracał po prostu referencję tego formularza, jako np. TCustomForm. Wtedy można by bez problemu bawić się zawartością okna potomnego z poziomu głównego okna.

Mając referencję obiektu, możesz sprawdzać czy forma jest widoczna, pokazywać ją i ukrywać, a także zwolnić, iterować po kontrolkach itd. A jeśli chciałbyś wychwycić moment zamknięcia okna-kalendarza, to wystarczy przypisać własne zdarzenie (zdefiniowane gdzieś w programie głównym) do zdarzenia OnDestroy okienka z biblioteki. W takim zdarzeniu, jedyne co trzeba by zrobić to przypisać nil do referencji trzymanej po stronie aplikacji.

Możesz sobie nadal mieć eksportowane funkcje np., CreateCalendar i DestroyCalendar, ale w tej do tworzenia okna-kalendarza, przekaż referencję własnego zdarzenia i przypisz ją do OnDestroy okna tworzonego po stronie biblioteki. Tak więc przykładowy CreateCallendar niech pobiera w parametrze TNotifyEvent i zwraca TCustomForm, a DestroyCalendar niech będzie bezparametrowy i nie zwraca niczego (albo nil, jak wolisz).

Referencja instancji klasy to zwykły wskaźnik, a ten możesz wykorzystywać i przekazywać gdzie dusza zapragnie, zgodnie z typem klasy obiektu na jaki wskazuje, albo nawet pod postacią liczby (w końcu to tylko liczbowy adres).

0

@furious programming: Nie ogarniam jeszcze takiego podejścia. Uczę się. Czy mógłbyś mi rozpisać na przykładzie jak to zaprogramować.

0
Rhode napisał(a):

@furious programming: Nie ogarniam jeszcze takiego podejścia.

Tylko, ze to są w sumie podstawy...

Uczę się.

Skoro tak, to tym bardziej nie pakuj się w DLL, zwłaszcza z formami.
Po co Ci to?
Nie chcesz mieć problemów, użyj BPL.

Czy mógłbyś mi rozpisać na przykładzie jak to zaprogramować.

Wizualne kontrolki w DLL jest zadaniem skomplikowanym i niesie ze sobą masę problemów do rozwiązania, kiedy ma działać to dobrze.
A nie ma tych problemów w ogóle kiedy jest to monolityczny exe lub jeśli są to BPL.

Nie polecam DLL dla aplikacji wizualnych; praktycznie nic nie wnosi (poza iluzoryczną możliwością programowania w różnych językach tego samego), a przynosi mnóstwo problemów.

2
Rhode napisał(a):

@furious programming: Nie ogarniam jeszcze takiego podejścia.

Nigdy nie utworzyłeś żadnego obiektu i nigdy nie używałeś zdarzeń? No nie mów. ;)

Przy czym jeśli programujesz dla zabawy czy chęci nauki, to wywal Delphi7 i albo zainstaluj darmowe Delphi Community, albo ucz się na darmowym i otwartym Lazarusie. Nie pchaj się w środowiska i dialekty martwe od 15 lat.

0

Ok, tu dowiem się kilku prawd życiowych. Dzięki, rozważę. Odpowiedzi ma moje pytanie poszukam gdzie indziej. No offence.
A, i może zaskoczę. Stary dziad jestem i piszę hobbystycznie oraz strukturalnie, więc tak - nie często przychodzi mi tworzyć obiekty :)

3
Rhode napisał(a):

Odpowiedzi ma moje pytanie poszukam gdzie indziej. No offence.

To brzmi jak foch. No offence. ;)

Stary dziad jestem i piszę hobbystycznie oraz strukturalnie, więc tak - nie często przychodzi mi tworzyć obiekty :)

Jeśli chcesz swobodnie programować w Delphi/Free Pascalu, to bez obiektów się nie obejdzie. Polecam poczytać tutoriale na temat programowania obiektowego, tak aby wiedzieć z czym się to je. Każda normalna aplikacja okienkowa stworzona w Delphi/Lazarusie wykorzystuje obiekty, więc bez ich znajomości, bardzo ciężko będzie świadomie klepać dobry kod.

DLL-ek nie używałem od 10 lat i nie mam czasu na testy, więc z głowy — to po stronie DLL:

type
  TCalendarForm = class(TForm)
  {..}
  end;

  procedure CreateCalendar(AOwner: TComponent; AOnDestroy: TNotifyEvent; var AInstance: TCustomForm);
  begin
    AInstance := TCalendarForm.Create(AOwner);
    AInstance.OnDestroy := AOnDestroy;
  end;
  
  procedure DestroyCalendar(var AInstance: TCustomForm);
  begin
    AInstance.Free();
    AInstance := nil;
  end;

exports
  CreateCalendar;
  DestroyCalendar;

A to po stronie aplikacji:

procedure CreateCalendar(AOwner: TComponent; AOnDestroy: TNotifyEvent; var AInstance: TCustomForm); stdcall; external 'calendar.dll';
procedure DestroyCalendar(var AInstance: TCustomForm); stdcall; external 'calendar.dll';
  
  
var
  Calendar: TCustomForm = nil;
  
  
type
  TMainForm = class(TForm)
  {..}
  private
    procedure NotifyDestroy(ASender: TObject);
  private
    procedure CreateCalendarForm();
    procedure DestroyCalendarForm();
  end;
  
  procedure TMainForm.NotifyDestroy(ASender: TObject);
  begin
    Calendar := nil;
  end;
  
  procedure TMainForm.CreateCalendarForm();
  begin
    if Calendar = nil then
    begin
      CreateCalendar(Self, @NotifyDestroy, Calendar);
      Calendar.Show();
    end;
  end;
  
  procedure TMainForm.DestroyCalendarForm();
  begin
    DestroyCalendar(Calendar);
  end;

Plus jest taki, że można robić co się chce na zmiennej Calendar, a także to, że nie trzeba wołać DestroyCalendar przy zamykaniu głównego okna, bo ono zwolni okienko kalendarza automatycznie (po to się AOwner ustawia). Można tak, można inaczej — wedle pomysłów.

Kod wymaga sprawdzenia, bo Delphi nie używam, a w Lazarusie składnia bywa nieco inna. Jeśli i aplikacja i DLL są pisane w Delphi i tylko dla Delphi to takie coś powinno działać. W razie czego zawsze można zamiast referencji operować na uchwycie okna.

1

@furious programming:

To brzmi jak foch. No offence. ;)

To brzmi jak:

  • Dzień dobry, chciałem powiesić obrazek i nie wiem jak przybić gwoździa do deski.
  • Dzień dobry, a chce Pan porozmawiać o technologi utwardzania wkrętów. Gwoździe już nie są modne. A w ogóle to kto to Panu tak sp...ił!
  • Ale ja gwoździa do deski chciałem... tylko nie wiem jak się młotek trzyma...
  • Paaaanie, to Pan domu nie zbudujesz i drzewa nie posadzisz jak Pan młotka trzymać nie umiesz.
  • No dobra, to zapytam kogoś innego.

Tak że tego :) ale dość o tym ;)

Dzięki za kod. Dowiedziałem się, jak przesłać procedurę między aplikacją a dll i właśnie tej wiedzy mi brakowało. Już wiem jak trzymać młotek :)

3

@Rhode: Skoro sam piszesz,że jesteś człowiekiem starszej daty, to pewnie pamiętasz, jak kilkadziesiąt lat temu się montowało wkręty w ścianach. Nie było kołków rozporowych, więc w dziurę się wciskało kawałek deseczki/drewniany kołek, a w niego się wkręcało wkręt lub wbijało gwóźdź.

Tak więc - przykład, który Ty podałeś, jest nie-do-końca trafny. Raczej powinno to być coś w stylu:

  • Dzień dobry, proszę o wkręt, bo chcę sobie powiesić obrazek
  • A ma Pan kołek rozporowy?
  • Nie, ja wsadziłem sobie w dziurę w ścianie kawałek drewna - tak, jak robił mój dziadek 50 lat temu, potrzebuję tylko wkrętu
  • Czy Pan nie wie, że teraz się zmieniły techniki mocowań i nie używa się drewnianych listewek, ale nylonowych kołków rozporowych?
  • Nie interesuje mnie to, mój dziadek tak robił, ja też tak będę
  • Ale tworzywowe kołki są lepszą opcją
  • No offence, ale do d**y macie ten sklep. Chcę kupić wkręt, a Wy mnie pouczacie odnośnie technik zamocowań

Nie wiem, czy zrozumiałeś co chciałem przekazać.

Chodzi o to, że możesz dostać odpowiedź na pytanie, ale w sytuacji w której o wiele bardziej doświadczone osoby od Ciebie widzą, że wciskasz drewnianą deseczkę w ścianę, zamiast skorzystać z tworzywowego kołka, to po prostu chcą Ci pomóc. Nie jest to złośliwość czy jakieś wywyższanie się, a raczej życzliwa kumpelska pomoc - na zasadzie "ej no Młody, robisz to źle. Tak jak chcesz też się da, ale niepotrzebnie sobie utrudniasz życie. Spróbuj TAK, będzie Ci o wiele łatwiej".

0

Napisałem Coś takiego ?

library FormaDLL;

uses
  SysUtils,
  Classes,
  Unit1 in 'Unit1.pas';

{$R *.res}

exports
  NowyFormularz;
end.
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TF_FORMA = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  F_FORMA: TF_FORMA;

function NowyFormularz(AHandle : THandle): TModalResult; StdCall;

implementation

{$R *.dfm}

function NowyFormularz(AHandle:THandle): TModalResult;
var
  F_FORMA: TF_FORMA;
  begin
  Application.Handle := AHandle;
  F_FORMA := TF_FORMA.Create(Application);
  try
  result := F_FORMA.ShowModal;
  Finally
  F_FORMA.Free;
  end;
end;

procedure TF_FORMA.Button1Click(Sender: TObject);
begin
  Close;
end;

end.
unit Unit2;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, Unit1, ExtCtrls, ShellApi;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Timer1: TTimer;
    ListBox1: TListBox;
    procedure Button1Click(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

function NowyFormularz(AHandle: THandle):TModalResult; StdCall;

implementation

{$R *.dfm}

function NowyFormularz; external 'FormaDLL.dll' name 'NowyFormularz';

function EnumWindowsProc(WHandle: HWND; LParM: LParam): Boolean;StdCall;Export;
var
Title,ClassName:array[0..128] of char;
sTitle, sClass,  Linia : string;
begin
 Result := True;
 GetWindowText(wHandle, Title,128);
 GetClassName(wHandle, ClassName,128);
 sTitle := Title;
 sClass := ClassName;

 if IsWindowVisible(wHandle) then
 begin
  Linia  := sTitle+' ### '+sClass+' ### ';

  if pos('F_FORMA',Linia) > 0 then
  Form1.Listbox1.Items.Add(Linia);
 end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  ListBox1.Clear;
  NowyFormularz(Application.Handle);
end;

procedure TForm1.Timer1Timer(Sender: TObject);
begin
 EnumWindows(@EnumWindowsProc,0);
end;

end.
1

A co jeśli DLLka mogła by informować program o tym że zamyka okno? Taki pomysł już padł w tym temacie, tylko że od strony stosownie zaawansowanej.
Idea jest prosta:
EXE wywołując procedurę przekazuje też adres do procedury którą DLL ma wykonać w stosownej chwili. W zasadzie odpowiednik tego co robisz powyżej z EnumWindows.

*(Nie bić! Nie jestem prymasem w tym temacie!)
*
Aplikacja:

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;

type
  PProcedure = ^TProcedure;
  TProcedure = procedure; stdcall;

  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

procedure PokazForme(AHandle: THandle; pProcOnClose : TProcedure); stdcall;

implementation

{$R *.dfm}
procedure PokazForme; external 'Project2.dll' name 'PokazForme';


procedure WykonajMniePrzyZamykaniuFormyZDLLa; stdcall;
begin
  ShowMessage('To jest już koniec!');
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  PokazForme(Application.Handle, WykonajMniePrzyZamykaniuFormyZDLLa);
end;

end.

DLL:

unit Unit2;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;

type
  PProcedure = ^TProcedure;
  TProcedure = procedure; stdcall;

  TForm1 = class(TForm)
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
  private
    { Private declarations }
    FProcOnClose : TProcedure;
  public
    { Public declarations }
    constructor Create(AOwner: TComponent; pProcOnClose : TProcedure);
  end;

var
  Form1: TForm1;


procedure PokazForme(AHandle: THandle; pProcOnClose : TProcedure); stdcall;


implementation

{$R *.dfm}

procedure PokazForme(AHandle: THandle; pProcOnClose : TProcedure);
begin
  Application.Handle := AHandle;
  Form1 := TForm1.Create(Application, pProcOnClose);
  Form1.Show;
end;

{ TForm1 }

constructor TForm1.Create(AOwner: TComponent; pProcOnClose : TProcedure);
begin
  inherited Create(AOwner);

  FProcOnClose := pProcOnClose;
end;

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  FProcOnClose();

  Action := caFree;

end;

end.

1 użytkowników online, w tym zalogowanych: 0, gości: 1