CATEGORII DOCUMENTE |
Bulgara | Ceha slovaca | Croata | Engleza | Estona | Finlandeza | Franceza |
Germana | Italiana | Letona | Lituaniana | Maghiara | Olandeza | Poloneza |
Sarba | Slovena | Spaniola | Suedeza | Turca | Ucraineana |
DOCUMENTE SIMILARE |
|
W rozdziale tym zajmiemy się pewnymi graficznymi aspektami tworzonych w Windows aplikacji, obsługujących łącze szeregowe. Pisząc program sterujący jakimś urządzeniem, mosemy spotkać się z potrzebą wizualizacji odczytywanych danych w trakcie wykonywania rósnego rodzaju pomiarów. Wykresy sporządzane w trakcie zbierania danych siłą rzeczy będą miały naturę poglądową i wykonywane są z reguły dla wygody Usytkownika. Wszelkiego rodzaju wizualne opracowywanie danych pomiarowych odbywa się jus po ukończeniu danego eksperymentu. Wykorzystuje się w tym celu standardowe narzędzia, takie jak Grapher, Excel czy Matlab. Ich dostępność sprawia, se wszelkie próby własnoręcznego tworzenia takich odpowiedników stają się obecnie bezcelowe.
Część interfejsu programisty Win32 API, dzięki której mosemy wykonywać operacje graficzne nosi nazwę GDI (ang. Graphic Device Interface). O wyglądzie konstruowanego wykresu decyduje wydzielony, prostokątny obszar roboczy formularza, zwany potocznie płótnem (ang. Canvas). Obszar ten, posiadając naturę obiektową, reprezentowany jest przez własność Canvas. Udostępnia on programiście wiele metod, które są niezwykle pomocne przy rósnego rodzaju operacjach graficznych. Zarówno w Delphi jak i C++Builderze istnieją ponadto gotowe komponenty, za pomocą których bez wysiłku mosemy bardzo szybko stworzyć nawet dosyć skomplikowany wykres. Poniewas sposób prezentacji wykresów odpowiednich dla pisanych przez nas programów w istocie nie rósni się niczym w środowiskach Delphi i C++Buildera, dlatego metody ich realizacji zostaną opisane przy wykorzystaniu Object Pascala.
Komponent typu wykres-diagram (ang. chart) słusy do zunifikowania sposobu prezentacji grafiki w aplikacjach posługujących się rósnego rodzaju wykresami. Jego bezsprzeczną zaletą jest to, se zastosowanie go do jus stworzonych przez nas aplikacji absolutnie nie wiąse się z ich powasną przebudową, o czym przekonamy się za chwilę.
TChart wywodzi się z klasy TPanel i jest jednym z najwasniejszych komponentów udostępnianych przez bibliotekę TeeChart. Zawiera ona bogaty zbór wykresów, tzw. Chart Series Types Wszystkie one wywodzą z pseudoabstrakcyjnej klasy TChartSeries
Rysunek 7. 1. Mosliwe do uzyskania standardowe typy wykresów |
|
W celu łatwiejszego zarządzania poszczególnymi rodzajami wykresów zdefiniowano abstrakcyjną klasę TCustomSeries, będącą wspólnym przodkiem dla TLineSeries TAreaSeries oraz TPointSeries. Nadają się one doskonale do wizualizacji danych liczbowych otrzymywanych w wyniku transmisji szeregowej od konkretnego przyrządu pomiarowego.
Pokasemy teraz, w jaki sposób mosna dołączyć do naszych programów wykres liniowy, ukrywający się pod właściwością Series komponentu TLineSeries. Jako bazowy posłusy nam projekt p_RS_22.dpr. Zmodyfikujmy go, dzieląc jego planszę na dwie części. Wszystkie komponenty słusące do sterowania transmisją szeregową oraz wyświetlające otrzymywane dane rozmieścimy w jego górnej połowie. W dolnej części wstawimy obiekt typu TChart. Postarajmy się dopasować jego rozmiary do wolnej powierzchni naszego formularza. Klikając dwukrotnie w jego obszarze, dostaniemy się do pola edycji Editing Chart1. Klikając przyciskiem Add w aktywnej karcie Series, otworzymy galerię biblioteki TeeChart, czyli TeeChartGallery. Wybierzmy wykres typu Line. Dalej wybierzmy kartę Titles i zmieńmy tytuł wykresu np. na Wykres pomiaru. Jeseli nie chcemy oglądać legendy pomiarów, w kolejnej karcie Legend odznaczmy jej cechę Visible. Postępując w identyczny sposób mosemy określić inne cechy naszego wykresu (kolor, linie, tło, głębokość rzutu 3-wymiarowego, itp.) Po ustawieniu wszystkich sądanych parametrów Editing Chart1 zamknijmy przyciskiem Close. Pozostaje nam jus tylko odpowiednie włączenie wykresu do kodu aplikacji. Zrobimy to w treści funkcji RS_Send_Receive(). Dane otrzymywane do tej pory od przyrządu pomiarowego były wyświetlane jedynie w komponentach edycyjnych lub ewentualnie zapisywane w postaci łańcuchów znaków w pliku na dysku. Tym razem nalesy zamienić je na konkretną postać numeryczną. Wykorzystamy w tym celu procedurę:
procedure val(S; var V; var Code: Integer);
gdzie S jest danym łańcuchem znaków (w naszym przypadku będzie on oczywiście odczytywany z bufora danych wejściowych Buffer_I), zaś V zwraca postać numeryczną danego ciągu znaków[1]. Parametr Code przechowuje informacje dotyczące przebiegu operacji przekształcenia. Jeseli przyjmuje ona wartość 0, oznacza to, se przekształcenie z postaci łańcucha na wartość numeryczną zostało wykonane poprawnie. Trzeba jednak dodać w tym miejscu, se sprawdzanie tego ostatniego warunku w naszych aplikacjach z oczywistych względów nie ma wielkiego sensu. W praktyce mosna spotkać się z sytuacją, w której urządzenie zwraca wyniki pomiaru w formacie pokazanym na rysunku 5.9. Nalesy wówczas zadbać o odpowiednie pozbycie się zbędnego zera. Zawsze mosna w tym celu usyć funkcji copy()
Wartości odkładane na osi Y wykresu przechowywane są w buforze wejściowym, zaś kolejnymi wartościami osi X będą po prostu kolejne punkty pomiarowe. Te dwie pary liczb nalesy uczynić widocznymi w naszym wykresie. W tym celu skorzystamy z metody AddXY()
function AddXY(Const AXValue, AYValue: Double; Const AXLabel: String;
AColor: TColor) : Longint;
która w naszym programie zostanie usyta następująco:
Form1.Series1.AddXY(intVar, V, '',clTeeColor);
gdzie intVar jest dobrze znaną zmienną, przechowującą aktualny numer pomiaru. W miejsce AXLabel wstawiłem pusty znak po to, by na osi X pojawiały się jedynie kolejne punkty pomiarowe. Równies parametr AColor został usyty opcjonalnie jako clTeeColor. Jeseli ktoś zechciałby mieć wykres koloru np. zielonego, wystarczy wpisać: clGreen. Przykład bardzo uniwersalnej funkcji, w której zarówno wysyłamy zapytanie do urządzenia jak i odczytujemy, a następnie wyświetlamy w komponentach edycyjnych oraz na wykresie otrzymane dane, został przedstawiony ponisej:
function RS_Send_Receive(P : Pointer): Integer;
var j : Integer;
begin
while(bResult = TRUE) do
BEGIN
while(Write_Comm(hCommDev, StrLen(Buffer_O)) = 0) do
FlushFileBuffers(hCommDev);
Form1.Memo1.Lines.Add('');
Sleep(intVarSleep);
//-------odczyt danych z portu--------
if (Read_Comm(hCommDev, SizeOf(Buffer_I)) > 0) then
begin
Form1.Memo2.Lines.Add(AnsiString(Buffer_I));
Inc(intVar); // zliczanie kolejnych pomiarów
Form1.Memo1.Lines.Add(AnsiString(IntToStr(intVar)));
val(Buffer_I, V, Code);
Form1.Series1.AddXY(intVar, V, '',clTeeColor);
end
else
begin
Form1.Memo2.Lines.Add('x0'); // brak lub błędna wartość
// odczytu
Beep();
Form1.Memo2.Lines.Add('');
for j := 0 to cbInQueue do
Buffer_I[j] := char(0);
end;
END; // koniec while
Result:=0;
end;
W porównaniu z jej poprzednimi wersjami zastosowałem tu jeszcze jedną modyfikację, polegającą na dokładnym wyczyszczeniu bufora wejściowego po jakimkolwiek dodatkowym i nieprzewidzianym błędzie w transmisji. Postępując nieco asekuracyjnie przy pisaniu programów obsługujących urządzenia pomiarowe, na pewno ani im ani sobie w niczym nie zaszkodzimy.
Przykładowy projekt p_wykres.dpr, którego formularz pokazany jest na rysunku 7.2, realizuje proces odczytu temperatury aktualnie mierzonej miernikiem cyfrowym z jednoczesną jej wizualizacją na wykresie uzyskanym dzięki zastosowaniu komponentu typu TChart. Główną częścią kodu (patrz wydruk 7.1) tego projektu jest opisana wcześniej funkcja RS_Sen_Receive(). Dzięki prezentowanej aplikacji mamy mosliwość nie tylko śledzenia pomiaru w postaci wykresu. W kasdej chwili dotychczasowe wyniki mosna przekopiować poprzez schowek do dowolnego arkusza kalkulacyjnego czy innego zaawansowanego programu graficznego, gdzie mogą być poddane dalszej obróbce nawet w trakcie dalszego działania macierzystego programu. Przyciski umosliwiające kopiowanie danych w trakcie pomiaru zostały umieszczone w widocznych miejscach jedynie ze względów praktycznych. Ktoś o większym poczuciu estetyki odpowiednie opcje mose zamieścić w dyskretnym menu (patrz rysunek 5.8). Dla wygody Usytkownika istnieje ponadto mosliwość przedstawienia wykresu w postaci linii lub jej 3-wymiarowego rzutu. Wykorzystałem w tym celu właściwość View3D komponentu typu TChart. Zadania te realizowane są poprzez procedury obsługi zdarzeń Picture2Dclick() oraz Picture3Dclick(). Głębokość 3-wymiarowego rzutu mosna regulować, korzystając z właściwości Chart3DPercent. Nie została ona usyta w naszym programie, jednak jej ewentualny zapis będzie bardzo prosty:
procedure TForm1.Percent3DClick(Sender: TObject);
begin
Chart1.Chart3DPercent := PrecentNumber;
end;
gdzie PrecentNumber mose być liczbą całkowitą z zakresu od 1 do 100 (domyślnie przyjmowana jest jako 15).
Rysunek 7.2. Formularz działającej aplikacji posługującej się komponentem TChart (testowanym miernikiem było urządzenie produkcji LakeShore). |
|
Wydruk 7.1. Kompletny kod przykładowego modułu rs_wykres.pas aplikacji wykorzystującej komponent TChart do wyświetlania na wykresie odebranych w wyniku transmisji szeregowej danych liczbowych.
unit rs_wykres;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls, ComCtrls, ExtCtrls, Buttons,
TeEngine, Series, TeeProcs, Chart;
type
TForm1 = class(TForm)
Memo1: TMemo;
Memo2: TMemo;
Panel1: TPanel;
Panel2: TPanel;
Panel3: TPanel;
CheckBox1: TCheckBox;
CheckBox2: TCheckBox;
SpeedButton1: TSpeedButton;
SpeedButton2: TSpeedButton;
OpenComm: TButton;
Start: TButton;
Suspend: TButton;
Resume: TButton;
CloseComm: TButton;
TrackBar1: TTrackBar;
Edit1: TEdit;
Label1: TLabel;
Label2: TLabel;
Label3: TLabel;
Label4: TLabel;
Chart1: TChart;
Series1: TLineSeries;
Picture3D: TRadioButton;
Picture1D: TRadioButton;
procedure StartClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure SuspendClick(Sender: TObject);
procedure ResumeClick(Sender: TObject);
procedure CloseCommClick(Sender: TObject);
procedure OpenCommClick(Sender: TObject);
procedure SpeedButton1Click(Sender: TObject);
procedure SpeedButton2Click(Sender: TObject);
procedure TrackBar1Change(Sender: TObject);
procedure Picture3DClick(Sender: TObject);
procedure Picture2DClick(Sender: TObject);
private
public
end;
var
Form1: TForm1;
implementation
const
// -- wartości znaczników sterujących portu szeregowego --
dcb_fBinary = $0001;
dcb_fParity = $0002;
cbInQueue = 32; // rozmiary buforów danych
cbOutQueue = 32;
query_1 : PChar = '*IDN?'+#13+#10;
query_2 : PChar = 'CDAT?'+#13+#10; // przykładowe zapytania
var
Buffer_O : ARRAY[0..cbOutQueue] of Char; // bufor wyjściowy
Buffer_I : ARRAY[0..cbInQueue] of Char; // bufor wejściowy
Number_Bytes_Read : DWORD;
hCommDev : THANDLE;
lpFileName : PChar;
fdwEvtMask : DWORD;
Stat : TCOMSTAT;
Errors : DWORD;
dcb : TDCB;
intVar : Integer; // licznik pomiarów
intVarSleep : Integer; // licznik późnienia
bResult : BOOL; // 'niema' zmienna logiczna
hThread_SR : THANDLE;
ThreadID_SR: Cardinal;
Code, V : Integer;
procedure TForm1.CloseCommClick(Sender: TObject);
var
iCheckProcess: Integer;
begin
iCheckProcess := MessageDlg('Zakończenie pomiaru i zamknięcie'+
' aplikacji?', mtConfirmation, [mbYes, mbNo], 0);
case iCheckProcess of
idYes:
begin
SuspendThread(hThread_SR);
CloseHandle(hCommDev);
Application.Terminate();
end;
idNo: Exit;
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
TrackBar1.Position := 1000;
TrackBar1.Max := 2000;
TrackBar1.Min := 1;
TrackBar1.Frequency := 100;
OpenComm.Enabled := TRUE;
intVar := 0;
intVarSleep := 1000;
bResult := TRUE;
Form1.BorderIcons:=[biSystemMenu, biMinimize];
Form1.Series1.LinePen.Color := clBlue;
Form1.Series1.LinePen.Style := psSolid;
Picture3D.Checked := TRUE;
end;
function Write_Comm(hCommDev: THANDLE;
nNumberOfBytesToWrite: DWORD): Integer;
var
NumberOfBytesWritten : DWORD;
begin
WriteFile(hCommDev, Buffer_O, nNumberOfBytesToWrite,
NumberOfBytesWritten, NIL);
if (WaitCommEvent(hCommDev, fdwEvtMask, NIL) = TRUE) then
Write_Comm := 1
else
Write_Comm := 0;
end;
function Read_Comm(hCommDev: THANDLE;
Buf_Size: DWORD): Integer;
var
nNumberOfBytesToRead: DWORD;
begin
ClearCommError(hCommDev, Errors, @Stat);
if (Stat.cbInQue > 0) then
begin
if (Stat.cbInQue > Buf_Size) then
nNumberOfBytesToRead := Buf_Size
else
nNumberOfBytesToRead := Stat.cbInQue;
ReadFile(hCommDev, Buffer_I, nNumberOfBytesToRead,
Number_Bytes_Read, NIL);
Read_Comm := 1;
end
else
begin
Number_Bytes_Read := 0;
Read_Comm := 0;
end;
end;
function RS_Send: Integer;
begin
Repeat
FlushFileBuffers(hCommDev);
Until (Write_Comm(hCommDev, StrLen(Buffer_O)) <> 0);
Result := 0;
end;
procedure TForm1.OpenCommClick(Sender: TObject);
var i : Integer;
begin
if (CheckBox1.Checked = TRUE) then
lpFileName:='COM2';
hCommDev:= CreateFile(lpFileName, GENERIC_READ or GENERIC_WRITE, 0,
NIL, OPEN_EXISTING, 0, 0);
if (hCommDev <> INVALID_HANDLE_VALUE) then
BEGIN
SetupComm(hCommDev, cbInQueue, cbOutQueue);
dcb.DCBlength := sizeof(dcb);
GetCommState(hCommDev, dcb);
if (CheckBox2.Checked = TRUE) then
dcb.BaudRate:=CBR_1200;
//-przykładowe ustawienia znaczników sterujących DCB-
dcb.Flags := dcb_fParity;
dcb.Parity := ODDPARITY;
dcb.StopBits :=ONESTOPBIT;
dcb.ByteSize :=7;
SetCommState(hCommDev, dcb);
GetCommMask(hCommDev, fdwEvtMask);
SetCommMask(hCommDev, EV_TXEMPTY);
StrCopy(Buffer_O, query_1);
RS_Send; // zapytanie o identyfikację urządzenia
Sleep(1000);
if (Read_Comm(hCommDev, SizeOf(Buffer_I)) > 0) then
begin
// -- wyświetlanie numeru przyrządu
Application.MessageBox(PChar(AnsiString(Buffer_I)),
'Identyfikacja urządzenia przyłączonego do wybranego'+
' portu :' ,MB_OK);
OpenComm.Enabled := FALSE;
end
else
Application.MessageBox('Urządzenie nie odpowiada ',
'Uwaga !' ,MB_OK);
for i:=0 to cbInQueue do
begin
Buffer_O[i] := char(0);
Buffer_I[i] := char(0);
end;
Sleep(1000);
END
else
case hCommDev of
IE_BADID:
begin
lpFileName := '';
Application.MessageBox('Niewłaściwa nazwa portu lub'+
' jest on aktywny', 'Uwaga !',MB_OK);
end;
end;
end;
function RS_Send_Receive(P : Pointer): Integer;
var j : Integer;
begin
while(bResult = TRUE) do
BEGIN
while(Write_Comm(hCommDev, StrLen(Buffer_O)) = 0) do
FlushFileBuffers(hCommDev);
Form1.Memo1.Lines.Add('');
Sleep(intVarSleep);
//-------odczyt danych z portu--------
if (Read_Comm(hCommDev, SizeOf(Buffer_I)) > 0) then
begin
Form1.Memo2.Lines.Add(AnsiString(Buffer_I));
Inc(intVar); // zliczanie kolejnych pomiarów
Form1.Memo1.Lines.Add(AnsiString(IntToStr(intVar)));
val(Buffer_I, V, Code);
Form1.Series1.AddXY(intVar, Round(V), '',clTeeColor);
end
else
begin
Form1.Memo2.Lines.Add('x0');
Beep();
Form1.Memo2.Lines.Add('');
for j := 0 to cbInQueue do
Buffer_I[j] := char(0);
end;
END; // koniec while
Result:=0;
end;
procedure TForm1.StartClick(Sender: TObject);
begin
if (hCommDev > 0) then
begin
StrCopy(Buffer_O, query_2);
hThread_SR := BeginThread (NIL, 0, @RS_Send_Receive, NIL, 0,
ThreadID_SR);
end
else
Application.MessageBox('Niewłaściwa nazwa portu lub jest on'+
' aktywny ', 'Uwaga !',MB_OK);
end;
//----------wstrzymanie pomiaru -------- ----- ------ -----
procedure TForm1.SuspendClick(Sender: TObject);
begin
SuspendThread(hThread_SR);
end;
//----------wznowienie pomiaru -------- ----- ------ ------
procedure TForm1.ResumeClick(Sender: TObject);
begin
ResumeThread(hThread_SR);
end;
//-----kopiowanie okna edycji Memo2 do schowka----- ----- --------------
procedure TForm1.SpeedButton1Click(Sender: TObject);
begin
Form1.Memo2.SelectAll;
Form1.Memo2.CopyToClipboard;
end;
//-----kopiowanie okna edycji Memo1 do schowka----- ----- --------------
procedure TForm1.SpeedButton2Click(Sender: TObject);
begin
Form1.Memo1.SelectAll;
Form1.Memo1.CopyToClipboard;
end;
procedure TForm1.TrackBar1Change(Sender: TObject);
begin
intVarSleep := TrackBar1.Position; // sterowanie późnieniem
Edit1.Text := IntToStr(TrackBar1.Position);
end;
//---------wykres 3-wymiarowy-------- ----- ------ -----
procedure TForm1.Picture3DClick(Sender: TObject);
begin
Chart1.View3D := TRUE;
end;
//---------wykres 2-wymiarowy-------- ----- ------ ------
procedure TForm1.Picture2DClick(Sender: TObject);
begin
Chart1.View3D := FALSE;
end;
end.
Z wielu metod udostępnianych przez komponent TChart mosna równies wykorzystać mosliwość powiększania lub pomniejszania wykresu w trakcie działania programu. Temu celowi słusą metody AnimatedZoom ZoomPercent oraz UndoZoom. Ich ewentualne usycie w programie zapewnią nam procedury obsługi zdarzeń, zaprojektowane według następujących schematów:
procedure TForm1.ZoomINClick(Sender: TObject);
begin
Chart1.AnimatedZoom := TRUE;
Chart1.ZoomPercent(125); // powiększenie do 125%
end;
procedure TForm1.ZoomOutClick(Sender: TObject);
begin
Chart1.AnimatedZoom := TRUE;
Chart1.ZoomPercent(75); // pomniejszenie do 75%
end;
procedure TForm1.UndoZoomClick(Sender: TObject);
begin
Chart1.UndoZoom; // przywrócenie domyślnego rozmiaru
end;
Przedstawiony program testowany był podczas komunikacji z bardzo szybkim, nowoczesnym urządzeniem mierzącym temperaturę. Zastosowany algorytm działał poprawnie nawet dla przedziału czasu próbkowania łącza wynoszącego 100 ms, tzn. dla minimalnego czasu, w którym urządzenie było zdolne odczytać wysłaną komendę (zapytanie o mierzoną wielkość), przestroić się, dokonać pomiaru oraz zwrócić wartość aktualnie zmierzonej temperatury.
Wszystkie komponenty wysszego rzędu realizujące grafikę mają właściwość Canvas, będącą obiektem klasy TCanvas. Rósnego rodzaju wykresy mosna projektować w ramach obszaru roboczego formularza. Jeseli jednak zechcemy, aby realizowane były w jego określonym fragmencie, wygodnie jest skorzystać z komponentu TPaintBox. Wszelkie współrzędne wykresu będą wyznaczone właśnie przez ten obiekt dzięki jego właściwościom Top Left Height oraz Width. Linie mosemy rysować, usywając metod MoveTo() oraz LineTo(). Z powodzeniem mosna tes wykorzystać funkcję PolyLine(), której parametrem jest tablica punktów w sensie ich współrzędnych. W celu określenia współrzędnych odpowiednich punktów mosna skorzystać z bardzo prostej funkcji Point(), wywoływanej z dwoma parametrami. Funkcja ta zwraca rekord typu TPoint składający się z dwóch zmiennych: X oraz Y. Przykład wywołania tej funkcji mosna znaleźć w treści procedury PaintLine() na wydruku 7.2. Rysunek 7.3 przedstawia formularz działającej aplikacji KODYDELPHIRS_23p_RS_23.dpr, w której dane otrzymywane od urządzenia pomiarowego wyświetlane są w postaci wykresu liniowego.
Rysunek 7.3. Formularz działającej aplikacji p_RS_23.dpr |
|
Projektując kod omawianego programu, zastosowałem bardzo prosty sposób skalowania osi. W procedurze obsługi zdarzenia OpenCommClick() tus po odczycie numeru przyrządu wywoływana jest powtórnie funkcja RS_Send(), dokonująca pierwszego pomiaru mierzonej wielkości. Pierwszy element tablicy YAxis[1] słusy do dalszego skalowania osi i całego wykresu. Dane na wykresie wyświetlane są w porcjach po 400 punktów pomiarowych w trakcie działania osobnego wątku, w którym jednocześnie dokonuje się właściwy pomiar.
Wydruk 7.2. Kod modułu RS_23.pas aplikacji wykorzystującej metody komponentu TPaintBox
unit RS_23;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls, ComCtrls, ExtCtrls, Buttons;
type
TForm1 = class(TForm)
Memo1: TMemo;
Memo2: TMemo;
Panel1: TPanel;
Panel2: TPanel;
Panel3: TPanel;
CheckBox1: TCheckBox;
CheckBox2: TCheckBox;
SpeedButton1: TSpeedButton;
SpeedButton2: TSpeedButton;
OpenComm: TButton;
Start: TButton;
Suspend: TButton;
Resume: TButton;
CloseComm: TButton;
TrackBar1: TTrackBar;
Edit1: TEdit;
Edit2: TEdit;
Label1: TLabel;
Label2: TLabel;
Label3: TLabel;
Label4: TLabel;
Label5: TLabel;
Label6: TLabel;
PaintBox1: TPaintBox;
procedure StartClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure SuspendClick(Sender: TObject);
procedure ResumeClick(Sender: TObject);
procedure CloseCommClick(Sender: TObject);
procedure OpenCommClick(Sender: TObject);
procedure SpeedButton1Click(Sender: TObject);
procedure SpeedButton2Click(Sender: TObject);
procedure TrackBar1Change(Sender: TObject);
private
public
end;
var
Form1: TForm1;
implementation
const
// -- wartości znaczników sterujących portu szeregowego --
dcb_fBinary = $0001;
dcb_fParity = $0002;
cbInQueue = 32; // rozmiary buforów danych
cbOutQueue = 32;
query_1 : PChar = '*IDN?'+#13+#10;
query_2 : PChar = 'CDAT?'+#13+#10; // przykładowe zapytania
var
Buffer_O : ARRAY[0..cbOutQueue] of Char; // bufor wyjściowy
Buffer_I : ARRAY[0..cbInQueue] of Char; // bufor wejściowy
Number_Bytes_Read : DWORD;
hCommDev : THANDLE;
lpFileName : PChar;
fdwEvtMask : DWORD;
Stat : TCOMSTAT;
Errors : DWORD;
dcb : TDCB;
intVar : Integer; // licznik pomiarów
intVarSleep : Integer; // licznik późnienia
bResult : BOOL; // 'niema' zmienna logiczna
hThread_SR : THANDLE;
ThreadID_SR: Cardinal;
const
MaxMeasure = 3000; // maksymalna liczba punktów pomiarowych !
MarginY = 30;
MarginX = 40;
XAxisLenght = MarginX + 400;
var
V, Value, Code : Integer;
XAxis, YAxis : ARRAY[1..MaxMeasure] of Integer;
procedure TForm1.CloseCommClick(Sender: TObject);
var
iCheckProcess: Integer;
begin
iCheckProcess := MessageDlg('Zakończenie pomiaru i zamknięcie'+
' aplikacji?', mtConfirmation, [mbYes, mbNo], 0);
case iCheckProcess of
idYes:
begin
SuspendThread(hThread_SR);
CloseHandle(hCommDev);
Application.Terminate();
end;
idNo: Exit;
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
TrackBar1.Position := 1000;
TrackBar1.Max := 2000;
TrackBar1.Min := 1;
TrackBar1.Frequency := 100;
OpenComm.Enabled := TRUE;
intVar := 0;
intVarSleep := 1000;
bResult := TRUE;
Form1.PaintBox1.Canvas.Font.Size := 10;
Form1.PaintBox1.Canvas.Brush.Color := clBtnFace;
Form1.PaintBox1.Canvas.Font.Color:=clBlack;
Form1.BorderIcons:=[biSystemMenu, biMinimize];
Form1.label5.Visible := FALSE;
Form1.label6.Visible := FALSE;
Form1.Edit2.Visible := FALSE;
end;
function Write_Comm(hCommDev: THANDLE;
nNumberOfBytesToWrite: DWORD): Integer;
var
NumberOfBytesWritten : DWORD;
begin
WriteFile(hCommDev, Buffer_O, nNumberOfBytesToWrite,
NumberOfBytesWritten, NIL);
if (WaitCommEvent(hCommDev, fdwEvtMask, NIL) = TRUE) then
Write_Comm := 1
else
Write_Comm := 0;
end;
function Read_Comm(hCommDev: THANDLE;
Buf_Size: DWORD): Integer;
var
nNumberOfBytesToRead: DWORD;
begin
ClearCommError(hCommDev, Errors, @Stat);
if (Stat.cbInQue > 0 ) then
begin
if (Stat.cbInQue > Buf_Size) then
nNumberOfBytesToRead := Buf_Size
else
nNumberOfBytesToRead := Stat.cbInQue;
ReadFile(hCommDev, Buffer_I, nNumberOfBytesToRead,
Number_Bytes_Read, NIL);
Read_Comm := 1;
end
else
begin
Number_Bytes_Read := 0;
Read_Comm := 0;
end;
end;
function RS_Send: Integer;
begin
Repeat
FlushFileBuffers(hCommDev);
Until (Write_Comm(hCommDev, StrLen(Buffer_O)) <> 0);
Result := 0;
end;
procedure TForm1.OpenCommClick(Sender: TObject);
var i : Integer;
begin
if (CheckBox1.Checked = TRUE) then
lpFileName:='COM2';
hCommDev:= CreateFile(lpFileName, GENERIC_READ or GENERIC_WRITE, 0,
NIL, OPEN_EXISTING, 0, 0);
if (hCommDev <> INVALID_HANDLE_VALUE) then
BEGIN
SetupComm(hCommDev, cbInQueue, cbOutQueue);
dcb.DCBlength := sizeof(dcb);
GetCommState(hCommDev, dcb);
if (CheckBox2.Checked = TRUE) then
dcb.BaudRate:=CBR_1200;
//-przykładowe ustawienia znaczników sterujących DCB-
dcb.Flags := dcb_fParity;
dcb.Parity := ODDPARITY;
dcb.StopBits :=ONESTOPBIT;
dcb.ByteSize :=7;
SetCommState(hCommDev, dcb);
GetCommMask(hCommDev, fdwEvtMask);
SetCommMask(hCommDev, EV_TXEMPTY);
StrCopy(Buffer_O, query_1);
RS_Send;
Sleep(1000);
if (Read_Comm(hCommDev, SizeOf(Buffer_I)) > 0) then
begin
Application.MessageBox(PChar(AnsiString(Buffer_I)),
'Identyfikacja urządzenia przyłączonego do wybranego'+
' portu :' ,MB_OK);
OpenComm.Enabled := FALSE;
end
else
Application.MessageBox('Urządzenie nie odpowiada ',
'Uwaga !' ,MB_OK);
for i:=0 to cbInQueue do
begin
Buffer_O[i] := char(0);
Buffer_I[i] := char(0);
end;
StrCopy(Buffer_O, query_2);
RS_Send;
Sleep(1000);
if (Read_Comm(hCommDev, SizeOf(Buffer_I)) > 0) then
begin
val(Buffer_I, V, Code);
YAxis[1] := Round(0.5*V);
Form1.Memo2.Text:=AnsiString(Buffer_I);
end;
END
else
case hCommDev of
IE_BADID:
begin
lpFileName := '';
Application.MessageBox('Niewłaściwa nazwa portu lub'+
' jest on aktywny', 'Uwaga !',MB_OK);
end;
end;
end;
procedure PaintLine(Canvas: TCanvas; X, Y, Lenght: Integer);
begin
Canvas.PolyLine([Point(X, Y ), Point(X, Lenght)]);
end;
function RS_Send_Receive(P: Pointer): Integer;
var j : Integer;
begin
Form1.Edit2.Top := Form1.Height - 2*MarginY;
YAxis[1] := Form1.PaintBox1.Height - YAxis[1];
Form1.PaintBox1.Canvas.MoveTo(MarginX + 2, YAxis[1]);
Form1.PaintBox1.Canvas.Pen.Width := 2;
while( bResult = TRUE) do
BEGIN
while(Write_Comm(hCommDev, StrLen(Buffer_O)) = 0) do
FlushFileBuffers(hCommDev);
Form1.Memo1.Lines.Add('');
Sleep(intVarSleep);
//-------odczyt danych z portu-------- ----- ------
if (Read_Comm(hCommDev, SizeOf(Buffer_I)) > 0) then
begin
Form1.Memo2.Lines.Add(AnsiString(Buffer_I));
Inc(intVar); // zliczanie kolejnych pomiarów
Form1.Memo1.Lines.Add(AnsiString(IntToStr(intVar)));
val(Buffer_I, V, Code);
YAxis[intVar] := Form1.PaintBox1.Height - Round(V*0.5);
Form1.PaintBox1.Canvas.Font.Color:=clBlack;
if (intVar < 400) then
begin
Form1.PaintBox1.Canvas.Pen.Color := clRed;
XAxis[intVar] := MarginX + 1 + intVar;
//Form1.PaintBox1.Canvas.Pixels[XAxis[intVar],
YAxis[intVar]] := 255;
Form1.PaintBox1.Canvas.LineTo(XAxis[intVar],
YAxis[intVar]);
Form1.Edit2.Left := XAxis[intVar] + 20;
Form1.Edit2.Text := IntToStr(intVar);
end
ELSE BEGIN
case intVar of
400:
begin
Value := intVar;
Form1.PaintBox1.Canvas.Pen.Color := clBtnFace;
for j:=1 to Value do
Form1.PaintBox1.Canvas.LineTo(XAxis[j],
YAxis[j]);
Form1.PaintBox1.Canvas.MoveTo(MarginX + 2,
YAxis[Value - 1]);
end;
800:
begin
Value := intVar;
Form1.PaintBox1.Canvas.Pen.Color := clBtnFace;
for j := 400 to Value do
Form1.PaintBox1.Canvas.LineTo(XAxis[j],
YAxis[j]);
Form1.PaintBox1.Canvas.MoveTo(MarginX + 2,
YAxis[Value - 1]);
end;
1200:
begin
Value := intVar;
Form1.PaintBox1.Canvas.Pen.Color := clBtnFace;
for j := 800 to Value do
Form1.PaintBox1.Canvas.LineTo(XAxis[j],
YAxis[j]);
Form1.PaintBox1.Canvas.MoveTo(MarginX + 2,
YAxis[Value - 1]);
end;
1600:
begin
Value := intVar;
Form1.PaintBox1.Canvas.Pen.Color := clBtnFace;
for j := 1200 to Value do
Form1.PaintBox1.Canvas.LineTo(XAxis[j],
YAxis[j]);
Form1.PaintBox1.Canvas.MoveTo(MarginX + 2,
YAxis[Value - 1]);
end;
2000:
begin
Value := intVar;
Form1.PaintBox1.Canvas.Pen.Color := clBtnFace;
for j := 1600 to Value do
Form1.PaintBox1.Canvas.LineTo(XAxis[j],
YAxis[j]);
Form1.PaintBox1.Canvas.MoveTo(MarginX + 2,
YAxis[Value - 1]);
end;
end; // koniec case
Form1.PaintBox1.Canvas.Pen.Color := clRed;
XAxis[intVar] := MarginX + 1 + (intVar-Value);
Form1.PaintBox1.Canvas.LineTo(XAxis[intVar],
YAxis[intVar]);
Form1.Edit2.Left := XAxis[intVar] + 20;
Form1.Edit2.Text := IntToStr(intVar);
END;
end // koniec if
else
begin
Form1.Memo2.Lines.Add('x0');
Beep();
Form1.Memo2.Lines.Add('');
for j := 0 to cbInQueue do
Buffer_I[j] := char(0);
end;
END; // koniec while
Result:=0;
end;
procedure TForm1.StartClick(Sender: TObject);
var i, iScal : Integer;
begin
if (hCommDev > 0) then
begin
StrCopy(Buffer_O, query_2);
hThread_SR := BeginThread (NIL, 0, @RS_Send_Receive, NIL, 0,
ThreadID_SR);
PaintBox1.Canvas.Pen.Width := 2;
PaintBox1.Canvas.Pen.Color := clBlack;
iScal := Round((Form1.PaintBox1.Height - MarginY)/YAxis[1]);
PaintBox1.Canvas.MoveTo(MarginX,
Form1.PaintBox1.Height - MarginY);
PaintBox1.Canvas.LineTo(XAxisLenght,
Form1.PaintBox1.Height - MarginY);
PaintBox1.Canvas.MoveTo(MarginX,
Form1.PaintBox1.Height - MarginY);
PaintBox1.Canvas.LineTo(MarginX, Round(iScal/YAxis[1]));
PaintBox1.Canvas.Pen.Width := 1;
Form1.PaintBox1.Canvas.Font.Color:=clBlack;
i := 0;
Repeat // oś X
PaintLine(Form1.PaintBox1.Canvas, MarginX + i,
Form1.PaintBox1.Height - MarginY,
Form1.PaintBox1.Height - MarginY + 2 + 5);
i := i + 50;
Until(i > XAxisLenght - MarginX);
i:=0;
Repeat // oś Y
PaintBox1.Canvas.PolyLine([Point(MarginX,
Form1.PaintBox1.Height - MarginY -i ),
Point(MarginX - 10, Form1.PaintBox1.Height - MarginY -i)]);
PaintBox1.Canvas.TextOut(MarginX-40,
Form1.PaintBox1.Height - 40 - i, IntToStr(Trunc(2.5*i)));
i := i + 20;
Until(i >= 1.5*YAxis[1]);
Form1.label5.Visible := TRUE;
Form1.label6.Visible := TRUE;
Form1.Edit2.Visible := TRUE;
end
else
Application.MessageBox('Niewłaściwa nazwa portu lub jest on'+
' aktywny ', 'Uwaga !',MB_OK);
end;
//----------wstrzymanie pomiaru -------- ----- ------ -----
procedure TForm1.SuspendClick(Sender: TObject);
begin
SuspendThread(hThread_SR);
end;
//----------wznowienie pomiaru -------- ----- ------ ------
procedure TForm1.ResumeClick(Sender: TObject);
begin
ResumeThread(hThread_SR);
end;
//-----kopiowanie okna edycji Memo2 do schowka----- ----- --------------
procedure TForm1.SpeedButton1Click(Sender: TObject);
begin
Form1.Memo2.SelectAll;
Form1.Memo2.CopyToClipboard;
end;
//-----kopiowanie okna edycji Memo1 do schowka----- ----- --------------
procedure TForm1.SpeedButton2Click(Sender: TObject);
begin
Form1.Memo1.SelectAll;
Form1.Memo1.CopyToClipboard;
end;
procedure TForm1.TrackBar1Change(Sender: TObject);
begin
intVarSleep := TrackBar1.Position; // sterowanie późnieniem
Edit1.Text := IntToStr(TrackBar1.Position);
end;
end.
Konstruując samodzielnie wykresy, wyświetlane w czasie działania aplikacji odczytującej dane pochodzące od jakiegoś urządzenia pomiarowego, nalesy pamiętać, se w przypadku prostego formularza czy obiektu typu TPaintBox, zawartość odpowiedniego obiektu TCanvas nie jest przechowywana w pamięci i mose w pewnych warunkach ulec łatwemu zamazaniu przez np. następną uruchomioną aplikację. Tej wady nie mają wykresy rysowane na mapach bitowych.
Zarówno w Delphi jak i Builderze mapę bitową najlepiej jest wyświetlić za pomocą komponentu TImage. Mosna wstawić do niego mapę bitową z zewnętrznego pliku lub wykorzystać w tym celu edytor obrazów Image Editor, znajdujący się w opcji Tools głównego menu. Wstawiona do formularza mapa bitowa będzie stanowić tło dla naszego wykresu, posiadając oczywiście swój własny obiekt TCanvas. Mosliwym jest zatem usycie narzędzi graficznych płótna, takich jak: pióro (własność Pen), pędzel (Brush) czy czcionka (Font). Posłusymy się równies funkcjami (metodami) TextOut() LineTo() MoveTo() PolyLine() oraz Point(). Przykład działającego projektu aplikacji KODYDELPHIRS_24p_RS_24.dpr, wyświetlającej na wykresie rysowanym na mapie bitowej wyniki pomiaru temperatury pewnego układu fizycznego pokazano na rysunku 7.3.
Rysunek 7.3. Formularz działającej aplikacji p_RS_24.dpr |
|
Złudzenie całkowitego wypełnienia obszaru pod krzywą uzyskałem, rysując wykres za pomocą funkcji PolyLine() wywoływanej w treści procedury PaintLine(), identycznie jak zostało to przedstawione na wydruku 7.2. Wydruk 7.3 prezentuje część algorytmu, realizującego pomiar interesującej nas wielkości fizycznej (w tym przypadku temperatury) oraz wyświetlającego wyniki w postaci odpowiedniego wykresu.
Wydruk 7.3. Fragment kodu modułu RS_24.pas aplikacji wykorzystującej komponent TImage
const
MaxMeasure = 3000; // maksymalna liczba pomiarów
MarginY = 30;
MarginX = 40;
XAxisLenght = MarginX + 400;
var V, Value, Code : Integer;
XAxis, YAxis : ARRAY[1..MaxMeasure] of Integer;
function RS_Send_Receive(P: Pointer): Integer;
var j : Integer;
begin
while( bResult = TRUE) do
BEGIN
while(Write_Comm(hCommDev, StrLen(Buffer_O)) = 0) do
FlushFileBuffers(hCommDev);
Form1.Memo1.Lines.Add('');
Sleep(intVarSleep);
//-------odczyt danych z portu-------- ----- ------ -
if (Read_Comm(hCommDev, SizeOf(Buffer_I)) > 0) then
begin
Form1.Memo2.Lines.Add(AnsiString(Buffer_I));
Inc(intVar); // zliczanie kolejnych pomiarów
Form1.Memo1.Lines.Add(AnsiString(IntToStr(intVar)));
val((Buffer_I), V, Code);
YAxis[intVar] := Form1.Image1.Height - Round(V*0.5);
Form1.Image1.Canvas.Font.Color:=clBlack;
if (intVar < 400) then
begin
Form1.Image1.Canvas.Pen.Color := clRed;
XAxis[intVar] := MarginX + intVar;
PaintLine(Form1.Image1.Canvas, XAxis[intVar] ,
Form1.Image1.Height - MarginY-2, YAxis[intVar]);
Form1.Image1.Canvas.TextOut(MarginX + intVar,
Form1.Image1.Height - MarginY + 5,
IntToStr(intVar));
end
ELSE BEGIN
case intVar of
400:
begin
Value := intVar;
Form1.Image1.Canvas.Pen.Color := clBtnFace;
for j:=1 to Value do
PaintLine(Form1.Image1.Canvas, XAxis[j] ,
Form1.Image1.Height - MarginY -2,
YAxis[j]);
Form1.Image1.Canvas.TextOut(MarginX + 400,
Form1.Image1.Height - MarginY + 5, ' ');
end;
800:
begin
Value := intVar;
Form1.Image1.Canvas.Pen.Color := clBtnFace;
for j := 400 to Value do
PaintLine(Form1.Image1.Canvas, XAxis[j] ,
Form1.Image1.Height - MarginY -2,
YAxis[j] );
Form1.Image1.Canvas.TextOut(MarginX + 400,
Form1.Image1.Height - MarginY + 5, ' ');
end;
1200:
begin
Value := intVar;
Form1.Image1.Canvas.Pen.Color := clBtnFace;
for j := 800 to Value do
PaintLine(Form1.Image1.Canvas, XAxis[j] ,
Form1.Image1.Height - MarginY -2,
YAxis[j]);
Form1.Image1.Canvas.TextOut(MarginX + 400,
Form1.Image1.Height - MarginY + 5, ' ');
end;
1600:
begin
Value := intVar;
Form1.Image1.Canvas.Pen.Color := clBtnFace;
for j := 1200 to Value do
PaintLine(Form1.Image1.Canvas, XAxis[j] ,
Form1.Image1.Height - MarginY -2,
YAxis[j]);
Form1.Image1.Canvas.TextOut(MarginX + 400,
Form1.Image1.Height - MarginY + 5, ' ');
end;
2000:
begin
Value := intVar;
Form1.Image1.Canvas.Pen.Color := clBtnFace;
for j := 1600 to Value do
PaintLine(Form1.Image1.Canvas, XAxis[j] ,
Form1.Image1.Height - MarginY -2,
YAxis[j]);
Form1.Image1.Canvas.TextOut(MarginX + 400,
Form1.Image1.Height - MarginY + 5, ' ');
end;
end; // koniec case
Form1.Image1.Canvas.Pen.Color := clRed;
XAxis[intVar] := MarginX + 1 + (intVar-Value);
PaintLine(Form1.Image1.Canvas, XAxis[intVar] ,
Form1.Image1.Height - MarginY -2, YAxis[intVar]);
Form1.Image1.Canvas.TextOut(MarginX + 1+ (intVar-Value),
Form1.Image1.Height - MarginY + 5,
IntToStr(intVar));
END;
end
else
begin
Form1.Memo2.Lines.Add('x0');
Beep();
Form1.Memo2.Lines.Add('');
for j := 0 to cbInQueue do
Buffer_I[j] := char(0);
end;
END; // koniec while
Result:=0;
end;
procedure TForm1.StartClick(Sender: TObject);
var i, iScal : Integer;
begin
if (hCommDev > 0) then
begin
StrCopy(Buffer_O, query_2);
hThread_SR := BeginThread (NIL, 0, @RS_Send_Receive, NIL, 0,
ThreadID_SR);
Image1.Canvas.Pen.Width := 2;
Image1.Canvas.Pen.Color := clBlack;
iScal := Round((Form1.Image1.Height - MarginY)/YAxis[1]);
Image1.Canvas.MoveTo(MarginX, Form1.Image1.Height - MarginY);
Image1.Canvas.LineTo(XAxisLenght,
Form1.Image1.Height - MarginY);
Image1.Canvas.MoveTo(MarginX, Form1.Image1.Height - MarginY);
Image1.Canvas.LineTo(MarginX, Round(iScal/YAxis[1]));
Image1.Canvas.Pen.Width := 1;
Form1.Image1.Canvas.Font.Color:=clBlack;
i := 0;
Repeat // oś X
PaintLine(Form1.Image1.Canvas, MarginX + i,
Form1.Image1.Height - MarginY,
Form1.Image1.Height - MarginY + 2 + 5);
i := i + 50;
Until(i > XAxisLenght - MarginX);
i := 0;
Repeat // oś Y
Image1.Canvas.PolyLine([Point(MarginX,
Form1.Image1.Height - MarginY -i ),
Point(MarginX - 10,
Form1.Image1.Height - MarginY -i)]);
Image1.Canvas.TextOut(MarginX-40,
Form1.Image1.Height - 40 - i,
IntToStr(Trunc(2.5*i)));
i := i + 20;
Until(i >= 1.5*YAxis[1]);
Form1.label5.Visible := TRUE;
Form1.label6.Visible := TRUE;
end
else
Application.MessageBox('Niewłaściwa nazwa portu lub jest on'+
' aktywny ', 'Uwaga !',MB_OK);
end;
Projektując powysszy algorytm, przewidziałem mosliwość rejestracji co najwysej 3000 punktów pomiarowych, których współrzędne przechowywane są w elementach jednowymiarowych tablic XAxis oraz YAxis. Jednak w praktyce liczba pomiarów mose być wielokrotnie większa. Nalesy wówczas skorzystać z tablic deklarowanych dynamicznie, jeseli oczywiście w dowolnej chwili zechcemy odtworzyć całą historię pomiaru. Pamiętać równies nalesy, se usywanie mapy bitowej uszczupli nieco zasoby systemu operacyjnego oraz pamięć naszego PC.
W celu prostszego i efektywniejszego zarządzania mapami bitowymi zdefiniowano klasę TBitmap, wykorzystującą definicje typów Win32: HBITMAP oraz HPALETTE. Samodzielne utworzenie mapy bitowej wymaga zadeklarowania zmiennej typu TBitmap
var
TheBitmap : TBitmap;
Następnie nalesy stworzyć i przypisać jej obiekt tego samego typu. Operację taką wraz z ustaleniem rozmiaru mapy bitowej, koloru jej obszaru oraz sposobu wyświetlania wygodnie jest wykonać w oddzielnej procedurze:
procedure BitMapCreate;
begin
TheBitmap := TBitmap.Create;
TheBitmap.Height := 265;
TheBitmap.Width := 521;
TheBitmap.Canvas.Brush.Color := clBtnFace;
TheBitmap.Transparent := TRUE;
end;
Wywołując w odpowiednim miejscu programu taką procedurę, zainicjujemy obszar mapy bitowej, na którym mosna rysować dowolny wykres. Aby tak otrzymany wykres wyświetlić w danym miejscu formularza, nalesy usyć metody Draw(), która kopiuje mapę do określonego obszaru roboczego. Jeseli chcielibyśmy zobaczyć nasz wykres, w miejscu formularza o współrzędnych np. 22, 240 wystarczy zapisać:
Form1.Canvas.Draw(22, 240, TheBitmap);
Pojedyncze usycie wymienionej metody zapewni jednorazowe wyświetlenie jednego punktu pomiarowego. W naszych aplikacjach operacje odczytu i wyświetlania danych wykonywane są cyklicznie. Musimy więc metodę tę wywoływać kasdorazowo po dokonaniu kolejnego odczytu, który jest równoznaczny z uzupełnieniem o kolejny punkt aktualnie rysowanego wykresu. Trzeba przyznać, se komplikuje to nieco algorytm. Wydruk 7.4 prezentuje funkcję, której zadaniem jest odczytywanie danych z portu szeregowego oraz ich graficzne ich przedstawienie. Funkcję tę wywołujemy w procedurze obsługi zdarzenia StartClick(), uruchamiającego odrębny wątek programu. Z tego właśnie powodu wszystkie operacje związane z odczytem danych, skalowaniem, rysowaniem osi oraz samego wykresu, który jest następnie odpowiednio „przewijany” w obszarze mapy bitowej muszą być zawarte w jednej funkcji.
Wydruk 7.4. Fragment kodu modułu RS_25.pas aplikacji wykorzystującej mapę bitową
function RS_Send_Receive(P: Pointer): Integer;
var iScal, i, j : Integer;
begin
BitMapCreate;
TheBitmap.Canvas.Pen.Width := 2;
TheBitmap.Canvas.Pen.Color := clBlack;
iScal := Round((TheBitmap.Height - MarginY)/YAxis[1]);
TheBitmap.Canvas.MoveTo(MarginX, TheBitmap.Height - MarginY);
TheBitmap.Canvas.LineTo(XAxisLenght, TheBitmap.Height - MarginY);
TheBitmap.Canvas.MoveTo(MarginX, TheBitmap.Height - MarginY);
TheBitmap.Canvas.LineTo(MarginX, Round(iScal/YAxis[1]));
TheBitmap.Canvas.Pen.Width := 1;
TheBitmap.Canvas.Font.Color:=clBlack;
i := 0;
Repeat // oś X
PaintLine(TheBitmap.Canvas, MarginX + i,
TheBitmap.Height - MarginY,
TheBitmap.Height - MarginY + 2 + 5);
i := i + 50;
Until(i > XAxisLenght - MarginX);
i:=0;
Repeat // oś Y
TheBitmap.Canvas.PolyLine([Point(MarginX,
TheBitmap.Height - MarginY -i ),
Point(MarginX - 10, TheBitmap.Height - MarginY -i)]);
TheBitmap.Canvas.TextOut(MarginX-40, TheBitmap.Height - 40 - i,
IntToStr(Trunc(2.5*i)));
i := i + 20;
Until(i >= 1.5*YAxis[1]);
Form1.label5.Visible := TRUE;
Form1.label6.Visible := TRUE;
Form1.Canvas.Draw(22, 240, TheBitmap); // wyświetlanie mapy bitowej
while( bResult = TRUE) do
BEGIN
while(Write_Comm(hCommDev, StrLen(Buffer_O)) = 0) do
FlushFileBuffers(hCommDev);
Form1.Memo1.Lines.Add('');
Sleep(intVarSleep);
//-------odczyt danych z portu-------- ----- ------ -
if ( Read_Comm(hCommDev, SizeOf(Buffer_I)) > 0) then
begin
Form1.Memo2.Lines.Add(AnsiString(Buffer_I));
Inc(intVar); // zliczanie kolejnych pomiarów
Form1.Memo1.Lines.Add(AnsiString(IntToStr(intVar)));
val(Buffer_I, V, Code);
YAxis[intVar] := TheBitmap.Height - Round(V*0.5);
TheBitmap.Canvas.Font.Color:=clBlack;
if (intVar < 400) then
begin
TheBitmap.Canvas.Pen.Color := clRed;
XAxis[intVar] := MarginX + intVar;
PaintLine(TheBitmap.Canvas, XAxis[intVar] ,
TheBitmap.Height - MarginY-2, YAxis[intVar] );
TheBitmap.Canvas.TextOut(MarginX + intVar,
TheBitmap.Height - MarginY + 5,
IntToStr(intVar));
Form1.Canvas.Draw(22, 240, TheBitmap);
end
ELSE BEGIN
case intVar of
400:
begin
Value := intVar;
TheBitmap.Canvas.Pen.Color := clBtnFace;
for j:=1 to Value do
PaintLine(TheBitmap.Canvas, XAxis[j] ,
TheBitmap.Height - MarginY -2, YAxis[j]);
TheBitmap.Canvas.TextOut(MarginX + 400,
TheBitmap.Height - MarginY + 5, ' ');
Form1.Canvas.Draw(22, 240, TheBitmap);
end;
800:
begin
Value := intVar;
TheBitmap.Canvas.Pen.Color := clBtnFace;
for j := 400 to Value do
PaintLine(TheBitmap.Canvas, XAxis[j] ,
TheBitmap.Height - MarginY -2, YAxis[j]);
TheBitmap.Canvas.TextOut(MarginX + 400,
TheBitmap.Height - MarginY + 5, ' ');
Form1.Canvas.Draw(22, 240, TheBitmap);
end;
1200:
begin
Value := intVar;
TheBitmap.Canvas.Pen.Color := clBtnFace;
for j := 800 to Value do
PaintLine(TheBitmap.Canvas, XAxis[j] ,
TheBitmap.Height - MarginY -2,
YAxis[j]);
TheBitmap.Canvas.TextOut(MarginX + 400,
TheBitmap.Height - MarginY + 5, ' ');
Form1.Canvas.Draw(22, 240, TheBitmap);
end;
1600:
begin
Value := intVar;
TheBitmap.Canvas.Pen.Color := clBtnFace;
for j := 1200 to Value do
PaintLine(TheBitmap.Canvas, XAxis[j] ,
TheBitmap.Height - MarginY -2,
YAxis[j]);
TheBitmap.Canvas.TextOut(MarginX + 400,
TheBitmap.Height - MarginY + 5, ' ');
Form1.Canvas.Draw(22, 240, TheBitmap);
end;
2000:
begin
Value := intVar;
TheBitmap.Canvas.Pen.Color := clBtnFace;
for j := 1600 to Value do
PaintLine(TheBitmap.Canvas, XAxis[j] ,
TheBitmap.Height - MarginY -2,
YAxis[j]);
TheBitmap.Canvas.TextOut(MarginX+400,
TheBitmap.Height - MarginY + 5, ' ');
Form1.Canvas.Draw(22, 240, TheBitmap);
end;
end; // koniec case
TheBitmap.Canvas.Pen.Color := clRed;
XAxis[intVar] := MarginX + 1 + (intVar-Value);
PaintLine(TheBitmap.Canvas, XAxis[intVar] ,
TheBitmap.Height - MarginY -2, YAxis[intVar]);
TheBitmap.Canvas.TextOut(MarginX + 1+ (intVar-Value),
TheBitmap.Height - MarginY + 5,
IntToStr(intVar));
Form1.Canvas.Draw(22, 240, TheBitmap);
END;
end
else
begin
Form1.Memo2.Lines.Add('x0');
Beep();
Form1.Memo2.Lines.Add('');
for j := 0 to cbInQueue do
Buffer_I[j] := char(0);
end;
END; // koniec while
Result:=0;
end;
procedure TForm1.StartClick(Sender: TObject);
begin
if (hCommDev > 0) then
begin
StrCopy(Buffer_O, query_2);
hThread_SR := BeginThread (NIL, 0, @RS_Send_Receive, NIL, 0,
ThreadID_SR);
end
else
Application.MessageBox('Niewłaściwa nazwa portu lub jest on'+
' aktywny ', 'Uwaga !',MB_OK);
end;
Przed zakończeniem działania programu obszar pamięci przydzielony tak skonstruowanej mapie bitowej musi być zwolniony. Czynimy to zwykle przy usyciu metody Free, dezaktywującej obiekt mapy
TheBitmap.Free;
Kompletny projekt tej aplikacji mosna znaleźć na CD w katalogu KODYDELPHIRS_25p_RS_25.dpr. Porównując przedstawione sposoby konstruowania wykresów na mapach bitowych, na pewno zauwasymy, se drugi sposób, chocias być mose bardziej elegancki, nie jest tak naprawdę funkcjonalny. Jeseli ktoś ma do dyspozycji trochę słabszy monitor o mniejszej częstości odświesania, na pewno zauwasy charakterystyczne migotanie wykresu w trakcie odczytu danych. Związane jest to z koniecznością ciągłego wywoływania metody Draw()
W niniejszym rozdziale zostały przedstawione najczęściej wykorzystywane metody sporządzania wykresów w aplikacjach obsługujących urządzenia pomiarowe. Zapoznaliśmy się z częścią niezwykle bogatych właściwości oferowanych przez komponent TChart. Umiemy tes wykorzystywać go w naszych programach. Niestety, nie jest on dostępny we wszystkich rozpowszechnianych wersjach zarówno Delphi, jak i C++Buildera. Dlatego powinniśmy posiadać tes pewne umiejętności samodzielnego projektowania tego typu wykresów. Wiąse się to oczywiście z koniecznością smudnego skalowania osi i budowania algorytmów wyświetlających dane w odpowiednim miejscu formularza. Poznaliśmy metody konstruowania takich rysunków, zarówno bezpośrednio na płótnie formularza jak i w obszarach map bitowych.
W C++Builderze mosna skorzystać z funkcji strtol() lub strtoul(), których prototypy znajdują się w pliku stdlib.h.
Politica de confidentialitate | Termeni si conditii de utilizare |
Vizualizari: 896
Importanta:
Termeni si conditii de utilizare | Contact
© SCRIGROUP 2024 . All rights reserved