2012-02-17 10 views
2

WPFとF#を使用して自動テストハーネスを作成しようとしています。私は、集計テスト結果を複数列のリストに行として表示したいと思います。理想的には、各セルに豊富なコンテンツを提示したいと思います。カラーを使用した特定のテストの問題を強調表示したり、ツールチップを使用して詳細を表示できます。しかし、私は単純な文字列よりも豊富なものをマルチカラムListViewに入れる方法を理解することはできません。リッチなセルの内容を持つWPFの複数列のリスト

さらに、私はXAMLまたはデータバインディングのファンではなく、バニラF#コードを書くことを好みます。私はマルチカラムWPF ListViewを表示する記述することができた最も簡単なプログラムは次のとおりです。

open System.Windows 

let gridView = Controls.GridView() 
let listView = Controls.ListView(View=gridView) 

type SystemParams = { Name: string; Value: obj } 

[<System.STAThread>] 
do 
    let column header binding = 
    let binding = Data.Binding binding 
    Controls.GridViewColumn(Header=header, DisplayMemberBinding=binding) 

    for header, binding in ["Name", "Name"; "Value", "Value"] do 
    column header binding 
    |> gridView.Columns.Add 

    for prop in typeof<SystemParameters>.GetProperties() do 
    if prop.PropertyType <> typeof<ResourceKey> then 
     { Name = prop.Name; Value = prop.GetValue(null, null) } 
     |> listView.Items.Add 
     |> ignore 

    Application().Run(Window(Content=listView)) |> ignore 

これは動作しますが、私はそれが両方の型定義で複製するフィールド名を要求する方法が気に入りませんおそらく実行時にそれらを解決するためにリフレクションを使用するWPFに供給される文字列として(yuk!)。理想的には、Addobj arrayを付けて、各セルにWPFコントロールを与えたいと思います。

ListViewはこれが可能ですか?もしそうなら、2D配列のコントロールを受け入れ、それらを可視化するListViewを返す関数をどのように記述しますか?

そうでない場合は、おそらくGridを使用します。私は前にDataGridを試してみました、それが...比較して痛みだけの世界です

EDIT:以下の答えに

おかげで、私は解決策を考え出すことができました。以下のプログラムでmultiColumnList関数は、指定したヘッダと選択行の内容とコントロールのリストを作成します。

open System.Windows 

let multiColumnList columns contents onSelection = 
    let gridView = Controls.GridView() 
    let list = Controls.ListView(View=gridView) 
    let column index header = 
    let binding = Data.Binding(sprintf "[%d]" index) 
    Controls.GridViewColumn(Header=header, DisplayMemberBinding=binding) 
    |> gridView.Columns.Add 
    Seq.iteri column columns 
    list.ItemsSource <- 
    [|for row in contents -> 
     [|for elt in row -> 
      box elt|]|] 
    list.SelectionChanged.Add onSelection 
    list 

[<System.STAThread>] 
do 
    let columns = ["Name"; "Value"] 
    let contents = 
    [ for prop in typeof<SystemParameters>.GetProperties() do 
     if prop.PropertyType <> typeof<ResourceKey> then 
      yield [box prop.Name; prop.GetValue(null, null)] ] 
    Application().Run(Window(Content=multiColumnList columns contents ignore)) 
    |> ignore 
+0

あなたは「さらに、私はXAMLやデータバインディングのファンではなく、バニラF#コードを書いています」と言い、これを前にDataGridを試してみました。 '最初の声明はそれが問題ではないのですか? –

+1

@AngelWPF:いいえ、最初のステートメントは解決策です。痛みの世界が私がバニラ・コードを書くことを好む理由です。例えば、私が本当に望むのは、コントロールの2D配列をとり、それらを含む複数列のリストコントロールを返す関数です。 WPFはこれを必要以上に困難にします。 WPFの上記の例でのリフレクションの使用は、あらかじめ定義された固定数の列しか処理できないことを意味します。 –

+1

なぜ2D配列がWPFの項目コントロールにバインドできないと思っていますか?XAMLを使用しているか、XAMLを使用していますか? 'FrameworkElementFactory'!でコーディングされた正しいテンプレートが必要です。 –

答えて

2

あなたの特定の問題ではなく、メンバー名の、結合ベースのインデックスを使用することによって回避することができます。ここにあなたの例に変更を参照してください。ListViewコントロールの配列の配列を与えることについて

open System 
open System.Windows 

let gridView = Controls.GridView() 
let listView = Controls.ListView(View=gridView) 

[<System.STAThread>] 
do 
    let column index header = 
    let binding = Data.Binding(sprintf "[%d]" index) 
    Controls.GridViewColumn(Header=header, DisplayMemberBinding=binding) 

    ["Name"; "Value"] 
    |> List.mapi column 
    |> List.iter gridView.Columns.Add 

    for prop in typeof<SystemParameters>.GetProperties() do 
    if prop.PropertyType <> typeof<ResourceKey> then 
     ([| prop.Name; prop.GetValue(null, null) |] : Object array) 
     |> listView.Items.Add 
     |> ignore 

    Application().Run(Window(Content=listView)) |> ignore 

、それはListViewが使用されることが意図されているよりもやや低いレベルです。 ListViewおよびは、オブジェクトのほぼ同種のコレクション(通常は行として表示する)と、それらのオブジェクトについて表示する情報(列の定義)のアイディアを持っていることを前提としています。両方のコントロールはそのような状況に役立ちますが、タイプのメンバーに対してリフレクションを使用するという一般的な前提が迷惑になることに同意します。

コントロールのグリッドを指定できるようにする場合は、おそらくGridパネルレイアウトが適しています。

+1

+1完璧、ありがとう! –

4

はい、それは可能ですが、それは少しトリッキーですが、あなたがアプローチを習得したら、それは非常に柔軟です。 WFPにはコードとXAMLの両方で利用できる柔軟なテンプレートシステムがありますが、これはコードでこれを行う方法がはるかに少ないことを除きます。

基本的には、FrameworkElementFactoryを使用してリストボックスのデフォルトテンプレートを上書きし、必要なUI要素を表示する作業を行います。次に、Bindingクラスを使用して、コントロールをデータにバインドする方法を指定します。

私はWPFとF#でTwitterクライアントを作成しました。このアプローチを使用してリストボックスにツイートの列を表示します。 の機能の仕組みを見てみましょう。 https://github.com/robertpi/TwitMemento/blob/master/Strangelights.TwitMemento.WPF/View.fs

また、リストボックスの各行をどのようにレイアウトするかについて、高度な制御が必要な場合を除いて、データグリッドを使用する方が簡単かもしれません。

+1

うわー、それは私が望んでいたものに比べて信じられないほど複雑です。たぶん、F#コミュニティはWPF上でまともな宣言型ラッパーAPIを書いて、これを1ライナーにする必要がありますか? –

+1

私はしばしば、F#ラッパーを使っていくつかのsillier WPFのより詳細なビットを整理するのはいいですが、WPFを構築して1つにするのに十分ではありません。 – Robert

2

単純なコンビネータライブラリを作成してWPF UIからコードを作成しました。このパターンをピットプロジェクトでHTML要素を作成するために使用します。

名前空間FSharp.WPF オープンシステム オープンSystem.Windows オープンSystem.Windows.Controls

[<AutoOpen>] 
module Combinator = 
    type XDef = 
    | Attr  of string * obj 
    | Tag  of Type * XDef list 
    //| Element of FrameworkElement 

    [<AutoOpen>] 
    module Operators = 
     let (@=) (p:string) (v:obj) : XDef = Attr(p,v) 

    module internal Helpers = 
     let createEl (ty:Type) = new FrameworkElementFactory(ty) 

    let tag name attr = Tag(name,attr) 
    //let el dom   = Element(dom) 

    let rec build (tag:XDef) = 
     match tag with 
     | Tag(ty,defs) -> 
      let attrs = defs |> List.choose(fun t -> match t with | Attr(k,v) -> Some(k,v) | _ -> None) 
      let tags = defs |> List.choose(fun t -> match t with | Tag(k,v) -> Some(t) | _ -> None) 
      /// create the element and set attributes 
      let el = Helpers.createEl(ty) 
      let rec setProps (d:(string*obj) list) = 
       match d with 
       | []  ->() 
       | (p,v) :: t -> 
        let dp = System.ComponentModel.DependencyPropertyDescriptor.FromName(p, el.Type,el.Type) 
        el.SetValue(dp.DependencyProperty,v) 
      setProps attrs 

      let rec gen (d:XDef list) = 
       match d with 
       | [] ->() 
       | h::t -> 
        let childEl = build(h) 
        el.AppendChild(childEl) 
        gen(t) 
      gen tags 
      el 
     //| Element(el)  -> el 
     | Attr(key,value) -> failwith "Unrecognized sequence" 

    let make xdef = 
     let fEl = build xdef 
     let contentEl = new ContentControl() 
     contentEl.ContentTemplate <- new DataTemplate(VisualTree=fEl) 
     contentEl :> FrameworkElement 

その非常にロープロファイル今、ちょうどオブジェクトを作成しますが、はるかに関係するように拡張することができデータバインディングやその他のことがあります。型チェックのビットは、オブジェクトの作成時にエラーを見つけるはずです。

使用法: モジュールテスト= オープンSystem.Windows.Controls

let create() = 
     tag typeof<System.Windows.Controls.Button> ["Content"@="Hello World"] 
     |> Combinator.make 

    let create2() = 
     tag typeof<StackPanel> [ 
      tag typeof<Button> ["Content"@="Button 1"] 
      tag typeof<Button> ["Content"@="Button 2"] 
     ] 
     |> Combinator.make 

[<STAThread>] 
[<EntryPoint>] 
let main(_) = 
    let el  = Test.create2() // Test.create1() 
    let window = new Window(Content = el, Height = 600.0, Width = 800.0, Title = "WpfApplication1") 
    let app = new Application() 
    app.Run(window) 

ご覧のように、入れ子の要素は、パネルの要素を意味するが、そこにできたタイプは、パネル要素やコンテンツを識別することができますいくつかの余分なレバレッジ要素。しかし、あなたはアイデアを得る、これは有用かもしれません。どう思いますか?フィールド名を複製する

-Fahad

関連する問題