このブログを検索

ラベル

C# (6) Objective-C (2) Qt (2) .NET (1) Visual Studio (1)

2011年5月29日日曜日

Objective-Cのメモ#2

 私は日常ではC#を使用していますので、C#使用者から見たObjective-Cの目新しい点についてメモしておきます。
ちなみに、参考書は掌田津耶乃著「Objective‐C 2.0徹底解説」です。



カテゴリ
クラスをいくつかのカテゴリに分け、それらを総合的にひとまとめにしてクラスを構成する機能です。メソッドを追加する定義のファイルにはクラス名(カテゴリ名)と記述します。
インスタンス変数はカテゴリに分けることができないのでC#で言うところのpartialクラスと言うよりは、拡張メソッドに近い感じでしょうか。


以下の例では、test.h/test.mに定義したtestクラスに対して、test+.h/test+.mでaddMethodメソッドを追加しています。カテゴリ名は任意で良いので、addとしています。
test.h

test.m

test+.h

test+.m

main.m

Qtの覚え書き

これは、Qtを勉強した時の覚え書きです。

シグナルとスロット 

  • Qtのwidgetは、ユーザが操作を行ったり状態が変化するとsignalを送信します。
  • signalはslotに結びつけることができます。
  • signalが送信されると、結びつけられているslotが自動的に実行されます。
  • シグナルとスロットを使用するにはQ_OBJECTマクロの宣言が必要です。

例 



class MyDialog : public QDialog
{
  Q_OBJECT
 
private:
  QPushButton *okButton;
 
signals:
  void okSignal();
 
private slots:
  void okClicked()
  {
    emit okSignal();
  };
 
public:
  MyDialog(QWidget *parent = 0)
  {
    connect(okButton, SIGNAL(clicked()), this, SLOT(okClicked()));
  };
};

上記の例は、以下の流れで動作します。
  1. okButtonボタンのclicked()シグナルがMyDialogクラスのokClicked()スロットに接続されています。
  2. okButtonボタンがクリックされると、(QPushButtonクラスの)clicked()シグナルが送信されます。
  3. okButtonボタンのclicked()シグナルをMyDialogクラスのokClicked()スロットが受信します。
  4. okClicked()スロットは、MyDialogクラスのokSignal()シグナルを送信します。

詳細 
  • スロットは通常のC++メンバ関数とほぼ同じです。
    • 仮想関数にできます。
    • オーバーロードできます。
    • public、protected、privateのどれでも可能です。
    • 直接呼び出せます。
  • connect()メソッドの呼び出し方法
    connect(sender, SIGNAL(signal), receiver, SLOT(slot));
  • senderとrecieverはQObjectへのポインタです。
  • signalとslotは関数宣言から引き数名を省略したものです。
    slots:
      void slotFunc(int num, QObject* obj);
    なら、connect()には、以下のように記述します。
    SLOT(slotFunc(int, QObject*)
  • 複数のシグナルを1つのスロットに接続できます。
  • 「on_オブジェクト名_シグナル名()」というスロットを記述すると、「オブジェクトのシグナル名()」というシグナルに自動的に接続されます。
    slots:
      on_lineEdit_textChanged();
    と記述されていると、
    connect(lineEdit, SIGNAL(textChanged(const QString&)), this, SLOT(on_lineEdit_textChanged()));
    が自動的に生成されます。

暗黙の共有 

例えばQStringを以下のように記述すると、



QString str1 = "Hampty"; 
QString str2 = str1;     //(1)
str2[0] = 'D';           //(2)
str1 = str2;             //(3)

(1)でstr1とstr2は同じメモリ領域を指します。

(2)でstr2は"Dumpty"となりますが、str1は"Humpty"のままになります。

(3)でstr1が指していた"Humpty"は不要になり、自動的にメモリから解放されます。

動作的にはJavaと同じようです。

また、イテレータを作るときも暗黙の共有が働くため、低いコストでイテレータを作成できます。



QListIterator<int> i(list);


アプリケーションの翻訳 

ユーザが目にする全ての文字列を必ずtr()に渡すようにします。後で国際化しやすくなります。

tr()を使うには、Q_OBJECTマクロの宣言が必要です。



label = new QLabel(tr("文字列"));


ショートカットキー 

ボタンのtextやグループボックスのtitleプロパティの文字列に&をつけるとショートカットキーになります。

例えば、ボタンのtextを「&OK」とするとAlt+Oでボタンを押すことができます。

Widgetの理想的なサイズを取得 

QWidget::sizeHint()関数で得られます。

QObjectのリソース管理 

QObjectは、親子関係を構築できます。親を破棄すると、子オブジェクトのリストを走査して、全ての子を破棄します。親オブジェクトを破棄する前に、子オブジェクトを破棄しても問題ありません。



QObject* qobj = new QObject();
  MyClass* tmp = MyClasstest();
 
  tmp->setParent(qobj);
 
  delete qobj;  //ここでtmpのデストラクタも呼ばれます。


リソースファイル 

  • :/を先頭につけてリソースファイルを指定します。
    setWindowIcon(QICON(":/images/icon.png"));
  • リソースファイルはXMLフォーマットですが、Qt Creatorを使えばGUIで簡単に編集できます。

ウィンドウを閉じるのを中止する 

QWidget::closeEvent()をオーバーライドします。



void closeEvent(QCloseEvent* event){
  …
  event->ignore();  //閉じるの中止
  …
}


ウィンドウを閉じてもアプリケーションを終了させない 

QApplicationのquitOnLastWindowClosedプロパティをfalseにします。



int main(int argc, char *argv[])
{
  QApplication a(argc, argv);
  a.quitOnLastWindowClosed(false); //これ
  MainWindow w;
  w.show();
  return a.exec();
}


QString 


比較 

==演算子で行います。

arg()関数 

arg()関数は、QStringの文字列の最も小さい数字の%nを引数で置き換えて、結果の文字列を返します。



tr("%1の%2").arg(first).arg(tr("abc"));


number()関数 

数値を文字列に変換します。



QString str = QString::number(59.6);


文字列を数値に変換する 

toInt()、toLongLong()、toDouble()などがあります。



bool ok;
double d = str.toDouble(&ok); //変換に成功/失敗するとokにtrue/falseが入ります


mid()関数、left()関数、right()関数 




str.mid(9,4); //10文字目から4文字取り出す
str.mid(9);   //10文字目以降を取り出す
str.left(8);  //先頭から8文字取り出す
str.right(9); //末尾から9文字取り出す


indexOf()関数 

文字列がある特定の文字を含んでいるか、正規表現にマッチするかどうかを調べます。戻り値は見つかった位置です。



int i = str.indexOf("middle");


startsWith()関数、endsWith()関数 

文字列がある文字列で始まっている、または終わっているかどうかを調べます。



if(uri.startsWith("http:") && uri.endsWith(".png"))…


replace()関数 

文字列を置換します。



str.replace(2, 6, "sunny"); //3文字目から6文字を"sunny"で置換します


trimmed()関数 

文字列の両端のホワイトスペースを削除します。



str.trimmed();


simplified()関数 

連続したホワイトスペースを一つの空白にします。



str.simplified();


split()関数 

文字列を分割し、QStringListに格納します。
QStringList words = str.split("\t"); //文字列の分割
QString list2str = words.join("\t"); //文字列リストの結合

const char*に変換する 




str.toAscii().data();
qPrintable(str);      //こちらでもOK


ウィンドウをモーダルに表示する 

ダイアログをshow()で表示するとモードレスウィンドウになり、exec()で表示するとモーダルウィンドウになります。

アプリケーション設定の保存 

QSettingsクラスを使用します。
  • 保存の例
    QSetting settings("Software Inc.", "Spreadsheet");
     
    settings.setValue("geometry", geometry());
    settings.setValue("recentFiles", recentFiles);
    settings.setValue("showGrid", showGridAction->isChecked());
    settings.setValue("autoRecalc", autoRecalcAction->isChecked());
  • 読み込みの例
    QSettings settings("Software Inc.", "Spreadsheet");
     
    QRect rect = settings.value("geometry",
                                   QRect(200, 200, 400, 400)).toRect();
    recentFiles = settings.value("recentFiles").toStringList();
    bool showGrid = settings.value("showGrid", true).toBool();
    bool autoRecalc = settings.value("autoRecalc", true).toBool();

ウィンドウを閉じたときにメモリを解放する 

ウィンドウのクラスのコンストラクタで以下の通り記述します。



コンストラクタ()
{
  …
  setAttribute(Qt::WA_DeleteOnClose);
  …
}


クリップボード 

  • コピー
    QApplication::clipboard()->setText(str);
  • ペースト
    QString str = QApplication::clipboard()->text();

Qt Designerの覚え書き 


タブオーダーの順序の設定 

Edit → Edit Tab OderメニューからGUIでタブオーダーを設定できます。
taboder.png

QTreeWidgetのヘッダカラムの文字列の変更の仕方 

QTreeWidgetをダブルクリックすると、下図が表示されます。
下図の列の名前をダブルクリックすると文字列を編集できます。
EditTreeViewColumnString.png

ソートする(qSort()) 

QListをソートするのにqSort()関数が使えます。
QListの要素のクラスのためにoperator<()とoperator=()をオーバーロードする必要があります。



class test
{
public:
  QString key;
  double value1;
  double value2;
 
  void operator=(const test& d);
}
bool operator<(const test& d1, const test& d2){ … } //operator<()はtestクラスのメンバ関数ではないことに注意
…
QList<test> list;
…
qSort(list);

また、qSort()は、内部でデータの操作を行っているため、constなメソッド内では使用できない。

Tabキーでフォーカスを移動させない 

イベントフィルタでKeyPressイベントを乗っ取ることで実現できます。

イベントフィルタは、対象のオブジェクトにイベントが送られる前に、監視を行うオブジェクトがイベントを受け取って必要な処理を行う仕組みです。
  • 監視を行うオブジェクトを対象オブジェクトのinstallEventFilter()を呼び出して登録します。
  • 監視を行うオブジェクトのeventFilter()関数で対象オブジェクトのイベントを処理します。

あるボタンからTabキーでフォーカスを移動させない例 

  • ヘッダファイル
    class MainWindow : public QMainWindow
    {
     Q_OBJECT
     
    public:
      MainWindow(QWidget *parent = 0);
      ~MainWindow();
     
    private:
      Ui::MainWindowClass *ui;
      bool eventFilter(QObject *target, QEvent *event);  //ここ
    };
  • ソースファイル
    MainWindow::MainWindow(QWidget *parent)
       : QMainWindow(parent), ui(new Ui::MainWindowClass)
    {
      ui->setupUi(this);
     
      ui->pushButton->installEventFilter(this);  //ここ
    }
     
    bool MainWindow::eventFilter(QObject *target, QEvent *event)
    {
      if(target == ui->pushButton){
        if(event->type() == QEvent::KeyPress){
          QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
          if(keyEvent->key() == Qt::Key_Tab){
            //何か処理をおこなう
            keyEvent->accept();
            return TRUE;
          }
        }
      }
     
      return QMainWindow::eventFilter(target, event);
    }

重い処理を実行中のレスポンスの維持 

重い処理を実行中は画面が無反応になる場合があります。そのようなときは、QApplication::processEvents()を呼び出せば解決します。C#のApplication.DoEvents()のようなものです。

ただし、反応するからといってメインウィンドウを閉じたりすると、その後の動作はどのようになるか分からなくなり、危険です。このようなときは、QApplication::processEvents()を以下のように書き換えます。



QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);

上記だけではなく、ボタンをdisableにするなど、GUIの対応もした方がユーザにとって嬉しいでしょう。

コンテナクラス 


紹介 

種別名前説明
順序付きクラスQVector・末尾に項目を追加するのが非常に高速
・末尾以外の追加は遅い
QLinkedList・要素の挿入や削除が一定時間で行える
・ランダムアクセスできない([ ]演算子は無い)
QList・末尾と先頭への要素の挿入と削除が非常に高速
・ランダムアクセスできる
・要素が1000個を超える場合、途中の要素の挿入は高速ではない
QStringListQList<QString>のサブクラスで、文字列を扱うのに適した関数を持つ
QStackpush()、pop()、top()をもつベクタ
QQueueenqueue()、dequeue()、head()を持つリスト
連想コンテナQMap/QHashキーと値のペアとして格納する
[]演算子で要素の読み書きが可能
QMapはキーで要素を昇順ソートする
QHashはキーで要素を排他処理する
QMapのキーはoperator<()を持っている必要あり
QHashのキーはoperator==()を持ち、グローバル変数qHash()でハッシュ値が計算できる必要あり
キーに対応する値を複数持ちたい場合は、サブクラスのQMultiMap/QMultiHashを使う

イテレータ 

Javaスタイルのイテレータが用意されています。
コンテナクラス読み込み専用イテレータ読み書き可能イテレータ
QVectorQVectorIteratorQMutableQVectorIterator
QLinkedListQLinkedListIteratorQMutableLinkedListIterator
QListQListIteratorQMutableListIterator
QMapQMapIteratorQMutableMapIterator
QHashQHashIteratorQMutableHashIterator
  • イテレータの例
    QMutableListIterator<double> i(list);
    while(i.hasNext()){
      if(i.previous() < 0.0) i.remove; //要素の削除
      int val = i.next();
      if(val < 0.0) i.setValue(-val);  //要素の変更
    }

Tips 

  • QMap/QHashを存在しないキーで[]演算子を呼ぶと、要素が新たに作成される(エラーにならない)

    この問題を回避するにはvalue()関数を使います。
    QMap<QString, int> map;
    ~
    int val map.value("abc");
    キーが存在しない場合は、値の方のデフォルトコンストラクタで生成される値が返されます。基本型とポインタ型の場合は0が返ります。

汎用アルゴリズム 

QtAlgorithmsヘッダは、コンテナに適用する基本的なアルゴリズムを実装したグローバル関数を提供します。
  • qFind()

    最初に見つかった値を指すイテレータか、値が見つからなかった場合はendイテレータを返します。
    QStringList list << "Emma" << "Karl" << "James" << "Mariette";
    QStringList::iterator i = qFind(list.begin(), list.end(), "Karl");
  • qBinaryFind()

    アイテムが照準でソートられているコンテナに使用するqFind()の高速版です。
  • qFill()

    コンテナの要素に指定した値をセットします。
    QLinkedList<int> list(10);
    qFill(list.begin(), list.begin() + 5, 555);  //最初の5つの要素を555にセット
    qFill(list.end() - 7, list.end(), 777);      //最後の7つの要素を777にセット
  • qCopy()

    コンテナを別のコンテナにコピーします。
    QVector<int> vect(list.count());
    qCopy(list.begin(), list.end(), vect.begin());
  • qSort()

    コンテナの要素をソートします。
    qSort(list.begin(), list.end());                   //昇順でソート
    qSort(list.begin(), list.end(), qGreater<int>());  //降順でソート
    qSort(list.begin(), list.end(), mySort);           //ソート方法を指定
    bool mySort(const int &d1, const int &d2){…}
  • qStableSort()

    qSort()との違いは、同じ要素の順序が保持されることです。
  • qSwap()

    2つの値を交換します。
    int x1 = line.x1();
    int x2 = line.x2();
    if(x1 > x2) qSwap(x1, x2);

入出力 


QFile 

ローカルファイルにアクセスできます。



QFile file("facts.dat");

QFileオブジェクトは、スコープを抜けて破棄されると自動的にファイルがクローズされます。

バイナリデータの入出力 

  • 出力

    データのバイトオーダはビックエンディアンに統一されます。
    QImage image("abc.png");
    QMap<QString, QColor> map;
    …
    QFile file("data.dat");
    if(!file.open(QIODevice::WriteOnly)){
    //エラー処理
    }
    QDataStream out(&file);
    out.setVersion(QDataStream::Qt_4_1);
    out << quint32(0x12345678) << image << map;
  • 入力
    quint32 n;
    QImage image;
    QMap<QString, QColor> map;
    …
    QFile file("data.dat");
    if(!file.open(QIODevice::ReadOnly)){
    //エラー処理
    }
    QDataStream in(&file);
    in.setVersion(QDataStream::Qt_4_1);
    in<< n >> image >> map;
  • ユーザクラスの入出力
    演算子と>>演算子をオーバーロードすれば、ストリームで使用できます。
    QDataStream& operator<<(QDataStream& out, const myClass& obj)
    {
      out << obj.getStr1() << obj.getStr2() << quint32(obj.getInt1());
      return out;
    }
     
    QDataStream& operator>>(QDataStream& in, myClass& obj)
    {
      QString str1;
      QString str2;
      int int1;
     
      in >> str1 >> str2 >> int1;
      obj = myClass(str1, str2, int1);
      return in;
    }

テキストデータの入出力 

  • 出力
    QFile file("data.dat");
    if(!file.open(QIODevice::WriteOnly)){
    //エラー処理
    }
    QTextStream out(&file);
    out.setVersion(QDataStream::Qt_4_1);
    out << "A" << "B" << 200 << endl;
  • 入力

    データのバイトオーダはビックエンディアンに統一されます。
    QFile file("data.dat");
    if(!file.open(QIODevice::ReadOnly)){
    //エラー処理
    }
    QDataStream in(&file);
    while(!in.atEnd()){
      QString line in.readLine();
      …
    }

ディレクトリ走査 

ディレクトリを走査して、含まれるファイルの一覧を取得する例を示します。



QStringList walk(const QString& path)
{
   QDir dir(path);
   QStringList list;
 
   foreach(QString file, dir.entryList(QDir::Files)){
       list.append(path + QDir::separator() + file);
   }
 
   foreach(QString subDir, dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)){
       list = list << walk(path + QDir::separator() + subDir);
   }
 
   return list;

}

プロセス間通信 


外部プログラムが終了するまでブロックする 




QStringList param;
…
QProcess::execute("実行ファイル名", param);


非同期に外部プログラムを実行する 

  • ヘッダファイル
    class ExecuteApplication : public QObject
    {
      Q_OBJECT
     
    public:
      void start();
      ExecuteApplication();
     
    private slots:
      void processFinished(int exitCode, QProcess::ExitStatus exitStatus);
     
    private:
      QProcess process;
    };
  • ソースファイル
    ExecuteApplication::ExecuteApplication()
    {
      connect(&process, SIGNAL(finished(int, QProcess::ExitStatus)),
              this, SLOT(processFinished(int,QProcess::ExitStatus)));
    }
    void ExecuteApplication::start()
    {
      process.start("外部プログラム");
    }