スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

F#雑記 テンキー専用一文字計算機

BlUEPIXYさんの記事をみて、ちょっとやってみました。(ただ入力方法はテンキー専用となっています。)
 
フォームのイベント・ハンドラのみでキー処理を行うため、フォームのKeyPreviewプロパティをTrueに設定します。
mainForm.KeyDown.AddHandler(fun _ e ->....................)とイベントハンドラーを追加しますが、KeyPressEventArgs eのKeyDataプロパティをToString()すると、テンキーの+を入力したときは"Plus",-は"Subtract",*は"Multiply"/は"Divide"が返されます。またテンキーの6を入力したときは"NamPad6"が返されます。
これらの「文字列と、演算を対応させたもの」と、「文字列と数字を対応させたもの」をそれぞれMapにしておきます。
 
> open System 
open System.Windows.Forms 
open System.Drawing ;;
 
> let opMap = Map.ofList [("Add",(+));("Subtract",(-));("Multiply",(*));("Divide",(/))];;
 
val opMap : Map<string,(int -> int -> int)> =
  map
    [("Add", <fun:opMap@7-4>); ("Divide", <fun:opMap@7-7>);
     ("Multiply", <fun:opMap@7-6>); ("Subtract", <fun:opMap@7-5>)]
 
> let numMap = 
    [0..9]
     |> List.map (fun i -> ("NumPad" + i.ToString(),i))
     |> Map.ofList;;
 
val numMap : Map<string,int> =
  map
    [("NumPad0", 0); ("NumPad1", 1); ("NumPad2", 2); ("NumPad3", 3);
     ("NumPad4", 4); ("NumPad5", 5); ("NumPad6", 6); ("NumPad7", 7);
     ("NumPad8", 8); ...]
     
 
次に状態をDiscriminated unionで表現することにします。
状態としては、
(1)+,-,/,*が入力されて、次の数字待ち
(2)数字が入力されて、次の+,-,/,*の入力待ち
(3)不正な入力をして、次の数字待ち
の3通りです。
 
> type CurrentState =
   |ExpectingNum of int*(int->int->int) //現在までの結果と直前に入力された演算
   |ExpectingOp  of int                 //現在までの結果
   |Fail
   //"Add"や"NumPad2"などの文字列を受け取り次の状態を返すメソッド
   //if elseのインデントが表示上おかしくなってます。

   member this.nextState (keyDataStr:string) =
      match this with
      |ExpectingNum(result,op) -> if (numMap.TryFind keyDataStr).IsSome then
                                     ExpectingOp(op result numMap.[keyDataStr])
                                  else Fail
      |ExpectingOp(result)     -> if (opMap.TryFind keyDataStr).IsSome then
                                     ExpectingNum(result,opMap.[keyDataStr])
                                  else Fail
      |Fail                    -> if (numMap.TryFind keyDataStr).IsSome then
                                     ExpectingOp(numMap.[keyDataStr])
                                  else Fail;;
 
type CurrentState =
  | ExpectingNum of int * (int -> int -> int)
  | ExpectingOp of int
  | Fail
  with
    member nextState : keyDataStr:string -> CurrentState
  end
 
表示用の関数も定義しておきます。
 
> let display (tb:TextBox) (cs:CurrentState) =
    match cs with
    |ExpectingNum(result,_) -> tb.Text <- result.ToString()
    |ExpectingOp(result)    -> tb.Text <- result.ToString()
    |Fail                   -> tb.Text <- "Error";;
 
val display : TextBox -> CurrentState -> unit
 
準備が整いましたので、フォーム等を準備して表示します。
 
> let mainForm = new Form(Width = 200, Height = 150, Text = "One Letter Calc",KeyPreview = true )  
let myTextBox0 = new TextBox(Location = new Point(30,50), TextAlign = HorizontalAlignment.Center,
                             ReadOnly = true, BackColor = Color.White, TabStop = false) 
let mutable cs = ExpectingNum(0,(+)) //初期状態
 
display myTextBox0 cs  //初期状態の表示
 
mainForm.KeyDown.AddHandler(fun _ e -> cs <- cs.nextState (e.KeyData.ToString())
                                       display myTextBox0 cs) 
mainForm.Controls.Add(myTextBox0)
mainForm.Show() |> ignore ;;
 
 
val mainForm : Form = System.Windows.Forms.Form, Text: One Letter Calc
val myTextBox0 : TextBox = System.Windows.Forms.TextBox, Text: 0
val mutable cs : CurrentState = ExpectingNum (0,<fun:cs@36-1>)
 
これでできあがりです。
全コードは次の通りです。
 
open System 
open System.Windows.Forms 
open System.Drawing 
 
let opMap = Map.ofList [("Add",(+));("Subtract",(-));("Multiply",(*));("Divide",(/))]
 
let numMap = 
    [0..9]
     |> List.map (fun i -> ("NumPad" + i.ToString(),i))
     |> Map.ofList
 
type CurrentState =
   |ExpectingNum of int*(int->int->int) //現在までの結果と直前に入力された演算
   |ExpectingOp  of int                 //現在までの結果
   |Fail
   //"Add"や"NumPad2"などの文字列を受け取り次の状態を返すメソッド
   //if elseのインデントが表示上おかしくなってます。
   member this.nextState (keyDataStr:string) =
      match this with
      |ExpectingNum(result,op) -> if (numMap.TryFind keyDataStr).IsSome then
                                     ExpectingOp(op result numMap.[keyDataStr])
                                  else Fail
      |ExpectingOp(result)     -> if (opMap.TryFind keyDataStr).IsSome then
                                     ExpectingNum(result,opMap.[keyDataStr])
                                  else Fail
      |Fail                    -> if (numMap.TryFind keyDataStr).IsSome then
                                     ExpectingOp(numMap.[keyDataStr])
                                  else Fail              
 
 
let display (tb:TextBox) (cs:CurrentState) =
    match cs with
    |ExpectingNum(result,_) -> tb.Text <- result.ToString()
    |ExpectingOp(result)    -> tb.Text <- result.ToString()
    |Fail                   -> tb.Text <- "Error"
 
 
let mainForm = new Form(Width = 200, Height = 150, Text = "One Letter Calc",KeyPreview = true )  
let myTextBox0 = new TextBox(Location = new Point(30,50), TextAlign = HorizontalAlignment.Center,
                             ReadOnly = true, BackColor = Color.White, TabStop = false) 
let mutable cs = ExpectingNum(0,(+)) //初期状態
 
display myTextBox0 cs  //初期状態の表示
 
mainForm.KeyDown.AddHandler(fun _ e -> cs <- cs.nextState (e.KeyData.ToString())
                                       display myTextBox0 cs) 
mainForm.Controls.Add(myTextBox0)
[<STAThread()>]
do Application.Run(mainForm) 

上のコードで、プロジェクトのプロパティで、ApplicationタブのOutput TypeでWindows Applicationを選択した上で
コンパイル実行すると、ウィンドウのみが表示されます。  

スポンサーサイト

テーマ : プログラミング
ジャンル : コンピュータ

コメントの投稿

非公開コメント

No title

取り上げて頂いて、恐縮です。
また、余計なお世話だけど、
仮にもアプリケーションとするなら、
>mainForm.Show() |> ignore
じゃなくて、
[<STAThread()>]
do Application.Run(mainForm)
とかした方がいいような気がします。

No title

コメントありがとうございます。本文の最後を修正しました。
昔デザイナーがないころにSwingのプログラムを一本作ったことがあるのですが、とにかくリスナーをいっぱい作るのが大変だった思い出があります。「こんな簡単にイベントハンドラを付け加えることができるようになるなんて!」と感慨深いものがあります。

釈迦に説法

もし、コンパイル時と対話環境を区別するなら、
#if COMPILED
[<STAThread>]
do Application.Run(form)
#endif
のようにする

No title

これが一番よいですね。次からはこの線でいってみたいと思います。
プロフィール

T GYOUTEN

Author:T GYOUTEN
F#と英単語とフリーソフトと読書に興味があります。
ホームページでフリーソフトも公開しています。どぞ御贔屓に。

最新記事
最新コメント
最新トラックバック
月別アーカイブ
カテゴリ
フリーエリア
フリーエリア
blogram投票ボタン
検索フォーム
RSSリンクの表示
リンク
ブロとも申請フォーム

この人とブロともになる

QRコード
QRコード
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。