Rave Reports - raportowanie dynamiczne inaczej
Juhas
Cześć. Używając RaveReports dostrzegłem, że tworzenie raportów za pomocą ich edytora jest bardzo mozolne i denerwujące. Czasami nawet nie działa. Zwłaszcza jest to denerwujące, jak trzeba stworzyć wiele raportów do aplikacji. Pomyślałem o tym, żeby tworzyć je dynamicznie. Okazało się, że można i faktycznie tworzenie dynamiczne jest dużo prostsze i szybsze. Opiera się właściwie na napisaniu zapytania i wywołaniu jednej funkcji.
Jednakże mój kod wymaga kilku rzeczy:
#Forma bazowa o nazwie np: TBaseRaportForm. Na formie następujące komponenty:
*BaseRVSystem: TRvSystem
*BaseRVProject: TRVProject
*BaseRVDataset: TRVDatasetConnection
*BaseQuery: TIBQuery(lub inny tego typu komponent, np. TADOQuery)
*BaseTrans: TIBTransaction(jeśli używamy TIBQuery)
wszystkie te komponenty powinniśmy znaleźć w standardzie Delphi z zainstalowanym RaveReports
#Na formie 3 przyciski: "Podgląd raportu", "Drukuj raport" i "Zamknij"
#Pusty plik raportu, tzn. muszą na nim być:
*Region1: Region Component
*Band1: Band Component
Ustawienia komponentów:
Właściwie wg własnych potrzeb ;) Ważne, żeby właściwość BaseRVDataset.Dataset wskazywała na nasze Query(lub analogiczny komponent)
Plik raportu
W tym pliku na Band1 możemy sobie położyć to, co chcemy. Ja mam text component z tytułem i kilka DataText componentów, które będą robić za parametry
(jeśli ktoś nie wie, jak się robi parametry w RaveReports i do czego służą to odsyłam do jakiegoś helpa, czy czegoś takiego.)
Możemy do formy oczywiście powsadzać sobie jeszcze jakieś filtry np. do dat i.t.d.
Rzeczą kolejną jest kod :)
Tworzymy sobie typ(ja go mam w oddzielnym unicie) TReportColumn:
type
TReportColumn = record
cCaption: string;
cWidth: Double;
cLeft: Double;
FieldName: string;
cAlignment: TPrintJustify;
end;
Mały opis:
cCaption - tytuł nagłówka
cWidth - szerokość kolumny(W Rave podawana w calach)
cLeft - położenie X
FieldName - nazwa pola, do którego się odnosi kolumna
cAlignment - położenie tekstu względem kolumny
OK, teraz w sekcji private tworzymy sobie kilka zmiennych:
RepCol: array of TReportColumn;
FColWidthSum: Double;
Zmienna FColWidthSum będzie przechowywała łączną szerokość zdefiniowanych kolumn. Potrzebne do ustawienia szerokości poziomych linii.
W tej samej sekcji dodajemy jeszcze kilka metod:
function CheckData: boolean;
procedure LoadReportFile(ReportFileName: string);
procedure BuildReport;
Funkcja CheckData sprawdza, czy są jakieś dane:
function TBaseRaportForm.CheckData: boolean;
begin
if not BaseQuery.Active then BaseQuery.Open;
if BaseQuery.RecordCount = 0 then
begin
application.MessageBox('Brak danych dla wybranych filtrów', '', mb_OK+mb_IconInformation);
result:=false;
end else result:=true;
if BaseQuery.Active then BaseQuery.Close;
end;
Procedura LoadReportFile oczywiście wczytuje przygotowany wcześniej pusty plik raportu:
procedure TBaseRaportForm.LoadReportFile(ReportFileName: string);
begin
BaseRVProject.ProjectFile:=ReportFileName;
end;
Tutaj można dać oczywiście jeszcze jakieś sprawdzenie i inne tego typu rzeczy.
Następnie procedura BuildReport - jest to metoda właściwie najważniejsza, ponieważ to ona odpowiada za budowanie dynamiczne raportu. Znalazłem ją na stronie Rave'a i trochę przerobiłem(nie będę jej dokładnie omawiał):
procedure TBaseRaportForm.BuildReport;
Var
MyBand: TRaveBand;
MyDataBand: TRaveDataBand;
MyDataCnx: TRaveDataConnection;
MyDataText: TRaveDataText;
MyDataView: TRaveDataView;
MyLine: TRaveHLine;
MyVLine: TRaveVLine;
MyPage: TRavePage;
MyRegion: TRaveRegion;
MyText: TRaveText;
nMarginBottom, nMarginLeft, nMarginRight, nMarginTop: Double;
i: integer;
lName: integer;
Begin
if length(RepCol) = 0 then exit;
BaseRvProject.Open;
// 1 - use a blank page "Page 1 in Report 1" ======================
MyPage := BaseRvProject.ProjMan.FindRaveComponent('Report1.Page1', Nil) As TRavePage;
if myPage = nil then raise Exception.Create('Nie utworzono odpowiedniej strony!');
MyPage.WasteFit := True;
// Set all margins to “waste” or 0.5 which ever is greater
nMarginBottom := MaxValue([RpDev.Waste.Bottom / RpDev.YDPI, 0.5]);
nMarginLeft := MaxValue([RpDev.Waste.Left / RpDev.XDPI, 0.5]);
nMarginRight := MaxValue([RpDev.Waste.Right / RpDev.XDPI, 0.5]);
nMarginTop := MaxValue([RpDev.Waste.Top / RpDev.YDPI, 0.5]);
// 2 - Create DataView using Connection ===========================
MyDataCnx := CreateDataCon(BaseRvDataset);
MyDataView := BaseRvProject.ProjMan.NewDataObject(TRaveDataView) As TRaveDataView;
MyDataView.ConnectionName := MyDataCnx.Name;
MyDataView.DataCon := MyDataCnx;
CreateFields(MyDataView, Nil, Nil, true);
// 2a - place “Page Number” directly ON the page NOT a band
MyDataText := MyPage.CreateChild(TRaveDataText, 'dtPage') As TRaveDataText;
MyDataText.DataField := '"Strona " + Report.CurrentPage + " z " + Report.TotalPages';
MyDataText.FontJustify := pjRight;
MyDataText.Width := 2; // Width MUST be before LEFT assignment
MyDataText.Left := MyPage.PageWidth - nMarginRight - MyDataText.Width;
MyDataText.Top := nMarginTop;
// 3 - create a region and set it's properties ====================
MyRegion:=(BaseRVProject.ProjMan.FindRaveComponent('Region1', MyPage) as TRaveRegion);
if MyRegion = nil then raise Exception.Create('Brak odpowiedniego regionu!');
MyRegion.Top := nMarginTop + 0.4; // Top MUST be before HEIGHT assignment
MyRegion.Width := MyPage.PageWidth - (nMarginLeft + nMarginRight);
MyRegion.Height := MyPage.PageHeight - (MyRegion.Top + nMarginBottom);
// 4 - Create Databand named "DetailBand" First (controller) ======
MyDataBand := MyRegion.CreateChild(TRaveDataBAnd, 'DetailBand') As TRaveDataBand;
MyDataBand.DataView := MyDataView; // Always set the band dataview property
// 4a - Place DataText components in "DetailBand" just created
MyDataText := MyDataBand.CreateChild(TRaveDataText, 'dtName') As TRaveDataText;
lName:=1;
MyVLine := MyDataBand.CreateChild(TRaveVLine, 'VLine'+intToStr(lName)) As TRaveVLine;
MyVLine.Left:=0;
MyVLine.Height:=myDataBand.Height;
lName:=2;
//tworzenie kolumn z zawartością (detail)
for i:=low(RepCol) to high(RepCol) do
begin
MyDataText := MyDataBand.CreateChild(TRaveDataText, 'dt'+RepCol[i].FieldName) As TRaveDataText;
MyDataText.DataField := RepCol[i].FieldName;
MyDataText.DataView := MyDataView;
MyDataText.FontJustify := RepCol[i].cAlignment;
MyDataText.Left := RepCol[i].cLeft;
MyDataText.Top := 0.01;
MyDataText.Width := RepCol[i].cWidth;
MyVLine := MyDataBand.CreateChild(TRaveVLine, 'VLine'+intToStr(lName)) As TRaveVLine;
MyVLine.Left:=RepCol[i].cLeft+RepCol[i].cWidth;
MyVLine.Height:=MyDataBand.Height;
lName:=lName+1;
MyLine := MyDataBand.CreateChild(TRaveHLine, 'HLine'+intToStr(lName)) As TRaveHLine;
MyLine.Left:=0;
MyLine.Width:=FColWidthSum;
MyLine.Top:=MyDataBand.Bottom;
lName:=lName+1;
end;
// 5 - Create Header Band controlled by "DetailBand" ==============
MyBand := MyRegion.CreateChild(TRaveBand, 'HeaderBand') As TRaveBand;
// BandStyle Print Location codes
// BandStyle PrintLoc b plBodyFooter
// BandStyle PrintLoc g plGroupFooter
// BandStyle PrintLoc r plRowFooter
// BandStyle Printloc D plDetail
// BandStyle Printloc R plRowHeader
// BandStyle Printloc G plGroupHeader
// BandStyle Printloc B plBodyHeader
MyBand.BandStyle.PrintLoc := MyBand.BandStyle.PrintLoc + [plBodyHeader];
// BandStyle Print Occurrence codes
// BandStyle PrintOcc C poNewColumn
// BandStyle PrintOcc P poNewPage
// BandStyle PrintOcc 1 poFirst
MyBand.BandStyle.PrintOcc := MyBand.BandStyle.PrintOcc + [poFirst, poNewPage];
MyBand.ControllerBand := MyDataBand;
MyBand.Height := 0.2;
MyBand.Left := MyRegion.Left;
MyBand.MoveBehind; // Looks better when you "view it"
MyBand.Top := MyRegion.Top;
MyBand.Width := MyRegion.Width;
MyLine:=MyBand.CreateChild(TRaveHLine, 'HLine'+intToStr(lName)) as TRaveHLine;
MyLine.Left:=0;
MyLine.Width:=FColWidthSum;
MyLine.Top:=0;
lName:=lName+1;
MyVLine := MyBand.CreateChild(TRaveVLine, 'VLine'+intToStr(lName)) As TRaveVLine;
MyVLine.Left:=0;
MyVLine.Height:=MyBand.Height;
lName:=lName+1;
//nagłówki dla ww kolumn
for i:=low(RepCol) to high(RepCol) do
begin
MyText := MyBand.CreateChild(TRaveText, 'Text'+RepCol[i].FieldName) As TRaveText;
MyText.Left := RepCol[i].cLeft;
MyText.Top := 0.01; // Band.Top ???
MyText.Font.Size := 10;
MyText.Font.Style := MyText.Font.Style + [fsBold];
MyText.FontJustify := pjCenter;
MyText.Text :=RepCol[i].cCaption;
MyText.Width :=RepCol[i].cWidth;
MyVLine := MyBand.CreateChild(TRaveVLine, 'VLine'+intToStr(lName)) As TRavevLine;
MyVLine.Left:=RepCol[i].cLeft+RepCol[i].cWidth;
MyVLine.Height:=MyBand.Height;
lName:=lName+1;
end;
// 5d - draw a horizontal line
MyLine := MyBand.CreateChild(TRaveHLine, 'BottomLine') As TRaveHLine;
MyLine.Left := 0; // MyBand.Left;
MyLine.LineWidth := 2;
//MyLine.LineWidthType := wtPoints;
MyLine.Top := MyBand.Height - 0.01;
MyLine.Width := FColWidthSum;
end;
Następnym krokiem jest utworzenie w sekcji protected (na dobrą sprawę public też może być) 3 kolejnych metod:
procedure BuildQuery; virtual;
procedure BuildColumns; virtual;
procedure CreateColumn(cCaption, FieldName: string; cWidth: Double; cAlignment: TPrintJustify = pjCenter);
2 pierwsze będziemy wykorzystywali później. Jak się można domyśleć tutaj mają puste ciało.
Natomiast procedura CreateColumn wygląda tak:
procedure TBaseRaportForm.CreateColumn(cCaption, FieldName: string; cWidth: double; cAlignment: TPrintJustify = pjCenter);
begin
setLength(RepCol, length(RepCol)+1);
RepCol[high(RepCol)].cCaption:=cCaption;
RepCol[high(RepCol)].cWidth:=cWidth;
RepCol[high(RepCol)].FieldName:=FieldName;
RepCol[high(RepCol)].cLeft:=FColWidthSum;
RepCol[high(RepCol)].cAlignment:=cAlignment;
FColWidthSum:=FColWidthSum+cWidth;
end;
Teraz musimy uzupełnić kod do przycisków.
Najpierw Podgląd wydruku
(Właściwie to podgląd i drukowanie można umieścić jakoś w jednej procedurze, nie zrobiłem tego, specjalnie)
Screen.Cursor:=crSQLWait;
try
//tutaj można dać jeszcze jakieś metody robiące coś np. z parametrami
BuildQuery;
if not CheckData then exit;
RepCol:=nil;
FColWidthSum:=0;
BuildColumns;
BuildReport;
Screen.Cursor:=crDefault;
BaseRVProject.Execute;
BaseRVProject.Close;
finally
Screen.Cursor:=crDefault;
end;
Kod do drukowania wygląda prawie tak samo:
Screen.Cursor:=crSQLWait;
//tutaj można dać jakiś kod
BuildQuery;
if not CheckData then exit;
RepCol:=nil;
FColWidthSum:=0;
BuildColumns;
BuildReport;
BaseRVSystem.defaultDest:=rdPrinter;
Screen.Cursor:=crDefault;
BaseRVProject.Execute;
To właściwie wszystko, co trzeba zrobić w tej formie, żeby to śmigało. A teraz jak tego użyć.
<font size="3">Użyj mnie!</span>
No skoro mamy już formę bazową, to na jej podstawie tworzymy nową formę(trzeba z niej dziedziczyć).
Następnie musimy zrobić 2 rzeczy, a mianowicie przeładować procedury wirtualne, czyli BuildQuery i BuildColumns.
BuildQuery może np. wyglądać tak:
procedure TRapAForm.BuildQuery;
var
myQuery: string;
begin
inherited;
myQuery:='SELECT kol1, kol2 FROM tabela';
BaseQuery.SQL.Text:=myQuery;
end;
W tej procedurze musimy po prostu zbudować zapytanie i wrzucić je do BaseQuery.
Możemy również zmienić parametry w raporcie, np:
BaseRVProject.SetParam('Wydrukowal', 'Wydrukował: Zdichu Liter');
Następnie procedura BuildColumns, która tworzy nam kolumny:
procedure TRapAForm.BuildColumns;
begin
inherited;
CreateColumn('Kolumna 1', 'kol1', 1.6);
CreateColumn('Inna kolumna', 'kol2', 1.5);
end;
W tej procedurze po prostu musimy podać jakby definicje kolumn.
To by było na tyle.
Dużo szybciej jest przeładować 2 proste metody niż projektować cały raport ręcznie. Mam nadzieję, że kod będzie Wam służył długo :)
Jeśli macie jakieś pytania to pytajcie.
Autor: Juhas
uses
RpCon, RpConDS, RpDevice,
RvClass , RvCsData, RvCsDraw, RvCsStd, RvCsRpt, RvData, RvDefine, RvDirectDataView, RvUtil;
Takie coś załatwiło sprawę - konkretnie RvCsData, ale profilaktycznie wrzuciłem wszystko do IMPLEMENTATION
I oczywiście prośba - jaki unit dodać do USES by to mogło pójść?
I drugie pytanie - czy istnieje możliwość z poziomu programu zmiany np . położenia pola tupu TDATATEXT?
Czyli - tworzę raport ręcznie w RAVIE i ZANIM wykonam execute w programie chce poprzestawiać pewne elementy - jak to zrobić?
Jest gdzies HELP w necie dotyczący tego problemu (tzn tutorial do rave'a)?
Wszystko pięknie, ale jeden problem - TRaveDataText nie jest znanym typem i nie mogę znaleźć, w którym jest unicie.....
Niestety powyższy kod jest niekompilowalny (delphi XE proffesional)
Piotr, proszę dla Ciebie: Rave Reports - Raporty Master-Detail
Master - detail, chodzi np:
Master1
Detail1
Detail2
Detail3
Master2
....
Jeśli tak, to nie robiłem. A kodu unitu nie zamieszczę, bo w moim oryginale jest jeszcze kupa innych rzeczy, i usuwać tego wszystkiego mi się nie chce ;)
Jeszcze jak byś mógł dać przykład raportu master-detail
Podaj kod całego unita to będzie łatwiej analizować twój artykuł