2010年9月4日土曜日

ボタンを押されると何が起きるのか--シグナルとハンドラとプログラム

えー、前回、GTKオブジェクトのシグナルをGladeで設定して、Pythonコードに、そのシグナルハンドラを実装してみる、ということをやりました。

シグナルとハンドラ


そもそもシグナルってなんぞ、という疑問もあると思います。
Gladeを起動して、シグナルのタグを見ると、モノによってはたくさんのシグナルが並んでいます。
これ、なにかっていうと、そのGTKウィジェットに対して行われた操作に対して、GTKウィジェットが発することが出来る「通知」なんです。
ボタンなら、「押されました」とか、チェックボックスなら「チェック状態が変わりました」とかですね。
Linuxの場合には、基本的にユーザー操作はXを経由してGTKに送られ、GTKがプログラムに送ります。
プログラムは、送られてきたシグナルを選んで、ハンドリングして、処理を行うわけです。
イメージとしては以下の感じ。
#イメージね。(笑)


そんで、この「ユーザー操作」を「待ち受ける」処理が、gtk.main()なんですね。
プログラムの下の方にある。
メッセージループなんて言い方もしますが、通知されてくるメッセージ(シグナル)を待ち受けて、メッセージが来たら、それを各ハンドラに振り分けを行うわけです。

ちょっと前に、関数とは、入力に対してアクションを行う箱だ、と説明しましたが、GUIアプリそのものが、そういう側面を持っています。
ユーザー入力に対して、あるいはシステムからのシグナルに対して、「どんなアクションを起こすのか」を延々と記述していくのが、GUIアプリ作成ということになります。

非常にコンピュータ的というか。(笑)
「仕事をしろ」と指示をしないと何もしてくれないのがGUIなのです。
CLI(CommandLineInterface)アプリケーションは、起動すると同時に、物凄い勢いで仕事をしてくれるひとなわけですが、GUIアプリは全然違います。いちいち、これをしろ、あれをしろ、と命じてやらないと何もしてくれないし、プログラムも、そういう書き方になるんですね。
「あれをしろって言われたら、じゃあこうするか」みたいな。
#まあ、CLIアプリは起動時のオプションで、すでにやることが指示済みなので突っ走れるってワケですが。

GUIアプリの基本的な考え方は、シグナルを待ち受けて、シグナルが来たら、対応する処理をする、ってことになります。

とはいえ。
全部のシグナルを処理する必要なんてないので、そこは心配は要らないのです。
処理が必要なシグナルだけを拾って、自分のプログラムでは、それをハンドリングするだけでOKです。
今回のサンプルでは、ウィンドウが閉じられたとき、そして、OK、キャンセルボタンが押された時の三つのシグナルさえハンドルしてしまえば、あとは、GTKの標準の動きに任せてOKです。

ちょっと凝ったこと、たとえば、このチェックボックスをチェックされたら、同時に別のチェックボックスもチェックをつける、とかそういうことをやりたい場合には、また個別に対象のチェックボックスのシグナルをハンドルして、処理を付け加えることになりますが、まあ、おっさんは、そんな凝ったことしなくてもいいと思うのです。
まずはシンプルな処理、シンプルな実装で、GUIとPythonの「最低限の動作」を覚えてしまいましょう。
応用は、その後でもいくらでもよい文献がありますし、自分で調べることもできます。

ボタンが押されたら、どうなるのというイメージをここでは持ってもらえれば十分です。
そして、じゃあ、こっちのボタンをおしたらどうなるのかな〜、と妄想を広げてもらえばいいんじゃないかと思います。(笑)
おっさんですので、妄想はきっと得意でしょう。おっぱいの先っちょを押したら、そこからシグナルが発生して「あっ…」と声が出る、みたいなもんです。シグナルとハンドラの関係は。
おっぱいの先っちょを押す、というシグナルに対して、「あっ…」と声を漏らす、という処理が組み込まれているだけ、なのです。

そう、GUIは、そういうものと捉えるとイメージしやすいかも知れませんし、妄想もふくらませやすいかも知れません。
右側のおっぱいと左側のおっぱいでは、アクションが違うことも考えられるでしょう。
それは、左右のおっぱい、それぞれにシグナルがあり、それが別々のハンドラによって処理されている、ということなのです。

てなところで、妄想を広げて、悶々としといてください。
次回は、実際にハンドラ、関数の定義の仕方について書こうかと思います。
まあ、定型的なものであるので、そこはさらっと流して、関数定義、メソッド定義の書式的な部分の座学ってところでしょうか。

どうして、おっぱいの先っちょをを押すと、声が漏れるのか、その仕組みを実装するためには、どのような手順で行うのか、だと思ってもらってもいいです。(違

プログラムの全体像


前回のサンプルで、一応、シグナルに対してのアクションを記述し、まがりなりにもGUIプログラム、と呼べるものが出来ています。
まあ、具体的なシグナルやそれに対応するメソッドであるハンドル定義は次回やることとして、ここではさらっとプログラムの全体像のおさらいをしとこうかと思います。

そのためには、実行される順番に付いて考えてみるのがいいのかな、と思います。
えっと、まずは食事に誘います。(違

プログラムは、基本的には上から順番に実行されるのですが、サンプルの場合には、importにより、各種ライブラリを使用する宣言をしたあとで、クラス定義がありますので、実際の実行をそこを飛ばされて、if __name__ == "__main__":のところまで来てしまいます。
そこで、MikunchApp()が実行されるわけです。
これは、Pythonでは、MikunchAppクラスが実体を持つことを意味します。
なので、実体を持つときに実行されるコンストラクタが、実行されることになります。
つまり、次に実行されるのは、def __init__(self):なんですね。
そうして、ウィンドウが表示されて、コンストラクタが終了し、gtk.main()が実行されて、ユーザーの入力を待ち受けることになります。
現在のプログラムだと、ボタンを押したり、☓ボタンを押すと、それぞれのハンドラが呼び出されて、gtk.main_quit()が実行され、gtk.main()が終了し、その後には処理するプログラムが何も書かれていないので、プログラムが終了する、ということになります。

実行される順番を整理するとこうなりますかね。
  1. MikunchApp()
  2. MikunchApp.__init__(コンストラクタ)
  3. gtk.main()
  4. ユーザー操作(☓ボタン)
  5. on_TopLevel_destroy(...)
  6. gtk.main_quit()

今回、アプリケーションクラスとして、クラスをひとつ定義しているので、このような構造になってます。
本来であれば、アプリケーションクラスの実体を作る前に、本当に必要なプログラムの引数(コマンドラインのオプション等)の処理を行って、アプリケーションクラスを構築することになります。
そして、最後に、gtk.main()の後で、本当のアプリケーションの後始末を行なってから、プログラム終了という構造になります。

ただ、実際に動作中の処理は、すべてアプリケーションクラスで受け止めて、処理をする、というプログラム構造になってますね。

実行順番と、プログラム構造について、おっさんにも少し解りやすく例示すると、こんな感じでしょうか。
  1. ホテルを選ぶ
  2. ホテルに入る
  3. アプリケーションクラス内:シャワーを浴びる(コンストラクタ)
  4. アプリケーションクラス内:エロビデオを見ながら待つ(シグナル待ち受け)
  5. アプリケーションクラス内:ゴニョゴニョする(シグナル処理)
  6. アプリケーションクラス内:服を着る(シグナル待ち受け終了)
  7. ホテルにお金を払う
  8. ホテルを出る
こんな感じになります。(笑)
で、実際、おっさんにはホテルそのものはどうでもよくて、中でゴニョゴニョが大事なのは理解出来るかと思います。

この中でゴニョゴニョの部分を処理しているのが、アプリケーションクラスのシグナルハンドラってことになりますね。
そう、先に述べている左の乳首と右の乳首では、シグナルも異なれば処理も異なる、という部分に繋がっていたのです。(違

というわけで、プログラムの全体像も、なんとなく理解出来たと思います。
大事なのは、中で何をするか、ってことです。
中出しには責任が伴うのです。(謎

では、次回は予告通り、具体的なシグナル処理とその定義に付いてやっつけて見ましょう。
中でゴニョゴニョ編です。(謎

0 件のコメント:

コメントを投稿

'},ClipboardSwf:null,Version:'1.5.1'}};dp.SyntaxHighlighter=dp.sh;dp.sh.Toolbar.Commands={ExpandSource:{label:'+ expand source',check:function(highlighter){return highlighter.collapse;},func:function(sender,highlighter) {sender.parentNode.removeChild(sender);highlighter.div.className=highlighter.div.className.replace('collapsed','');}},ViewSource:{label:'view plain',func:function(sender,highlighter) {var code=dp.sh.Utils.FixForBlogger(highlighter.originalCode).replace(/'+code+'');wnd.document.close();}},CopyToClipboard:{label:'copy to clipboard',check:function(){return window.clipboardData!=null||dp.sh.ClipboardSwf!=null;},func:function(sender,highlighter) {var code=dp.sh.Utils.FixForBlogger(highlighter.originalCode).replace(/</g,'<').replace(/>/g,'>').replace(/&/g,'&');if(window.clipboardData) {window.clipboardData.setData('text',code);} else if(dp.sh.ClipboardSwf!=null) {var flashcopier=highlighter.flashCopier;if(flashcopier==null) {flashcopier=document.createElement('div');highlighter.flashCopier=flashcopier;highlighter.div.appendChild(flashcopier);} flashcopier.innerHTML='';} alert('The code is in your clipboard now');}},PrintSource:{label:'print',func:function(sender,highlighter) {var iframe=document.createElement('IFRAME');var doc=null;iframe.style.cssText='position:absolute;width:0px;height:0px;left:-500px;top:-500px;';document.body.appendChild(iframe);doc=iframe.contentWindow.document;dp.sh.Utils.CopyStyles(doc,window.document);doc.write('

'+highlighter.div.innerHTML+'

');doc.close();iframe.contentWindow.focus();iframe.contentWindow.print();alert('Printing...');document.body.removeChild(iframe);}},About:{label:'?',func:function(highlighter) {var wnd=window.open('','_blank','dialog,width=300,height=150,scrollbars=0');var doc=wnd.document;dp.sh.Utils.CopyStyles(doc,window.document);doc.write(dp.sh.Strings.AboutDialog.replace('{V}',dp.sh.Version));doc.close();wnd.focus();}}};dp.sh.Toolbar.Create=function(highlighter) {var div=document.createElement('DIV');div.className='tools';for(var name in dp.sh.Toolbar.Commands) {var cmd=dp.sh.Toolbar.Commands[name];if(cmd.check!=null&&!cmd.check(highlighter)) continue;div.innerHTML+=''+cmd.label+'';} return div;} dp.sh.Toolbar.Command=function(name,sender) {var n=sender;while(n!=null&&n.className.indexOf('dp-highlighter')==-1) n=n.parentNode;if(n!=null) dp.sh.Toolbar.Commands[name].func(sender,n.highlighter);} dp.sh.Utils.CopyStyles=function(destDoc,sourceDoc) {var links=sourceDoc.getElementsByTagName('link');for(var i=0;i');} dp.sh.Utils.FixForBlogger=function(str) {return(dp.sh.isBloggerMode==true)?str.replace(/
|<br\s*\/?>/gi,'\n'):str;} dp.sh.RegexLib={MultiLineCComments:new RegExp('/\\*[\\s\\S]*?\\*/','gm'),SingleLineCComments:new RegExp('//.*$','gm'),SingleLinePerlComments:new RegExp('#.*$','gm'),DoubleQuotedString:new RegExp('"(?:\\.|(\\\\\\")|[^\\""\\n])*"','g'),SingleQuotedString:new RegExp("'(?:\\.|(\\\\\\')|[^\\''\\n])*'",'g')};dp.sh.Match=function(value,index,css) {this.value=value;this.index=index;this.length=value.length;this.css=css;} dp.sh.Highlighter=function() {this.noGutter=false;this.addControls=true;this.collapse=false;this.tabsToSpaces=true;this.wrapColumn=80;this.showColumns=true;} dp.sh.Highlighter.SortCallback=function(m1,m2) {if(m1.indexm2.index) return 1;else {if(m1.lengthm2.length) return 1;} return 0;} dp.sh.Highlighter.prototype.CreateElement=function(name) {var result=document.createElement(name);result.highlighter=this;return result;} dp.sh.Highlighter.prototype.GetMatches=function(regex,css) {var index=0;var match=null;while((match=regex.exec(this.code))!=null) this.matches[this.matches.length]=new dp.sh.Match(match[0],match.index,css);} dp.sh.Highlighter.prototype.AddBit=function(str,css) {if(str==null||str.length==0) return;var span=this.CreateElement('SPAN');str=str.replace(/ /g,' ');str=str.replace(/');if(css!=null) {if((/br/gi).test(str)) {var lines=str.split(' 
');for(var i=0;ic.index)&&(match.index/gi,'\n');var lines=html.split('\n');if(this.addControls==true) this.bar.appendChild(dp.sh.Toolbar.Create(this));if(this.showColumns) {var div=this.CreateElement('div');var columns=this.CreateElement('div');var showEvery=10;var i=1;while(i<=150) {if(i%showEvery==0) {div.innerHTML+=i;i+=(i+'').length;} else {div.innerHTML+='·';i++;}} columns.className='columns';columns.appendChild(div);this.bar.appendChild(columns);} for(var i=0,lineIndex=this.firstLine;i0;i++) {if(Trim(lines[i]).length==0) continue;var matches=regex.exec(lines[i]);if(matches!=null&&matches.length>0) min=Math.min(matches[0].length,min);} if(min>0) for(var i=0;i

フォロワー