「Qt」 カテゴリーの記事です。

Qtの「レイアウト」には、VSでいうところのDock=Fillの概念がない


 
ええ、やってますよ。Qt(キュート)

先だって、最新版の、ver.5.3がリリースされ、大幅な機能強化、モバイルファーストなんて、いま流行りの方針を打ち出し、少し活気を取り戻してきた感があります。

ところで、Qt Designerを起動して、メインフォームのデザインをしているのですが、けっこう戸惑うことが多いです。

たとえば、ウィンドウいっぱいにコンポーネント(Qtではウィジェット)を広げて置きたいとき、どうして良いかわからない。

Visual Studio にあるような、フォームにおいたコンポーネントに「Dock」というプロパティがあって、画面一杯に表示する(=Fill)という設定がないのです。

VSでのDockプロパティ設定

Qtでは、いわゆるウィンドウを拡縮したときの自動レイアウトの考え方が根本的にちがいます。

これは、Visual Studioに慣れきったプログラマーもそうですが、C++BuilderDelphi系から入ってきた人も戸惑うところでしょう。

ちなみに、C++Builderや、Delphiなんかのレイアウトデザイン上では、「Align」プロパティの「alClient」の値に当たります。

C++BuilderでのAlignプロパティ設定

で、これを踏まえた上で、Qtでレイアウトをしようとすると、けっこうな違和感を抱きます。
おそらくそれは、Qtのデフォルトの状態で新規プロジェクトを作成すると、CentralWidgetが追加された上で、無効になった状態ではじまるからではないでしょうか。

CentralWidgetが無効になっている

僕としては、このスタート状態が、Qt初心者の混乱する元かな、と思っています。非常に説明しにくい(理解しにくい)状態です。

CentralWidgetとは、「ウィジェット」と銘打っていますが、実際はレイアウトコンテナに近い役割をしていて、たとえば動的にプログラミングから、「setCentralWidget(パーツ);」と実行すれば、そのパーツは中央でいっぱいに拡がった状態で表示されることになります。

とはいえ、フォームのリサイズに応じて、各コンポーネントも自動的にリサイズしてくれるようなレイアウトにしたい。

実際は、こうやります。

まず、メインウィンドウに、「QTabWidget」を配置します。

QTabWidgetを配置

次に、「CentralWidget」が選択されているのを確認しながら、上にあるツールバーの、「格子状に並べる(G)」をポチります。

グリッドレイアウトに変更する

これで、配置したQTabWidgetは、メインウィンドウいっぱいに拡がったかと思います。

QTabWidgetがウィンドウいっぱいになる

さて、ここにボタンを配置してみたら、どうなるでしょうか。追加したばかりの、QTabWidgetの下に配置してみます。
左のウィジェットボックスからドラッグ&ドロップで持ってくると、その場所に青い線が表示されます。

QTabWidgetの下にボタン配置

ところが、配置したボタンは、上のQTabWidgetに引きずられるように画面いっぱいに幅が拡がってしまいました。

QTabWidgetの下のボタンも拡がる

これは、上下、2つのグリッドになっていると考えるとわかりやすいかと思います。↓

QTabWidget、QPushButtonもグリッドで配置されている

ではここに、もう一つボタンを追加してみましょう。

QPushButtonを右側にもう一つ配置

すると、追加したボタン分の空きが、QTabWidgetの横にできてしまいました。しかも比率がおかしなことに。

QPushButton分のスペースができてしまった

QTabWidgetをどうにかして、幅いっぱいまで拡げたいが、なぜかできません。幅を可変するカーソルには変わるんですが・・・

リサイズカーソルは出るが動かせない

他のウィジェット(List Viewや、Text Editなど)は、幅を変えることができるので、QTabWidgetだけの仕様?
あるいは、バグだったりして。

そこで、その代替手段として、いったんQTabWidgetを削除し、ボタンの上に、Vertical Layoutを配置してみましょう。
こんなふうに。↓

Vertical Layoutを配置する

おそらくは、QTabWidgetと同じように左へ寄って配置される(ドラッグ&ドロップした場所によっては右に配置される)
と思いますが、これは幅の調整が可能です。

Vertical Layoutを調整する

これは、ようするに、グリッドレイアウトの、2マスつかって配置している、ということになります。

Vertical Layoutはグリッド上こうなる

ここでようやく、Vertical Layout内に、QTabWidgetを配置します。Vertical Layoutの中で、目一杯に拡がって張り付くはずです。

QTabWidgetを再配置する

以上です。

丁寧に、ウィジェットを配置し、慣れていけば、Qt独特のパーツ配置は便利になってくるかもしれません。
とはいえ、最初にCentralWidgetが無効になっているのはどうなのかしらん?とも思いますが、
逆にパーツを配置するたびに、ウィンドウいっぱいに張り付かれても戸惑うところでしょうか。

ようは慣れろ、ということなのですが、Visual StudioやC++builder、Delphiなどのいわゆるレガシーな(Qtもレガシーだろ、という方もいらっしゃりそうですが(笑))、統合開発環境をやってきた人には、正直「えっ?」となる部分かな、と思います。

この辺りのレイアウトの仕組みをきちんと整理されているサイトが見当たらなかったので、ここに記事としてまとめてみました。
 
 

QtでMacOSXアプリケーションをつくる


最近また、Qt を始めています。

実は、NokiaがQtを手放してから、もう終わったか・・・と、しばらく触っていなかったのですが、
しばらく見ないうちに、高機能で、マルチプラットフォーム化が進んでいたので、びっくりです。
商用ですが、Android、iOSにも対応しているようです。

そんなわけで、改めてなぜいま Qt なのかというと、Mac OSX アプリケーションが作りたくて。
心地よく日本語テキストを打ち込める、軽量な執筆エディターがほしくて探し回りましたが、
ぴったりのものが見つからないんですよね。

海外製のは、変換候補があさっての方へ飛んでいってしまったり、
段落下げしたら、以降、全部段落が下がるという、完全に海外文書仕様になっていて、
日本語的な一字下げのみということができなかったり、そもそも日本語対応は微妙なのが多い。

で、とりあえず僕はいま、Qtに、面倒なMacOSX側の処理をやってもらって、
あとは、エディタ部分も含め、WebView(Weblit+JavaScript)の方で
表示・編集できるように開発してこうと考えています。

いわゆる The QtWebKit Bridge(http://qt-project.org/doc/qt-5/qtwebkit-bridge.html)を使います。

Qt Creatorから、「新しいプロジェクト」を開くと、
「HTML5アプリケーション」という項目があるので、基本はそれで作ります。

ただ、これだと、シングルウィンドウのWebブラウザアプリと変わりがなくなってしまいます。
ファイルを保存したり、ファイルを開く、アプリ自体の設定なんかも各々のマシンに記憶しておきたい。

そうなると、MacOSXのネイティブなところをいじる必要があるわけです。
つまり、Qt(MacOSX)ネイティブ+JavaScriptのハイブリッドアプリケーションの構成で、というわけです。

ちなみに、エディタ部分は、CodeMirrorを使います。

これがなかなかの高機能で、ライセンスも、MITライセンス。
これをWebKitに組み込んで使っている海外製のエディタアプリケーションも多い。

このサンプルソースは、なかなか見当たらないのですが、以下の記事が参考になりました。

hybrid web+native: desktop codemirror
http://ariya.ofilabs.com/2011/09/hybrid-webnative-desktop-codemirror.html

「CodeMirrorを使う」という目的だけの、エディタの機能としては少なめのサンプルです。
とはいえ、限定されている分、ソースはシンプルで分かりやすいかもしれません。

ソースコードは、同ページにリンクが貼ってありますが、わかりにくいので、ここに再掲。
https://github.com/ariya/X2/tree/master/webkit/codemirror

ゆくゆくは、App Storeに出したいなあ、とは思っていますが、年間登録料をケチって野良配布するかもしれません。

とりあえず自分が使いたいツールを作るのが目標です。オープンソースも視野に入れています。
 
 

QtでMacOSXアプリケーションメニューに「環境設定」項目を追加、表示する


一昨日の土曜日、はじめてQt 勉強会 #11 @Tokyoにも参加。まとめは、こちらです。

めっちゃ濃いことやっている人たちばかりで腰引けました(笑)。

僕はせいぜいライブラリの寄せ集めで、さくっとアプリつくっちゃうという軽さが、とても恥ずかくなります(笑)。続々成果が発表されるなか、勉強会でやりたかったこと、をやる前に、ちがう問題が発生し、さらにそれもその日に解決できずに、宿題になってしまったという・・・

ただ、主催者の @task_jpさんからのアドバイスもあり、その後、それら宿題を解消できたのでここにシェアします。

メインメニュー「環境設定」というは、いわゆるコレです。

環境設定メニューの画像

Qtの公式ページを見ると、
http://qt-project.org/doc/qt-5/qmenubar.html#qmenubar-on-mac-os-x

If you want all windows in a Mac application to share one menu bar, you must create a menu bar that does not have a parent. Create a parent-less menu bar this way:

  
QMenuBar *menuBar = new QMenuBar(0);
  

親のないメニューバーから生成する、とある。
ただ、すでにそのようなメニューは作っていて、どこに「それら」を挿入するのかが分からない。

  
QMenu *fileMenu = menuBar()->addMenu("&File");
fileMenu->addAction(tr("&New Window"), this, SLOT(fileNew()), QKeySequence::New);
fileMenu->addAction(tr("&Open..."), this, SLOT(fileOpen()), QKeySequence::Open);
fileMenu->addAction(tr("&Save"), this, SLOT(fileSave()), QKeySequence::Save);
fileMenu->addAction(tr("Save &As..."), this, SLOT(fileSaveAs()));
  

ファイルメニュー

結論を言うと、なんか、実に微妙な実装なのですが、AddMenu() にある特定の文字列があれば、
QMenu のどこに存在してようと、良いみたい。

マニュアルにもそう書いてある。
http://qt-project.org/doc/qt-5/qmenubar.html#qmenubar-on-mac-os-x

String matches Placement Notes
about.* Application Menu | About <application name> The application name is fetched from the Info.plist file (see note below). If this entry is not found no About item will appear in the Application Menu.
config, options, setup, settings or preferences Application Menu | Preferences If this entry is not found the Settings item will be disabled
quit or exit Application Menu | Quit <application name> If this entry is not found a default Quit item will be created to call QApplication::quit()

なので、ソースコードには、こんなふうにしました。

   
QMenu *fileMenu = menuBar()->addMenu("&File");
fileMenu->addAction(tr("&New Window"), this, SLOT(fileNew()), QKeySequence::New);
fileMenu->addAction(tr("&Open..."), this, SLOT(fileOpen()), QKeySequence::Open);
fileMenu->addAction(tr("&Save"), this, SLOT(fileSave()), QKeySequence::Save);
fileMenu->addAction(tr("Save &As..."), this, SLOT(fileSaveAs()));

//ファイルメニューにぶら下がるようにコードを挿入
fileMenu->addAction(tr("about.*"), this, SLOT(aboutPaneView()));
fileMenu->addAction(tr("preferences"), this, SLOT(optionPanelView()));

こう書くと、「ファイル」メニューのプルダウン項目に表示されそうですが、そこには表示されず、
実際は、アプリケーションメニュー内に表示されます。

アプリケーションメニュー

「about.*」は、ママです。そのままアスタリスクを書きます。.plistにある「アプリケーション名」をQt が自動的に引っ張ってきて、「About AppName」という感じに表示してくれます。たぶん、アプリケーションが日本語表示対応なら、「AppNameについて」というメニューになるはずです。

「preferences」も同様に、日本語環境に対応させると「環境設定」と表示されるようです。

File メニューの中に AddMenu() したのに、実際はアプリケーションメニューに表示されるという、なんだか気持ち悪い仕様ですが、MacOSXがそうなのであって、他のOSでは、ちがう実装になるということに注意が必要でしょう。

一昨日のQt 勉強会 #11 @Tokyoでも、「気持ち悪い」「ホントに大丈夫なの?」との感想がありましたが、「マニュアルがそうなっているので、そうなんでしょう」っていう結論でした(笑)。
 
 

Qt上でJavaScriptの実行をデバッグする


Qtで、WebKit Bridge で作成していると、Qt側での処理は、
QtCreator上でデバッグ可能ですが、値がWebKitへ渡った後の、
JavaScript実行までは追えません。

ただ、一昨日のQt 勉強会 #11 @Tokyoにて、
そのことについて口にしたところ、主催者の @task_jpさんから、

QWebInspectorクラスを使うといいよ」とのアドバイスをいただきました。

こんな便利なものまで、クラスであるのか!と喜び勇んで実装してみました。

  
//QWebInspector instance.
QWebInspector *inspector = new QWebInspector;
inspector->setPage(this->page());
inspector->show();
  

this->page() は、WebKitの、QWebView::page() のポインタを指定します。

ところが、とても残念な表示に。。。

空のWebインスペクタ

ちゃんと、show() しているんですけどね、、、なぜ出ないのだぁ〜、と悩むこと小一時間、もうすでに消えてしまったWebページで、検索キャッシュの中に答えを見つけました(笑)。

  
//↓これを追加。
view->page()->settings()->setAttribute(QWebSettings::DeveloperExtrasEnabled, true);

QWebInspector *inspector = new QWebInspector;
inspector = new QWebInspector;
inspector->setPage(this->page());
inspector->show();
  

表示する側、Webページの方の、設定も変更しないと出ないということがわかりました。

検索しても、あまり事例が出ないところを見ると、QWebInspectorクラスがQtにあることを知らないか、
かつ、Web Bridgeとかする前に、HTML状態のJavaScriptをデバッグしてから載せろってことでしょうかね。。。

とりあえず、これも勉強会の宿題だったので、一つ解決っと。
 
 

CodeMirriorコンストラクタの罠


いま、Qt の QtWebKit Bridge(http://qt-project.org/doc/qt-5/qtwebkit-bridge.html)という機能を使っての、MacOSXアプリケーションをつくっています。

いわゆるMarkdown記法を元に、アウトラインを作りながら書いていく、日本語入力のエディタを考えていて、
エディタ部分には、CodeMirroir というライブラリを使います。

ただ、どうもCodeMirrior のコンストラクタで、エディタのインスタンスが、生成されていないみたい。

  
  editor = CodeMirror(document.body, {
    value: "\n",
    mode:  "markdown",
    smartIndent: false,
    tabSize: 2,
    indentWithTabs: true,
    electricChars: false,
    lineWrapping: true,
    lineNumbers: true,
    foldGutter: true,
    enableCompositionMod: true,
    gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"]
  }).on(
    //Changeイベント
    'change', function() {
        //alert('changes!');
        jsBridge.changes();
    });

  editor.setValue("abc");  //←ここでエラー
  

こんな、エラーが発生します。↓

JavaScriptエラー

で、よくよく調べてみたら、意外なところに落とし穴がありました。

ヒントがあったのは、このページ。
https://groups.google.com/d/msg/codemirror/DfR57zf6k18/9kLma4I6dagJ

CodeMirrior は、jQueryみたいにオブジェクトを返さないよー、とある。

な、ん、だ、と・・・

完全に、jQuery脳っていうか、JavaScript的に考えれば、オブジェクトを返すはずで、メソッドチェーンするのは当然だと思ってました。
いや、ふつうそうじゃないのか・・・

なんで、CodeMirrior ではできないのか?

答え:そういう仕様だから(笑)。

コンストラクタした内容をオブジェクト変数に入れて、使い回すことはできない。これ、仕様。

だから、こう書きます。

  
  editor = CodeMirror(document.body, {
    value: "\n",
    mode:  "markdown",
    smartIndent: false,
    tabSize: 2,
    indentWithTabs: true,
    electricChars: false,
    lineWrapping: true,
    lineNumbers: true,
    foldGutter: true,
    enableCompositionMod: true,
    gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"]
  }).on(

  editor.on(
    //Changeイベント
    'change', function() {
    //alert('changes!');
    jsBridge.changes();
  });

  editor.setValue("abc");
  

最初のコンストラクタのみ、オブジェクトを返すので、メソッドチェーンはできません。

なぜ? どうせならオブジェクトをそのまま返してくれればいいだけなのに(笑)。

 
 

s