vklu4itesvet
vklu4itesvet

Reputation: 385

How to prevent Multibinding with MultiValueConverter from getting value several times and with proper parameters

I want to display time information regarding to a specified time scale (for instance hours, minutes, seconds). This info is displayed within items of a listbox. To do this, I created a custom ControlTemplate for ListBoxItem-s like:

<ControlTemplate TargetType="ListBoxItem">                      
   <Controls:ExtendedTextBlock x:Name="time" d:LayoutOverrides="Width">
     <Controls:ExtendedTextBlock.Text>
       <MultiBinding Converter="{StaticResource scaledDateToStringConverter}" FallbackValue="">
          <Binding Path="Time"></Binding>
          <Binding Path="TimelineScale"></Binding>
      </MultiBinding>                       
</ControlTemplate>

Converter does the following:

public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        var scale = (TimelineScale)values[1];
        var timestamp = (DateTime)values[0];
        string convertedTimestamp;

        if (timestamp == default(DateTime))
        {
            convertedTimestamp = "LIVE";
        }
        else
        {
            switch (scale)
            {
                case TimelineScale.Seconds:
                    convertedTimestamp = timestamp.ToString("T", CultureInfo.CurrentCulture);
                    break;
                case TimelineScale.Minutes:
                    convertedTimestamp = timestamp.ToString("t", CultureInfo.CurrentCulture);
                    break;
                case TimelineScale.Hours:
                    convertedTimestamp = timestamp.AddMinutes(-timestamp.Minute).AddSeconds(-timestamp.Second).ToString("t", CultureInfo.CurrentCulture);
                    break;
                default:
                    convertedTimestamp = timestamp.ToString("T", CultureInfo.CurrentCulture);
                    break;
            }
        }

        return convertedTimestamp;
    }

The problem is that for some items, Convert method is invoked more then one time and at first invocation, instead of actual time value -DependencyProperty.UnsetValue is passed as a parameter, causing InvalidCastException during unboxing the value from an input array. How can I achieve convertation only once and (at least)with valid parameters?

There are some interesting remarks about this issue: The value of time is defenitely defined. This code is working for a long time, since .NET ver. 4.0 but the problem appeared only now (at least it started constantly reproducing by customers and our testing machines)

Upvotes: 0

Views: 1017

Answers (1)

NextInLine
NextInLine

Reputation: 2204

Most likely the converter is evaluating as each of the bound properties is set. Therefore, whichever property (Time or TimelineScale) is set first, the MultiBinding evaluates and the other will have the unset value since it has not been evaluated yet.

There are a couple of ways to handle this.

Return UnsetValue

Add the following to the top of your Convert method:

if (!(values[1] is TimelineScale) || !(values[0] is DateTime))
{
    return DependencyProperty.UnsetValue;
}

Use default values

var scale = (values[1] as TimelineScale?).GetValueOrDefault();
var timestamp = (values[0] as DateTime?).GetValueOrDefault();

In either case, once the binding evaluates a second time, Convert will have the parameters it needs and your MultiBinding will update correctly.

Edit: It is possible that you have an upstream binding that is incorrect. I have a co-worker that recently ran into an issue with UnsetValue appearing on a value with a binding where the binding path was misspelled. It is possible this is a new behavior introduced in .NET/WPF 4.0.

Upvotes: 1

Related Questions