Development/Tutorials/Saving and loading (fa)

From KDE TechBase


ذخیره و بازیابی
سری آموزشی   آموزش مقدماتی
پیش‌نیازها   آموزش ۳ - KActionها
پس از این   آموزش ۵ - استفاده از KCmdLineArgs
مطالعه‌ی بیشتر   KIO::NetAccess QFile

چکیده

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

‏KDE برای کار با فایل‌ها کلاس‌هایی دارد که کار را برای برنامه‌نویسان بسیار آسان می‌سازد. کتاب‌خانه‌ی KIO به شما امکان می‌دهد به سادگی به فایل‌ها دسترسی پیدا کنید. هم‌چنین با کمک KIO می‌توانید از کادرهای استاندارد بازکردن و ذخیره‌سازی فایل‌ها در برنامه‌تان استفاده کنید.

کدهای منبع

main.cpp

#include <KApplication>
#include <KAboutData>
#include <KCmdLineArgs>

#include "mainwindow.h"

int main (int argc, char *argv[])
{
  KAboutData aboutData( "tutorial4", "tutorial4",
        ki18n("Tutorial 4"), "1.0",
        ki18n("A simple text area which can load and save."),
        KAboutData::License_GPL,
        ki18n("Copyright (c) 2007 Developer") );
  KCmdLineArgs::init( argc, argv, &aboutData );
  KApplication app;

  MainWindow* window = new MainWindow();

  window->show();

  return app.exec();
}

main.cpp نسبت به آموزش قبل هیچ تغییری نکرده فقط tutorial3ها به tutorial4 تغییر داده‌شده‌اند.

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <KXmlGuiWindow>
#include <KTextEdit>

class MainWindow : public KXmlGuiWindow
  Q_OBJECT

{
  public:
    MainWindow(QWidget *parent=0);

  private:
    KTextEdit* textArea;
    void setupActions();
	QString fileName; //new

  private slots:
	void newFile(); //new
	void openFile(); //new
	void saveFile(); //new
	void saveFileAs(); //new
	void saveFileAs(const QString &outputFileName); //new
};

#endif

از آنجایی که می‌خواهیم قابلیت‌های ذخیره و بازیابی فایل‌ها را به برنامه بیافزاییم، باید توابعی اضافه کنیم که این کارها را برایمان انجام دهند. چون این توابع از طریق مکانیزم signal/slot مربوط به Qt فراخوانی می‌شوند، باید آن‌ها را به عنوان slot معرفی کنیم؛ که در سطر ۱۹ همین اتفاق می‌افتد. به علاوه، چون در این کلاس slot تعریف می‌کنیم، باید ماکروی Q_OBJECT را به تعریف آن اضافه کنیم.

هم‌چنین در طول برنامه، باید نام فایلی که کاربر باز کرده‌است را بدانیم؛ بنابراین متغیر {qt|QString} fileName را اعلان می‌کنیم.

mainwindow.cpp

#include "mainwindow.h"

#include <KApplication>
#include <KAction>
#include <KLocale>
#include <KActionCollection>
#include <KStandardAction>
#include <KFileDialog> //new
#include <KMessageBox> //new
#include <KIO/NetAccess> //new
#include <KSaveFile> //new
#include <QTextStream> //new

MainWindow::MainWindow(QWidget *parent)
  : KXmlGuiWindow(parent),
	fileName(QString()) //new
{
  textArea = new KTextEdit;
  setCentralWidget(textArea);

  setupActions();
}

void MainWindow::setupActions()
{
  KAction* clearAction = new KAction(this);
  clearAction->setText(i18n("Clear"));
  clearAction->setIcon(KIcon("document-new"));
  clearAction->setShortcut(Qt::CTRL + Qt::Key_W);
  actionCollection()->addAction("clear", clearAction);
  connect(clearAction, SIGNAL(triggered(bool)),
          textArea, SLOT(clear()));

  KStandardAction::quit(kapp, SLOT(quit()),
                        actionCollection());

  KStandardAction::open(this, SLOT(openFile()),
                        actionCollection()); //new

  KStandardAction::save(this, SLOT(saveFile()),
                        actionCollection()); //new

  KStandardAction::saveAs(this, SLOT(saveFileAs()),
						actionCollection()); //new

  KStandardAction::openNew(this, SLOT(newFile()),
						actionCollection()); //new

  setupGUI();
}

//New from here on

void MainWindow::newFile()
{
	fileName.clear();
	textArea->clear();
}

void MainWindow::saveFileAs(const QString &outputFileName)
{
	KSaveFile file(outputFileName);
	file.open();

	QByteArray outputByteArray;
	outputByteArray.append(textArea->toPlainText().toUtf8());
	file.write(outputByteArray);
	file.finalize();
	file.close();

	fileName = outputFileName;
}

void MainWindow::saveFileAs()
{
	saveFileAs(KFileDialog::getSaveFileName());
}
 
void MainWindow::saveFile()
{
	if(!fileName.isEmpty())
	{
		saveFileAs(fileName);
	}
	else
	{
		saveFileAs();
	}
}
 
void MainWindow::openFile()
{
	QString fileNameFromDialog = KFileDialog::getOpenFileName();
 
	QString tmpFile;
	if(KIO::NetAccess::download(fileNameFromDialog, tmpFile,
			this))
	{
		QFile file(tmpFile);
		file.open(QIODevice::ReadOnly);
		textArea->setPlainText(QTextStream(&file).readAll());
		fileName = fileNameFromDialog;
		 
		KIO::NetAccess::removeTempFile(tmpFile);
	}
	else
	{
		KMessageBox::error(this,
			KIO::NetAccess::lastErrorString());
	}
}

tutorial4ui.rc

<?xml version="1.0" encoding="UTF-8"?>
<gui name="tutorial4"
     version="1"
     xmlns="http://www.kde.org/standards/kxmlgui/1.0"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://www.kde.org/standards/kxmlgui/1.0
                         http://www.kde.org/standards/kxmlgui/1.0/kxmlgui.xsd" >

  <MenuBar>
    <Menu name="file" >
      <Action name="clear" />
    </Menu>
  </MenuBar>

  <ToolBar name="mainToolBar" >
    <text>Main Toolbar</text>
    <Action name="clear" />
  </ToolBar>

</gui>

این فایل هم دقیقا مشابه tutorial3ui.rc از آموزش ۳ است. تنها نام آن را به tutorial4 تغییر داده‌ایم. نیازی به هیچ‌گونه اطلاعات اضافی از KStandardActionها در این فایل نیست. جای هر یک از گزینه‌ها در منوها را به طور خودکار KDE تنظیم می‌کند.

شرح

خوب. پس می‌خواهیم برنامه‌ای بنویسم که ذخیره و بازیابی انجام دهد. این کار در mainwindow.cpp اتفاق می‌افتد.

نخستین کاری که انجام می‌دهیم، اضافه‌کردن

fileName(QString())

به تعریف تابع سازنده‌ی MainWindow در سطر ۱۶ است. با این کار، اطمینان حاصل می‌کنیم که fileName از همان ابتدا تهی است.

افزودن actionها

در اینجا، ارائه‌ی یک رابط بیرونی به کاربران برنامه، اولین کاری خواهد بود که می‌خواهیم انجام دهیم. رابط کاربر برنامه به کاربران اجازه می‌دهد به برنامه بگویند فایل را باز کن، ذخیره کن یا فایل جدیدی بساز. به مانند گزینه‌ی quit در آموزش ۳، از KStandardActionها استفاده می‌کنیم. در طول سطرهای ۳۷ تا ۴۷ actionهای جدید را به منو اضافه می‌کنیم و هر کدام را به slot مناسبی که در فایل سرایند تعریف کرده‌ایم، پیوند می‌دهیم.

ساختن یک فایل جدید

اولین تابعی که می‌نویسیم، تابع newFile() است.

void MainWindow::newFile()
{
  fileName.clear();
  textArea->clear();
}

fileName.clear() مقدار متغیر fileName را تهی قرار می‌دهد. این کار، نمودی از این حقیقت است که فایل هنوز بر روی دیسک وجود ندارد. آن‌گاه textArea->clear() کادر متنی وسط پنجره را خالی می‌کند. این همان تابعی است که به clearAction در آموزش ۳ پیوند زدیم.

ذخیره‌کردن یک فایل

saveFileAs(QString)

حالا می‌خواهیم اولین کدی که با فایل‌ها سر و کار دارد، بنویسیم. می‌خواهیم تابعی پیاده‌سازی کنیم که نوشته‌های درون کادر متن را در یک فایل، با نامی که به عنوان پارامتر رد می‌کنیم، ذخیره کند. KDE کلاسی در اختیار ما می‌گذارد که به کمک آن بتوانیم با اطمینان فایلی را ذخیره کنیم. این کلاس KSaveFile نام دارد و از کلاس QFile متعلق به Qt مشتق شده است.

اعلان تابع به این صورت است:

void MainWindow::saveFileAs(const QString &outputFileName)

سپس شیء KSaveFile خود را می‌سازیم و باز می‌کنیم:

KSaveFile file(outputFileName);
file.open();

حالا که فایلمان باز شده و آماده‌ی نوشتن است، باید نوشته‌های درون کادر متنی را به قالبی درآوریم که بتوان آن را در فایل نوشت. برای این کار، یک QByteArray می‌سازیم و آن را با متن ساده‌ی درون کادر متن پر می‌کنیم:

QByteArray outputByteArray;
outputByteArray.append(textArea->toPlainText().toUtf8());

حالا با استفاده از تابع KSaveFile::write()، آرایه‌ی ساخته شده را در فایل می‌نویسیم. اگر از یک QFile استفاده می‌کردیم، تغییرات بلافاصله در فایل ذخیره می‌شدند. با این کار، اگر در میانه‌ی نوشتن مشکلی پیش بیاید، فایل خراب می‌شود. به همین دلیل، KSaveFile ابتدا در یک فایل موقتی می‌نویسد و سپس هنگامی که KSaveFile::finalize() را صدا می‌زنید، تغییرات در فایل واقعی اعمال می‌شوند.

file.write(outputByteArray);
file.finalize();
file.close();

در پایان، مقدار عضو داده‌ی fileName در کلاس MainWindow را به نام فایلی که نوشته‌ها را در آن ذخیره کردیم، تغییر می‌دهیم.

fileName = outputFileName;

saveFileAs()

این تابعی است که اسلات saveAs به آن پیوند خورده‌است. این تابع تنها تابع عمومی saveFileAs(QString) را با مقدار برگشتی تابع KFileDialog::getSaveFileName() به عنوان آرگومان فرا می‌خواند.

void MainWindow::saveFileAs()
{
  saveFileAs(KFileDialog::getSaveFileName());
}

این اولین استفاده‌ی واقعی ما از کتاب‌خانه‌ی KIO است. KFileDialog تعدادی تابع استاتیک در اختیار ما می‌گذارد تا بتوانیم کادرهای محاوره‌ای عمومی فایل (یعنی همان کادرهای Open/Save File) را نمایش دهیم. با فراخوانی KFileDialog::getSaveFileName()، پنجره‌ای باز می‌شود که از آن‌جا کاربر می‌تواند برای فایل خود نامی برگزیند. تابع نام کامل فایل (همراه با مسیر ذخیره‌سازی) برمی‌گرداند. این مقدار بازگشتی، سپس، به تابع saveFileAs(QString) رد می‌شود.

saveFile()

void MainWindow::saveFile()
{
  if(!fileName.isEmpty())
  {
    saveFileAs(fileName);
  }
  else
  {
    saveFileAs();
  }
}

در این تابع چیز چندان جالب توجهی وجود ندارد؛ فقط منقطی که بر اساس آن تصمیم بگیریم پنجره‌ی ذخیره‌ی فایل را نمایش بدهیم یا نه. اگر fileName خالی نباشد، در این صورت فایل با همان نام fileName ذخیره خواهد شد. در غیر این صورت پنجره‌ی ذخیره‌ی فایل برای کاربر به نمایش در می‌آید تا بتواند نامی انتخاب کند.

بازکردن یک فایل

در پایان، باید به برنامه قابلیت بازیابی فایل‌ها بدهیم. همه‌ی کد این کار در تابع MainWindow::openFile() قرار گرفته است.

اول باید از کاربر بپرسیم نام فایلی که می‌خواهد باز کند چیست. این کار را با استفاده از تابع دیگری از KFileDialog انجام می‌دهیم؛ این بار getOpenFileName():

QString fileNameFromDialog = KFileDialog::getOpenFileName();

حالا از کتاب‌خانه‌ی KIO استفاده می‌کنیم تا فایل را دریافت کنیم. KIO به ما امکان می‌دهد فایل را به صورت یک QFile دریافت کنیم؛ حتی اگر فایل در یک ارتباط راه دور مانند یک FTP قرار داشته باشد. تابع download از NetAccess را به صورت زیر فرا می‌خوانیم:

KIO::NetAccess::download(fileNameFromDialog, tmpFile, this)

اولین آرگومان، نام فایلی است که می‌خواهیم دریافت کنیم. دومی یک متغیر QString است که پس پایان دریافت، مکان کپی موقت فایل را در خود خواهد داشت. از این به بعد با همین tmpFile سر و کار خواهیم داشت.

خروجی تابع، بسته به موفقیت آمیز بودن یا نبودن عملیات، true یا false خواهد بود. اگر عملیات با شکست مواجه شد، پیامی حاوی خطای رخ داده نمایش می‌دهیم:

KMessageBox::error(this, KIO::NetAccess::lastErrorString());

در غیر این صورت، به باز کردن فایل ادامه می‌دهیم.

یک QFile می‌سازیم و فایل موقتی که NetAccess::download() ساخته را به تابع سازنده‌اش رد می‌کنیم. هم‌چنین مشخص می‌کنیم که فایل به صورت «فقط خواندنی» باز شود.

QFile file(tmpFile);
file.open(QIODevice::ReadOnly);

برای نمایش محتویات فایل، باید از یک QTextStream استفاده کنیم. با رد کردن اشاره‌گر file به تابع سازنده‌ی QTextStream یک نمونه می‌سازیم و سپس تابع readAll() آن را فراخوانی می‌کنیم تا نوشته‌ها را از فایل بخوانیم. سپس آن را به تابع setPlainText() مربوط به کادر متنی رد می‌کنیم.

textArea->setPlainText(QTextStream(&file).readAll());

سپس مسیر فایل بازشده را در fileName نگه‌داری می‌کنیم:

fileName = fileNameFromDialog;

و نهایتا فایل موقتی که NetAccess::download() ساخته است را پاک می‌کنیم:

KIO::NetAccess::removeTempFile(tmpFile);

ساخت، نصب و اجرا

CMakeLists.txt

project(tutorial4)
 
find_package(KDE4 REQUIRED)
include_directories(${KDE4_INCLUDES})
 
set(tutorial4_SRCS 
  main.cpp
  mainwindow.cpp
)
 
kde4_add_executable(tutorial4 ${tutorial4_SRCS})
 
target_link_libraries(tutorial4 ${KDE4_KDEUI_LIBS} 
                                ${KDE4_KIO_LIBS})
 
install(TARGETS tutorial4 DESTINATION ${BIN_INSTALL_DIR})
install(FILES tutorial4ui.rc 
        DESTINATION ${DATA_INSTALL_DIR}/tutorial4)

چون حالا از کتاب‌خانه‌ی KIO استفاده می‌کنیم، باید به CMake بگوییم آن را هم با برنامه‌مان لینک کند. این کار را با رد کردن ${KDE4_KIO_LIBS} به تابع target_link_libraries() انجام می‌دهیم.

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

 mkdir build && cd build
 cmake .. -DCMAKE_INSTALL_PREFIX=$HOME
 make install
 $HOME/bin/tutorial4

به پیش

حالا می‌توانید به سوی آموزش KCmdLineArgs پیش بروید.