Suppose I have a business object (without an AllowNullLiteralAttribute
).
type Person(name: string) =
member val Name = name
override x.ToString() = name
And a view model, with the selected person optionally set.
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")
What is the best way to bind a WPF control's SelectedItem
property to an F# option property (without using AllowNullLiteralAttribute
)?
If I do this...
<StackPanel>
<ListBox ItemsSource="{Binding People}"
SelectedItem="{Binding SelectedPerson}"
DisplayMemberPath="Name" />
<TextBlock Text="{Binding SelectedPerson}" />
</StackPanel>
...it results in an error, Cannot convert 'George' from type 'Person' to type 'Microsoft.FSharp.Core.FSharpOption`1[Person]'
The approach I am currently taking is to write my own 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 |])
And then my XAML looks like this:
<StackPanel>
<ListBox ItemsSource="{Binding People}"
SelectedItem="{Binding SelectedPerson, Converter={StaticResource OptionsTypeConverter1}}"
DisplayMemberPath="Name" />
<TextBlock Text="{Binding SelectedPerson, Converter={StaticResource OptionsTypeConverter1}}" />
</StackPanel>
There may be a simpler way. This converter may already exist in the framework. There may be better implementations out there.
I think using an IValueConverter
is probably the cleanest approach.
Alternatively, you could use the fact that option type has Value
member (which returns the value for Some
or throws an exception for None
). So, if you know that there is always a value, or if the exception does not break anything (I have not tried), you might be able to write:
<ListBox ItemsSource="{Binding People}"
SelectedItem="{Binding SelectedPerson.Value}"
DisplayMemberPath="Name" />
If the exception is a problem, then this past SO question suggests to use 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>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With