スポンサーサイト

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

F#入門 Windowsアプリケーション編 ListView(3)

今回は簡単な関数を作成して、列見出しをクリックするとソートが行われるようにします。
 
いくつか関数を定義した後で、
 
let mainForm = new Form(Width = 480, Height = 200, Text = "MyListView Window")
mainForm.Show() |> ignore;;
 
let myListView = new ListView(Location = new Point(30,20),Width = 400)
mainForm.Controls.Add(myListView)
 
[(typeof<int>,"ID",70,HorizontalAlignment.Right);
(typeof<string>,"品目",100,HorizontalAlignment.Left);
(typeof<int>,"価格",100,HorizontalAlignment.Right);
(typeof<DateTime>,"UpDateTime",100,HorizontalAlignment.Right)]
|> setUpListView myListView
 
[["21";"りんご";"178";"2010/01/02"];["1";"みかん";"12";"2012/07/01"];["111";"バナナ";"114";"2011/03/20"]]
|> myAddItem myListView
 
とすることで以下のように表示され、列見出しをクリックすると正順と降順で交互にソートされるようにしました。 
 
817-1.jpg 
 
さてまずはソートに使うクラスの定義です。前回もやったように、System.Collections.IComparerインターフェイスを実装する必要があります。数値は数値に変換して、DateTime型はDateTime型に変換して、比較ソートをしたいので、コンストラクタでこの部分の処理を担当する関数を引数で渡すようにしています。あとの引数はsubItemsのインデックスを指定するcolと、正順、降順をコントロールするためのsignです。(これは実際には1か-1という値しかとりません。)この部分のコードは以下の通りです。
 
//ソート用のクラス
type LVIComparer (col:int, f : (string*string->int), sign : int) = //signが+1か-1で昇順か降順かが決まる
    interface System.Collections.IComparer with
        member this.Compare (x:obj,y:obj) = //x,yにはListViewItemのインスタンスが渡される(キャストが必要)
            let l = ((x :?> ListViewItem).SubItems.Item col).Text
            let r = ((y :?> ListViewItem).SubItems.Item col).Text
            sign * (f (l,r)) 
            
つぎに、ソート用のクラスのインスタンスを作り出す関数mkLVICompareです、これは、in_col,in_typ,in_signを引数としますが、in_colとin_signは上のクラスのコンストラクタにそのまま渡されます。in_typeはSystem.Type型の値で型情報です。この型情報に基づいて、コンストラクタに渡す関数を場合別で作ります。この部分のコードは次の通りです。
 
let mkLVIComparer (in_col:int,in_typ : System.Type,in_sign :int)  =
    let fSub : string*string -> int =
        match in_typ with
        | _ when in_typ = typeof<string> ->     
                        (fun (l,r) -> String.Compare (l,r))
        | _ when in_typ = typeof<int>    ->
                        (fun (l,r) -> (Int32.Parse l - Int32.Parse r))
        | _ when in_typ = typeof<DateTime> ->
                        (fun (l,r) -> DateTime.Compare(DateTime.Parse l ,DateTime.Parse r))                 
        | _ -> failwith "cannot handle this type"
 
    new LVIComparer (in_col,fSub,in_sign)
 
実際に「ListViewに対してコラムを設定し、コラムを押したときのイベントハンドラーを登録する」関数が次の関数です。それぞれのコラムのTagプロパティに、正順用と逆順用のMyListViewItemComparerのインスタンスを登録して、イベントが発火した時は、そこからLVIComparerクラスのインスタンスを呼び出して、実際にソートを行います。またこの際に、次にイベントが発火したときに、もう一方の(正順が呼び出された時は逆順、逆順が呼び出された時は正順)インスタンスが呼び出されるように、タプルの成分を入れ替えています。コードは以下の通りです。
 
let setUpListView (targetListView : ListView) (colTextWidthLst : list<System.Type*string*int*HorizontalAlignment>) =
    
    targetListView.View <- View.Details
    targetListView.GridLines <- true
 
    colTextWidthLst
    |> List.mapi (fun i (typ,text,width,algin) ->
                     let colhead = new ColumnHeader(Text = text,Width = width,TextAlign = algin)
                     //Tagに正順用と逆順用のMyListViewItemComparerのインスタンス(列i,型typ用)をタプルとして収める
                     colhead.Tag <- ( (mkLVIComparer (i,typ,1)), (mkLVIComparer (i,typ, -1))) 
                     targetListView.Columns.Add (colhead) |> ignore)
    |> ignore
    
    targetListView.ColumnClick.Add((fun e ->
                                        let curColIndex = e.Column
                                        let curCol = targetListView.Columns.Item  curColIndex
                                        let (LVIcom1,LVIcom2) = (curCol.Tag :?> LVIComparer*LVIComparer)
                                        targetListView.ListViewItemSorter <- LVIcom1  //タプルの第一成分を使いソート
                                        curCol.Tag <- (LVIcom2,LVIcom1) //タプルの成分を入れ替える(トグルするため)
                                        ))
 
 
listViewにItemを加える関数は前回紹介したものと同じものです。
let myAddItem (targetListView : ListView) (itemData : list<list<string>>) =
    List.map (fun strLst -> targetListView.Items.Add(new ListViewItem (Array.ofList strLst)) |> ignore) itemData
    |> ignore
 
 
では全コードは次のようになります。
 
open System
open System.Windows.Forms
open System.Drawing
open System.Reflection
 
 
//ソート用のクラス
type LVIComparer (col:int, f : (string*string->int), sign : int) = //signが+1か-1で昇順か降順かが決まる
    interface System.Collections.IComparer with
        member this.Compare (x:obj,y:obj) = //x,yにはListViewItemのインスタンスが渡される(キャストが必要)
            let l = ((x :?> ListViewItem).SubItems.Item col).Text
            let r = ((y :?> ListViewItem).SubItems.Item col).Text
            sign * (f (l,r))  
             
//ソート用のクラスのインスタンスを作り出す関数(下のsetUpListViewのhelper関数)
let mkLVIComparer (in_col:int,in_typ : System.Type,in_sign :int)  =
    let fSub : string*string -> int =
        match in_typ with
        | _ when in_typ = typeof<string> ->     
                        (fun (l,r) -> String.Compare (l,r))
        | _ when in_typ = typeof<int>    ->
                        (fun (l,r) -> (Int32.Parse l - Int32.Parse r))
        | _ when in_typ = typeof<DateTime> ->
                        (fun (l,r) -> DateTime.Compare(DateTime.Parse l ,DateTime.Parse r))                 
        | _ -> failwith "cannot handle this type"
 
    new LVIComparer (in_col,fSub,in_sign)
 
//ListViewに対してコラムを設定し、コラムを押したときのイベントハンドラーを登録する
let setUpListView (targetListView : ListView) (colTextWidthLst : list<System.Type*string*int*HorizontalAlignment>) =
    
    targetListView.View <- View.Details
    targetListView.GridLines <- true
 
    colTextWidthLst
    |> List.mapi (fun i (typ,text,width,algin) ->
                     let colhead = new ColumnHeader(Text = text,Width = width,TextAlign = algin)
                     //Tagに正順用と逆順用のMyListViewItemComparerのインスタンス(列i,型typ用)をタプルとして収める
                     colhead.Tag <- ( (mkLVIComparer (i,typ,1)), (mkLVIComparer (i,typ, -1))) 
                     targetListView.Columns.Add (colhead) |> ignore)
    |> ignore
    
    targetListView.ColumnClick.Add((fun e ->
                                        let curColIndex = e.Column
                                        let curCol = targetListView.Columns.Item  curColIndex
                                        let (LVIcom1,LVIcom2) = (curCol.Tag :?> LVIComparer*LVIComparer)
                                        targetListView.ListViewItemSorter <- LVIcom1  //タプルの第一成分を使いソート
                                        curCol.Tag <- (LVIcom2,LVIcom1) //タプルの成分を入れ替える(トグルするため)
                                        ))
 
let myAddItem (targetListView : ListView) (itemData : list<list<string>>) =
    List.map (fun strLst -> targetListView.Items.Add(new ListViewItem (Array.ofList strLst)) |> ignore) itemData
    |> ignore
 
let mainForm = new Form(Width = 480, Height = 200, Text = "MyListView Window")
mainForm.Show() |> ignore;;
 
let myListView = new ListView(Location = new Point(30,20),Width = 400)
mainForm.Controls.Add(myListView)
 
[(typeof<int>,"ID",70,HorizontalAlignment.Right);
(typeof<string>,"品目",100,HorizontalAlignment.Left);
(typeof<int>,"価格",100,HorizontalAlignment.Right);
(typeof<DateTime>,"UpDateTime",100,HorizontalAlignment.Right)]
|> setUpListView myListView
 
[["21";"りんご";"178";"2010/01/02"];["1";"みかん";"12";"2012/07/01"];["111";"バナナ";"114";"2011/03/20"]]
|> myAddItem myListView
 
なお型毎にデフォルトの比較関数を使用していますが、型情報を渡す代わりに、列挙型を用いて自分で色々な並べ替えの方法を登録して、それを選ぶ方法もよいかと思います。
参考リンクhttp://dobon.net/vb/dotnet/control/lvitemsort.html
スポンサーサイト

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

コメントの投稿

非公開コメント

プロフィール

T GYOUTEN

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

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

この人とブロともになる

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