معرفی فریمورک اپلیکیشن نویسی ‏Qt ‏(17)

سلام بر دوستان گرامی.
من شخصا با استفاده از کتابخانهء رمزگذاری مورد نظر در Qt مشکلات زیادی داشتم.
ظاهرا نسخهء کامپایل شدهء کتابخانهء معروف crypto++ که برای دانلود قرار داده شده (crypto++ یک کتابخانه رمزگذاری نوشته شده در زبان سی++ هست) توسط کامپایلر میکروسافت (MSVC) کامپایل شده و با کامپایلر پکیج mingw کار نمیکنه. بنابراین ما در استفاده از امکانات رمزگذاری این کتابخانه در برنامه های Qt خودمون به مشکل جدی برمیخوریم (بنظرم مگر اینکه Qt ما هم با MSVC کامپایل شده باشه).
اما خوشبختانه بنده پس از تلاشهای چندی بطور غیرمنتظره ای موفق به حل این مسئله شدم.
من این روش رو بصورت قدم به قدم برای هر جوینده ای درحال و آینده در این تاپیک قرار میدم.

شروع:

ابتدا پکیج cryptopp552.zip رو از سایت پروژهء crypto++ در sourceforge دانلود کنید.
این پکیج محتوی فایلهای کد منبع این کتابخانه هست. حجمش هم حدود 1 مگابایت.

خب چرا این فایل؟
چون ظاهرا این آخرین نسخه از این کتابخانه بوده که توسط دیگران هم بصورت موفقیت آمیز با mingw کامپایل شده (اما من فرم کامپایل شدهء اون رو پیدا نکردم).

محتویات cryptopp552.zip رو به C:\cryptopp552 اکسترکت کنید.

حالا فایل C:\cryptopp552\fipstest.cpp رو با یک ویرایشگر مناسب باز کنید و تمام عبارات OutputDebugString رو به OutputDebugStringA تغییر بدید و فایل رو ذخیره کرده و ببندید. مجموعا سه تا از این عبارت ها در این فایل وجود داره.

فایل C:\cryptopp552\GNUmakefile رو دلیت کنید.

خط فرمان Qt رو باز کنید (من از Qt SDK 2009.05 استفاده کردم).
فرمانهای زیر رو به ترتیب در خط فرمان وارد کنید:

c:
cd \cryptopp552
qmake -project

حالا فایل cryptopp552.pro رو که با فرمان آخر در C:\cryptopp552 ایجاد شده برای ویرایش باز کنید و این کارها رو انجام بدید:

TEMPLATE = app رو به TEMPLATE = lib تغییر بدید.
یک خط با محتوای LIBS += -lws2_32 به انتهای این فایل اضافه کنید.

دوباره فرمانهای زیر رو در خطر فرمان Qt وارد کنید:

qmake
mingw32-make all

اجرای فرمان آخر بسته به سرعت سیستم شما دقایقی کم یا زیاد طول میکشه.

حالا ما باید فایلهایی با نام libcryptopp552.a و cryptopp552.dll در هر دوی فولدرهای C:\cryptopp552\release و C:\cryptopp552\debug داشته باشیم. تمام توابع و کلاسهای کتابخانهء crypto++ در همین یک فایل dll هست.

C:\cryptopp552\release\libcryptopp552.a رو به فولدر lib در فولدر Qt کپی کنید.
توجه کنید که یک فولدر دیگر بنام lib در یک سطح بالاتر از فولدر Qt در فولدر نصب Qt SDK وجود داره که نباید این دوتا رو با هم قاطی کنید. فایل رو باید در فولدر lib خود Qt بریزید اما اگر فایل رو در هر دو هم کپی کنید مشکلی پیش نمیاد.

فایل C:\cryptopp552\release\cryptopp552.dll رو به فولدر bin در فولدر Qt کپی کنید.
توجه کنید که یک فولدر دیگر بنام bin در یک سطح بالاتر از فولدر Qt در فولدر نصب Qt SDK وجود داره که نباید این دوتا رو با هم قاطی کنید. فایل رو باید در فولدر bin خود Qt بریزید اما اگر فایل رو در هر دو هم کپی کنید مشکلی پیش نمیاد.

یک فولدر بنام cryptopp در فولدر include که در فولدر Qt هست ایجاد کنید و تمام فایلهای هدر (فایلهایی با پسوند h) رو که در فولدر C:\cryptopp552 وجود دارن به داخل این فولدری که ایجاد کردید کپی کنید.

اکنون ما میتونیم crypto++ رو تست کنیم و ببینیم که چطور باید از اون در برنامه های Qt خودمون استفاده کنیم.

مثال اول برنامه ای هست که هش MD5 یک رشته رو (که در برنامه hard code شده) محاسبه و چاپ میکنه:

main.cpp

#include <iostream>

#define CRYPTOPP_DEFAULT_NO_DLL
#include <cryptopp/dll.h>
#ifdef CRYPTOPP_WIN32_AVAILABLE
#include <windows.h>
#endif
#include <cryptopp/md5.h>

USING_NAMESPACE(CryptoPP)
USING_NAMESPACE(std)
const int MAX_PHRASE_LENGTH=250;

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

CryptoPP::MD5 hash;
byte digest[ CryptoPP::MD5::DIGESTSIZE ];
std::string message = "Hello World!";

hash.CalculateDigest( digest, (const byte*)message.c_str(), message.length());

CryptoPP::HexEncoder encoder;
std::string output;
encoder.Attach( new CryptoPP::StringSink( output ) );
encoder.Put( digest, sizeof(digest) );
encoder.MessageEnd();

std::cout << "Input string: " << message << std::endl;
std::cout << "MD5: " << output << std::endl;

return 0;
}

کد برنامه برگرفته از: : Hash Functions – Crypto++ Wiki

یادتون باشه قبل از اینکه شروع به مراحل کامپایل برنامه (معمولا با فرمانهای qmake و mingw32-make) بکنید باید این خطوط رو به فایل پروژهء اون (فایلی با پسوند pro) اضافه کنید:

LIBS += -lcryptopp552
CONFIG+=console

برنامه چنین خروجی ای رو باید در پنجرهء کنسول چاپ کنه:

Input string: Hello World!
MD5: ED076287532E86365E841E92BFC50D8C

مثال دوم برنامه ای هست که سه آرگومان که هر کدام نام/آدرس یک فایل هستن میگیره.
برنامه بعد از کاربر یک پسورد میخواد، بعد یک نسخهء رمز شده از فایل اول رو در فایل دوم ذخیره میکنه و بعد یک نسخهء از رمز درآمده از فایل دوم رو در فایل سوم ذخیره میکنه. یعنی اول رمز میکنه و بعد فایل رمز شده رو رمزگشایی میکنه.

خط فرمان نمونه ای که من استفاده کردم مثل این بوده:

release\cryptopptest.exe 1.jpg 2.jpg 3.jpg

فقط نیاز داریم که فایل اول رو داشته باشیم که در اینجا یک فایل تصویری بوده.

main.cpp

#include <iostream>

#define CRYPTOPP_DEFAULT_NO_DLL
#include <cryptopp/dll.h>
#include <cryptopp/default.h>
#ifdef CRYPTOPP_WIN32_AVAILABLE
#include <windows.h>
#endif

USING_NAMESPACE(CryptoPP)
USING_NAMESPACE(std)

const int MAX_PHRASE_LENGTH=250;

void EncryptFile(const char *in,
const char *out,
const char *passPhrase);
void DecryptFile(const char *in,
const char *out,
const char *passPhrase);

int main(int argc, char *argv[])
{
try
{
char passPhrase[MAX_PHRASE_LENGTH];
cout << "Passphrase: ";
cin.getline(passPhrase, MAX_PHRASE_LENGTH);
EncryptFile(argv[1], argv[2], passPhrase);
DecryptFile(argv[2], argv[3], passPhrase);
}
catch(CryptoPP::Exception &e)
{
cout << "nCryptoPP::Exception caught: "
<< e.what() << endl;
return -1;
}
catch(std::exception &e)
{
cout << "nstd::exception caught: " << e.what() << endl;
return -2;
}
}

void EncryptFile(const char *in,
const char *out,
const char *passPhrase)
{
FileSource f(in, true, new DefaultEncryptorWithMAC(passPhrase,
new FileSink(out)));
}

void DecryptFile(const char *in,
const char *out,
const char *passPhrase)
{
FileSource f(in, true,
new DefaultDecryptorWithMAC(passPhrase, new FileSink(out)));
}

RandomPool & GlobalRNG()
{
static RandomPool randomPool;
return randomPool;
}
int (*AdhocTest)(int argc, char *argv[]) = NULL;

کد برنامه برگرفته از: : Crypto++ Holds the Key to Encrypting Your C++‎‎ Application Data – CodeGuru

یادتون باشه قبل از اینکه شروع به مراحل کامپایل برنامه (معمولا با فرمانهای qmake و mingw32-make) بکنید باید این خطوط رو به فایل پروژهء اون (فایلی با پسوند pro) اضافه کنید:

LIBS += -lcryptopp552
CONFIG+=console

————————–

در پایان باید بگم که میتونید از نسخه های آمادهء کتابخانه cryptopp که شخصا با mingw کامپایل کردم استفاده کنید:

دانلود نسخهء Release کتابخانهء رمزنگاری cryptopp
حجم: 2.5 مگابایت

دانلود نسخهء Release و Debug کتابخانهء رمزنگاری cryptopp
حجم: 12 مگابایت

دانلود رفرنس رسمی کتابخانهء رمزنگاری cryptopp نسخهء 5.5.2
حجم: 8 مگابایت

البته شاید رفرنس رسمی بیش از حد حجیم و خشک و تئوریک باشه. در این صورت میتونید از راهنماهای کم حجمی مثل این هم استفاده کنید: CryptoPPGuide.chm
حجم: 87 کیلوبایت

معرفی فریمورک اپلیکیشن نویسی Qt ‏ (16)

یکی از دوستان درمورد چگونگی استفاده از سرورهای SQL مثل MySQL و اوراکل و غیره در Qt پرسیده بودن. این پرسش منجر به تحقیق و تست و درج مطالبی در چند پست در اینمورد شد که خلاصهء مفید اونها رو در این پست ارائه میکنم.

اولا این مطلب از رفرنس کیوت درمورد دیتابیس های پشتیبانی شده توسط کیوت:

The table below lists the drivers included with Qt. Due to license incompatibilities with the GPL, not all of the plugins are provided with the Qt Open Source Edition.

Driver name DBMS
QDB2 IBM DB2 (version 7.1 and above)
QIBASE Borland InterBase
QMYSQL MySQL
QOCI Oracle Call Interface Driver
QODBC Open Database Connectivity (ODBC) – Microsoft SQL Server and other ODBC-compliant databases
QPSQL PostgreSQL (versions 7.3 and above)
QSQLITE2 SQLite version 2
QSQLITE SQLite version 3
QTDS Sybase Adaptive Server

Note: To build a driver plugin you need to have the appropriate client library for your Database Management System (DBMS). This provides access to the API exposed by the DBMS, and is typically shipped with it. Most installation programs also allow you to install “development libraries”, and these are what you need. These libraries are responsible for the low-level communication with the DBMS.

مورد تست دیتابیس MySQL بوده.
ما ابتدا از ODBC برای اتصال به MySQL استفاده میکنیم.
ابتدا درایور ODBC مربوطه را دانلود میکنیم:

درج این آدرس بخاطر این بود که دسترسی به لینک دانلود بخاطر تحریم کمی مشکل بود (نیاز به فیلترشکن)، وگرنه بطور معمول خودتون باید جستجو و نسخهء مناسب رو از مکان مناسب دانلود کنید.
بعد از نصب و انجام تنظیمات ODBC لازم در ویندوز (این بخشها به خود کیوت مربوط نمیشن و توضیح نمیدم)، میتونیم با چنین کد تست ساده و مختصری برقراری اتصال و دسترسی موفق رو بررسی کنیم:

#include <QtSql>
#include <QApplication>
#include <QDebug>

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

QSqlDatabase db = QSqlDatabase::addDatabase("QODBC");
db.setDatabaseName("mysqlxx");
if(!db.open()) {
qDebug("error in opening!");
QSqlError err=db.lastError();
qDebug()<<err.databaseText();
qDebug()<<err.driverText();
return 1;
}
else qDebug("success");

QSqlQuery query;
query.exec("SELECT * FROM counter");

while (query.next()) {
QString c = query.value(0).toString();
qDebug() << c << '\n';
}

qDebug("-------------end---------------\npress Ctrl+C to exit!");

return app.exec();
}

در قسمت setDatabaseName توجه میکنیم که بجای نام دیتابیس اصلی نامی رو که برای data source name در تنظیمات ODBC ویندوز انتخاب کردیم بهش میدیم. ضمنا ظاهرا دیگه نیازی به یوزرنیم و پسورد MySQL هم نبود! (اگر لازم بود به نمونه کد روش بعدی مراجعه کنید).
یادتون باشه به فایل پروژهء این برنامهء تست این دو خط رو هم اضافه کنید:

QT += sql
CONFIG+=console


روش دیگر استفاده از درایور مخصوص خود MySQL هست.

در کیوت احتمالا بخاطر مواردی مثل حجم و غیره (یا مورد ناسازگاری مجوز که گفته)، پلاگین درایور MySQL و کتابخانهء اون بصورت پیشفرض وجود ندارن.
طرز ساخت و استفاده از پلاگین MySQL رو طوری که خودم انجام دادم شرح میدم. توجه کنید که بعضی پارامترها ممکنه برای شما و نسخه هایی که استفاده میکنید تفاوت داشته باشن.

اولا چون من از EasyPHP 2.0b1 برای محیط برنامه نویسی وب روی سیستم خودم استفاده میکردم و میدونیم که EasyPHP شامل وب سرور آپاچی همراه با ماجول PHP، و سرور دیتابیس MySQL هست، خواستم تا از همین نسخه از MySQL که همراه EasyPHP هست استفاده کنم. ضمنا من این نسخه از EasyPHP رو از قبل روی سیستم نصب داشتم و احتمالا الان دیگه قدیمی شده. نکتهء دیگه اینکه اگر شما فقط به یک سرور MySQL نیاز دارید و نه یک محیط برنامه نویسی وب، بهتره فقط یک سرور MySQL رو جداگانه نصب و اجرا کنید؛ استفاده از EasyPHP در اینجا فقط بخاطر این بوده که بنده برنامه نویسی وب هم میکنم و این برنامه از قبل روی سیستمم نصب بوده.
خب اول باید نسخهء MySQL رو بفهمیم. اینکار از راههای مختلفی امکان پذیر هست. احتمالا راحتترین راهش اینه که روی آیکون EasyPHP در تسکبار کلیک راست کرده و گزینهء Administration رو انتخاب کنید که در نتیجه در صفحهء باز شده نسخهء 5.0.27 رو برای من نشون میداد.
حالا باید هدرها و کتابخانه های مختص این نسخه از MySQL رو پیدا و دانلود کنیم.
من نهایتا پس از جستجو از این آدرس دانلودشون کردم:

این آدرس مستقیم رو بعنوان نمونه گذاشتم و ضمنا اینکه بعلت تحریم نیاز به استفاده از روشهایی مثل فیلترشکن برای رسیدن به این آدرس بود.
بعد از دانلود باید برنامهء نصبی رو که داخل فایل زیپ هست اجرا و اجزای MySQL رو نصب کنید (موقع نصب گزینهء Complete رو انتخاب کردم).

حالا اینکارها رو انجام میدیم:
- همهء فایلهای موجود در C:\Program Files\MySQL\MySQL Server 5.0\include رو به E:\Qt2009.05\qt\include کپی کنید.
مسلمه که آدرس محل نصب Qt SDK و MySQL که دانلود کردید ممکنه برای شما متفاوت باشه.
- از آدرس C:\Program Files\MySQL\MySQL Server 5.0\lib\opt فایل libmysql.lib رو به E:\Qt2009.05\qt\lib کپی کنید.
- از دایرکتوری C:\Program Files\MySQL\MySQL Server 5.0\lib\opt فایل libmysql.dll رو به E:\Qt2009.05\qt\bin کپی کنید.
- خط فرمان کیوت رو اجرا کرده و دایرکتوری جاری رو به E:\Qt2009.05\qt\src\plugins\sqldrivers\mysql تغییر بدید.
- فرمان qmake رو اجرا کنید.
- فرمان mingw32-make رو اجرا کنید.

بعد از تمام شدن اجرای این فرمانها کار تمامه!
اگر به دایرکتوری E:\Qt2009.05\qt\plugins\sqldrivers مراجعه کنید باید فایلهای مربوط به MySQL رو ببینید (درمورد مال من، 4 فایل هست که در نام اونها عبارت mysql وجود داره).
این هم یک برنامهء تست خیلی مختصر و ساده:

#include <QtSql>
#include <QApplication>

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

QSqlDatabase db = QSqlDatabase::addDatabase("QMYSQL");
db.setHostName("localhost");
db.setDatabaseName("database1");
db.setUserName("mysql_username");
db.setPassword("mysql_password");
if(!db.open()) {
qDebug("error in opening!");
return 1;
}
else qDebug("success");

QSqlQuery query;
query.exec("SELECT * FROM table1");

while (query.next()) {
QString c = query.value(0).toString();
qDebug() << c << '\n';
}

qDebug("-------------end---------------\npress Ctrl+C to exit!");

return app.exec();
}

یادتون باشه به فایل پروژهء این برنامهء تست این دو خط رو هم اضافه کنید:

QT += sql
CONFIG+=console

بعد با فرمان qmake و بعد mingw32-make release برنامه رو کامپایل میکنیم (استفاده از release بخاطر بالا بردن سرعت کامپایل و سرعت اجرای برنامه هست).
بعد با فرمان release\test.exe برنامه رو تست میکنیم. البته حتما قبلش باید سرور MySQL درحال اجرا باشه (درمورد بنده یعنی EasyPHP اجرا شده باشه).
حالا فقط یادتون باشه وقتی برنامتون رو میخواید توزیع کنید باید علاوه بر dll های معمول و اونهایی که صریحا درخواست میکنه، حتما اینکارها رو هم انجام بدید:
- یک نسخه از libmysql.dll رو همراه برنامه قرار بدید (در دایرکتوری خود برنامه).
- یک دایرکتوری بنام sqldrivers در دایرکتوری برنامه ایجاد کرده و از آدرس E:\Qt2009.05\qt\plugins\sqldrivers فایل qsqlmysql4.dll رو به داخل این دایرکتوری کپی کنید.

معرفی فریمورک اپلیکیشن نویسی Qt ‏(15)

این پست درمورد اینه که چطور برای فایل exe خودمون آیکون بذاریم.
انجام این کار خوشبختانه خیلی ساده است:
- فایل آیکون خودتون رو با فرمت ico ایجاد و در دایرکتوری پروژه ذخیره کنید؛ در اینجا فرض کنید که نامش myappico.ico هست.
- یک فایل متن، در اینجا با نام myapp.rc، ایجاد کنید که خط زیر درونش باشه:

IDI_ICON1 ICON DISCARDABLE "myappico.ico"

- خط زیر رو به فایل پروژه اضافه کنید:

RC_FILE = myapp.rc

- دستورات زیر رو برای تولید make file و فایل اجرایی برنامه اجرا کنید:

qmake
mingw32-make release

معرفی فریمورک اپلیکیشن نویسی Qt ‏(14)

در اینجا میخوایم برای اینکه مطالب مقدماتی کیوت کامل باشن کمی هم درمورد استفاده از رابط های کاربری ای که بصورت ویژوال طراحی شدن صحبت کنیم. این رابطها با نرم افزار Qt Creator ایجاد میشن. البته در نسخه های قدیمی تر کیوت برنامهء جامع Qt Creator وجود نداشت و بجاش فقط برنامهء Qt Designer رو داشتیم. بهرحال در عمل برای کاربرد ایجاد رابطهای کاربری گرافیکی این دوتا فرقی نمیکنن. Qt Creator برنامهء جامعتری برای ایجاد برنامه های دسکتاپ هست که Qt Designer قدیم رو هم درواقع در داخل خودش مجتمع کرده و Qt Designer دیگه یک برنامهء مجزا که بطور مستقل در دسترس باشه نیست.

طرز استفادهء کامل از Qt Creator و طراحی ویژوال رابط گرافیکی توسط اون رو خودتون باید یاد بگیرید و این تاپیک همونطور که از ابتدا گفتم، به این مسائل نمیپردازه. اما من یک مثال کوچک از بکارگیری رابطهای طراحی شده به روش ویژوال رو در اینجا نشون میدم.

Qt Creator رو اجرا کنید.
از منوی File گزینهء New رو انتخاب کنید.
Qt Designer Form رو انتخاب کنید.
Widget رو انتخاب کنید.
یک دایرکتوری برای ذخیره ایجاد/انتخاب کنید.
طراحی ویژوال: یک دکمه روی فرم (صفحهء رابطی که میخواید طراحی کنید) خودتون قرار بدید.
نام این دکمه رو pb بذارید (مقدار فیلد objectName).
خب حالا کار طراحی فرم ما تموم شده و میتونید فرم خودتون رو سیو کرده و برنامهء Qt Creator رو ببندید.

چون من در اینجا میخوام فقط طرز استفاده از فرمهای تولید شده رو در سورس برنامه ها نشون بدم، بقیهء کارها رو به روش خیلی پایه ای استفاده از خط فرمان کیوت انجام میدم، وگرنه میشه بقیهء فایلهای برنامه رو هم با استفاده از Qt Creator ایجاد و در همون محیط کدنویسی کرد و مراحل کامپایل رو هم از طریق Qt Creator براحتی انجام داد (البته خودم هنوز تست نکردم!!).

ضمنا توجه داشته باشید روشی که مطرح میشه یکی از چند روش استفاده از فایلهای تولید شده توسط محیط ویژوال کیوت هست که من این روش رو بخاطر مناسبتر و عمومی تر بنظر اومدن از بین بقیه انتخاب کردم.

الان ما باید یک فایل با پسوند ui در دایرکتوری ای که Qt Creator انتخاب کرد یا ما براش انتخاب کردیم داشته باشیم. این فایل محتوی شرح رابط کاربری طراحی شده توسط شما، در فرمت XML هست. این فایلهای ui یا موقع ساخت برنامه به فایل و کدهای سی++ متناظر ترجمه و بعد کامپایل میشن و یا میتونن موقع اجرای برنامه بطور دینامیک لود و اجرا بشن. ما در اینجا به حالت اول، یعنی تبدیل و کامپایل اولیه میپردازیم چون بنظرم عمومی تر و بهینه تر هست.

فایل من اسمش form.ui هست که در دایرکتوری ای با نام test روی دسکتاپم ذخیره شده.
وقتی من اقدام به ساخت برنامه بکنم یک فایل به نام ui_form.h از روی این فایل ایجاد خواهد شد که محتوی یک کلاس با نام Form خواهد بود که این کلاس کدهای سی++ معادل رابط گرافیکی ای رو که بصورت ویژوال طراحی کردیم در خودش داره.

حالا من یک فایل با نام myui.h در دایرکتوری برنامه ایجاد میکنم که محتویاتش اینهاست:

#include <QWidget>
#include "ui_form.h"

class myUi : public QWidget, private Ui::Form
{
Q_OBJECT

public:
myUi(QWidget *parent = 0) : QWidget(parent)
{
setupUi(this);
}

private slots:
void on_pb_clicked() {
qApp->quit();
}
};

در خط یک و دو اینکلودهای لازم رو انجام دادیم.
ما نیاز داریم یک کلاس درست کنیم (اسم این کلاس رو myUi گذاشتم) که ترکیبی از QWidget یا (هر کلاس رابط گرافیکی دیگری که میخوایم) باشه که فرم ما نیاز داره اون رو بعنوان Parent خودش داشته باشه و بعد بتونیم اجزای دیگری رو هم که میخوایم، بدون نیاز به دستکاری فایل فرم مورد نظر به کلاس خودمون اضافه کنیم؛ بنابراین ما کلاسی میسازیم (خط 4) که ترکیبی از هردوی QWidget و کلاس تعریف شده در ui_form.h باشه.
private در private Ui::Form میگه که اجزای کلاس رابط گرافیکی طراحی شده باید فقط از داخل این کلاس ترکیبی در دسترس باشن (این یک طراحی استاندارد و اصولی بحساب میاد).
فضای نامی Ui هم در ui_form.h تعریف شده؛ بهتره به محتویات ui_form.h خوب نگاه کنید (این فایل موقع اجرای فرمان mingw32-make release تولید میشه). چیز حجیم و پیچیده ای داخلش نیست که قابل درک یا نوشتن توسط خودمون نباشه!
حتما یادتون هست که در تعریف کلاسهایی که در اونها سیگنال و اسلات (Signals and Slots) ایجاد میکنیم به ماکروی Q_OBJECT نیاز داریم. ضمنا یک نکتهء ریز قابل ذکر اینه که فایلی که این ماکرو درش هست باید پسوند h داشته باشه (قبلا در این تاپیک گفتم چرا).
در constructor ما setupUi رو که عضو کلاس تعریف شده در ui_form.h هست فراخوانی میکنیم و کلاس جاری رو که یک شیء رابط کاربری گرافیکی هست (باید از QWidget ارث بری داشته باشه) بعنوان Parent بهش پاس میکنیم.
کار setupUi اینه که اجزای رابط گرافیکی ما رو در کلاس Ui::Form ایجاد میکنه؛ بعد از این مرحله ما به این اجزاء (مثلا دکمهء خودمون) از داخل کلاس خودمون هم دسترسی مستقیم داریم. مثلا میتونیم این کد رو بکار ببریم:

pb->setText("Quit app!");

البته این یه مثال بود و بدیهی هست که برچسب روی دکمه رو میتونستیم در همون Qt Creator براحتی تعیین کنیم.
در خط 16 ما اسلاتی رو تعریف کردیم و الگوی خاص نامگذاری این اسلات (به کلمات اضافه شده در سمت چپ و راست pb توجه کنید) باعث میشه که سیگنال clicked دکمه ای که ما قبلا طراحی کرده بودیم و اسمش رو pb گذاشته بودیم بطور خودکار به این اسلات متصل بشه.
ضمنا کار این اسلات اینه که خیلی ساده باعث خروج از برنامه میشه و بنابراین با کلیک بر روی دکمه، باید برنامه خاتمه پیدا کنه و اینطوری میفهمیم که کار اتصال خودکار سیگنال و اسلات در برنامهء ما واقعا انجام شده.

خب نهایتا ما یک فایل main.cpp هم داریم:

#include <QApplication>
#include "myui.h"

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

myUi ui;
ui.show();

return app.exec();
}

در این فایل که حاوی تابع اصلی و نقطهء شروع و اجرای برنامهء ماست، ما بسادگی یک نمونه از کلاس ترکیبی خودمون رو ایجاد کرده و نمایش دادیم.

حالا فایل اجرایی برنامه رو در خط فرمان کیوت ساخته و اجرا میکنیم.
برای اینکار، خط فرمان کیوت رو اجرا کرده و prompt (اعلان خط فرمان) رو به دایرکتوری test میبریم و بعد فرمانهای زیر رو به ترتیب اجرا میکنیم:

qmake -project
qmake
mingw32-make release
release\test.exe

معرفی فریمورک اپلیکیشن نویسی Qt ‏(13)

این پست یک مثال از تبادل اطلاعات با وب هست.
یک کاربرد جالب و مهم که بعضی وقتا بهش نیاز داریم آپلود کردن یک فایل از روی رایانه به سایت مورد نظر هست.
چون مثالی برای آپلود فایل در رفرنس کیوت نیامده و نمونه کدهایی هم که در اینترنت پیدا کردم با استفاده از کلاس QHttp بود من این مثال رو برای استفاده از QNetworkAccessManager تطبیق دادم.
QNetworkAccessManager کلاسی با رابط سطح بالاتری هست که شما باید در اغلب برنامه هاتون استفاده کنید؛ مگر اینکه نیاز به ارتباط سطح پایینتری برای تبادل اطلاعات با وب داشته باشید که در چنان صورتی از کلاسهای دیگه مثل QHttp استفاده میکنید.

این برنامه یک فایل با نام test.txt رو که در دایرکتوری جاری قرار داره به سرور آپلود میکنه.
البته محدودیتی وجود نداره که فایل ما فقط از نوع متنی باشه؛ هر نوع فایل دیگری میتونه باشه.

فایلهای کد برنامه:

فایل main.cpp:

#include <QApplication>

#include "uploader.h"

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

Uploader u;

return app.exec();
}

فایل uploader.h:

#include <QFile>
#include <QObject>
#include <QNetworkAccessManager>
#include <QUrl>
#include <QNetworkRequest>
#include <qdebug.h>
#include <QNetworkReply>
#include <QBuffer>
#include <QApplication>

class Uploader : public QObject
{
Q_OBJECT

public:

Uploader() {
uploadManager = new QNetworkAccessManager(this);
connect(uploadManager, SIGNAL(finished(QNetworkReply*)),
this, SLOT(uploadFinished(QNetworkReply*)));
postData=new QByteArray;
buffer=new QBuffer(postData, this);
QFile *file=new QFile("test.txt");
file->open(QIODevice::ReadOnly);
boundary = "----xmdjekop098356td----";
(*postData)=QString("--" + boundary + "\r\n").toAscii();
(*postData) += "Content-Disposition: form-data; name=\"test\"; filename=\"test.txt\"\r\n";
(*postData) += "Content-Type: text/plain\r\n\r\n";
(*postData) += file->readAll();
(*postData) += "\r\n";
(*postData) += QString("--" + boundary + "--\r\n").toAscii();
buffer->open(QBuffer::ReadOnly);
QNetworkRequest req(QUrl("http://localhost/upload.php"));
req.setHeader(QNetworkRequest::ContentTypeHeader, "multipart/form-data; boundary="+boundary);
qDebug()<<"uploading result...";
uploadManager->post(req, buffer);
}

public slots:

void uploadFinished(QNetworkReply* r) {
if(r->error()) {
qDebug()<<"Upload network error: "<<r->error();
r->deleteLater();
qApp->exit(1);
return;
}
qDebug()<<"upload reply: "<<r->readAll();
r->deleteLater();
qApp->exit(0);
}

private:
QNetworkAccessManager* uploadManager;
QBuffer* buffer;
QByteArray* postData;
QString boundary;
};

محتویات فایل پروژه:

TEMPLATE = app
TARGET =
DEPENDPATH += .
INCLUDEPATH += .

# Input
HEADERS += uploader.h
SOURCES += main.cpp
QT+=network
CONFIG+=console

همونطور که میبینید در دو خط آخر فایل پروژه قابلیت شبکه و خروجی خط فرمان رو به برنامه اضافه کردیم.
اضافه کردن QT+=network برای بکار بردن ماجول شبکهء کیوت هست که در برنامه هایی که ارتباط شبکه ای دارن اضافه کردن این فرمان به فایل پروژه ضروری است و نیاز دارید تا بصورت دستی اضافه کنید (مگر اینکه از محیط گرافیکی طراحی کیوت برای تعریف پروژه استفاده کرده باشید و در اونجا مشخص کرده باشید که برنامهء کارکرد شبکه ای داره).
اضافه کردن امکان خروجی خط فرمان هم در اینجا صرفا بخاطر این هست که با خروجیهای چاپ شده توسط برنامه متوجه بشیم برنامه چکار میکنه و عملیات با موفقیت انجام شده یا خیر. مشخصه که معمولا برای برنامه در مراحل آزمایش یا مثالها، بعلت صرفه جویی زیاد در وقت و انرژی برنامه نویس، از رابط خط فرمان برای خروجی و گزارش وضعیت برنامه استفاده میکنیم.

اینهم نمونهء بسیار ساده شده ای از برنامهء سمت سرور ما که فایل آپلود شده رو دریافت و ذخیره میکنه.
شما برای تست برنامهء آپلودر کیوت به چنین اسکریپت یا برنامهء دیگری هم در سمت سرور نیاز دارید.
نام فایل upload.php هست که در دایرکتوری ریشهء localhost ذخیره میکنیم:

<?php

if (move_uploaded_file($_FILES['test']['tmp_name'], 'test.txt')) echo 'uploaded successfully.';
else echo 'upload failed!';

?>

معرفی فریمورک اپلیکیشن نویسی Qt ‏(12)

حتما تابحال برنامه هایی رو دیدید که موقعی که میخوایم سیستم رو shutdown یا logoff بکنیم پیام میدن و مثلا میگن فایلهای شما save نشده و میخواید چکار کنید.
حداقل بعضی از این برنامه ها (مثلا نوتپد ویندوز) حتی این امکان رو هم میدن که عملیات shutdown یا logoff رو cancel کنیم.
شما میتونید بوسیلهء کیوت هم چنین امکانی رو در برنامه هاتون قرار بدید.
این یک نمونه کد و برنامهء ساده هست که چگونگی انجام اینکار رو نشون میده:

#include <QApplication>
#include <QMessageBox>
#include <QSessionManager>
#include <QLabel>

class MyApp : public QApplication
{
public:

MyApp(int argc, char *argv[]) : QApplication(argc, argv) {}

void commitData(QSessionManager& manager) {
if(manager.allowsErrorInteraction()) {
int ret = QMessageBox::warning(
0, "My Application", "Realy quit and shutdown/log off?", QMessageBox::Yes|QMessageBox::No
);
if(ret==QMessageBox::No) manager.cancel();
}
else {
// we did not get permission to interact, then
// do something reasonable instead
}
}
};

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

QLabel lbl("This is my Qt application.");
lbl.show();

return app.exec();
}

همونطور که میبینید، ما تابع QApplication::commitData رو Reimplement کردیم.
برای آگاهی بیشتر به مستندات کیوت مراجعه کنید.
چند نکته که من در آزمایشهای خودم بدست آوردم اینهاست:
- این روش تنها برای شات داون یا لاگ آف از طریق محیط گرافیکی کار میکنه و مثلا اگر فرمان شات داون از خط فرمان فراخوانی شده باشه برنامه بدون امکان مقاومت بسته میشه.
- اگر برنامهء شما از نوع خط فرمان باشه یا اصولا در پس زمینه اجرا میشه، باید حداقل یک عنصر GUI در برنامتون قرار بدید تا این روش بازهم کار کنه. برای اینکه این جزء گرافیکی دیده نشه و در برنامه های خط فرمان یا مخفی و پس زمینهء ما اختلالی ایجاد نکنه من چنین ترفندی رو بکار گرفتم:

#include <QApplication>
...
#include <QSplashScreen>

class MyApp : public QApplication
{
...
};

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

QSplashScreen* spl=new QSplashScreen;
spl->show();
delete spl;

return app.exec();
}

معرفی فریمورک اپلیکیشن نویسی Qt ‏(11)

این یک برنامهء کوچکه که با Qt نوشته شده.
یک بازی ساده هست که در بخش خودآموز کیوت بعنوان مثال آورده شده بود؛ اما ویژگیهای متعددی رو که بعضی از اونها بعنوان تمرین مطرح شده بودن بهش اضافه کردم.
شاید بتونه بعنوان یک نمونه، قابلیت ها و انعطاف کیوت و همچنین رابطهء اونها با حجم کد رو نشون بده. از امکانات متنوعی از کیوت درش استفاده شده که باعث میشه مثال کوچک خوبی بنظر برسه.

کد منبع: qtcannon-src.zip

فایل اجرایی (ویندوز): qt_cannon-bin.zip

معرفی فریمورک اپلیکیشن نویسی Qt ‏(10)

میشه گفت کیوت سه روش برای ایجاد تایمر داره؛ QTimer، QBasicTimer و تایمر درونی QObject.
تایمر عمومی و استاندارد در کیوت QTimer هست که امکانات و انعطاف لازم رو برای تایمرهای حرفه ای مختلف داره.
اما گاهی نیاز به یک تایمر داریم که بسادگی تابعی رو در فواصل زمانی معین اجرا بکنه و برنامهء ما احتمالا ساده تر و کوچکتر از اونه که بخوایم یک کلاس اختصاصی با اسلات (Slot) مخصوص تایمر تعریف کنیم و تعریف اون رو در یک فایل جداگانه قرار بدیم و بعد یک QTimer رو هم به برنامه اضافه کرده و به اسلات کلاس خودمون متصل کنیم. احتمالا برنامهء ما کوچک و ساده هست یا یک برنامهء آزمایشی یا برای تست چیزی و میخوایم سریع و راحت و مختصر در تنها یک فایل تمام برنامهء خودمون رو گنجانده و کامپایل کنیم.
در اینگونه موارد میتونیم از تایمر درونی QObject استفاده کنیم. هر کلاس مشتق شده از QObject که عملا شامل تمامی کلاسهای خود کیوت هم میشه دارای امکان یک تایمر داخلی ساده هست.
این تایمر با تابع startTimer در QObject بکار میفته، و با فاصلهء زمانی تعیین شده تابع timerEvent رو مکرارا فراخوانی میکنه.

بعنوان نمونه این کد:

#include <QApplication>
#include <QLabel>
#include <QBasicTimer>

class TimerTest : public QObject
{

public:
TimerTest(QWidget *p, float t) {
this->p=p;
startTimer(int(t*1000));
}

void timerEvent(QTimerEvent *) {
p->setVisible(!p->isVisible());
}

QWidget *p;

};

int main(int argv, char **args) {
QApplication app(argv, args);

QLabel l("Blinking...");
l.setAlignment(Qt::AlignHCenter);
l.show();
TimerTest t(&l, .5);

return app.exec();
}

یک پنجرهء کوچک ایجاد میکنه که هر نیم ثانیه ظاهر و ناپدید میشه (نکته: چون این تنها پنجره و بنابراین پنجرهء اصلی برنامهء ما هم هست، دکمه برنامهء ما در Taskbar ویندوز هم هر بار با نامریی شدن پنجره حذف شده و با پدیدار شدنش دوباره ظاهر میشه).
همونطور که میبینید ما کلاس TimerTest خودمون رو از QObject مشتق کردیم تا امکانات QObject رو، در اصل تنها بخاطر تایمر درونی اون، به کلاس خودمون اضافه کنیم.

——————————

کلاس QBasicTimer هم خیلی شبیه به تایمر درونی QObject کار میکنه و تابع timerEvent شیء مورد نظر رو (که باید از QObject ارث بری داشته باشه) فراخوانی میکنه؛ اما بصورت یک کلاس جداگانه تعریف شده.
در مستندات این کلاس آمده که QBasicTimer در اصل برای کاربردهای داخلی خود کیوت طراحی شده (ظاهرا بخاطر پرفورمنس، از QTimer سبکتر و سریعتر هست) و بهتره برنامه نویسان از QTimer در برنامه های خودشون استفاده کنن. اما خب بهرحال این کلاس و روش تایمر داخلی QObject هم در دسترس برنامه نویسان هست و بدون مشکل کار میکنه و ضمنا همونطور که اشاره کردم مزایایی داره و در شرایط خاصی میتونه مناسبتر باشه.
توجه داشته باشید که ما از این روشهای دیگر استفاده نمیکنیم چون سبکتر و سریعتر هستن، چون این مقادیر در برنامه های برنامه نویسان اونقدری ناچیز هستن که بحساب نمیان و یک کد منعطف و خوانا خیلی بهتر از کم کردن منابع مصرفی برنامه به مقدار ناچیز هست؛ اما ما از اونها در ساده تر کردن و سریعتر کردن کدنویسی و ساخت برنامه های خیلی کوچک یا آزمایشی و یا شرایط خاص دیگر استفاده میکنیم، چون وقت و انرژی برنامه نویس حرفه ای از منابع بسیار ارزشمند محسوب میشن. البته اینها نظر و توجیه بنده برای استفاده از این روشهای نه چندان استاندارد هست!

معرفی فریمورک اپلیکیشن نویسی Qt ‏(9)

برای ساده ترین حالت پخش صوت و با فرمت wav در برنامه های کیوت میتونیم از کلاس QSound استفاده کنیم. بنظرم مثلا موقعیکه فقط نیاز داریم که دکمه هایی که کاربر کلیک میکنه صدای کوتاهی بدن و غیره.
اما برای کارهای پیچیده تر و پشتیبانی از فرمتهای دیگر، همونطور که گفتم باید از Phonon استفاده کنیم که خوشبختانه موفق به مجهز کردن کیوت نسخهء اپن سورس با MinGW به این امکان خاص شدیم.

من فعلا تنها مورد QSound رو معرفی میکنم:

#include <QApplication>
#include <QSound>
#include <QLabel>
#include <QDebug>

int main(int argv, char **args) {
QApplication app(argv, args);

QSound::play("test.wav");

QLabel l("do you hear the sound?");
l.show();

return app.exec();
}

در اینجا ما از متد استاتیک QSound برای پخش صوت استفاده کردیم که نتیجتا بلافاصله با اجرای برنامه صدا هم پخش میشه.
اما برای کنترل بیشتر، میتونیم یک شیء QSound با نام فایل مورد نظر ایجاد کنیم و بعد از متدهای play و stop استفاده کنیم که چون این متدها از نوع SLOT هستن میتونیم مثلا یک دکمه رو مستقیما به اونها اتصال بدیم (با استفاده از متد استاتیک QObject::connect که ضمنا در تمام کلاسهای کیوت هم بخاطر مشتق شدگی از QObject بهش دسترسی مستقیم داریم).

معرفی فریمورک اپلیکیشن نویسی Qt ‏(8)

ما تابع qDebug رو در کیوت برای فرستادن پیامهای خروجی که معمولا موقع برنامه نویسی و تست برنامه لازم داریم استفاده میکنیم. این پیامها ممکنه بعد از تکمیل و تست برنامه از برنامه برداشته بشن، و البته شاید گاهی هم نه و این پیامها جزیی از پیامهای خطای دایمی برنامه باشن.
بهرحال ما این تابع رو تقریبا بصورت تابع printf در زبان C بکار میبریم. مثلا:

qDebug("variable abc has a zero value");
qDebug("v1=%d, v2=%d", v1, v2);

ما یک فرم دیگه رو هم از این تابع میتونیم بکار ببریم که گاه راحتتره و شبیه cout در زبان C++‎ عمل میکنه.
اما برای اینکه بتونیم از این شکل هم در برنامه هامون استفاده کنیم باید فایل header خاصی رو به برنامه اضافه کنیم:

#include <QtDebug>

حالا میتونیم مثلا اینطور بنویسم:

qDebug()<<"condition 1"<<v1<<v2;

دقت کنید که پرانتزها ضروری هستن.
در این فرم از بیشتر انواع دادهء هم سی++ و هم کیوت میشه استفاده کرد.

خب تا اینجا دیدیم که چطور میشه پیامهایی رو به خروجی فرستاد. کاری که ظاهرا میشه توسط printf و cout هم که آشنای برنامه نویسان سی و سی++ هستن انجام داد. البته مشخصا یک مزیت استفاده از qDebug این هست که نیازی به درج اینکلود هدر سی و سی++ نداریم و یک مزیت دیگر هم اینکه با انواع داده ای خاص کیوت میشه راحتتر کار کرد و نیاز به تبدیل ندارن؛ چیزی که ممکنه درمورد توابع printf و cout نیاز بشه (مثلا احتمالا درمورد رشته های کیوت که از نوع یونیکد هستن).

مسلمه که ما فقط موقعی میتونیم از خروجی متنی در برناممون کمک بگیریم که برنامهء ما یک رابط خط فرمان داشته باشه؛ یعنی ما دستور CONFIG+=console رو به فایل پروژه اضافه کرده باشیم.
البته درصورتیکه برنامه در یک محیط IDE که امکانات دیباگ داره بکار بره، این خروجی مستقیما به دیباگر ارسال میشه و بنظرم دیگه نیازی به داشتن خط فرمان ندشته باشیم (تست نکردم، ولی در مستندات ظاهرا اینطور نوشته).

حالا ممکنه مثلا گاهی ما بخوایم خط فرمان نداشته باشیم یا اینکه اصولا این دستورات خروجی (توسط qDebug) در برنامه باقی بمونن و جزیی از پیامهای خطای برنامه باشن که میخوایم موقعی که برنامه رابط خط فرمان هم نداره، اطلاع داده بشن.
به این منظور میتونیم اینطور عمل کنیم:

#include <QErrorMessage>

و بعد در اوایل بدنهء برنامه:

QErrorMessage::qtHandler();

این باعث میشه پیامهای qDebug بجای رفتن به کنسول (محیط متنی و خط فرمان) یا دیباگر، توسط یک پنجرهء گرافیکی که به کاربر اجازه میده انتخاب کنده که پیام یکسانی دوباره نمایش داده نشه، به اطلاع کاربر برسه.

راستی از QErrorMessage میشه بصورت مستقل و بدون اینکه مجبور باشیم اون رو بعنوان نمایش دهندهء پیامهای qDebug تنظیم کنیم هم استفاده کرد. مثلا به اینصورت:

QErrorMessage errOut;
errOut.showMessage("application's config file is missing!");