Scrigroup - Documente si articole

     

HomeDocumenteUploadResurseAlte limbi doc
BulgaraCeha slovacaCroataEnglezaEstonaFinlandezaFranceza
GermanaItalianaLetonaLituanianaMaghiaraOlandezaPoloneza
SarbaSlovenaSpaniolaSuedezaTurcaUcraineana

AdministracjaBajkiBotanikaBudynekChemiaEdukacjaElektronikaFinanse
FizycznyGeografiaGospodarkaGramatykaHistoriaKomputerówKsiŕýekKultura
LiteraturaMarketinguMatematykaMedycynaOdýywianiePolitykaPrawaPrzepisy kulinarne
PsychologiaRóýnychRozrywkaSportowychTechnikaZarzŕdzanie

Wykorzystanie niektórych narzędzi graficznych

komputerów



+ Font mai mare | - Font mai mic



DOCUMENTE SIMILARE

Wykorzystanie niektórych narzędzi graficznych

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 TChart 

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.

Komponent TPaintBox

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.

Komponent TImage

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.

Samodzielne tworzenie mapy bitowej

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()

Podsumowanie

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



DISTRIBUIE DOCUMENTUL

Comentarii


Vizualizari: 896
Importanta: rank

Comenteaza documentul:

Te rugam sa te autentifici sau sa iti faci cont pentru a putea comenta

Creaza cont nou

Termeni si conditii de utilizare | Contact
© SCRIGROUP 2024 . All rights reserved