I have a C#/WPF MVVM program that uses a flagged enum to track the types of search mode the user is currently in and to manipulate the background colors of various controls depending on the given search modes. I am using an enum-to-bool converter with a property to indicate if the enum should be treated as a flag.
The enum
namespace EnumNamespace.Enums
{
/// <summary>
/// Flag enabled enumeration values for the various search modes.
/// </summary>
[Flags]
public enum ESearchMode
{
None = 0,
RegionSearch = 1,
ValueSearch = 2,
CombinedSearch = RegionSearch | ValueSearch
}
}
In my resource dictionary I have
<pluginConverters:EnumToBoolConverter
x:Key="FlaggedEnumToBoolConverter"
TreatAsFlag="True"
Match="True"
NoMatch="False"/>
Example use of my converter in XAML
<TextBox.Resources>
<Style
TargetType="TextBox">
<Style.Triggers>
<DataTrigger
Binding="{Binding ActiveSearchMode,
UpdateSourceTrigger=PropertyChanged,
Converter={StaticResource FlaggedEnumToBoolConverter},
ConverterParameter={x:Static enums:ESearchMode.ValueSearch}}"
Value="True">
<Setter
Property="Background"
Value="{StaticResource SearchBackgroundColor}" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Resources>
And finally, the converter itself
public abstract class EnumConverter<T> : IValueConverter
{
/// <summary>
/// When True uses the Enum.HasFlag() comparator.
/// When False uses Equals() comparator.
/// </summary>
public bool TreatAsFlag { get; set; }
/// <summary>
/// Object to return if the enum matches.
/// </summary>
public T Match { get; set; }
/// <summary>
/// Object to return if the enum doesn't match
/// </summary>
public T NoMatch { get; set; }
protected EnumConverter(T match, T noMatch)
{
Match = match;
NoMatch = noMatch;
TreatAsFlag = false;
}
public virtual object Convert(object value, Type targetType, object trueValue, CultureInfo culture)
{
//Ensure both types are valid.
if (value != null && trueValue != null && value.GetType().IsEnum)
{
if (TreatAsFlag)
return ((Enum) value).HasFlag((Enum) trueValue) ? Match : NoMatch;
return Equals(value, trueValue) ? Match : NoMatch;
}
//Do not perform binding if input was invalid.
return DependencyProperty.UnsetValue;
}
public virtual object ConvertBack(object value, Type targetType, object trueValue, CultureInfo culture)
{
//Refer to implementation since we need to know the base class type to convert back. (If we can)
throw new NotImplementedException();
}
}
/// <summary>
/// Allows conversion from enumerations to booleans. Can only convert back for matches since non matches could be any enum.
/// Example: <converters:EnumToBoolConverter x:Key="TrueIfEnumMatches" Match="True" NoMatch="False"/>
/// </summary>
public class EnumToBoolConverter : EnumConverter<bool>
{
public EnumToBoolConverter() : base(true, false) { }
public override object ConvertBack(object value, Type targetType, object trueValue, CultureInfo culture)
{
//If being set to true then we know the enum to return.
if (value is bool val && val)
return trueValue;
//Otherwise we can't go backwards since false could be any other enum.
return Binding.DoNothing;
}
}
My problem is the designer throws an argument exception and won't draw anymore. I can still run the program and the logic works great, but I can't view the designer, which is less than ideal for obvious reasons. The designer window gives me the following error:
The argument type, 'EnumNamespace.Enums.ESearchMode', is not the same as the enum type 'Mocks.EnumNamespace_Enums_ESearchMode_8_18537287'
The stacktrace points me to my HasFlag check here:
return ((Enum) value).HasFlag((Enum) trueValue) ? Match : NoMatch;
This is the first time I have seen a 'Mocks._' type and assume it is part of the designer's background magic in order to process and show what's in the XAML. I would assume there is information out there on the interwebs about how the designer runs its code to resolve types, but I have been unable to find anything that describes this particular issue. I don't understand why the designer would be passing my actual enum type, but also it's mocked version as well. Is this an error or known limitation in the designer code? Does it have to do with how I'm passing in the converter parameter? To add to the confusion, I am using this converter with flagged-enums in another view without any issues with the designer.
I would love for someone to point me to some information about Mocks and how the designer uses them as well as if anyone has any insights into why the designer isn't using the mocked version of my enum for both the bound property and the converter parameter. If there is an obvious solution/workaround to my exception, I'd be open to that too.
CodePudding user response:
I haven't found a good document describing the designers use of mocks, but I have at least found a work around to my issue.
Using information found here I was able to debug the designer and find that the issue involved both my bound value and converter parameter I was passing in. Both were of the 'Mock.Enum_' type which I think confirms that the designer is using mocks to generate the view but doesn't explain why it isn't able to resolve the type correctly.
After reading the accepted solution on this question, I realized I could just convert my Enum to its unsigned long value and do bitwise operations on it to check for my flag. My convert function now looks like this:
public virtual object Convert(object value, Type targetType, object trueValue, CultureInfo culture)
{
//Ensure both types are valid.
if (value != null && trueValue != null && value.GetType().IsEnum)
{
if (TreatAsFlag)
{
// Due to the designer having issues sometimes resolving enums with using the 'HasFlag' function, we will convert our enums to ulongs to compare using bitwise operations
// Set our enums as ulong values so we can perform bitwise operations
ulong setValues = System.Convert.ToUInt64(value);
ulong setTrueValues = System.Convert.ToUInt64(trueValue);
// Compare our value with the flag we are checking against
return (setValues & setTrueValues) == setTrueValues;
}
return Equals(value, trueValue) ? Match : NoMatch;
}
//Do not perform binding if input was invalid.
return DependencyProperty.UnsetValue;
}
Still open to any other input on Mocks and the designer.