2

I'm trying to implement the MVVM design pattern in my WPF application but I have some problems to bind my Views with my ViewModels.

In one of my ViewModels, I have the following property :

public IPEndPoint EndPoint
{
    get { return _serverInfos.EndPoint; }
    private set
    {
        _serverInfos.EndPoint = value;
        RaisePropertyChanged("EndPoint");
    }
}

I want to bind this property in the related View like that :

<TextBox Text="{Binding EndPoint.Address}" />
<TextBox Text="{Binding EndPoint.Port}" />

The EndPoint.Port binding works as expected but the other one doesn't because EndPoint.Address is not a string (it's an IPAddress). Of course, I could define two string properties instead of one IPEndPoint but I think that it's not a good solution.

I also have the same problem using Enums when I want to convert them into int :

<ComboBox SelectedIndex="{Binding MyEnumProperty}" />

How could I solve these problems ?

Thank you for your help.

  • Please provide more code to understand your issue better. – Ayyappan Subramanian Mar 04 '15 at 21:09
  • The first binding is not working because of this: `Cannot create default converter to perform 'two-way' conversions between types 'System.Net.IPAddress' and 'System.String'. Consider using Converter property of Binding. BindingExpression:Path=EndPoint.Address; DataItem='MainWindow' (Name=''); target element is 'TextBox' (Name=''); target property is 'Text' (type 'String')` Basically it can't do a TwoWay Binding, because it doesn't know how to construct an IPAddress from a string. If you add `{Binding EndPoint.Address, Mode=OneWay}` it'll start working, only in one direction, obviously. – Szabolcs Dézsi Mar 04 '15 at 21:11
  • As the error message suggests, you have to create a Converter implementing the Convert and ConvertBack methods appropriately. – Szabolcs Dézsi Mar 04 '15 at 21:12
  • You could read [this question](http://stackoverflow.com/questions/397556/how-to-bind-radiobuttons-to-an-enum/406798). – dymanoid Mar 04 '15 at 21:14
  • Thank you Szabolcs Dézsi for your feedback. I did not know binding converter. I will try to develop one for my need. –  Mar 04 '15 at 21:25
  • I'll provide one for the IPAddress class as an answer. – Szabolcs Dézsi Mar 04 '15 at 21:26
  • Thank you dymanoid for giving me this link. I have search for similar post without success. –  Mar 04 '15 at 21:27
  • @Brice Welcome to Stack Overflow! You might want to consider marking one of these three answers as "accepted" if you feel that it answers your original question. – Steven Rands Mar 05 '15 at 14:11

3 Answers3

2

Normally you want a view model to take things from the model and expose them in a way that the view can consume. As such

  1. MyEnumProperty should be an System.Int32 (int) for ComboBox.SelectedIndex to consumer
  2. You should probably implement two separate properties for EndPointPort and EndPointAddress, and EndPointAddress should be a string that converts to an IPAddress when working with the model

You can use IValueConverters for both of those, but then you are reducing some of the utility of a separate view model in the first place if all it does is act like the model.

NextInLine
  • 2,126
  • 14
  • 23
  • Ok, I understand that point of view. Maybe I have to reconsider the choice of my properties. In fact, I have implemented my ViewModel that way because it's easier for me to work with these types. Example : for a command of my ViewModel, I use "if ( MyEnumProperty == MyEnum.EnumValue1 )" instead of "if ( MyEnumProperty == 1 ). So the code is easier to read. –  Mar 04 '15 at 21:56
  • @Brice enums are implemented using an integer type, normally `int` (`System.Int32`). If your enum values are equal to the order they are in the ComboBox, you can just cast back and forth in the view model. Of course if your ComboBox items are enum values, then you should use SelectedValue instead of SelectedIndex. – NextInLine Mar 04 '15 at 22:29
  • Ok, that is what I will do. One last thing : you said that I should separate properties (EndPointPort & EndPointAddress) in my ViewModel. How could I do that if my ViewModel expose a list of model objects (ObservableCollection MyList) where each model object contains one single IPEndPoint property. Do I have to redefine my ModelObject so it has two string properties instead of the IPEndPoint property? –  Mar 04 '15 at 22:44
  • @Brice, if your view is only displaying the collection items and not modifying them but can't display directly from the original ModelObject items themselves, then you can create a `ObservableCollection MyVMList` and synchronize the two collections by subscribing to the `MyList.CollectionChanged` event, converting ModelObjects to ViewModelObjects as they are added. Just make sure to unsubscribe from the `CollectionChanged` event if your Model can outlast your ViewModel, or you can end up with a memory leak. – NextInLine Mar 05 '15 at 18:07
  • @Brice, if you are changing properties on ModelObject sub-properties in the view, then things get a bit more complicated - your ViewModelObjects (or ModelObjects if those work) need to implement INotifyPropertyChanged or inherit from Freezable (which provides a SubPropertyChanged event), then you need to subscribe to this event for each item in the collection (including subscribing when new items are added and unsubscribing when old ones are removed to prevent memory leaks and other bugs). – NextInLine Mar 05 '15 at 18:09
  • it seems to be very complicated to work with sub-properties. I think I will arrange to avoid using them. Thank you for the tips about using ViewModels for lists. I will point me in that direction. –  Mar 06 '15 at 08:59
2

A converter you can use for converting between IPAddress and string.

public class IPAddressConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var ipAddress = value as IPAddress;

        if (ipAddress != null)
        {
            return ipAddress.ToString();
        }

        return DependencyProperty.UnsetValue;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var text = value as string;
        IPAddress ipAddress;

        if (text != null && IPAddress.TryParse(text, out ipAddress))
        {
            return ipAddress;
        }

        return DependencyProperty.UnsetValue;
    }
}

And then in a ResourceDictionary or a Resources collection of a FrameworkElement

<IPAddressConverter x:Key="IpAddressConverter" />

And in the binding:

<TextBox Text="{Binding EndPoint.Address, Converter={StaticResource IpAddressConverter}}" />
Szabolcs Dézsi
  • 8,743
  • 21
  • 29
  • Thank you for providing me with this converter. I have one last question. Using this method, the setter of my "EndPoint" property will be properly called when the IP address or the port number changes? –  Mar 04 '15 at 22:02
  • 1
    @Brice, the Address setter on the current EndPoint object will be called. If EndPoint class does not notify its callers that its properties have changed, then you're better off having separate properties for Port and Address on the view model itself. – NextInLine Mar 04 '15 at 22:32
  • Very late addition, but I changed the Binding.DoNothings to DependencyProperty.UnsetValues. Those are better to return in a converter. We don't want to stop the binding engine completely (Binding.DoNothing would stop the FallbackValue mechanism for example) – Szabolcs Dézsi Nov 26 '15 at 23:57
0

As far as the IPEndPoint type, this is a perfect case for a type converter (IValueConverter). It would look something like this, assuming your type has a valid ToString implementation:

public class IPEndPointConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        IPEndPoint endPoint = (IPEndPoint)value;
        return endPoint.ToString();
    }

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

You could then add the converter to your XAML file with a ResourceDictionary as follows (assuming you've added the relevant namespace, here called "converters", to your XAML):

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

You would then simply use this anywhere you need it within your XAML by adding it to the binding:

<TextBox Text="{Binding Path=EndPoint.Address, Converter={StaticResource ResourceKey=ipEndPointConverter}}" />
karfus
  • 869
  • 1
  • 13
  • 20