RealBasic University

このコラムはStone Table Softwareのオーナーであり、またREALbasic Developerの編集者でもあるMarc Zeedar氏により書かれたものを、著者の許可を得て翻訳したものです。この翻訳はHREM Researchにより提供されています。この日本語版へのご意見はRBU-Jまでご連絡下さい。

URL: http://www.applelinks.com/rbu/010/
INDEXに戻る

openFileルーチンの完成

先週、皆さんにGenderChangerの openFile ルーチンを書くという問題を出しておきました。幾つかのヒントを出しておきましたが、解決できなかった場合のために、ここで復習をしておきましょう。それは丁度 saveFile の逆なので、それほど難しくはありません。

ルーチンは使用する変数の定義から始まります。多くは saveFile と同じですが、"out" が "in" に変わる、textOutputStream の替わりに textInputStream にするということに注意して下さい。

  dim f as folderItem
  dim in as textInputStream
  dim theLine as string
  dim n as integer

次に、openAsTextFile でファイルを読み込みのために開きます、そしてメインのループを書きます。初期設定ファイルの folderItem を取得する行とエラーチェックのコード(if-then 文)は両方のルーチンで同じです。

しかし、この場合には、for-next ループの替わりの while ループを使います。それは、我々は前もってどれだけの項目がファイルに保存されているかを知らないからです(ですから、どこまでカウントするかを前もって決められません)。while はファイルの最後に達するまで読み続けます。EOF 属性はファイルにテキストが無くなったときにに設定される 論理型 boolean 変数です。我々は EOF の値を反転するのに not 演算子を使っています:これで EOF が偽のときにはループを続けることになります。

  f = preferencesFolder.child(kPrefName)
  if f <> nil then
    in = f.openAsTextFile
    if in <> nil then
      n = 0
      while not in.EOF
      
      wend
      in.close
      return
    end if // in = nil
  end if // f = nil

丁度、saveFile と同様に、ループが終わったときには、ファイルを閉じて、そして return コマンドを実行します。これにより openFile ルーチンから抜け出ます。

しかし、このルーチンの中身は実際にテキストを読み込んで、処理するコードです。このコードは幾つかの部分に分かれます。

我々は前もってファイルにどれだけのレコードが保存されているのか分かりませんので、必要になるに従って配列の領域を動的に割当てねばなりません。我々のファイルフォーマットはファイルの各行がレコードであるというものですので、ループを回るたびに n を1ずつ増加させます(n はレコードの数を表しています)。そして、配列をより大きなサイズ nredim します。Redim はプログラムの実行時に、その場で、領域を再割当て(増加あるいは減少)する特別なコマンドです。dim コマンドはプログラムの最初に、どのようなコードよりも前に、置く必要がありますが、redim はどこでも使えます(ただし、すでに宣言がなされた配列に対してのみ)。これらのコマンドの他の重要な違いは dim では変数(例えば n)を用いては定義できません:必ず確定した値(10 や 216など)でなければなりません。

では、次のものを while-wend の中に置きましょう:

    n = n + 1
    redim gTheFileList(n)
    theLine = in.readLine

配列を redim した後で、ファイルから新しい行のテキストを読み込んで、そしてそれを文字列変数 theLine に代入しています。これで、配列に保存する処理を行なうための準備が整いました。

しかし、ちょっと待って:配列が特別に定義された fileTypeClass だということを思い出して下さい。これは配列の個々のレコードがオブジェクトだということです。オブジェクトは new コマンドで生成するまでは存在しません。オブジェクトを new するまえに、それを使用しようとすると、REALbasic は Nil Object Exception エラーでクラッシュするでしょう。あなたは存在しないオブジェクトを使用しようとしています。そして、RB はそのような道理に反した状況をどのように取り扱うのかを知らないので、ただ終了してしまいます。

では、その後に次の行を付け加えましょう:

    gTheFileList(n) = new fileTypeClass

いったんREALbasic にオブジェクト生成するように指示すれば、オブジェクトにものを自由に代入できます。この場合には、オブジェクトは配列の1つの要素ですので、配列の要素の正しい指数を使うということを思い出す必要があります。我々はたんに配列を追加しているので、これは最後の要素、あるいは n です。

さて、配列のそれぞれのオブジェクトは3つの属性を持っています: name, macCreator および macType です。我々はそれらを正しい値に設定したいのですが、それらは変数 theLine からどのようにして取得できるのでしょうか?レコードは次のようなタブによって区切り文字で仕切られたフィールド(欄)からなる1行のテキストであることを思い出して下さい(ここではタブを表すのに * を使っています):

   FreeHand*FH80*AGD3

どのようにして name 欄を表している最初の部分を取り出せばいいのでしょうか?これは簡単です:nthField と呼ばれる REALbasic の関数を使います。

NthField は調べようとする文字列、使われている区切り文字(我々の場合にはタブ)、および希望する欄の番号を必要とします。そして、希望した欄に対応するテキスト(区切り文字を除いた)を返します。

例えば、次のようなコードでは:

   msgBox nthField("FreeHand*FH80*AGD3", "*", 2)

REALbasic はダイアログボックスに "FH80" と表示するでしょう。(試してみて下さい。)これは nthField に "*" を区切り文字とした文字列 "FreeHand*FH80*AGD3" の2番目の欄を返すように指示したからです。

TipnthField に文章と区切り文字としてスペースを与えることにより、文章中のここの単語を取り出すことができます。しかし、段落の間にはスペースが無いので、この方法は複数の段落の場合にはうまくいかないでしょう。

NthField は非常に強力なコマンドですが、極端に長い文字列(数千のレコードあるいは百万バイトのデータなど)では遅くなることがあります。大きなデータベースに対してはより効率的な方法があります。しかし、我々の目的には、nthField はぴったりです。

よこ道:nthField の兄弟分は countFields と呼ばれる関数です。 それは分割される文字列と区切り子を引数として持ち、文字列内のフィールドの数を返します。(上の例では、それは3を返します。)
Tip:スペースを区切り子とした countFields を洗練されていませんが簡単な単語を数える方法として使うこともできます。

以下はGenderChangerでその後に利用するコードです。関数 chr は渡されたASCIIコードの文字を返すということを憶えていますか。タブは ASCII コード番号9ですので、chr(9) は我々が区切り子として使うタブを返します。

   gTheFileList(n).name = nthField(theLine, chr(9), 1)
   gTheFileList(n).macCreator = nthField(theLine, chr(9), 2)
   gTheFileList(n).macType = nthField(theLine, chr(9), 3)

コードの最後はsaveFileと同じです;ただ表示されるエラーメッセージを変えるだけです(これは "end if // f = nil"の行の後に置きます):

  beep
  msgBox "Unknown Error. Couldn't open the preferences file."

全体のルーチンはこのようになるでしょう(REALbasic ver.3では):

あなたのものも同じですか?もし違っていれば、誤りを直して下さい。それではGenderChangerの別の部分に移りましょう。

listBox1のコード

我々はこれで globalsModule を完成しました:そこでは、我々は1つの定数、2つの属性と2つのメソッドを追加しました。それらは我々の必要とする全てです。ですから、次に Window1 に移動しましょう。Window1 がプロジェクトウィンドウで選択されている状態で、Option キーを押しながらタブキーを押して Window1 のコードエディタを開きましょう。

左の欄に小さな三角形が付いている Controls と呼ばれる項目があるでしょう。その三角形をクリックしてその内容(Window1control のリスト)を表示しましょう。次に、listBox1 の横の三角形をクリックしましょう。今度は、listBox1Open メソッドをクリックしましょう。右の欄にテキストカーソルが現れて、コードが入力出来るようになるはずです。

Window1.ListBox1.Openのコード

  me.heading(0) = "File Path"
  me.heading(1) = "Creator"
  me.heading(2) = "Type"
  
  me.acceptFileDrop("anyfile")

コントロールの open コードはそれがオープンされるとき(そのコントロールを使っているウィンドウが開かれた直後)に実行されます。ここはコンロトール(この場合は、listBox1)を初期化するコードを置くところです。ここであなたが行なうことはコントロールが表示されるに実行されます -- ですから、コントロールが生まれ出る前に、そのどのような属性も変えることができます。

ここでは、我々は listBox1 に"File Path", "Creator", そして "Type" という名前の3つの見出しを作るように指示しています。(見出し heading 属性は hasHeading 設定が true に設定されている時にのみ有効です。)

listBox1とする代わりに me 関数を使っていることに注意しましょう。これはコードをより汎用的にします:それは listBox1だけではなく、どのようなリストボックスでも使うことができます。 me 関数はそれを含んでいるコントロールの名前を返します。メソッドはコントロールではありませんので、この関数はメソッドの中では使えません。(コントロールの属性を変更するメソッドでは、コントロールを明示的に指定しなければなりません。)

この open イベントの最後の行は重要です:我々は listBox1 にどのような種類のファイルでもドロップできるように指示しています。これはユーザがファイルを変換待ちのリストに追加するために、Finder からリストボックスにファイルをドラッグすることを可能にします。

次に、listBox1の keydown イベントをクリックしましょう。ここで我々のしようとしていることは非常に簡単です:ユーザがリストから項目を削除できるようにします。

我々は2つのことをチェックします:ユーザが delete キー(ASCII番号8)を押したか、そしてリストボックスの行が選択されているか? 後者は listBox1listIndex 属性を調べることでチェックします:この属性はいつも現在の選択を返しますので、もし -1 でなければ、どれかの行が選択されていることが判ります(-1 は選択がなされていないことを意味します)。

Window1.ListBox1.KeyDownのコード

  if key = chr(8) and me.listIndex > -1 then
    me.removeRow me.listIndex
  end if

もしユーザがある行が選択されているときに delete を押した場合には、その行を removeRow メソッドで削除します。

では、 dropObject イベントをクリックしましょう。ここは殆どの仕事がなされるところです。

ここで我々が行ないたいのはリストボックスにドロップされたファイルを変換リストに追加することです。listBox1 は3つの列に分割されていますので、3つの要素をそれぞれの行に追加しなければなりません。

先ず最初は、folderItem オブジェクトがドロップされたのかを確かめます:ここでもエラーチェックです。もし、そうであれば、do ループを開始します。このループはすべてのドロップされたファイルが処理されるまで繰返されます。

よこ道do ループと while-wend ループの違いは何でしょうか?while ループは満足されなければならない条件がその最初にあります、一方 do ループではその最後にあります。これは条件が満足されなければ、while ループではループ内のコードは決して実行されない、一方 do ループはコードを一度実行して、そして終了することを意味します。我々の場合には、もし我々がループに行き着いたときには1つ以上のファイルがあることが判っています。そしてユーザが1つ以上のファイルをドロップした時にのみループは繰返しますので、do ループは我々の望むところです。

obj はコントロールにドロップされたオブジェクト(この場合には、1つ以上のファイル)の名前であることに注意しましょう。

Window1.ListBox1.DropObjectのコード

  if obj.folderItemAvailable then
    do
      me.addRow obj.folderItem.absolutePath
      me.cell(me.lastIndex, 1) = obj.folderItem.macCreator
      me.cell(me.lastIndex, 2) = obj.folderItem.macType
    loop until not obj.nextItem
  end if

最初の列はファイルのパスですので、リストボックスの新しい行にファイルの absolutePath 属性を追加します。それから、他の列の内容を設定するために cell メソッドを使います。cell は変更しようとする行番号と列番号を必要とします。我々は行を追加したところなので、追加した行番号を求めるのに lastIndex 属性を使うことができます。

重要:リストボックスの列はゼロから番号付けられていますので、2番目の列は番号1,3番目は番号2となります。これは非常に混乱しますので、よく覚えておきましょう!                                                                                                                                                                                                                                                                     

2番目と3番目のセルの内容はドロップされたファイルの creator と type に設定されています。それらはGenderChangerによっては使われませんが、ユーザに対する情報として表示されます。

よこ道:上の行の"obj.folderItem.macCreator"の多くのドットに混乱してはいませんか?混乱することはありません。ピリオドはオブジェクトのサブエレメントを示しているということを思い出して下さい。(サブエレメントは属性、メソッド、あるいは他のオブジェクトなどです。)obj.folderItem は folderItem オブジェクトであり、macCreatorfolderItem オブジェクトの正しい属性ですので、このコードは正しいのです。専門的には、全てのオブジェクトは他のオブジェクトのサブエレメントで、アプリケーションそのものであるルートオブジェクトにまでさかのぼります。ですから、複数のピリオドは混乱させるようではありますが、それが OOP のやり方なのです。

我々のループは nextItem 属性の状態をチェックする loop until コマンドで終わっています。NextItem は他のファイルが残っていればを返します。nextItem をチェックすることにより、それまでの folderItem を捨て去り、変換待ちの次のファイルをobjの folderItem 属性に設定します。ループは nextItem真でなくなったとき(すなわち、になったときに)繰返しが止まりますので、変換待ちのファイル が無くなったときに、ループは終了します。

PopupMenu1のコード

PopupMenu1 の横の小さな三角形をクリックしてその隠れている内容を見えるようにして、それから change イベントをクリックしましょう(もし、それがすでに選択されていなければ)。

この change イベントはユーザがポップアップメニューから選択する項目を変化させたときに起ります。例えば、"One" と "Two" の2つの項目がメニューにあって、現在 "One" が表示されているとしましょう。ユーザがそれを"Two" に変化させると、この change イベントが実行されます。

重要change はユーザが同じ項目を再び選択しても呼び出されません。上の例では、ポップアップメニューが "One" に設定されていて、そしてユーザがメニューをチェックして、再び "One" に決めたときは、この change イベントは起りません。これはあなたがユーザがポップアップの項目を選択したときにはいつも何かの処理を行ないたい場合には問題になります。

我々は、ボタンがクリックされたときに、ドラッグされているファイルのファイルタイプを PopupMenu1 で選択されているファイルタイプに変更する変換(convert)ボタンを作ろうとしています。ですから、もし PopupMenu1 がどれかのファイルタイプに設定されていない場合には、ボタンを選択不能にしたいと思います。

我々のコードは単純です:我々はただ PopupMenu1の listIndex 設定が -1 であるかチェックするだけです。もし、そうであれば、ボタンを選択不能にします。そうでなければ、ボタンを選択可能にします。

Window1.PopupMenu1.Changeのコード

  if me.listIndex = -1 then
    pushButtonConvert.enabled = false
  else
    pushButtonConvert.enabled = true
  end if

ここでプロジェクトを保存しましょう。今週はこれで終わりです。

クイズ:上のPopupMenu1.Change コードをもっと短い(濃縮された)ものにできないでしょうか?(答えはLettersの後にあります。)

次週

GenderChangerのメニューのためのコードを追加し、またファイルタイプを変更する中心となるルーチンを作ります。

Letters

今週の手紙は Hayden Coonさんからのものです。彼はOOP (Object-Oriented Programming)について質問しています。

Zeedar教授殿,

あなたのペースは遅いと思いますが、あなたの内容は非常に素晴らしいとおもいます:実に正しい教授法と明快さの例です。私はずっと以前からの、しかし、言うまでもなく、古い流儀(非OOP )のプログラマです(1956/7 年のIBM 650の機械語から始まっています)。私はいつの日かもっとOOPに抵抗が無くなれると信じてあなたの講座を読んでいます。私の本来の言語は現在は CLOS (lispのOOP 版)に取って代わられた 'Common Lisp' です。私はまた Assembler, C++, Pascal, そして, もちろん, 古典的な Basic でも仕事をしました。私は主にシミュレーションの研究(人工知能、オートマータなど)を補助するためにプログラミングを利用している生物学の研究者です。

あなたはすでにそれに関して説明されていますが、 哺乳類時代の恐竜である私のような人間(OOP時代のトップダウン手続信奉者)に対してねらいを定めた「余談」がもっとあって欲しいと思います。私は正直なところOOPモデルの見かけの鈍感さに当惑しています - それは私のしたいことのどれにもまったく適合するようには見えません。私は、「それら」が実行時にユーザに入力をもとめるというような単純なことをどのようにするのかを理解しようとしてつまずきながら時を過ごしています。INPUT, DATA, READ コマンドに対応するものはどこにあるのでしょうか?私はあらかじめ準備された応答をするボタンを押したくはありません - 私はプログラムに現在行われていること、およびその進展を評価し、そして答えにしたがって次に何を行なうかの選択に対する私の洞察を聞いて欲しいのです。これをOOPで行なうのは、私にとっては容易ではありません。あなたは、OK, 「あなたは単に適切なボタンを押して、応答リストから適切なものを選びなさい」 というかも知れません:私はどのようにすればいいのか理解できません(これは私の弱点です)、そして、なぜあなたは私がそうしたいのにそれをするのを妨げるのですか?

OOP流の考え方に「迷い込み」そして「救助されない」私のような人間の数えきれない例があります。では、そして良い仕事を続けて下さい;私はあなたのコラムを理解しようと努力しています。

Hayden Coon, Sebago, Maine

Haydenさん、難しそうな手紙をありがとう。「教授」の部分は冗談だと思います。私はちょっとの間講義を受け、そして幾つかを独習しましたが(いつも猛烈に)、プログラムに関する公式のトレーニングはほとんど受けていません。でも、あなたは古典的なプログラムの豊富な経験をお持ちのようですね。

あなたの手紙からすると、あなたは OOP と モーダル modal でない(モードレス modeless な ) プログラミングという2つの考えを混同されているように思えます。その2つは非常に異なっていますが、いくらか絡み合っています。説明してみましょう。

「昔」(とはいっても1980年代)には、コンピュータはほとんどモーダルでした。すなわち、それらはモードに従い動いていました。コンピュータプログラムが「入力」モードであれば、その他のことは行われません:その時に期待される種類の入力を認識しただけです。

例えば、私が最初にコンピュータに接したときには、私はWordStarと呼ばれるワープロを使いました。その時のWordStarは、WYSIWYGではありませんでしたが、テキストのブロックをコピーしてペーストするという機能を含む今日の多くのワープロに似た特徴を持っていました。しかし、マウスはありませんでしたので、テキストの一節を選択するということはできませんでした。その代わり、文章のブロックの先頭に行き、そして特殊なキーコマンドを押さなければなりませんでした。それから、矢印キーを使ってカーソルをブロックの最後まで移動し、そして "end block" コマンドを入力します。これは非常に不便で、モーダルな処理です:もし、あなたが最初の "start block" コマンドの後に何か別のことをすれば、WordStartは開始点を忘れてしまい、あなたは最初からやり直さなければなりません。

ほとんど全てのコンピュータプログラムはこのように書かれていました。あなたがあるモードにいるときには、そのモードを出るまでは別のことは何もできませんでした。Macが出てきたときに、そこに新しい考え方を持ち込みました:ユーザ駆動のプログラムです(これはまたイベント駆動プログラムとも呼ばれます。)

通常のMacプログラムはモードを持っていません。それはあなたが何かをするのをじっと待っています。あなたはどんなことでもできます:メニューから何かを選ぶ、入力を行なう、ウィンドウを移動する、ボタンをクリックする、別のプログラムに切換えるなど。ユーザであるあなたが制御しているのです。

プログラマの見地からは、これは全く別のものです。古典的なモーダルプログラミングはステップ1,ステップ2,ステップ3,ステップ4というように直線的です。Macでは、あるいはモーダルでないプログラミングでは、ユーザがその機能を選択したときには、それらのステップのどれでもが何時起っても構いません。もし、あなたが別の方法でプログラムをすることに慣れ親しんでいれば、これは大きな思考の転換です。PCを使ったモーダルなプログラムを数年間書いていた後で、Macのプログラムを始めた80年代末には私も苦労したことを憶えています。

解決の鍵は、プログラマではなく、ユーザのように考えることです。あなたはプログラムをどのように使おうとしているのでしょうか?例えば、ユーザの反応をその場で生成したいのだとおっしゃいました。古典的なプログラムでは、ユーザに逐次的な構造を強いています。このことはMacにおいてもモーダルダイアログを表示することにより簡単に行なうことができます:ユーザがダイアログを処理するまでその他のことは起りません。しかし、Macの観点からすると、これはいつも最善の選択ではありません。Macユーザはこのようなことを好みません:それは不自然で、強制されているように感じます。Macユーザはモーダルに考えることには慣れていません -- 彼らはしばしば順序通りには行動しません:ファイルを開くためのダイアログを開いて、ファイルの名前を変更しなければならないのを思いだし、それで Finder に切換えて名前を変更するというように。Macプログラムはこのように柔軟で、ユーザに幾らかの自由度をあたえるものでなければなりません。

ユーザ駆動のプログラムは今まで通りユーザにある経路をとらせることも可能です -- あなたは巧妙にそれを行ないます。例えば、文書を開くまではスペルをチェックすることは実際上行なえません。Macはテキストの入ったドキュメントが使えるようになるまで "Check Spelling" コマンドを選択不能にする(灰色にする)ことによりそのことを知らせます。あるときにはコマンドに適切な状況が生じるまで、それらのコマンドを見えなくすることもあります(コンテキストメニューはこの最適な例です)。

あなたの場合、プログラムで何をしたいのかを詳しくは述べていませんが、あなたが何をしたいのかを想像してみました(多分、生物学のシミュレーションでしょう?)。あなたのしたいことをより小さな部分に分割することを提案したいと思います。全体のプロジェクトを1から10までの逐次的な処理として考える代わりに、それをユーザの入力に依存しないより小さな塊に解体することです。

例えば、生物学のシミュレーションをしているとしましょう。ユーザは最初に「開始」ボタンをクリックしなければなりません。これは多分ユーザが設定しなければならない幾つかの選択を持つダイアログを表示するでしょう。これが終われば、シミュレーションの最初の部分が、ことによるとプログレスバーあるいはユーザが進行状況を監視することのできるグラフが表示されながら実行されるでしょう。プログラムがユーザの相互作用が必要な分技点に達したときに、プログラムは止まって、それまでの結果を表示するでしょう。もしそれが単純な分技であれば、ユーザは A または Bをクリックするすることもできます、あるいは必要になる追加データを入力することもできます。しかし、モーダルである代わりに、これはユーザ駆動のプログラムです:ユーザは中止したり、別のプログラムに切換えたり、処理をやり直したり、あるいは何か別のことをすることが可能です。(明らかに、あなたのプログラムの追加機能は使用不能になっています。例えば、グラフ機能はグラフを描くデータが生成されるまでは使用することは不可能です。)

これでいいでしょうか?OOPはイベント駆動プログラムを書くのを容易にするのを除けば、OOPそれ自身はイベント駆動プログラムとは何の関係もありません。OOPでは、オブジェクトはそれ自身でどう振る舞うかを知っています。REALbasicでは、例えば、リストボックスはそれへのデータの追加の仕方を知っています。これはあなたのプログラムがまさにユーザが制御することのできる一群のオブジェクトであることを意味します -- あなたのプログラムであるところの「メイン」ルーチンを持つことからの大きな転換です

非OOPのMacプログラムでは、しかしながら、イベントを監視するためだけの「メイン」ルーチンがあります。ユーザはマウスをクリックしたか?オー、マウスがウィンドウ、メニューバーか、ボタン等の上でクリックされたのかを調べるために「マウスクリック」ルーチンを呼ぼう。 ですから、非OOPプログラムは恐れさすことが少ないように見えるますが、それは実際的には同じことです。ただ、オブジェクトが無くって、プログラミングにより多くの労力が必要なだけです。

特にあなたが古典的な手法に慣れていればいるほど、ユーザ駆動プログラムへの転換は大仕事です。ある仕事には古典的な方法は申し分ありません:例えば、計算の実行は通常いつでも逐次的に行われます。しかし、ユーザの介入を必要とするプログラムはモードレスであるように書かれる必要があります:ユーザにその運命を制御する力を与えねばなりません。ユーザはそれを好み、その方が快適であり、そして彼らに制御を許さないプログラムには腹を立てるのです。

そういうわけで、Haydenさん、これはあなたが何をしようとしているのか、そしてあなたが遭遇している問題を正確には知ることなしに、私ができることの最善の解答です。 遠慮なくあなたの現実の問題をまで送って下さい、そうすればそれについて何かお手伝いできるかを検討してみます。

RBUの通知への申し込み!

別のお知らせ:Applelinksは要望の大きかったREALbasic Universityへのメーリングリストを開設しました。今日申し込みをすれば、コラムが発表されるたびにお知らせの emailがあなたに届きます!これはRBUに遅れを取らないための便利なお知らせです。

RBU-Jの通知サービス!コラムが発表されるたびに日本語版REALbasic Universityのお知らせの emailがあなたに届きます。登録・削除は ここ から。


クイズの解答:プログラマはよく誰が最も少ない行数(あるいは文字数)のルーチンを書けるかという競争をします。それは楽しいコンテストですが、それはまた有用なテクニックでもあります。しかし、現実の世界では時おりより読みやすいコードがよりコンパクトなコードよりも優れています。

答えは以下の通りです。1つの式に2つの等号があって一風変わって見えます、しかしそれは論理的には正しいのです(少なくとも2値倫理の見地からは)。

  pushButtonConvert.enabled = not (me.listIndex = -1)

INDEXに戻る