将WPF的控件绑定到F#选项

时间:2013-07-09 19:38:15

标签: wpf data-binding f#

假设我有一个业务对象(没有AllowNullLiteralAttribute)。

type Person(name: string) =
    member val Name = name
    override x.ToString() = name

一个视图模型,可选择设置所选人员。

type MainWindowModel() =

    let mutable selectedPerson: Person option = None
        :
    member val People = ObservableCollection<Person>()
    member x.SelectedPerson
        with get() = selectedPerson
        and set(v) =
            if selectedPerson <> v then
                selectedPerson <- v
                x.RaisePropertyChanged("SelectedPerson")

将WPF控件的SelectedItem属性绑定到F#选项属性(不使用AllowNullLiteralAttribute)的最佳方法是什么?

如果我这样做......

<StackPanel>
    <ListBox ItemsSource="{Binding People}"
             SelectedItem="{Binding SelectedPerson}"
             DisplayMemberPath="Name" />
    <TextBlock Text="{Binding SelectedPerson}" />
</StackPanel>

...导致错误,无法将'George'从'Person'类型转换为'Microsoft.FSharp.Core.FSharpOption`1 [Person]'

2 个答案:

答案 0 :(得分:8)

我目前采用的方法是编写自己的IValueConverter

open System
open System.Globalization
open System.Windows.Data

type OptionsTypeConverter() =

    // from http://stackoverflow.com/questions/6289761
    let (|SomeObj|_|) =
      let ty = typedefof<option<_>>
      fun (a:obj) ->
        let aty = a.GetType()
        let v = aty.GetProperty("Value")
        if aty.IsGenericType && aty.GetGenericTypeDefinition() = ty then
          if a = null then None
          else Some(v.GetValue(a, [| |]))
        else None

    interface IValueConverter with

        member x.Convert(value: obj, targetType: Type, parameter: obj, culture: CultureInfo) =
            match value with
            | null -> null
            | SomeObj(v) -> v
            | _ -> value

        member x.ConvertBack(value: obj, targetType: Type, parameter: obj, culture: CultureInfo) =
            match value with
            | null -> None :> obj
            | x -> Activator.CreateInstance(targetType, [| x |])

然后我的XAML看起来像这样:

<StackPanel>
    <ListBox ItemsSource="{Binding People}"
             SelectedItem="{Binding SelectedPerson, Converter={StaticResource OptionsTypeConverter1}}"
             DisplayMemberPath="Name" />
    <TextBlock Text="{Binding SelectedPerson, Converter={StaticResource OptionsTypeConverter1}}" />
</StackPanel>

可能有一种更简单的方法。该转换器可能已经存在于框架中。可能有更好的实现。

答案 1 :(得分:3)

我认为使用IValueConverter可能是最干净的方法。

或者,您可以使用选项类型具有Value成员(返回Some的值或抛出None的异常)这一事实。所以,如果你知道总有一个值,或者异常没有破坏任何东西(我还没有尝试过),你可能会写:

<ListBox ItemsSource="{Binding People}"
         SelectedItem="{Binding SelectedPerson.Value}"
         DisplayMemberPath="Name" />

如果异常有问题,则this past SO question suggests使用PriorityBinding

<ListBox ItemsSource="{Binding People}" DisplayMemberPath="Name">
  <ListBox.SelectedItem>
    <PriorityBinding>
        <Binding Path="SelectedPerson.Value" />
        <Binding Source="{x:Null}" />  <!-- or some other default... -->
    </PriorityBinding>
  </ListBox.SelectedItem>
</ListBox>