在 ViewModel 中覆盖 List.ToString()

Override List.ToString() in ViewModel

我有一个显示部件的数据网格。每个部分都可以有多个标识符,网格看起来像这样

Name Identifiers Size ...
-------------------------
P1    A,B         3
P2    C,D         4
P3    E           2

在我的模型中,标识符存储在每个零件的列表中

class PartListModel {
    public ObservableCollection<Part> Parts { get; set; }
}
class Part {
    public IList<Identification> Identifications { get; set; }
}

但是为了让它在我的视图中正确显示,我必须在我的部分

中添加一个 Getter
class Part {
    public string IdentNames { 
          get { return string.Join(",", Identifications.Select(i => i.ArticleNumber)); } 
    }
}

在我的视图中显示它

<DataGridTextColumn Header="Identifiers" Binding="{Binding IdentNames}" />

这是我想避免的黑客行为。如何让我的 ViewModel 覆盖我的列表的 ToString() 方法而不需要在我的模型中添加丑陋的 Getter?

一个选项(您可能会使其更通用以处理不同的情况)是创建一个值转换器来输出所需的值:

public class MyIdentificationConverter : IValueConverter
{
    public object Convert(object value, Type targetType,
        object parameter, CultureInfo culture)
    {
        return string.Join(",", 
            ((List<MainWindowViewModel.Identification>)value).Select(x => x.ArticleNumber));
    }

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

然后在您的 XAML:

中使用它
<Window.Resources>
    <myWpfApp:MyIdentificationConverter x:Key="MyIdentificationConverter" />
</Window.Resources>

...

<DataGridTextColumn Header="Identifiers" Binding="{Binding Path=Identifications,
                              Converter={StaticResource MyIdentificationConverter}}" />

这是一个更通用的版本,使用反射找到正确的 属性:

public object Convert(object value, Type targetType,
    object parameter, CultureInfo culture)
{
    return string.Join(",",
        (((IEnumerable<object>)value).Select(x => x.GetType()
                                     .GetProperty(parameter.ToString()).GetValue(x))));
}

再次从 XAML 调用:(将 属性 作为参数传递给 select)

<DataGridTextColumn Header="Identifiers" Binding="{Binding Path=Identifications,
                              Converter={StaticResource MyIdentificationConverter},
                              ConverterParameter=ArticleNumber}" />

我写了IEnumerable<T>的扩展方法,大家可以添加使用:

public static string ToString<T>(this IEnumerable<T> values,string seperator,Func<T,string> property)
{
    return string.Join(seperator, values.Select(property));
}

并像这样使用它:

List<PhoneDictionary> table = new List<PhoneDictionary>() 
            { 

             new PhoneDictionary { Phone1 = 1, Phone2 =2},
             new PhoneDictionary { Phone1 = 1, Phone2 =2},
             new PhoneDictionary { Phone1 = 2, Phone2 =3},
             new PhoneDictionary { Phone1 = 3, Phone2 =4},
             new PhoneDictionary { Phone1 = 3, Phone2 =4},
            };


 var phones = table.ToString(",",x => x.Phone1.ToString());

输出:

1,1,2,3,3

我的Class:

public class PhoneDictionary
{
    public int Phone1 { get; set; }
    public int Phone2 { get; set; }
}

在你的情况下你可以这样调用:

string articles = partsViewModel.Parts.ToString(",",x=>x.ArticleNumber);

您可以通过编写本身也是值转换器的标记扩展来获得更好的 XAML 语法。

[MarkupExtensionReturnType(typeof(BindingBase))]
[ValueConversion(typeof(object), typeof(string))]
public sealed class ConcatenateExtension : MarkupExtension, IValueConverter
{
    public ConcatenateExtension()
    {
        Separator = ",";
    }

    public ConcatenateExtension(string path)
        : this()
    {
        Path = path;
    }

    public string Path { get; set; }
    public string Property { get; set; }
    public string Separator { get; set; }

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        var binding = new Binding(Path);
        binding.Converter = this;
        return binding;
    }

    object IValueConverter.Convert(object value, Type targetType,
        object parameter, CultureInfo culture)
    {
        // 'value' is the property value obtained by binding to `Path`
        // We're assuming here that 'value' can be enumerated
        // Code will obviously throw an exception if it can't

        IEnumerable<object> sequence =
            from object element in value as IEnumerable<object>
            let propertyValue =
                Property == null ?
                    element :
                    element.GetType().GetProperty(Property).GetValue(element)
            select propertyValue;

        return String.Join(Separator, sequence);
    }

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

您可以像这样在 XAML 中使用它:

xmlns:my="clr-namespace:..."

...

<DataGridTextColumn Header="Ids" Binding="{my:Concatenate
    Identifications, Property=ArticleNumber, Separator=' / '}" />

Separator 属性 是可选的,默认为单个逗号。

不幸的是,代码仍然使用反射,但我看不出有什么方法可以避免它。