MultiConverter usage

2019-09-04 16:39发布

问题:

Here is pseudo code for what I want to implement using a multi-converter.

IF vm.AvatarFilePath IS NOT NULL THEN
    Image.Source = {Binding AvatarPath}
ELSE
    If vm.Gender == {x:Static vm:Gender.Female} THEN
        Image.Source = {StaticResource Img_Female}
    ELSE
        Image.Source = {StaticResource Img_Male}
    ENDIF
ENDIF

Please note that I am not convinced that a multi-converter is the right approach, and I posted a similar question that tries to solve this problem with DataTriggers here.

My attempt at a MultiConverter implementation (below) has issues:

  1. it crashes with a {Dependency.UnsetValue} which suggests the binding is wrong
  2. it may or may not be returning the right type, BitmapSource. It needs to handle two sources for the image, either one determined by Gender, a Resx based source, or a file path. I do not know how to catch a bad file path just examing the result, as no error is thrown (I could do a File.Exists but seems overkill).
  3. The fact that the code is exposed for unit testing is not as useful as it might seem, since I am not aware of an easy and cheap way to compare image equality (see unit test below).

How can I start to clean up this converter code and binding?

Cheers,
Berryl

Converter.Convert code

public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) 
{
    values.ThrowIfNull("values");
    foreach (var value in values) {
        if (value != null && value.ToString().Equals("{DependencyProperty.UnsetValue}")) return Binding.DoNothing;
    }

    values[1].ThrowIfNull("gender (value[1])");

    var avatarPath = values[0] as string;
    BitmapSource result;

    if (string.IsNullOrWhiteSpace(avatarPath)) {
        var gender = (Gender) values[1];
        object bitmapSource;
        switch (gender)
        {
            case Gender.Female:
                bitmapSource = DomainSubjects.Img_Female;
                break;
            default:
                bitmapSource = DomainSubjects.Img_Male;
                break;
        }
        result = BitmapHelper.GetBitmapSource(bitmapSource);
    }
    else {
        var bi = new BitmapImage();
        bi.BeginInit();
        bi.UriSource = new Uri(avatarPath, UriKind.RelativeOrAbsolute);
        bi.EndInit();
        result = bi;
    }

    return result;
}

BitmapHelper code

public static BitmapSource GetBitmapSource(object source)
{
    BitmapSource bitmapSource = null;

    if (source is Icon)
    {
        var icon = source as Icon;

        // For icons we must create a new BitmapFrame from the icon data stream
        // The approach we use for bitmaps (below) doesn't work when setting the
        // Icon property of a window (although it will work for other Icons)
        //
        using (var iconStream = new MemoryStream())
        {
            icon.Save(iconStream);
            iconStream.Seek(0, SeekOrigin.Begin);
            bitmapSource = BitmapFrame.Create(iconStream);
        }
    }
    else if (source is Bitmap)
    {
        var bitmap = source as Bitmap;
        var bitmapHandle = bitmap.GetHbitmap();
        bitmapSource = Imaging
            .CreateBitmapSourceFromHBitmap(bitmapHandle, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
        bitmapSource.Freeze();
        DeleteObject(bitmapHandle);
    }
    return bitmapSource;
}

xaml binding

<Image Grid.Column="1" Grid.Row="4" 
       HorizontalAlignment="Center" Margin="10, 0" Width="96" Height="96" Stretch="UniformToFill"
       >
    <Image.Source>
        <MultiBinding Converter="{StaticResource avatarPathConv}">
            <Binding Path="AvatarPath"/>
            <Binding Path="Gender"/>
        </MultiBinding>
    </Image.Source>
</Image>

Unit Test that is useless (extra credit!)

   [Test]
    public void Convert_AvatarPath_IfBadString_DefaultImage() {
        var conv = new AvatarPathConverter();
        var result = conv.Convert(new object[] {"blah", Gender.Male}, null, null, CultureInfo.InvariantCulture);
        Assert.That(result, Is.EqualTo(DomainSubjects.Img_Male));
    }