简体   繁体   中英

Manage the targetType of a Binding in a MultiBinding

So, I have a multi-binding with a converter that takes in some values and finds the max of them. The problem is that one of the bindings utilises a converter that expects a double target type, while the binding has an object target type. I was wondering if there was any way to modify the target type of a binding in any way.

Below is an approximation of my xaml:

<TextBlock>
  <TextBlock.Width>
    <MultiBinding Converter="{StaticResource _maxValueConverter}">
      <Binding Source="{StaticResource _constantZeroValue}"/>
      <Binding Path="ActualWidth"
               ElementName="_previousTextBlock"
               Converter="{StaticResource _requiresDoubleTargetConverter}"/>
    </MultiBinding>
  </TextBlock.Width>
</TextBlock>

So basically if there is any way to tell the second binding that it is outputting to a double value, that'd be great.

Minimal Verifiable Complete Example:

MainWindow.xaml

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:sys="clr-namespace:System;assembly=mscorlib"
        xmlns:local="clr-namespace:WpfApplication1"
        DataContext="{Binding RelativeSource={RelativeSource Self}}"
        Title="MainWindow" Height="350" Width="525">
   <StackPanel>
      <StackPanel.Resources>
         <sys:Double x:Key="constantZero">0</sys:Double>
         <local:RequiresDoubleTargetConverter x:Key="requiresDoubleTargetConverter" />
         <local:MaxValueConverter x:Key="maxValueConverter" />
      </StackPanel.Resources>

      <Border x:Name="topBorder"
              BorderThickness="1"
              BorderBrush="Black"
              HorizontalAlignment="Left">
         <TextBlock x:Name="topTextBlock"
                    Background="Aqua"
                    Text="{Binding TopText}" />
      </Border>

      <Border BorderThickness="1"
              BorderBrush="Black"
              MinWidth="100"
              HorizontalAlignment="Left">
         <TextBlock Background="ForestGreen"
                 Text="{Binding BottomText}"
                 TextWrapping="Wrap"
                 MinWidth="100">
            <TextBlock.Width>

               <MultiBinding Converter="{StaticResource maxValueConverter}">
                  <MultiBinding.Bindings>
                     <Binding Path="ActualWidth" ElementName="topTextBlock" Converter="{StaticResource requiresDoubleTargetConverter}" />
                     <Binding Source="{StaticResource constantZero}" />
                  </MultiBinding.Bindings>
               </MultiBinding>

            </TextBlock.Width>
         </TextBlock>
      </Border>

   </StackPanel>
</Window>

MainWindow.xaml.cs

using System;
using System.Diagnostics;
using System.Globalization;
using System.Windows;
using System.Windows.Data;

namespace WpfApplication1
{
   /// <summary>
   /// Interaction logic for MainWindow.xaml
   /// </summary>
   public partial class MainWindow : Window
   {
      public string TopText
      {
         get { return "Hello World!"; }
      }

      public string BottomText
      {
         get { return "hi earth."; }
      }

      public MainWindow()
      {
         InitializeComponent();
      }
   }

   public class RequiresDoubleTargetConverter : IValueConverter
   {
      public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
      {
         // I am looking for a way to manually ensure that "targetType == typeof(double)" evaluates to true.
         if (targetType != typeof(double))
         {
            return null;
         }
         else
         {
            // Actual converter performs this calculation.
            return (double)value - 14;
         }
      }

      public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
      {
         // Irrelevant method for our purposes.
         throw new NotImplementedException();
      }
   }

   public class MaxValueConverter : IMultiValueConverter
   {
      public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
      {
         double max = double.NegativeInfinity;
         foreach (object value in values)
         {
            if (value is double)
            {
               max = Math.Max((double)value, max);
            }
            else
            {
               Debug.Fail("All values must be doubles");
            }
         }

         return max;
      }

      public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
      {
         // Irrelevant method for our purposes.
         throw new NotImplementedException();
      }
   }
}

This was created using Visual Studio 2015, and is verified to show the erroneous behaviour. What I am trying to determine is if it is possible to manually set the targetType of the RequiresDoubleTargetConverter from the xaml.

Bindings operate on object , as far as the type system is concerned. If you want a specific type, you'll need to ensure that yourself.

However you could use the target type passed to the converter to determine which type is required, and modify the converter's return value accordingly.

So is there a way to set the target type of the converter manually, or am I stuck with it being object?

You are stuck with it being object as the signature of the Convert method is always the same, ie it accepts an object[] of values and nothing else.

You will have to cast values[1] to a double in your Convert method (because the method will always and only be passed values of type object ):

double d = (double)values[1];

I've come up with a way around this, but it only works if you have access to the converter you're using on the MultiBinding itself (or you can add one.) as well as a little extra effort if it also uses a ConverterParameter, TargetNullValue and/or a StringFormat.

The trick is when you add the child Binding to the MultiBinding, you remove the Converter, ConverterParameter, TargetNullValue and StringFormat values from that child binding and store them somewhere that's accessible by the Convert method for the MultiBinding's converter. (We use a wrapper MarkupExtension to simulate the MultiBinding so we have access to everything before they're actually applied as once they are, you can't change them.)

Then in the MultiBinding's Convert method, you're now getting the raw, not-yet-converted/formatted/coalesced value from the child binding, but you also have the ultimate target that you need (double in this example) as it was handed to the Convert method for the MultiBinding that you're in.

With that information, you then call the child converter's Convert method manually, passing in the not-yet-converted value, the targetType (passed in to you), and the childConverterParameter.

You take the result of that call, and if null, return the TargetNullValue from the child binding.

If it's not null and both targetType is a string and you have a String Format, finally format the results.

Here's pseudo-code (ie off the top of my head. Prolly lots of syntax errors, etc. For the actual code, you can see me using it in my DynamicResourceBinding class that I have up on StackOverflow here .)

// Convert function for the MultiBinding
private object Convert(object[] values, Type targetType, object parameter, Culture culture){

    var rawChildBindingResult = values[0]; // assuming it's in the first position

    var convertedChildBindingResult = childConverter(rawChildBindingResult, targetType, childConverterParameter, culture);

    if(convertedChildBindingResult == null)
        convertedChildBindingResult = childTargetNullValue;
    else if(targetType == typeof(string) && childStringFormat != null)
        convertedChildBindingResult = string.Format(childStringFormat, convertedChildBindingResult);

    // Now do whatever you would with the 'convertedChildBindingResult' as if things were normal
}

Again, take a look at the link to see it in context.

Hope this helps!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM