Многостолбцовые списки в WPF с богатым содержимым ячеек - PullRequest
2 голосов
/ 17 февраля 2012

Я пытаюсь написать автоматический тестовый комплект с использованием WPF и F #. Я хотел бы отображать совокупные результаты испытаний в виде строк в списке из нескольких столбцов. В идеале я хотел бы представить богатый контент в каждой ячейке, например, выделение проблем с помощью специальных тестов с использованием цвета или предоставление деталей через всплывающие подсказки. Тем не менее, я не могу понять, как поместить что-то более богатое, чем простая строка, в несколько столбцов ListView.

Кроме того, я не фанат 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! ). В идеале я бы хотел Add и obj array, предоставляющих элементы управления WPF для каждой ячейки.

Способен ли ListView на это? Если да, то как написать функцию, которая принимает двумерный массив элементов управления и возвращает 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

Ответы [ 3 ]

4 голосов
/ 17 февраля 2012

Да, это возможно, но это немного сложно, но как только вы освоите подход, он станет достаточно гибким.ВПП имеет гибкую систему шаблонов, которая доступна как через код, так и через XAML, за исключением того, что примеров кода в коде гораздо меньше.

В основном это включает работу с использованием FrameworkElementFactory для переопределения шаблона по умолчанию для списка и отображения нужных элементов пользовательского интерфейса.Затем, используя класс Binding, укажите, как элементы управления должны быть связаны с данными.

Я написал клиент Twitter в WPF и F # и использую этот подход для отображения столбцов твитов в списках.Посмотрите, как работает функция createTweetContainerTemplate.https://github.com/robertpi/TwitMemento/blob/master/Strangelights.TwitMemento.WPF/View.fs

Опять же, если вам действительно не нужен высокий уровень контроля над тем, как должна располагаться каждая строка в списке, возможно, проще использовать сетку данных.

2 голосов
/ 20 февраля 2012

Вашей конкретной проблемы дублирования имен полей можно избежать, используя привязку на основе индекса вместо имени члена.Смотрите изменения в вашем примере здесь:

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.ListView и DataGrid оба предполагают, что у вас есть некая примерно однородная коллекция объектов, которые вы хотите показать (обычно в виде строк), и некоторое представление о том, какую информацию вы хотите показать об этих объектах (определения столбцов).Оба элемента управления помогут в этой ситуации, хотя я согласен, что их общее предположение о том, что вы хотите использовать отражение над членами типа, может раздражать.

Если вы хотите иметь возможность указывать сетку любогоэлементы управления, тогда, как вы упомянули, компоновка панели Grid, вероятно, больше подходит.

2 голосов
/ 18 февраля 2012

Я создал простую библиотеку комбинаторов для создания пользовательского интерфейса WPF через код. Я использую этот шаблон в своем проекте pit для создания элементов HTML.

Пространство имен FSharp.WPF open System open System.Windows open System.Windows.Элементы управления

[<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

Сейчас это очень низкий профиль, просто создавайте объекты, но его можно расширить, чтобы сделать гораздо больше с привязкой данных и другими вещами и т. Д., И небольшая проверка типов должна найти ошибки при создании объектов.

Использование: модуль Test = open 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)

Как видите, вложенные элементы означают элементы Panel, но могут быть некоторые дополнительные рычаги, позволяющие типу идентифицировать элементы панели.или элементы содержимого.Но вы поняли, это может быть полезно.Как вы думаете?

-Fahad

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...