如何在XAML中对联合类型的值进行数据绑定?

3

如何在XAML中将数据绑定到联合类型的值?

当前数据绑定的值指向String20对象,而不是联合类型(即String20)封装的实际字符串值。

示例:

<ListView ItemsSource="{Binding Modules}" DisplayMemberPath="Author.First" />

作者定义:

type Name = { 
    First:String20
    Last:String20
    Suffix:String20 option 
}

type Module = { 
    Author:Name
    Duration:Duration 
}

XAML:

<Window x:Class="Client.MainWindow"
        ...
        xmlns:manageModules="clr-namespace:ManageModules;assembly=ManageModules">

    <Window.DataContext>
        <manageModules:CreationViewModel />
    </Window.DataContext>

    <Grid>
        <ListView ItemsSource="{Binding Modules}" DisplayMemberPath="Author.First" 
                  TextElement.Foreground="LightBlue" Background="Black" />
    </Grid>
</Window>

ViewModel:

namespace ManageModules
    ...
    type CreationViewModel() =
        inherit ViewModelBase()
        let name =      { First=String20("Scott"); Last=String20("Nimrod"); Suffix=None }
        let duration =  { Hours=1; Minutes=30; Seconds=0 }
        let moduleItem = { Author=name; Duration=duration }

        let mutable (_modules:Module ObservableCollection) = ObservableCollection()

        do _modules.Add(moduleItem)

        member this.Modules
            with get()      = _modules
            and set(value)  = _modules <- value

        member this.Add moduleItem = 
            _modules.Add(moduleItem)

业务领域:

module ManageModule.Entities

type Duration = { 
    Hours:int
    Minutes:int
    Seconds:int 
}

type String20 = String20 of string

type Name = { 
    First:String20
    Last:String20
    Suffix:String20 option 
}

type Module = { 
    Author:Name
    Duration:Duration 
}

and Section = 
    | Introduction of Module
    | Conclusion of Module
    | Content of Module list
2个回答

6

我的建议是修改String20,使其包含一个额外的成员,该成员是一个普通的string值,然后您就可以绑定到普通的string成员。

要向类型添加成员,可以编写:

type String20 = 
  | String20 of string
  member this.Value = 
    let (String20(str)) = this
    str

然后您应该能够编写以下代码:
<ListView ItemsSource="{Binding Modules}" DisplayMemberPath="Author.First.Value" />

可能有更复杂的方法来解决这个问题 - 我猜WPF有一些机制可以在绑定背后指定发生的转换 - 但将Value作为成员添加可能是一个很好的简单起点。


0
另一种方法是使用值转换器,正如Tomas所提到的那样。
这个例子是用C#编写的。稍后,当我有更多时间时,我将展示使用F#相同的方法。
我更喜欢在领域逻辑中保持简单性,而不是让UI或数据存储框架来支配(即绑架)领域逻辑。
因此,我提供了以下使用C#编写的值转换器:
using System;
using System.Globalization;
using System.Windows.Data;
using static ManageModule.Entities;

namespace Client.Converters
{
    public class String20Converter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            var unionCase = value as String20;
            return unionCase.Item;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

更新后的 XAML 如下:

<Window x:Class="Client.MainWindow"
        ...
        xmlns:local="clr-namespace:Client"
        xmlns:manageModules="clr-namespace:ManageModules;assembly=ManageModules"
        xmlns:converters="clr-namespace:Client.Converters"
        mc:Ignorable="d"
        Background="Black"
        Title="MainWindow" Height="350" Width="525">

    <Window.DataContext>
        <manageModules:CreationViewModel />
    </Window.DataContext>

    <Window.Resources>
        <converters:String20Converter x:Key="String20Converter" />
    </Window.Resources>

    <Grid>
        <ListView ItemsSource="{Binding Modules}" Background="Black">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Author.First, Converter={StaticResource String20Converter}}"
                               Foreground="LightBlue" />
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </Grid>
</Window>

请注意我们如何保留原始域逻辑:

type Duration = { 
    Hours:int
    Minutes:int
    Seconds:int 
}

type String20 = String20 of string

type Name = { 
    First:String20
    Last:String20
    Suffix:String2
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接