Custom statusbar

2012.07.25. 13:10 | arabiata | Szólj hozzá!

Címkék: statusbar Qdialog QVBoxLayout

Statusbar alapban csak mainwindow-ban van, de nekünk kellene dialog ablakba is. Elméletileg egy layout-ban jelenítjük meg a statusbar-t.

Íme a nagyon bonyolult kód:

bar = new QStatusBar();
1. bar->setSizeGripEnabled(FALSE);
2. QVBoxLayout *statusbar_layout = new QVBoxLayout(this);
3. statusbar_layout->setAlignment(Qt::AlignBottom | Qt::AlignVCenter);
4. statusbar_layout->setContentsMargins(0,0,0,0);
5. statusbar_layout->addWidget(bar);
6. bar->clearMessage();
7. bar->setStyleSheet(GRAY_BORDER);

1. Ne akarja a user átméretezni a statusbar-t

2. Létrehozzuk a layout-ot, ami tartalmazni fogja a statusbar-t. Ezt designer-ben is megtehetnénk, de így csak be kell másolni a constructor-ba a fenti kódot, és nem kell illesztgetni a form-on.

3. Letesszük alulra teljes szélességben a layout-ot.

4. Gyárilag 11pixel margót hagy minden oldalon a rendszer, ez most nekünk nem kell.

5. No comment.

6. Ha valami szemét maradt a memóriában, azt kisöpörjük.

7.  GRAY_BORDER = #define GRAY_BORDER "QStatusBar {border: 1px solid gray;}" Így nem kell mindig újra definiálni a keretet. Ha módosítani kell, akkor is egyszerűbb egy helyen átírni, mint az összes fom-nál. A #define részt én egy közös header-be tettem (.h), és beforgatom minden osztályba, ahol kell

És a feszítő kérdés, hogy mi a nyákért nem egy sima QLabel-el csináltam meg az egészet? Mert a statusbar->showMessage utasítással külön timer vezérlése nélkül is tudunk meghatározott ideig üzenetet megjeleníteni.

Az eredmény:

Itt a vége, fuss el véle.

Legközelebb QTableView frissítése, úgy, hogy a header is mgmarad.

MySQL driver felélesztése win alatt

2012.07.15. 17:43 | arabiata | Szólj hozzá!

Címkék: plugin driver mysql

ODBC gáz, mert meg kell adni az adatbázis hozzáférést, így inkább szenvedtem.

Itt az eredeti link:
MySQL support
És a kiegészítés hozzá:

A másolás előtt átneveztem a cél könyvtárakat.

copy the folders lib, bin and include from F:\QtSKD\Desktop\Qt\4.7.4\mingw in my case to the folder F:\QtSKD\QtSources\4.7.4. Természetesen az "F" és a "4.7.4" értelemszerűen cserélendő

Unescaped backslashes are deprecated panaszra megdupláztam a \ karaktereket.

D:\QtSDK\QtSources\4.8.1\src\plugins\sqldrivers\mysql>qmake "INCLUDEPATH+=d:\\mysql\\include" "LIBS+=d:\\mysql\\lib\\libmysql.lib" -o Makefile mysql.pro

mingw32-make

D:\QtSDK\QtSources\4.8.1\src\plugins\sqldrivers\mysql>qmake "INCLUDEPATH+=d:\\mysql\\include" "LIBS+=d:\\mysql\\lib\\libmysql.lib" -o Makefile mysql.pro "CONFIG+=release"

mingw32-make

És az eredmény:

Apache virtual host 8080-as portra Ubuntu 10.10-en

2012.05.31. 12:17 | arabiata | Szólj hozzá!

Címkék: apache virtual host 8080 ubuntu 10.10

Sok igen okos és igen használhatatlan leírás átolvasása után a következő beállításokkal sikerült elindítanom (a virtual host neve www.tango.xxx):

cd /etc
sudo nano hosts:
127.0.0.1    www.tango.xxx tango.xxx

cd /etc/apache2/
sudo nano ports.conf:
Listen 8080
 
cd /var/www
sudo mkdir tango_cms
sudo chmod 777 tango_cms

cd /etc/apache2/sites-available
sudo nano www.tango.xxx:
<VirtualHost *:8080>
        ServerAdmin webmaster@example.org
        ServerName  www.tango.xxx
        ServerAlias tango.xxx
        # Indexes + Directory Root.
        DirectoryIndex index.php  index.html
        DocumentRoot /var/www/tango_cms/

    <Directory /var/www/tango_cms/>
    Options Indexes FollowSymLinks MultiViews
    # pcw AllowOverride None
    AllowOverride All
    Order allow,deny
    allow from all
    # This directive allows us to have apache2’s default start page
    # in /apache2-default/, but still have / go to the right place
    # Commented out for Ubuntu
    #RedirectMatch ^/$ /apache2-default/
    </Directory>

     # CGI Directory
     ScriptAlias /cgi-bin/ /var/www/tango_cms/cgi-bin/
     <Location /cgi-bin>
             Options +ExecCGI
     </Location>

     # Logfiles
     ErrorLog  /var/www/tango_cms/logs/error.log
     LogLevel warn
     CustomLog /var/www/tango_cms/logs/access.log combined
     ServerSignature On
</VirtualHost>

sudo a2ensite www.tango.xxx
sudo /etc/init.d/apache2 reload

Ha változtatni kell a virtual host file-ban, akkor
sudo a2dissite www.tango.xxx
sudo /etc/init.d/apache2 reload
sudo a2ensite www.tango.xxx
sudo /etc/init.d/apache2 reload

És hogyan futtassuk?

2012.05.09. 13:26 | arabiata | Szólj hozzá!

Címkék: mysql dll futtatás diver

Ha fejlesztőkörnyezet alatt már fut a progi, beköszönt az igény egy másik gépen történő futtatásra, tesztelésre is.

Röviden:

1. az exe könyvtárába kell másolni

  • libgcc_s_dw2-1.dll
  • mingwm10.dll
  • QtCore4.dll
  • QtGui4.dll
  • QtSql4.dll

2. Mysql ODBC drivert (qsqlodbc4.dll) pedig az exe alá kell tenni egy sqldrivers nevű könyvtárba.

 

QtCreator+ODBC

2011.12.25. 22:39 | arabiata | Szólj hozzá!

Címkék: driver odbc qtcreator qtsdk

A QtSDK 4.7.4 csak a Sqlite drivert tartalmazza, ezen változtatni kell. Az új világba vezető lépések:

1. A "D" meghajtóra letöltött QtSDK 4.7.4-et custom beállítással futtattam, hogy a 4.7.4 forráskód is hozzáférhető legyen.

2. Windows path elejére tettem: D:\QtSDK\Desktop\Qt\4.7.4\mingw\bin;D:\QtSDK\mingw\bin;

3.D:\QtSDK\QtSources\4.7.4\src\plugins\sqldrivers\odbc könyvtár meglátogatása

4. qmake.exe, majd mingw32-make.exe

5. Az eredmény bíztató, létrejött két könyvtár, csak a release nevű üres. Emiatt majd késöbb fogunk szívni

6. Átmásoljuk a debug könyvtárból a két értékes (.a,.dll) file-t a D:\QtSDK\Desktop\Qt\4.7.4\mingw\plugins könyvtárba

7. Egy korábbi bejegyzésben írtam, hogy milyen bonyolult program kell a létező driver-ek kiiratásához. Ezt most lefuttatjuk debug módban. WoW, látszik a két ODBC driver. Új világok nyíltak meg előttünk, tudunk adatbázishoz kapcsolódni. Amíg... debug módban futtatjuk a progit. Release módban futtatva viszont lelkileg összeomlunk. Eltüntek a szívünknek oly kedves driverek, csak az Sqlite vigyorog csúfondárosan.

8. Guglizás, anyázás után vissza a 3. pontra

9. mingw32-make.exe -f Makefile.Release

10. 6.pont debug = release. Progi újra futtatása release módban. Wow,stb.

11. Ha nagyon szofisztikáltak akarunk lenni, akkor a main.cpp meglátogatása során megfigyeljük, hogy a driver neve, amit a progi kiír, nem isteni adomány, hanem emberkéz műve, így módosítható.

Sql tárolt eljárás, handler, cursor

2011.08.16. 18:00 | arabiata | Szólj hozzá!

Címkék: select handler stored proc cursor if exists

 Vannak dolgok, amiket nem érdemes kliens oldalon erőltetni az adatmozgatás és sebesség miatt, hanem tárolt eljárásban megoldani. Előfordulhat olyan eset, amikor nem tudunk egy sima select vagy insert into ... (select...) utasítással feltölteni egy táblát, hanem végig kell a forrás táblát olvasni soronként. Erre való a cursor. Mutatok egy rövid példát és a benne rejlő buktatót is. 

Van két táblánk, amiket az "azon" mezők értéke kapcsol össze. A Tabla1_azon értéke alapján kell a Tabla2_mezo értékét kivenni, feldolgozni. Figyeljük meg, hogy egy sorban nem egyeznek meg az azonosító mezők értékei 

Tabla2

Tabla1

Tabla2_azon

Tabla2_mezo

Tabla1_azon

Ertek1

Mezo1

Ertek1

Ertek2

Mezo2

Ertek3

Ertek4

Mezo4

Ertek4

 

declare done default 0;

declare v_azon integer;

declare valtozo integer;

declare cursor1 for select tabla1_azon from tabla1;

declare continue handler for not found set done = 1;

 

open cur1;

read_loop: loop

fetch cursor1 into v_azon;

if done then

      leave read_loop;

end if;

 

if exists (select '' from tabla2 where tabla2_azon = v_azon) then

select tabla2_mezo into valtozo from tabla2 where tabla2_azon = v_azon;

end if; 

-- valtozo tovabbi feldolgozasa

end loop;

close cur1;

 A done változót a handler állítja be, ha nincs több olvasandó adat. Deklaráljuk a cursor-t, a handlert. Úgy hisszük, hogy ez akkor áll be, ha a cursor-nál elfogytak a sorok, vagyis végigolvastuk a Tabla1-et. Cursor nyitás, ciklus elejének beállítása, egyéb cirádák. A fetch beolvassa az into utáni változókba a következő sor mezőinek tartalmát. A mezők és változók számának, tipusának meg kell egyeznie. A ciklusból a fetch utáni if léptet ki.

Teoretikusan az "if exists" és az azt lezáró "end if" részt kommentezzük ki. Hivatalosan a Tabla2 megfelelő mező értékét visszakapjuk a valtozoban az azonosító mezők párosítása alapján.

És elkezdünk csalódni. Mert a mezo4 soha nem kerül be a valtozoba, pedig guvadt szemmel nézzük az azonosítók egyezését. Megvan a megoldás: bugos a mysql. A google is csak vonogatja a vállát, mindent a megadott példák szerint csináltunk.

Az igazi megoldás a következő: bár a handler-t rögtön a cursor után deklaráltuk, az nem akkor áll be, ha a cursor-nál elfogytak a beolvasandó sorok. Hanem ha valamit nem talál meg. Mint az állapota is mutassa - not found -. És mivel a handler hatóköre az egész tároltra kiterjed, nem csak a cursor-ra így a "select tabla2..." sornál billen be, mert a két azonosító egy helyen nem egyezik meg, vagyis not found.

Teoretikusan vegyük ki a kommenteket, és lőn. Csak azt a sort nem dolgozza fel a tárolt, ahol nem egyeznek az azonosítók.

Tehát egy tárolt eljárásban, ha cursor-t használunk, le kell kezelni minden hasonló ágat.

Mail küldés

2011.07.29. 10:00 | arabiata | 1 komment

Címkék: gmail hotmail smtp mail küldés

 Ez ott van a soros port kezelés mellett:), vagyis sehol. De van megoldás. A megoldás forráskódja itt található. Mivel a blog.hu nem engedi zip file feltöltését, így átneveztem txt-re. Letöltés után kéretik zip-re visszacsinálni a kiterjesztést. A programmal csatolni nem lehet (nem is erre a célra készült), viszont helyesen konvertálja az ékezetes karaktereket. Gmail és hotmail alatt kipróbáltam.

Egy nagyon jó összefoglalás a témában itt. A kész programot valahol ki is kell próbálni, lehetőleg könnyen elérhető mail serverről. Innen letölthető, windows alatt fut.

A mail részei (forrás):

Subject: When can we meet?
Date: Mon, 6 Aug 2001 10:04:11 –0500
From: alvernja@alverno.edu
To: anthonsb@alverno.edu

 

Indicates the header
When can we get together to work on our project? I am available any time this week after 5:00 PM. But I do have some other appointments next week. I would like to meet before we have our next class so email me and let me know what would work for you.
Thanks!
Indicates the body
Jane A. Alverno 
Student, Alverno College
Indicates the Signature

 Nézzük a megvalósítást:

- nyitunk egy client = new QTcpSocket;-et

- kapcsolódunk a mail serverhez 

client->connectToHost("smtp.live.com", 25);

if (client->waitForConnected(3000) == false) {

    qDebug() << "Error: " << client->errorString();

    Cleaner();

  }

 - megvárjuk a választ:

  connect(client, SIGNAL(readyRead()), this, SLOT(readyRead()));

  waitTimeout = client->waitForReadyRead(3000);

  if (responseFromServer != 220 || waitTimeout == false) {

    qDebug() << "CONN" << messageFromServer;

    Cleaner();

  }

 A connect-es sorban beállított slot akkor áll be, ha jön valami a serverről. A waitForReadyRead-el várunk a válaszra, vagy timeout.

 - minden adatforgalmazásnál ezt a sémát követjük

  *ts << "EHLO mail.sturm.xxx" << "\r\n";

  ts->flush();

  waitTimeout = client->waitForReadyRead(3000);

  if (responseFromServer != 250 || waitTimeout == false) {

    qDebug() << "EHLO" << messageFromServer;

    Cleaner();

  }

 

  A letöltött main.cpp fileban látható a küldő azonosításának módja is. Ez benne maradhat a programban, nem kell akkor sem kivenni, ha nincs rá szükség. Másik dolog, hogy érdemes html-ben hagyni a levél törzsét, fölösleges több részre bontani Példa itt.

Mire van ez az egész, ha nem lehet csatolni file-t? Mondjuk arra, ha egy program részeként kell hírleveleket, értesítéseket kiküldeni.

 

Soros port kezelés

2011.07.18. 10:16 | arabiata | Szólj hozzá!

Címkék: kommunikáció serial rs232 soros port

 Na, ez nincs a Qt-ban. Van egy qextserialport nevű utólag elkészített osztály, ami elvileg multiplatformos megoldást kínál. Belenéztem a forráskódjába, találtam benne néhány to-do és to be implemented megjegyzés. Ettől függetlenül lehet, hogy működik a dolog, de nem próbáltam ki. Hacsak nem akarjuk két gépet összekötve tesztelni a soros port kezelőt, van itt egy megoldás:

- Virtual serial port emulator (VSPE)

- Docklight scripting

- Putty

 A VSPE létrehoz két virtuális soros portot, amikre putty-al rákapcsolódva mehet a kommunikáció. A Docklight szerepe a putty-hoz hasonlít, csak sokkal többet tud. Ajánlok még két leírást http://www.robbayer.com/files/serial-win.pdf, és http://www.codeguru.com/cpp/i-n/network/serialcommunications/article.php/c5425.

Ezenkívül az http://msdn.microsoft.com/en-us/ is sok segítséget ad.

1. Port megnyitása:

/*

          adatok.at(3).toLocal8Bit().constData()

          adatok.at(3) -> QString

          adatok.at(3).toLocal8Bit() -> QByteArray

          adatok.at(3).toLocal8Bit().constData() -> const char *

          CreateFile -> WCHAR az első paraméter tipusa

          CreateFileA -> char az első paraméter tipusa

   */

 

  win_soros_leiro = CreateFileA(adatok.at(3).toAscii().constData(), GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0);

  if (win_soros_leiro == INVALID_HANDLE_VALUE) {

 

    log_iras(trUtf8("Soros port hiba kód: ") + QString::number(GetLastError()));

    return false;

 

  }

 Az adatok.at(3) tartalmazza a port nevét (COM1: pl.). Az overlapped szerepe, hogy ne álljon meg a program futása a soros portról várt adatok bejöveteléig.

 2. Paraméterek beállítása:

  DCB dcb;

  if (GetCommState(win_soros_leiro, &dcb) == false) {

    log_iras(trUtf8("GetCommState: ") + QString::number(GetLastError()));

    CloseHandle(win_soros_leiro);

    return false;

  }

  strcpy(munka, adatok.at(4).toAscii().constData());

  dcb.BaudRate = atoi(munka);

 

  strcpy(munka, adatok.at(5).toAscii().constData());

  dcb.ByteSize = atoi(&munka[0]);

 

  strcpy(munka, adatok.at(6).toAscii().constData());

  int_munka = atoi(&munka[0]);

  int_munka--;

  dcb.Parity = int_munka;

 

  strcpy(munka, adatok.at(7).toAscii().constData());

  int_munka = atoi(&munka[0]);

  int_munka--;

  dcb.StopBits = int_munka;

  if (SetCommState(win_soros_leiro, &dcb) == false) {

    log_iras(trUtf8("SetCommState: ") + QString::number(GetLastError()));

    CloseHandle(win_soros_leiro);

    return false;

  }

 A DCB struktúra tartalmazza a kommunikációs paramétereket: baud rate, paritás bit, stb. A CreateFile által visszaadott handler a win_soros_leiro. Kiolvassuk a DCB tartalmát, a szükséges elemeket módosítjuk, majd visszaírjuk az új tartalmat. Az adatok.at(6,7)-ből konvertálás után azért kell levonni 1-et, mert az adatbázisból az enum miatt 1-el nagyobb érték érkezik.

3. Időzítés beállítása:

  COMMTIMEOUTS timeouts = {0};

  timeouts.ReadIntervalTimeout = 0;

  timeouts.ReadTotalTimeoutMultiplier = 0;

  timeouts.ReadTotalTimeoutConstant = 2000;

  timeouts.WriteTotalTimeoutConstant = 0;

  timeouts.WriteTotalTimeoutMultiplier = 0;

  if (SetCommTimeouts(win_soros_leiro, &timeouts) == false) {

    log_iras(trUtf8("SetCommTimeouts: ") + QString::number(GetLastError()));

    CloseHandle(win_soros_leiro);

    return false;

  }

 

 Tekintve, hogy a soros port olvasását végtelen ciklusban végezzük, nem ildomos azt állandóan pörgetni. Így beállítottam egy 2 másodperces timeout értéket, hogy ennyit várjon a következő karakter érkezésére. Ha jön adat megszakad a timeout, és beolvassa a portról a ReadFile, ha nem jön semmi, a gép végzi a saját dolgát.

4. Overlapped struct beállítása:

  ov.Offset = 0;

  ov.OffsetHigh = 0;

  ov.Internal = 0;

  ov.InternalHigh = 0;

  ov.hEvent = NULL;

 5. Olvasunk:

int osztaly::olvasas() {

  bool retVal;

  DWORD atvitt_byte;

  olv[0] = 0;

  retVal = ReadFile(win_soros_leiro, &olv, sizeof (olv), NULL, &ov);

  if ((retVal == false) && (GetLastError() != ERROR_IO_PENDING)) {

    log_iras("ReadFile hiba: " + QString::number(GetLastError()));

    return -1;

  }

  retVal = GetOverlappedResult(win_soros_leiro, &ov, &atvitt_byte, TRUE);

  if (retVal == false) {

    if (GetLastError() != ERROR_IO_INCOMPLETE) {

      log_iras(trUtf8("GetOverlappedResult hiba: ") + QString::number(GetLastError()));

      return -1;

    }

  } else {

    feldolgozas(atvitt_byte);

  }

  return atvitt_byte;

}

 Ezt a függvényt futtatjuk végtelen ciklusban. A következő pontban megmutatom, hogy lehet kilépni a ciklusból. Itt egyedül a GetOverlappedResult érdekes, ez mondja meg, hogy befejeződött-e az olvasás a portról. Ha az utolsó paraméter FALSE, akkor nem várja meg az eredményközléssel az olvasás végét, hanem örömében TRUE-t ad vissza, ha valami adat érkezett. A feldolgozásnak elég gyorsnak kell lennie, hogy ne fogja meg az egész ciklus futását. Talán itt érdemes egy másik szálat futtani. ReadFile-nál ERROR_IO_PENDING akkor áll be, ha még folyik az olvasás a portról, és így a GetOverlappedResult is vár.

6. Kilépés a ciklusból:

void osztaly::futtato() {

  int retVal = -1;

  leallito = false;

  while (leallito == false) {

    retVal = olvasas();

    switch (retVal) {

    case -1:

      leallito = true;

      break;

    case 0:

      server_komm();

      break;

    }

  } // while

  takarito();

  return;

}

 Ha az olvasas -1-et ad vissza, gáz van. Ha 0-t, a beállított timeout járt le, semmi adat nem érkezett a portról.

7. Programozott leállítás:

void osztaly::server_komm() {

  int olv_bytes = 0;

  char olv[5];

  leallito = false;

  kliens->disconnectFromServer();

  kliens->connectToServer("qt_labor");

  if (kliens->state() == QLocalSocket::ConnectedState) {

    kliens->waitForReadyRead(1000);

    olv_bytes = kliens->readLine(olv, 5);

  }

  qDebug() << olv << olv_bytes;

  if (strcmp(olv, "STOP") == 0) {

    leallito = true;

  }

}

 

 

IPC Thread

2011.06.15. 12:31 | arabiata | Szólj hozzá!

Címkék: start run exec szál qthread currentthread movetothread

 Ott hagytuk abba, hogy egy bizonytalan ideig futó ciklust nem illik betenni egy modal ablakba. Akkor válasszuk le a ciklust a modal ablakról. Itt szintén több megoldás van:

- elindítunk egy külön programot (processzt) ami elvégzi a dolgát, esetünkben korábban elindított programok leállítását. Ennek hátulütője, hogy ezt a programot is le kell valahogy állítani.

- elindítunk egy programot a programon belül, vagyis egy thread-et. Ez is önálló életre kel, de könnyebb kezelni és különben is, ilyet még nem írtunk.

A Thread kezelés alapjait fogjuk most áttekinteni. Legyen a szál a gyerek, a hívó osztály a szülő. A thread (szál) :

  • prioritása állítható
  • önálló eseménykezelője (eventloop) van
  • kommunikálhat más szálakkal
  • szinkronizálható a szálak működése
  • futása felfüggeszthető
  • op. rendszer független

A szál egy sima osztály némi megkötéssel:

  • a QThread osztályból ered (class myThread : public QThread)
  • ha connect utasítást használunk Q_OBJECT kell
  • futását a "run" utasítás vezérli
  • a deklarált változók tulajdonosa változik

A szál alap felépítése:

myThread.h

 

class myThread : public QThread {

  Q_OBJECT

 public:

  myThread();

  ~myThread();

  void run();

 private:

  QObject *v_1,*v_2,*v_3,*v_4;

  void futtato();

private slots:

  void mySlot();

}

 

myThread.cpp

 

myThread::myThread{

v_3 = new QObject();

}

 

~myThread::myThread{

  delete v_1;

delete v_2;

delete v_3;

delete v_4;

}

 

void myThread::futtato(){

v_4 = new QObject();

}

 

void myThread::run(){

v_1 = new QObject();

        exec();

        return;

}

 

void myThread::mySlot(){

v_2 = new QObject();

}

 

 

Indítása a szülőből történik.

myThread = new myThread();

myThread->start(); 

A start() hívja meg a szál run() függvényét. Mire való a run() függvény? Itt lehet előkészítő munkákat végezni, és ez kezeli a szál eseménykezelőjét. A szálból kilépni return-el lehet, az eseménykezelőből az exit(int érték) és a quit() utasításokkal tudunk. A quit() mindig nullát ad vissza az exec() visszatérő értékének, az exit() az általunk megadottat. Az exec() tuképp egy végtelen ciklus. Természetesen illik a szál befejezésekor a lefoglalt memóriát (malloc, new) felszabadítani.

A deklarált változók tulajdonos változására a példa:

A v_1 a szülő osztályhoz fog tartozni, mert a run() függvényben kapott értéket, a többi változó a szálhoz (myThread) kötődik. Ezért a destruktorban hibaüzenetet kapunk, miszerint a v_1 változót nem tudjuk törölni, tekintve, hogy másik osztály a szülője. Ugyanez igaz akkor is, ha a futtató() függvényt a run()-on belül indítjuk. Ekkor minden, a futtatóban értéket kapó változó a szülő tulajdonába kerül. De ha a futtato()-t a konstruktorból indítjuk, akkor a gyerek (szál) lesz a változók tulajdonosa, és így a myThread osztály többi függvényében is tudjuk kezelni őket. Ezt a csomót empírikus úton feloldva az "értékes" függvényeket a konstruktoron keresztül indítom, a run()-ban csak az eseménykezelőt hagyom.

Egy változóhoz tartozó szál nevét a currentThread()

(qDebug() << v_2->currentThread()) függvénnyel tudom kiiratni. Változók tulajdonosát a moveToThread(QThread * targetThread)-el változtathatom, de csak egy irányban.

This function can only "push" an object from the current thread to another thread, it cannot "pull" an object from any arbitrary thread to the current thread

 Jó szórakozást:)

IPC programok közti kommunikáció

2011.05.26. 14:56 | arabiata | Szólj hozzá!

Címkék: read tcp ipc socket write listen qlocalserver qlocalsocket pendingconnection

 Az előző bejegyzésben elindítottuk a kliens programokat ugyanazon a gépen ahol a server fut. De le is kellene állítani őket a gép újraindítása nélkül. Erre való az IPC. Rövid összefoglaló itt található. Multi platformos megoldásként a sharedmemory és a tcp jöhet számításba. A sharedmemory linux alatt "beragad", ha lefagy a program, míg a windows elengedi. Marad a tcp, de ez is trükkös. Felépíthetünk egy klasszikus kliens - szerver kapcsolatot ip címmel, porttal, meg az ehhez szükséges init dolgokkal. Vagy használjuk az erre a célra tökéletesen megfelelő QLocalServer - QLocalSOcket kapcsolatot. Itt nem kell ip cím,port, hanem csakegy név amire a kliens hivatkozni tud. Ez végülis a kapcsolat neve. 

Server oldal:

  1. QLocalServer init (server = new QLocalServer();)
  2. várható kliensek számának beállítása (server->setMaxPendingConnections()), default 30
  3. új kliens kapcsolódásnál értesítés fogadása (connect(server, SIGNAL(newConnection()), this, SLOT(ertesito()));). Az ertesito függvény hívódik meg.
  4. fülelés indítása (server->listen("qt_labor");) Erre a névre fognak a kliensek kapcsolódni.
  5. newConnection beáll, levesszük a kliens adatait (QLocalSocket *kliens = server->nextPendingConnection();). Ez visszaad egy, a kliensre mutató pointert.
  6. írunk, olvasunk
    •   kliens->write("STOP");
    •   kliens->flush(); <- pufferezés nélkül rögtön kitolja az anyagot
    •   kliens->waitForReadyRead(2000); <- ennyi ms-t vár a válaszra
    •   kliens->read(olv, 3);

Kliens oldal:

  • QLocalSocket init (kliens = new QLocalSocket();)
  • ha jön olvasnivaló, értesítsen (connect(kliens, SIGNAL(readyRead()), this, SLOT(olvaso()));) <- ezt a connectToServer élesíti
  • amikor kell, felcsatlakozunk a szerverre (kliens->connectToServer("qt_labor");)
  • olvasunk, írunk 
    •   char olv[10];
    •   kliens->read(olv,10);
    •   kliens->write("OK");
    •   kliens->flush();
    •   kliens->disconnectFromServer();

Hibakezelés:

A socket aktuális állapotát a int : state() függvénnyel tudjuk lekérdezni. Az esetleges hibáról olvasható formában a QString : errorString() ad infót.

Ez eddig nagyon szép, egy kis hibával. Szerver oldalon nem automatikusan fut a kapcsolat kezelése, kliensekre várakozás, hanem egy esemény váltja ki. Pl. egy gombnyomás. És itt jön a nagy kérdés: hogyan lehet megoldani, hogy a szerver oldal addig várakozzon amíg az összes kliens be nem jelentkezik? Mondjuk ciklusban futtatjuk a szerver oldal 5,6 pontját, amíg be nem jelentkezett minden kliens. Ezzel dinamikusan megakasztjuk az egész program futását, hiszen a ciklus ki tudja meddig pörög. Jobb megoldás kell keresnünk. Legközelebb...

QProcess program indítása programból

2011.05.23. 13:43 | arabiata | Szólj hozzá!

Címkék: ipc qprocess program indítás startdetached

Jelen feladatban több automata adatait kell összegyűjteni egy adatbázisba. Ehhez vagy külön - külön megírjuk minden automata vezérlőprogramját vagy többszálú (multithread) programozással futtatjuk a lekérdező alkalmazásokat. Multithread programozásnál figyelni kell a futó szálak prioritására. Mire gondolok? Ha vannak mondjuk soros és tcp kapcsolatot használó automaták, akkor a sorosnak kell nagyobb prioritást adni az érzékenyebb adatátvitel miatt. Ezzel lelassítjuk a többi szál futását. Elméletben igen sok szálat indíthatunk, gyakorlatilag a szinkronizálási és üzenetváltási gondok miatt nem érdemes ezt a megoldást erőltetni. Tapasztalatom szerint többszálú megoldással négy modemet tud egy program kiszolgálni.  Ráadásul, ha leáll vagy befagy a szálakat kezelő program, akkor a többinek is kakukk. Tehát jobb megírni a külön futó vezérlő programokat. Technikailag nem kell többet programozni, mindkét megoldás  - az adatfeldolgozás szempontjából - ugyanazokat az elemeket tartalmazza.

Egy programnak másikból történő indítására a QProcess osztály szolgál. Az indító utasítások:

  • start -> elindítja a programot új processként, ha még nem fut
  • execute -> elindítja a programot új processként és vár a befejezésre
  • startDetached -> elindítja a programot új processként és leválik róla (elfelejti)

 Mindhárom megoldásnál QStringList-ben tudjuk átadni az indító paramétereket 

const QString & program, const QStringList & arguments

 

formában. Nekünk a startDetached kell. A vonatkozó programrészlet:

void StartCommForm::inditas_leallitas(QTableView *nezet, QString to_do) {

 

QSqlQuery query(db_leiro), allapot(db_leiro);

query.prepare("select * from automatak where automata_id = :0");

QString automata_id;

QStringList params;

QItemSelectionModel *munka = nezet->selectionModel();

QList<QModelIndex> kivalasztottak(munka->selectedRows(3));


for (int i = 0; i < kivalasztottak.count(); i++) {

    automata_id = kivalasztottak.at(i).data().toString();

   if (to_do == "I"){

      query.bindValue(0,automata_id);

      query.exec();

      query.next();

      for (int j = 4; j < 13; j++){

        params << query.value(j).toString();

       QProcess progi;

     if (progi.startDetached(query.value(2).toString(), params) == false){ // nem indult el

     allapot.prepare("update automatak set hiba = :0, fut = 'Nem' where  automata_id = :1");

     allapot.bindValue(0, hibakodok.at(progi.error()));

     allapot.bindValue(1, automata_id);

     allapot.exec();

    }else{ // elindult

    allapot.prepare("update automatak set hiba = :0, fut = 'Igen' where  automata_id = :1");

    allapot.bindValue(0, hibakodok.at(6));

    allapot.bindValue(1, automata_id);

    allapot.exec();

  } // elindult vagy nem if vege

}

 

}else{

 allapot.prepare("update automatak set hiba = :0, fut = 'Nem' where automata_id = :1");

 allapot.bindValue(0, hibakodok.at(6));

 allapot.bindValue(1, automata_id);

 allapot.exec();

 } // inditas vagy leallitas if vege

 

} // for

 Nézzük részleteiben:

inditas_leallitas(widget.indito_tableView, "I");

Átadom az automatákat tartalmazó tableView-t, és "I"ndítás vagy "L"eállítás kell-e.

QItemSelectionModel *munka = nezet->selectionModel();

QList<QModelIndex> kivalasztottak(munka->selectedRows(3));


for (int i = 0; i < kivalasztottak.count(); i++) {

    automata_id = kivalasztottak.at(i).data().toString();

 

 A tableView-t multiselectre állítottam, így nem kell ctrl-t nyomkodni ha több elemet akarok kiválasztani. A kiválasztott sorok harmadik oszlopának tartalmát betöltöm egy QList-be, és ezt pörgetem végig. Így tudni fogom az indítandó automaták egyedi azonosítóját. Ennek alapján megtudom az összes adatot az automatákról:

select * from automatak where automata_id = :0

 A params QStringList-ben a kommunikációra vonatkozó összes paramétert betöltöm, majd az automatát vezérlő program kiválogatja a neki kellőket.

A startDetach vissztérő állapotától függően írom vissza az adatbázisba az automata futási állapotát.

QStringList hibakodok << "FailedToStart" << "Crashed" << "Timedout" << "ReadError" << "WriteError" << "UnknownError" << "Running";

Az utolsó else ágba kerül majd a leállítás, amit egy local kliens - szerver tcp kapcsolaton keresztül oldunk meg. Konkrétan az indító progi futtat majd egy szerver ágat, amire a kliensek minden adatátviteli ciklus után felkapcsolódnak. Ha megkapják a leállító üzenetet, befejezik a futást. Felmerülhet a kérdés, hogy miért ezt a megoldást választom. Mert csak ez multiplatformos, a D-Bus (ha jól tudom) linux specifikus. A Sharedmemory pedig a leírás szerint is beragad linux alatt és csak kézzel (ipcs, ipcrm) lehet kipiszkálni.

De hogy veszem át a paramétereket az indított programban? Így:

 osztaly::osztaly(QStringList params){


  QStringList adatok(params);

  qDebug() << adatok.count();


}


osztaly::~osztaly(){

}

//---------------------------------------------------------------------

int main(int argc, char *argv[]) {


QCoreApplication app(argc, argv);

osztaly x(app.arguments());


return app.exec();

 Az app.arguments() kimenete egy stringlist. Ezt átadom a futtató osztály konstruktorának, és innen már egyenes az út.

Console application

2011.05.17. 13:59 | arabiata | Szólj hozzá!

Címkék: osztály class console application fifo qfile qtextstream

 Eljött az idő, midőn olyan programot kell írni, amihez nem kell grafikus felület. Ehhez a következő lépések kellenek:

  • sima C++ Qt application az indulás
  • Create main file maradjon bejelölve
  • létrejött egy db. main.cpp file
  • Source files -> new -> c++ header file
  • Project -> properties -> Qt 
  • QtGui nem kell, viszont QtSql igen (már, ha akarunk adatbázis kapcsolatot)
  • a Profile résznél kivehetjük a Show profiling indicators kapcsolót
  • a main.cpp elejéről kivesszük a #include <QtGui/QApplication> részt és a helyére tesszük ezt: #include <QtCore/QCoreApplication>
  • a QApplication app(argc, argv);-t lecseréljük QCoreApplication a(argc, argv);-re

Most van egy üres header file, amit be kell forgatni a cpp-be. Viszont nincs semmi class, stb. deklaráció. A code template-eknél látható, hogy cls+tab egy új osztályt hoz létre.

#ifndef NEWFILE_H

#define NEWFILE_H

class Class {

public:

    Class();

    Class(const Class& orig);

    virtual ~Class();

private:

};

#endif /* NEWFILE_H */

A "Class" helyére beírjuk az osztály nevét, amit a netbeans szépen behelyettesít. Ha bármilyen signal - slot kapcsolatot használni fogunk a programban, akkor a class osztálynév után írjuk be, hogy " : public QObject", majd új sorba: Q_OBJECT. Természetesen nem felejtjük el az #include <QObject>-et sem. Ha a majdani programnak csak egyszer kell lefutnia, a cpp végén az a.exec() helyére írjunk egy nullát.

Néhány finomság:

  • timer, ütemezés

QBasicTimer itt nem használható, helyette QTimer kell. A beállított idő leteltét (timeout) egy connect utasítással tudjuk figyelni:

  connect(hl7_timer, SIGNAL(timeout()), this, SLOT(hl7_timer_slot()));

 A hl7_timer_slot egy mezei függvény. Ezzel az ütemezett futtatást le is tudtuk.

  • file kezelés

Egy probléma jelentkezhet, ha a sor vég jel 0x0D = \r. Ezt nem hajlandó lekezelni a rendszer, így nem soronként olvassa be egy file tartalmát, hanem benyalja az egészet. Rémísztőnek hangzik, pedig nem az. Nekem egy 650 kb-os linux alatt megírt txt file-t csont nélkül betett egy QString változóba. A file deklarálásánál nem szabad megadni a QIODevice::Text-et, meg kell hagyni az alapértelmezett értéket. Csak így biztosítható, hogy a \r jelet ne gyomlálja ki a progi.

Megnyitjuk az olvasandó file-t:

QFile hl7_inp;

hl7_inp.setFileName("valami.txt");

hl7_inp.open(QIODevice::ReadOnly); <- itt nem kell megadni a QIODevice::Text-et

 Beolvassuk a tartalmát:

QStringList sorok;

  sorok.clear();

  olv_sor.clear();

  hl7_queue.clear();

 

  QTextStream in(&hl7_inp);

  olv_sor = in.readAll();

  sorok = olv_sor.split("\r");

 Az "olv_sor" változóba bekerül az egész tartalom, és innen átpakoljuk egy StringList-be, úgy, hogy "\r" karakter az elválasztó. Pl. az eredeti tartalom: elsosor\rmasodiksor, akkor a split után a sorok-ban már két bejegyzés lesz: elsosor masodiksor. sorok.count() = 2. Így már a sorok stringlist-ben bent van a file tartalma soronként. Egy for ciklussal végig is tudjuk pörgetni.

  • string kezelés

Nézzük egy hl7 üzenet fejrészét, amibő ki kell vennünk a 6. és 9. rész tartalmát:

MSH|^~\&|SOBO||kklab2||201101050125||ORM^O01|2718043|P|2.3|||NE|AL|

Elkezdhetem ciklusban számolni a "|" karaktereket, substring-gel eldobni a nem kellő darabokat vagy 

  sor.enqueue(olv_sor.section("|", 9, 9)); // uzenet azonosito

  sor.enqueue(olv_sor.section("|", 6, 6)); // uzenet kuldes ideje

 Kicsit egyszerűbb:)

De mi a sor.enqueue? A kivágott részeket adatbázisba kell írni. Ehhez a szükséges adatokkal feltöltök egy FIFO tömböt, és ezt olvasom végig soronként ciklusban.

  QSqlQuery query;

  query.prepare("insert into rendelesek values(:a,:b,:c,:d,:e,:f,0)");

  while (sor.isEmpty() == false) {

    query.bindValue(i, sor.dequeue());

    i++;

  }

  query.exec();

 Legközelebb programok közti kommunikáció, IPC

TreeWidget setItemWidget

2011.04.29. 13:26 | arabiata | Szólj hozzá!

Címkék: segmentation fault treewidget setitemwidge

 Az előző két postban arra mutattam példákat, hogy miként lehet a TreeWidget (TW) valamelyik eleméhez widget-et rendelni setItemWidget utasítással. Itt is igaz, hogy ha egy pointer egyszer már bármilyen formában kapcsolatba került TW-vel, azt mégegyszer nem tudjuk felhasználni. Pl. ha egy Qt Designer-ben létrehozott lineEdit-et rendelünk a TW egy eleméhez, azt nem tudjuk újra ugyanerre a célra használni, mert a TW lesz a widget tulajdonosa. A program segmentation fault üzenettel elszáll a setItemWidget második hívásakor. A probléma megkerülésére találtam a következő módszert:

 

void RequestsForm::automata_kivalasztasa_slot(QString automata_neve) {

 

kod_be_lineEdit = NULL;

delete (kod_be_lineEdit);

kod_be_lineEdit = new QLineEdit("?");

 

connect(kod_be_lineEdit, SIGNAL(returnPressed()), this, SLOT(kod_be_returnPressed_slot()));

widget.treeWidget->setItemWidget(kivalasztott_automata, 2, kod_be_lineEdit); 

kod_be_lineEdit->setFocus();

}

 

 Mint látható, nem ugyanazt a beillesztendő pointert használom újra meg újra, hanem dinamikusan létrehozom őket minden setItemWidget hívás előtt. Ez lesz a setWidgetItem 3. paramétere. Miután kiolvastam az adatokat a pointer által hivatkozott widget-ből, letörölhetem a pointert.

 

TreeWidget példa

2011.04.28. 10:06 | arabiata | Szólj hozzá!

Címkék: treewidget példa insertchild setcurrentitem toplevelitem

 Az előző postot kiegészítendő:

 

 

 

 

 

 

 

 

 

 

 

 

 

És a program hozzá:

 

newForm::newForm() {

  widget.setupUi(this);

  QStringList fejlec;

  QTreeWidgetItem *szulo, *gyerek;

 

  fejlec << trUtf8("első") << trUtf8("második") << trUtf8("harmadik");

   widget.tW->setHeaderLabels(fejlec);

 

  for (int i = 0; i < 5; i++) {

    szulo = new QTreeWidgetItem();

    szulo->setText(0, QString("%1").arg(i) + trUtf8(". szülő"));

    szulo->setToolTip(0, QString("%1").arg(i) + trUtf8(". szülő"));

    widget.tW->addTopLevelItem(szulo);

  }

 

  szulo = widget.tW->topLevelItem(2);

 

  for (int i = 0; i < 5; i++) {

    gyerek = new QTreeWidgetItem(szulo);

    gyerek->setText(0, QString("%1").arg(i) + trUtf8(". gyerek"));

  }

 

  gyerek = new QTreeWidgetItem();

  gyerek->setText(0, "zabigyerek");

  szulo->insertChild(3, gyerek);

 

  widget.tW->setCurrentItem(szulo);

   szulo = widget.tW->topLevelItem(2)->child(2);

 

  for (int i = 0; i < 5; i++) {

    gyerek = new QTreeWidgetItem(szulo);

    gyerek->setText(0, QString("%1").arg(i) + trUtf8(". unoka"));

  }

 

  gyerek = new QTreeWidgetItem();

  gyerek->setText(0, "zabigyerek");

  szulo->insertChild(3, gyerek);

 

  connect(widget.tW, SIGNAL(itemClicked(QTreeWidgetItem *, int)), this, SLOT(eger(QTreeWidgetItem *, int)));

 }

 

void newForm::eger(QTreeWidgetItem* selItem, int selCol) {

   qDebug() << selItem->parent() << selCol;

   if (selCol == 1) {

    widget.tW->setItemWidget(selItem, 1, widget.comboBox);

  }

}

 

newForm::~newForm() {

}

 

 

 

 

 

 

 

 

 

 

 

 

 

TreeWidget

2011.04.27. 14:05 | arabiata | Szólj hozzá!

Címkék: signal slot treewidget setitemwidget treeview

Adatok hierachikus ábrázolására használjuk a TreeWidget és TreeView osztályokat. Mi a különbség köztük?

TreeView

  • model vezérelt - setModel utasítással hozzárendelhetjük pl. egy sql query eredményét
  • elemeit a model módosításával tudjuk manipulálni
  • megjelenítésre szolgál
  • szerkesztési lehetőségei szűkek

TreeWidget

  • elem vezérelt - tőlünk függ a felépítése
  • elemeit közvetlenül (model nélkül) manipulálhatjuk
  • szerkeszteni tudjuk az elemeket

Ha mondjuk egy meghajtó könyvtár struktúráját ábrázoljuk, a TreeView a jó választás, hiszen itt sok mindent nem tudunk módosítani. De ha a fa elemeit módosítani szeretnénk, TreeWidget a megoldás. A programban a nagyobb alkotói szabadság jegyében a TreeWidget ( Tw )osztály használom. A Tw működését egymásra mutató pointerek biztosítják, hasonlóan a láncolt listákhoz. A pointerek formáját, kezelését a QTreeWidgetItem osztály biztosítja. Felépítését tekintve a "fa" törzsén vannak a vizsgálatok, az ágakon az automaták, és az ágak levelei lesznek az automata kódok.

A törzs részeit és az ágakat egy - egy QTreeWidgetItem típusú pointer alkotja. Fontos: egy pointert csak egyszer tudok a fára tenni

Nézzük a konkrét példát:

 

 

 

 

 

 

 

 

 

 

 

 

 

A programban egy vizsgálatot több automata is végezhet, ráadásul az automaták más - más kóddal küldik az eredményeket. Ezt kell egy editálható fa szerkezetben kell megvalósítani. Biztosítani kell a szerkesztés eseményeinek (elem kiválasztása kattintással, enter lenyomása, popup menü megjelenítése) kezelését is. Ezt a megszokott signal - slot párosításokkal oldjuk meg.

Funkciónként elmagyarázom a megoldásokat.

Vizsgálat hozzáadása

1. külön ablakban megadom a vizsgálat adatait, amiket adatbázisba írok.

2. sql query-vel visszaolvasom a vizsgálatok adatait

  query.prepare("select vizsgalat_kodja, vizsgalat_id, vizsgalat_neve, mertekegyseg from vizsgalatok");

3. QStringList-be (oszlopok) beteszem a megjelenítendő adatokat

oszlopok << query.value(0).toString() << query.value(1).toString();

Az 1. rejtett oszlopban van a vizsgálat egyedi azonosítója

4. a query pörgetésével létrehozom az új elemeket, amiket a fa törzséhez illesztek (addTopLevelItem)

    szulo = new QTreeWidgetItem(oszlopok);

    szulo->setToolTip(0, query.value(2).toString());

    widget.treeWidget->addTopLevelItem(szulo);

 Elem kiválasztása

1. egy elemre kattintva megkapom a hozzátartozó pointert, és a tartalmát

itemClicked(QTreeWidgetItem * selItem, int selCol) signal hatására ide ugrik a vezérlés:

void RequestsForm::elem_kivalasztasa_slot(QTreeWidgetItem * selItem, int selCol)

 A selCol tartalmazza a cella sorszámát. Itt a 0. a vizsgálat neve és az automaták neve is

Mivel a vizsgálatok és az automaták neve is a 0. oszlopban van, mi dönti el, hogy melyikre kattintottam? Ez: selItem->parent(). Ha nulla a visszakapott érték, vizsgálatra - szülőre - kattintottam. Ha nem nulla, akkor automatára - gyerekre -.

Vizsgálat módosítása

Az elem kiválasztása után újra felhasználom a vizsgálat hozzáadásakor használt ablakot

  modosit_groupBox = widget.uj_vizsg_groupBox;

  modosit_groupBox->setTitle(trUtf8("Vizsgálat adatainak módosítása"));

 Ezzel a megoldással az új vizsgálatkor használt ellenőrzéseket újrahasznosítom. Ha átmentek az adatok az ellenőrzésen -> sql update. Itt jön be az 1. -rejtett- oszlop:

vizsgalat_id = kivalasztott->text(1);

Automata hozzáadása

1. ComboBox-ot feltöltök a megjelenítendő adatokkal

2. A megfelelő oszlopba beillesztem a comboBox-ot (setItemWidget)

void RequestsForm::automata_hozzaadasa() {

  if (kivalasztott->parent() != 0) {

    return;

  }

  widget.automatak_comboBox->setVisible(true);

  kivalasztott_automata = new QTreeWidgetItem(kivalasztott);

  kivalasztott_automata->setText(0, "automata");

  widget.treeWidget->setCurrentItem(kivalasztott_automata);

  widget.treeWidget->setItemWidget(kivalasztott_automata, 0, widget.automatak_comboBox);

}

a. ha a kiválasztott elemnek va szülője (parent () != 0), akkor egy automatára kattintottam, ahhoz pedig nem adunk újabb gyereket

b. elem létrehozása, mint a vizsgálat hozzáadásánál, de itt a szülő a "kivalasztott" lesz, az a vizsgálat, amelyikhez automatát akarok hozzáadni

c. a már aktuális elem (automata) 0. oszlopában megjelenítem az automaták nevével feltöltött comboBox-ot

3. a comboBox egy elemének kiválasztását signal - slot kapcsolattal kezelem le, majd a még szükséges adatokat az előbb vázoltak szerint bekérem

  widget.treeWidget->setItemWidget(kivalasztott_automata, 2, widget.kod_be_lineEdit);

Automata módosítása

Hasonlóan működik a vizsgálat kiválasztásához, csak itt van a kiválasztott elemnek szülője

Összefoglalva

  • Már létező elemeket kezelni a QtreeWidgetItem osztályon keresztül
  • elemet a törzshöz adni a TreeWidget::addTopLevelItem(s) segítségével
  • elem kiválasztását lekezelni TreeWidget::itemClicked(QTreeWidgetItem * selItem, int selCol) signal-al
  • elemhez widget-et (comboBox, lineEdit, stb.) rendelni setItemWidget(QTreeWidgetItem * item, int column, QWidget * widget). A widget-nek setVisible() = true- nak kell lennie a hozzárendelés előtt
  • gyereket a törzs egy eleméhez adni a gyerek = new QTreeWidgetItem(QTreeWidgetItem *szulo) formában

tudunk.

 

 

MySql drivers, adatbázis kapcsolat

2011.04.23. 13:26 | arabiata | Szólj hozzá!

Címkék: mysql adddatabase mysql drivers adatbazis kapcsolat

 Általában ennél a pontnál kezdődik az anyázás. A leírásban van hivatkozás letöltendő mysql header filokra, meg fordítsuk le, meg hova kellene bemásolni a lefordított dll (linux: so) filet, hogy meg is találja a progi. Egy igen egyszerű megoldás:

1. A képen két fontos beállítás látható jobboldalt:

 

- a QtSql be van jelölve, ez betölti az adatbázis drivereket
- CONFIG += console, ennek segítségével tudunk a qDebug() függvénnyel dolgokat kiiratni a "dos" ablakba windows alatt.

Felmerülhet a kérdés, hogy miért tölti be a netbeans az adatbázis drivereket, míg a Qt Creator nem. Az ok igen egyszerű, és nem informatikai: a MySql átadta az ingyenes drivereket a Netbeans-nek, a Qt Creator-t fejlesztő cégnek pedig beintett.

Ez az egyik igen nyomós érv a Qt Designer + Netbeans combo mellett.

Milyen adatbázis drivereket tudunk elérni a két op. rendszer alatt?

Egy igen bonyolult, hosszú tervezést igénylő programmal tudjuk kiiratni:

#include <QSqlDatabase>

MainForm::MainForm() {

    widget.setupUi(this);

    QStringList x;

    x = QSqlDatabase::drivers();

    widget.listWidget->addItems(x);

}

És a várva várt eredmény:

 

 

 

 

 

 

 

 

 

 

 

 

 Miután minden fontos info birtokába jutottunk, nézzük hogy tudunk az adatbázishoz kapcsolódni.

bool MainClass::db_init() {

 

#ifdef Q_OS_WIN

db_leiro = QSqlDatabase::addDatabase("QODBC");

#else

db_leiro = QSqlDatabase::addDatabase("QMYSQL");

#endif

 

db_leiro.setHostName("localhost");

db_leiro.setDatabaseName("qt_labor");

if (db_leiro.open("stu06", "kilep") == false) {

QMessageBox::critical(0, "Hiba", trUtf8("Nem tudom megnyitni az adatbázist."), QMessageBox::Ok);

return false;

}

return true;

}

 Részletesen:

A futtató op.rendszertől függően töltjük be a drivert. Itt feltételezem, hogy csak windows és linux alatt futhat a progi. Ha van más is, pl. Mac, akkor tovább kell cifrázni.

A db_leiro egy QSqlDatabase típusú változó, késöbb ezt adom át a többi modal ablaknak azok konstruktorában, (pl. login), hogy ne kelljen mindig megnyitogatni az adatbázist. Query létrehozásánál (QSqlQuery query(db_leiro);) használom. A többi gondolom nem okoz gondot:). 

trUtf8 az ékezetes karakterek kiiratását intézi. Az addDatabase utasításban megadhatunk egy, a kapcsolatra utaló latin1 szöveget is. Ennek akkor van szerepe, ha több adatbázishoz kapcsolódunk, és így tudjuk kiválasztani az aktuálisat.

Figyelem: a removeDatabase(kapcsolat neve) utasítást csak akkor hívhatjuk meg eredményesen, ha kilépünk az addDatabase()-t futtató osztályból. Tehát, ha a MainClass-ban van az addDatabase(), akkor az osztályt hívó részben tudjuk futtatni a removeDatabase()-t.

 

int main(int argc, char *argv[]) {

    // initialize resources, if needed

    // Q_INIT_RESOURCE(resfile);

    QApplication app(argc, argv);

    MainClass x;

    x.show();

// ide tehetjük a removeDatabase-t

    // create and show your widgets here

 

    return app.exec();

}

 

 

Legközelebb QTreeWidget

TableView

2011.04.13. 13:18 | arabiata | Szólj hozzá!

Címkék: select rendezés tableview qheaderview qsqlquerymodel sort direction clicked signal

Hogyan tudjuk megjeleníteni az adatbázisban tárolt adatainkat? Táblázatban vagy mezőnként. Mindkét megoldásra mutatok példát, kezdjük a táblázattal.

A TableView egy model adatait mutatja meg táblázatos formában.

 

Ehhez először meg kell határozni a modelt:

  QSqlQueryModel *model = new QSqlQueryModel;

  model->setQuery("select...");

Ha az ablak megjelenésével együtt mutatjuk meg az adatokat, akkor javasolt a konstruktorba tenni. A "select" utasításban határozzuk meg, hogy mi jelenjen meg. De mivel alapban a select-ben megadott mezőneveket teszi be a progi a header-be, ezt illik emberi formára módosítani:

model->setHeaderData(0, Qt::Horizontal, trUtf8("ID"));

model->setHeaderData(1, Qt::Horizontal, trUtf8("Megnevezés"));

model->setHeaderData(2, Qt::Horizontal, trUtf8("Indító pr."));

model->setHeaderData(3, Qt::Horizontal, trUtf8("Aktív"));

model->setHeaderData(4, Qt::Horizontal, trUtf8("Kapcs. típ."));

 A trUtf8 biztosítja az ékezetes karakterek helyes megjelenítését. A TableView fejlécét a "QHeaderView" osztály segítségével módosítom. Először megcsinálom az összerendelést.

hv = new QHeaderView(Qt::Horizontal);

widget.tableView->setHorizontalHeader(hv);

A mezők szélességét is be kell állítani:

  hv->setDefaultSectionSize(60);

  hv->resizeSection(0, 30);

  hv->resizeSection(1, 210);

  hv->resizeSection(2, 210);

  for (int i = 5; i < hv->count(); i++) {

  hv->hideSection(i);  }

 Nem fogom egyenként állítgatni őket, hanem veszek egy alapot (60) és az ettől különbözőket módosítom. A "for" ciklusban a select-ben szereplő többi mezőt elrejtem. A section-t azonosító logikai index 0-val kezdődik. A táblázat adatait rendezni is kellhet különböző mezőkre, az erre szolgáló bigyót piros körrel jelöltem.

  hv->setSortIndicatorShown(true);

  hv->setClickable(true);

  if (irany == "asc") {

    hv->setSortIndicator(1, Qt::AscendingOrder);

  } else {

    hv->setSortIndicator(1, Qt::DescendingOrder);

  }

A második sorban állítom be, hogy generálódjon clicked üzenet a fejlécre kattintásnál. A clicked üzenethez hozzárendelek egy függvényt.

  connect(hv, SIGNAL(sectionClicked(int)), this, SLOT(rendezes_slot(int)));

Az üzenet hatására a függvényre ugrik a vezérlés.

void AutomataForm::rendezes_slot(int oszlop) {

 

  if (oszlop != 1) {

    hv->setSortIndicatorShown(false);

    return;

  }

  hv->setSortIndicatorShown(true);

 

  if (hv->sortIndicatorOrder() == Qt::AscendingOrder) {

    adatok_beolvasasa("asc");

  } else {

    adatok_beolvasasa("desc");

  }

}

 Az "oszlop" változó tartalmazza a kiválasztott mező index-ét. Nálunk a megnevezés mező a szerencsés kiválasztott, így csak akkor rendezem le az adatokat, ha arra kattint a felh. A többi fejlécre kattintásnál letiltom a rendezés jelet. Az "adatok_beolvasasa" függ. paramétere a rendezés iránya, amit egyszerűen hozzámásolok a "select" utasításhoz:

void AutomataForm::adatok_beolvasasa(QString irany) {

  model->setQuery("select... order by megnevezes " + irany, db_leiro);

Mi van, ha mondjuk módosításnál az automata azonosítójára van szükségem, de bármelyik cellára kattinthat a felh.? Erre van a TableView clicked signal, amit a Qt Designer-ben rendeltem egy függvényhez.

connect(widget.tableView, SIGNAL(clicked(QModelIndex)), this,SLOT(automata_kivalasztasa_slot(QModelIndex)));

 És a függvény:

void AutomataForm::automata_kivalasztasa_slot(QModelIndex selItem) {

  automata_id = selItem.sibling(selItem.row(), 0).data().toString();

A kiválasztott cellához tartozó sor 0-s sorszámú (legelső) mezőjénak tartalmát veszem ki.

Mezőkénti adatmegjelenítésre pl. akkor van szükség, ha adatokat akarok módosítani. Tudom, erre van lehetőség a TableView vagy TableWidget osztályoknál is, de szerintem szebb, ha külön mutatom meg a módosítható adatokat. Ehhez a Qt Designer-ben felpakolom a szükséges adatbeviteli mezőket (linedit, combobox, spinbox, stb). Az előbbiekhez hasonlóan kiveszem az adatokat a táblázatból, és értékadással feltöltöm őket.

  widget.megnevezes->setText(selItem.sibling(selItem.row(), 1).data().toString());

  widget.indito_pr_neve->setText(selItem.sibling(selItem.row(), 2).data().toString());

  widget.aktiv_comboBox->setCurrentIndex(selItem.sibling(selItem.row(), 13).data().toInt() - 1);

  widget.soros_tcp_comboBox->setCurrentIndex(selItem.sibling(selItem.row(), 14).data().toInt());

 Ugye az elején láttuk, hogy csak az első öt mezőt jelenítem meg a "select" által visszaadott adatokból. A többi adat is rendelkezére áll, még ha nem is látható. Így a 13,14 a "select" vonatkozó mezőinek sorszámát jelenti. 

Az előző bejegyzés végén írtam, hogy a felhasználói adatbevitel a legveszélyesebb része a program futásának, így minél jobban korlátozni kell ezt a műveletet. Íme két példa:

Soros adatátvitelnél tudnom kell az adatbitek számát. Ez 4 - 8 érték lehet. Ha hagyom, hogy a user egy lineedit mezőbe írja be az értéket, tízből tízszer el fogja rontani, ráadásul még ellenőriznem is kell a bevitt adatot. Ezt kikerülve egy spinbox-ot használok, beállított értékekkel

 

 

 

 

 

Ha nem tudom teljesen lekorlátozni az adatbevitelt, akkor is igyekszem szűkíteni a hibalehetőségeket.

 

 

 

 

 

Itt pl. maszkolom az adatbevitelt, vagyis csak számokat vihet be. Megadok egy default értéket is, ha ettől különböző értéket kapok a vizsgálatnál, akkor valszeg jó adatot írt be.

 

 

 

Érdekes megoldások

2011.04.05. 21:04 | arabiata | Szólj hozzá!

Címkék: screen center constructor parameter tableview multicolored rows

 Az új felhasználó felvétele, felhasználó adatainak módosítása, környezeti változók beállításának leprogramozása igazi unalmas favágómunka. Egy dialog-ot (új felhasználó felvétele) legyártottam, és a többit ennek mintájára készítettem el. Például egyforma a konstruktoruk:

NewUserForm::NewUserForm(QSqlDatabase db_leiro_ptr, QHash<QString, QString> *hash_ptr) {

widget.setupUi(this);

const QRect screen = QApplication::desktop()->screenGeometry(0);

this->move(screen.center()-QPoint(this->width()/2,this->height()/2));

hash = hash_ptr;

tetlen_ido = hash->value("tetlen_ido").toInt();

tetlen_timer.start(tetlen_ido * 1000, this);

db_leiro = db_leiro_ptr;

widget.nev->setFocus();

 A 2. és 3. sor beállítja a form-ot a képernyő közepére.
A hash táblában - mint egyedüli osztályok közti adatcsatornában - átadhatom a form bezárási idejét, ha nincs egérmozgás és a belépett felhasználó azonosítóját mondjuk logolás miatt. A MainClass-ban dinamikus pointerrel hoztam létre a hash táblát. 

QPalette p = palette();

p.setColor(QPalette::Base, QColor(qRgb(0xFF, 0xFF, 0xFF)));

p.setColor(QPalette::AlternateBase, QColor(qRgb(0xD5, 0xEA, 0xFF)));

widget.tableView->setPalette(p);

Tableview sorait két különböző háttérszínnel váltakozva jelenítem meg.

QSqlQueryModel *model = new QSqlQueryModel;

model->setQuery("select felh_nev from felhasznalok", db_leiro);

widget.tableView->setModel(model);

widget.tableView->show(); 

 Feltöltöm a tableview-t adatokkal. A select-ben meghatározott mezők adják a sorok,cellák tartalmát. Majd minden megjelenítő widget-hez lehet model-t rendelni. Ez igen hasznos, mert így pl. nem kell egy listview feltöltését ciklusban végezni.

void NewUserForm::mouseMoveEvent(QMouseEvent* event) {

tetlen_timer.stop();

tetlen_timer.start(tetlen_ido * 1000, this);

}

 void NewUserForm::timerEvent(QTimerEvent* event) {

done(0);

}

Egérmozgás, timer ütemeinek figyelése

QString munka = QFileDialog::getExistingDirectory(this, trUtf8("Output könyvtár kiválasztása"), "/home", QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);

widget.out_konyvtar->setText(munka);

 Könyvtár kiválasztása QFileDialog segítségével

Mint írtam, a konstruktor, egérmozgás figyelése, timer ütemezése mindegyik dialog form-nál egyforma. Így tulajdonképpen másolással, egy template alkalmazásával gyorsan tudok új modal form-okat létrehozni, csak a funkciónak megfelelő változásokat programozom le. De ebben is sokat segít a copy-paste, mert a hibaüzenetek felépítése is egyforma, csak a szöveg változik.

Ami minden felhasználói adatbevitelnél nagyon fontos:

  • a felhasználó hazudik
  • a felhasználó össze - vissza nyomkod minden gombot
  • a felhasználó sátáni örömet érez, ha kiakasztja a programot
  • a felhasználó türelmetlen
  • a felhasználó nem tud olvasni
  • a felhasználó pontosan követte a program utasításait
  • a felhasználó találékony

tehát minden adatbevitelt alaposan ellenőrizzünk, ígyekezzünk bolondbiztos programot írni.

Menükezelés,sharedmemory,hash tábla,actiongroup

2011.04.03. 12:02 | arabiata | Szólj hozzá!

Címkék: hash tábla menükezelés sharedmemory actiongroup

  A sok elmélet után kezdjük el a kódolást. Két mysql táblára lesz szükségünk, felhasznalok és kornyezet. Kézzel felvittünk egy admin jogú felhasználót

insert into felhasznalok values ('admin','admin','admin','admin','igen')

 A mezőket nem soroltuk fel, mivel mindegyiket kitöltjük. Kell még a kornyezet táblába az ablakok_tetlen_ideje mező másodpercben:

insert into kornyezet (ablakok_tetlen_ideje) values ('120')

MainClass konstruktora:

MainClass::MainClass() : sh_mem("qt_labor") {

widget.setupUi(this);

if (sh_mem_init() == false) {

exit(-1);

}

if (db_init() == false) {

exit(-1);

}

if (hash_init() == false) {

exit(-1);

}

if (kornyezeti_valtozok_olvasasa() == false) {

exit(-1);

}

menucsoportok_init();

}

A következő függvényekben csak a lényeget emelem ki.

sh_mem_init:

if (sh_mem.create(10, QSharedMemory::ReadOnly) == false) {

QMessageBox::critical(0, "Hiba", trUtf8("Már fut a program."), QMessageBox::Ok);

sh_mem.detach();

return false;

Ha nem tudjuk lefoglalni a memória területet, már fut a program.

db_init:

#ifdef Q_OS_WIN

db_leiro = QSqlDatabase::addDatabase("QODBC");

#else

db_leiro = QSqlDatabase::addDatabase("QMYSQL");

#endif

Windows és linux alatt más a neve a mysql driver-nek.

 hash_init:

hash_tabla = NULL;

hash_tabla = new QHash<QString, QString>;

if (hash_tabla == NULL) {

QMessageBox::critical(0, "Hiba", trUtf8("Nem tudom létrehozni a hash táblát."), QMessageBox::Ok);

return false;

 Kulcs, érték QString típusu, dinamikus pointerként hozom létre.

Menükezelés:

A menüpontokat a qtdesigner-ben felvett action-ök alapján érem el. Vagy minden menüponthoz rendelek slot-ot connect utasítással, ami sok-sok signal,slot összerendelést jelent, vagy a menubar-hoz csinalok egy slot-ot, és azon belül switch segítségével haladok menüpontonként:

void MainClass::menuValaszto_slot(QAction *action) {

int menupont = action->objectName().mid(2).toInt();

switch (menupont) {

case 1: // felhasználó váltás, login, azert kell a {} mert a switch-en belül deklaráltam változót

{

felh->setEnabled(false);

validalo->setEnabled(false);

admin->setEnabled(true);

LoginForm *theForm = new LoginForm(db_leiro, hash_tabla);

theForm->exec();

delete theForm;

if (hash_tabla->value("login_ok") == "1") {

menuvalaszto(hash_tabla->value("szint").toInt());

felh_nev = hash_tabla->value("felh_nev");

nev = hash_tabla->value("nev");

szint = hash_tabla->value("szint");

}

hash_tabla->remove("login_ok");

hash_tabla->remove("felh_nev");

hash_tabla->remove("nev");

hash_tabla->remove("szint");

}

break;

case 2: // kilépés

close();

break; 

 Hash táblában a login során visszakapott infók vannak.

A menükezeléshez tartozik még a felhasználói szintenkénti menüpontok kezelése. Erre jó a QACtionGroup, ahol csoportokba foglalom a szintenként engedélyezett menüpontokat, és ezeket egyszerre tudom engedélyezni - letiltani.

 

void MainClass::menucsoportok_init() {

felh = new QActionGroup(this);

felh->addAction(widget.a_8);

felh->addAction(widget.a_9);

felh->addAction(widget.a_10);

 

validalo = new QActionGroup(this);

validalo->addAction(widget.a_7);

validalo->addAction(widget.a_8);

validalo->addAction(widget.a_9);

validalo->addAction(widget.a_10);

 

admin = new QActionGroup(this);

admin->addAction(widget.a_3);

admin->addAction(widget.a_4);

admin->addAction(widget.a_5);

admin->addAction(widget.a_6);

admin->addAction(widget.a_7);

admin->addAction(widget.a_8);

admin->addAction(widget.a_9);

admin->addAction(widget.a_10);

 

felh->setEnabled(false);

validalo->setEnabled(false);

admin->setEnabled(true);

 Az admin csak fejlesztés idejére true. Az utolsó három sor megtalálható a switch login részében is, hiszen nem tudom előre, hogy ki fog bejelentkezni.

Környezeti változók beolvasása:

 

bool MainClass::kornyezeti_valtozok_olvasasa() {

 

QSqlQuery query(db_leiro);

query.prepare("select ablakok_tetlen_ideje from kornyezet");

query.exec();

if (query.next() == false) {

QMessageBox::critical(0, "Hiba", trUtf8("select ablakok_tetlen_ideje from kornyezet"), QMessageBox::Ok);

return false;

}

 

hash_tabla->insert("tetlen_ido", query.value(0).toString());

 A leírásban van egy ilyen:

QSqlQuery query("SELECT * FROM artist"); 

  Na, ez nekem nem ment, ezért választottam a fenti megoldást. 

Netbeans beállítások

2011.03.31. 20:43 | arabiata | Szólj hozzá!

Címkék: netbeans beallitasok netbeans telepítés

 A telepítés és alap beállítások olvashatóak a  Netbeans vonatkozó oldalán. Telepítés előtt le kell tölteni a Java futtató környezetet, de erre a netbeans is figyelmeztet. A képen látható állapotig kell eljutni a beállításokkal:

 

 A második képen az editor beállítási lehetőségei láthatóak. A szövegrészek behúzása, függvény nevek, zárójelek környékén szóköz elhelyezését is itt lehet állítani.

 

Gépelésnél sokat segít a Ctr+space is, egy osztály paramétereit, függvényeit jeleníti meg. De sima utasításokat is kegészít. Pl. sw Ctrl+space felhozza a sw kezdetű utasításokat. Tools -> Options -> Code templates részben lehet szerkeszteni a kód mintákat. 

Szöveg formázása: Alt+Shift+F. Egy változó összes előfordulásának átnevezése az aktuális file-ban a Ctrl+r (refactor) kombinációval történik.

Legközelebb megcsináljuk a login részt.

 

 

 

 

 

 

 

 

 

 

 

 

 

Adatbázis előkészítése 1

2011.03.28. 09:47 | arabiata | Szólj hozzá!

Címkék: mysql bug

 Miután feltelepítettük a MySQL Workench (w) programot belefutunk az első bug-ba. A telepítésél létrehozta a program a root felhasználót az általunk megadott jelszóval. De nem illik root-ként belépni és garázdálkodni, tehát kell egy korlátozott jogú felhasználó is. A w nem hagyja létrehozni az új user-t. Imígyen kerüljük meg a problémát: CREATE USER 'jeffrey'@'localhost' IDENTIFIED BY 'password'. Részletes leírás a MySQL account kezeléséről itt.
Ezután már tudunk jogokat adni a felhasználónak a server administration részben a következő módon:

  1. root-ként létrehozzuk az adatbázist
  2. Users and Privileges
  3. jobbra fent a létrehozott felh. kiválasztása
  4. Schema Privileges
  5. jobbra lent Add Entry
  6. megadjuk a host-ot, esetünkben localhost
  7. és végül a jogokat

Miért csak localhost? Mert a labor program azon a gépen fog futni, ahol az adatbázis van.

Bug no. 2: ez is MySQL, de más irányból. Ha az ODBC drivernél ( amit úgyis fel kell paraméterezni)  a details rész végén nem adom meg a kódlapot, akkor a SELECT utasítás programból végrehajtva kijelenti, hogy nem találja a táblában a mezőket. Cp1250, Utf8, stb. beállítható egyéni izlés szerint.

Éééééés, dobpergés. Létrehozzuk az első táblát. Javaslom kézzel beírni az adatokat, gyakorolni sosem árt.

CREATE TABLE `felhasznalok` (

  `felh_nev` varchar(45) NOT NULL,

  `jelszo` varchar(45) DEFAULT NULL,

  `nev` varchar(45) DEFAULT NULL,

  `szint` enum('felhasználó','validáló','admin') DEFAULT NULL,

  `aktiv` enum('igen','nem') DEFAULT NULL,

  PRIMARY KEY (`felh_nev`)

) ENGINE=InnoDB DEFAULT CHARSET=cp1250$$

 

A mezőknél meghagytam a default varchar(45)-öt, minek bonyolítsam az életet. Azért varchar és nem char, mert a varchar a tartalom tényleges hosszát foglalja le, a char pedig a mező teljes hosszát.

A felh_nev egyedi, - erre majd a programban figyelni kell - így használhatjuk indexként.
Szint,aktiv mezők: az enum adattípus olyan mint egy tömb. 

CREATE TABLE  `test`.`aaa` (

  `mezo` enum('a','b','c') NOT NULL,

  `nev` varchar(45) NOT NULL

) ENGINE=InnoDB DEFAULT CHARSET=cp1250;

insert into aaa values ('a','nev_1'),('c','nev_3'),('b','nev_2'),('a','nev_1');

select mezo+0,mezo,nev from aaa;

 

1, 'a', 'nev_1'

3, 'c', 'nev_3'

2, 'b', 'nev_2'

1, 'a', 'nev_1'

 

 Látható, hogy a mezo értékét visszakaphatjuk szövegként, és számként (tömb elem indexe) is. Ez igen nagy segítség, ha mondjuk biztonsági szinthez akarjuk kötni a menüpontok megjelenítését, és ugyanakkor ki is írjuk a felhasználói szintet.

Legközelebb netbeans beállítások.

 

 

Felhasználó kezelés - vázlat

2011.03.24. 20:03 | arabiata | Szólj hozzá!

Címkék: uml hash tábla shared memory

 Az előző post-ban áttekintettük, hogy milyen részei lesznek a programnak. Nézzük most a felhasználó kezelő modult. E nélkül be sem tudunk lépni a programba, így ez a legfontosabb rész. Az ábra mutatja, hogy milyen osztályokból áll össze.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

A dőlt betükkel írt változók felhasználó bevitelt jelentenek, az egyéb jelöléseket UML alapján használom. A hash_tabla magyarázata:

Az osztályok közötti adatok átadásának több módja van:

1. public változó, ekkor be kell forgatni a header fileokat mindkét osztályba.

2. közös osztály amit mindkét kommunikáló osztály elér. Ekkor a közös osztály header file-ját kell beforgatni mindenhova.

3. public függvényekkel private változókat írunk,olvasunk. A header probléma itt is megmarad

4. öröklődés, derived class protected változókkal. Itt a gyerek konstruktora meghívja a szülőét. Nem biztos, hogy jó megoldás. Nekem már sikerült végtelen ciklust létrehozni ezen az alapon

5. konstruktorban átadott értékek. de hogy adom vissza az eredményt a felsőbb osztálynak?

6. hash tábla. Itt csak a táblára mutató pointert kell az újonnan létrehozandó osztály konstruktorában átadni. Minden adatátvitel ezen a táblán megy keresztül.

Tekintve, hogy a hash tábla bejegyzésenként két értéket tartalmaz, az index-et és az igazi értéket, ez a legkényelmesebb. Egy kis bibi van csak, hogy az igazi érték csak egy féle lehet, pl. string vagy integer. Javasolt stringként kezelni mindent.

Eger_timer, egermozgas: az ablakokat nem hagyjuk örökké nyitva, hanem egy timer vezérlése alapján becsukjuk őket, ha nincs egérmozgás. Ha a felh. rángatja az egeret, mindig újraindítjuk a timer-t mouseMoveEvent segítségével.

shmem: megosztott memória terület (qsharedmemory). Ha azt akarom, hogy a program csak egy példányban fusson egy munkaállomásom, akkor írhatok egy 1 byte-os file-t, amit minden példány indulásakor megvizsgálok, és ha létezik, már fut a program. Ha hibával leáll a program, törölhetem az ott maradt pid file-t kézzel , hogy egyáltalán elinduljon a dolog. Hasonlóan működik a shared memory is. Minden példány indulásakor megpróbálok létrehozni egy virtuális pid file-t a memóriában. Ha nem sikerül, már fut a program. Persze a futás befejezésekor fel kell szabadítani ezt a memória területet.

Legközelebb egy kis adatbázis tervezés lesz a téma 

 

Programterv

2011.03.23. 10:32 | arabiata | Szólj hozzá!

Talán ez a legnehezebb része a dolognak. Fejben már tudjuk hogy fog működni a program, tele vagyunk jó ötletekkel. Lehetne kezdeni kódolni, hiszen igazán látványos, ahogy fejlődik a gyerek:) Ha meg egy programrészről kiderül, hogy evolúciós zsákutca, ott a delete gomb. De minden delete gomb nyomás növeli a fejlesztési időt, és csökkenti a spermaszámot lelkesedést. Így kerülendő. És különben is mennyivel jobb az ablakon kibámulni, mint véresre gondolkodni az ujjainkat.
Igen gazdag szakirodalma van  a programtervezésnek, sokszor egymásnak ellentmondó megoldásokat hirdetve egyedül üdvözítőnek. Nekem sok tévelygés után a következő módszer jött be:

  • milyen környezetben (lásd az előző bejegyzést) fog futni a progi?
  • menüpontokba szedem, hogy mit fog csinálni
  • felvázolom, hogy  a menüpontok mikhez kapcsolódnak

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

A Form végű részek felhasználói beavatkozást jelentenek, a class-nál ilyen nincs. Az AutomatakClass indítja, állítja le a külön exe-ként futó automata kapcsolattartó programokat. A két HL7 osztály külön - külön szálon fut a programon belül. Ez utóbbi részek kommunikálnak a medikai programmal.

A koncepció

2011.03.20. 14:40 | arabiata | Szólj hozzá!

Címkék: server mysql netbeans terminal qt

 

A képen látható, hogy milyen részekből áll a össze a labor automaták és az adatgyűjtő program kapcsolata. Ez a séma minden hasonló rendszer feljesztésére igaz.

 

 

 

 

 

 

 

 

 

 

 

 

 

Tekintve, hogy a küldött, várt adatok formátuma automatánként változik és ráadásul a medikai program is egy külön erre a célra kifejlesztett protokollt használ, így munkánk lényege, hogy fordítóprogramokat kell írnunk, amik közvetítenek az automaták adatformátuma és a HL7 között. 

Ha van x darab automatánk, ami soros porton kommunikál, akkor az adatokat fogadó sz.gépet kell valahogy rábírnunk, hogy fizikailag össze tudjuk kötni az automatákkal. Két módszer van:

  • soros kártyákkal telerakjuk a fogadó gépet, ami hmmm.....
  • használunk egy eszközt, ami összefogja a soros portokat és hálózaton keresztül továbbítja az adatokat

Ez az eszköz a terminál-szerver (t.sz.). Emulálja a soros kommunikációt, amit így TCP vagy UDP kapcsolaton keresztül tudunk kezelni. A t.sz. üzemelhet szerver vagy kliens módban, ennek beállítása a konkrét igények alapján történik.

Terminál szerverek működéséről itt olvashatsz. HL7 leírást itt nézheted.

A következő részben megnézzük részletesebben a program felépítését

Qt Software fejlesztés

2011.03.18. 20:04 | arabiata | Szólj hozzá!

Címkék: mysql netbeans qt c plus plus

Csakhogy Neked ne kelljen guglizni, fórumokat túrni.

Ha érdekel Qt programozás itt a helyed. Minden post egy - egy témát mutat be rövid példával, magyarázattal. Címszavakra jobb oldalt tudsz keresni.

Amikről szó lesz:

 

  • Adatbázis tervezés, használat (mysql)
  • táblázat-kezelés, fa struktúra
  • soros port kezelés
  • tcp/ip
  • multithread
  • programok közti kommunikáció (IPC)
  • felhasználói adatbevitel, menükezelés
  • timer kezelés
  • shared memory
  • hash tábla

 

Bejegyzéseket hétvégén szándékozom írni, ahogy haladok a munkával. Minden érdeklődő kommentet szívesen látok.

Amire szükség lesz:

  • netbeans IDE,
  • mysql + query browser + administrator, windows alatt mysql workbench,
  • Qt,
  • Diagram designer