如何在不更改viewmodel的属性getter的情况下格式化XAML中的字符串?

时间:2015-07-28 00:08:58

标签: c# wpf xaml

我的应用程序中有以下界面:

public interface IContactMedium
{
    string ContactString { get; set; }
    string Type { get; set;}
    bool IsValid();
}

此界面用于表示某人的某种联系的对象。它可以是电话,电子邮件等。ContactString属性是实际的联系人数据(例如,对于电话,它将是电话号码),Type用于区分情况一个人有一个以上(对于电话,一个人可以有家庭电话,工作电话,手机等)IsValid方法是每种不同类型的联系媒介的验证机制。

所以,让我们说我的应用程序中有两个对象 - EmailPhone - 都实现了接口。我将在应用程序中创建一个UserControl,其中包含管理此类对象列表的UI。所以viewmodel看起来像这样:

public class ContactsCollectionViewModel<T> : ViewModelBase where T : class, IContactMedium
{
    private ObservableCollection<T> _itemsCollection;

    public ContactCollectionViewModel(ObservableCollection<T> items)
    {
        ItemsCollection = items;
    }

    public ObservableCollection<T> ItemsCollection
    {
        get { return _itemsCollection; }
        set
        {
            if (_itemsCollection != value)
            {
                _itemsCollection = value;
                OnPropertyChanged(() => ItemsCollection);
            }
        }
    }
}

我想在IContactMedium接口中添加另一个属性/方法,该属性/方法在WPF中的Binding中使用时为ContactString属性提供正确的格式。这个想法是绑定到ContactString的文本框中的格式根据实际存储在集合中的具体对象而有所不同:

<TextBox x:Name="ContactString"
         Text="{Binding ContactString, StringFormat=???}" />

我在网上搜索了一个解决方案,但找不到任何东西。我看到人们建议修改ContactString属性,以便getter返回格式化的值。因此,对于Phone对象,例如,属性将如下所示:

public string ContactString
{
    get 
    {
        return string.Format("({0}) {1}-{2}", _contactString.Substring(0,3), _contactString.Substring(4,3), _contactString.Substring(7,3));
    }
    set {
        _contactString = value;
    }
}

然而,对我来说这不是一个好的解决方案。该信息不仅由UI使用。它还会发送到应用程序的其他部分,包括数据库,需要原始格式的电话号码:##########。

有没有办法为XAML提供格式化程序,以便在绑定的StringFormat属性中使用?格式化是否可以由实现接口的对象决定?如果是,那么它需要什么类型,以及如何让XAML中的Binding可以访问它?

4 个答案:

答案 0 :(得分:3)

  

格式化是否可以由实现接口的对象决定?

在Xaml中,可以提供与特定类关联的数据模板。

只需在模板中为结构提供与目标属性绑定的格式,如下所示:

<Grid>
    <Grid.Resources>
        <DataTemplate DataType="{x:Type c:Ship}">
            <TextBlock Text="{Binding Path=Name, StringFormat=Ship: {0}}"
                        Foreground="Red" />
        </DataTemplate>
        <DataTemplate DataType="{x:Type c:Passage}">
            <TextBlock Text="{Binding Path=Name, StringFormat=Passage: {0}}"
                        Foreground="Blue" />
        </DataTemplate>
    </Grid.Resources>
    <ListBox Name="myListBox"
             Height="300"
             Width="200"
             ItemsSource="{Binding OBSCollection}">
    </ListBox>
</Grid>

因此对于我的集合,ShipPassage的类实例都遵循ITreeEntity

 public ObservableCollection<ITreeEntity> OBSCollection ...

当绑定创建一个列表,其中绑定具有特定的字符串格式:

enter image description here

请注意,在设置数据时,首先添加船只,然后添加通道。无论如何,Xaml都没有订购它们。

需要从复合集合中列出一个ListBox中的不同类型对象吗?在这里查看我的答案:

答案 1 :(得分:0)

您可以使用转换器。保持你的财产简单。

public string ContactString { get; set; }

实施转换器

class MyConverter : IValueConverter
{
    public object Convert(object value, Type targetType,
           object parameter, System.Globalization.CultureInfo culture)
    {
        contactString = value as string;
        if(contactString == null)
        {
             throw new InvalidArgumentException();
        }

        return string.Format("({0}) {1}-{2}",
          contactString.Substring(0,3), contactString.Substring(4,3),
          contactString.Substring(7,3));
    }

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

将其添加为资源

<Window.Resources>
    <local:MyConverter x:Key="MyConverter"/>
</Window.Resources>

使用它

<TextBox x:Name="ContactString"
     Text="{Binding ContactString, Converter{StaticResource MyConverter}}" />

答案 2 :(得分:-1)

您可以简单地覆盖ToString()方法。默认情况下,ListBox将使用对象的ToString()方法作为项目的显示文本。

public override string ToString()
{
    return string.Format("({0}) {1}-{2}", _contactString.Substring(0,3), _contactString.Substring(4,3), _contactString.Substring(7,3));
}

这意味着您无需在ListBox中执行任何奇特的操作,例如定义DataTemplate,因为ListBox会自动获取格式化的字符串。

<ListBox Name="myListBox"
         Height="300"
         Width="200"
         ItemsSource="{Binding OBSCollection}"/>

答案 3 :(得分:-1)

  

事实是,实现接口的每个具体类都有不同的格式规则

  

格式化是否可以由实现接口的对象决定?

困境是,是否将格式化逻辑添加到业务对象(IContactMedium实现)或表示层。

如果是业务逻辑,那么是的,您应该将格式代码添加到业务对象中。

但最有可能的是表现逻辑。在这种情况下,要么创建DataTemplate foreach实现IContactMedium,要么创建转换器。在转换器中,您可以根据值类型选择正确的格式。如果输出只是纯文本,请使用转换器。如果它更纯文本,例如格式化文本,则使用datatemplates。

提示:您可以使用单元测试来测试IContactMedium的所有实现是否都有其DataTemplate,或者是否由转换器覆盖。