user2921009
user2921009

Reputation: 833

How to format a string in XAML without changing viewmodel's property getter?

I have in my application the following interface:

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

This interface is for objects that represent some sort of contact for a person. It could be a phone, email, etc. The ContactString property is the actual contact data (for a phone, for example, it would be the phone number), and the Type is for differentiation in case a person has more than one (for phone, a person can have a Home phone, a Work phone, Cell phone, etc.) The IsValid method is a validation mechanism for each different type of contact medium.

so, let's say I have two objects in my application - Email and Phone - both implement the interface. I'm going to make in the application a UserControl that holds a UI that manages a list of such objects. So the viewmodel would look something like this:

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);
            }
        }
    }
}

I want to add to the IContactMedium interface another property/method that provides proper formatting for the ContactString property when used in Binding in WPF. The idea is that the format in the text box bound to ContactString differs depending on the concrete object that is actually stored in the collection:

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

I searched online a solution for this and couldn't find anything. I saw people suggesting modifying the ContactString property so the getter returns a formatted value. So, for the Phone object, for example, the property would look like this:

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;
    }
}

However, this is not a good solution for me. The information is not only used by the UI. It is also sent to other parts of the application, including a database, that need the phone number in its raw form: ##########.

Is there a way to provide the XAML a formatter to use in the StringFormat attribute of the binding? Can the formatting be dictated by the object that implement the interface? If yes, what type does it need to be, and how can I make it accessible to the Binding in XAML?

Upvotes: 1

Views: 5228

Answers (4)

ΩmegaMan
ΩmegaMan

Reputation: 31616

Can the formatting be dictated by the object that implement the interface?

In Xaml one can provide data templates which are associated with a specific class.

Simply provide the structure in the template with a formatting on the binding to the target property as shown below:

<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>

So for my collection where both class instances of Ship and Passage adhere to ITreeEntity:

 public ObservableCollection<ITreeEntity> OBSCollection ...

When bound creates a list where the binding has a specific string format as such:

enter image description here

Note in setting up the data the ships were added first followed by the passages. Xaml is not ordering them in anyway.


Need to list different types objects in one ListBox from a composite collection? See my answers here:

Upvotes: 4

Liero
Liero

Reputation: 27348

The thing is that each concrete class that implements the interface would have different formatting rules

Can the formatting be dictated by the object that implement the interface?

The dilema is, whether to add the formating logic to your business objects (IContactMedium implementations) or to presentation layer.

if it is business logic, then yes, you should add the formatting code to your business object.

But most probably it is presentation logic. In that case, either create DataTemplate foreach implementation of the IContactMedium, or create converter. In the converter you can choose correct formatting based on the value type. If the output is just plain text, use converter. If its more that plain text, e.g formatted text, use datatemplates.

TIP: You can use unit tests to test if all implementations of IContactMedium have its DataTemplate or are covered by the converter.

Upvotes: 0

Mike Eason
Mike Eason

Reputation: 9713

You can simply override the ToString() method. By default, a ListBox will use the object's ToString() method as the display text for the item.

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

This means you don't have to do anything fancy in the ListBox, like defining a DataTemplate, as the ListBox will pick up the formatted string automatically.

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

Upvotes: -1

Hamlet Hakobyan
Hamlet Hakobyan

Reputation: 33381

You can use converters. Keep your property simple.

public string ContactString { get; set; }

Implement converter

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();
    }
}

Add it as resource

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

Use it

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

Upvotes: 0

Related Questions