问题
To have less redundant XAML markup i try to get a radiobutton-type selection control to be populated generically, i.e. i use an ItemsControl
with an enum as ItemsSource
and create a DataTemplate which shows which item is selected by checking whether the enum value of the item is the same as the current setting.
This alone cannot be done using a simple converter or DataTrigger because two bindings are needed, so i created a generic MutliValueConverter
to check for equality:
<CheckBox.Visibility>
<MultiBinding Converter="{StaticResource EqualityComparisonConv}">
<Binding Path="Key"/>
<Binding Path="DisplayMode_Current" Source="{x:Static local:App.Settings}"/>
</MultiBinding>
</CheckBox.Visibility>
public class EqualityComparisonConverter : IMultiValueConverter
{
#region IMultiValueConverter Members
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values.Length < 2) throw new Exception("At least two inputs are needed for comparison");
bool output = (bool)values.Skip(1).Aggregate(values[0], (x1, x2) => { return x1.Equals(x2); });
return output;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
#endregion
}
The obvious problem here is that the converter returns a boolean which i would need to convert to Visibility
first.
Wrapping the MultiBinding in another binding with just a converter does not work because the properties are not dependency properties (hence cannot have a binding assigned to them). I could think of a few workarounds like storing the bool in some Tag
property, so i can use that as a new binding source, but i would be more interested in something like this:
<CheckBox.Visibility>
<local:PipeConverter Converter="{StaticResource BooleanToVisibilityConv}">
<MultiBinding Converter="{StaticResource EqualityComparisonConv}">
<Binding Path="Key"/>
<Binding Path="DisplayMode_Current" Source="{x:Static local:App.Settings}"/>
</MultiBinding>
</local:PipeConverter>
</CheckBox.Visibility>
This class would need to update its output when changes in the original binding occur and it would need to be able to expose its output value to the Visibility
property but i do not know how to achieve either. One problem one runs into is that there is a need for dependency properties so inheriting from DependencyObject
would be nice, but inheriting from a Binding class would also make sense because the PipeConverter should bind and needs to be set as the value of another dependency property.
回答1:
Three options:
Option A: Convert your bool to visibility in your multivalueconverter (its just one line)
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values.Length < 2)
throw new Exception("At least two inputs are needed for comparison");
bool output = (bool)values.Skip(1).Aggregate(values[0], (x1, x2) =>
{ return x1.Equals(x2); });
return output ? Visibility.Visible : Visibility.Collapsed;
}
Option B: Use the existing booltovisibilityconverter programatically
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values.Length < 2)
throw new Exception("At least two inputs are needed for comparison");
bool output = (bool)values.Skip(1).Aggregate(values[0], (x1, x2) =>
{ return x1.Equals(x2); });
System.Windows.Controls.BooleanToVisibilityConverter booltovisibilityconverter = new System.Windows.Controls.BooleanToVisibilityConverter();
return booltovisibilityconverter.Convert(output, System.Type.GetType("System.Boolean"), parameter, culture);
}
PS: You may want to cache the booltovisibilityconverter instead of creating it everytime.
Option C: Pipe your converters
Piping Value Converters in WPF
PS: Beaware that this article is quite old.
回答2:
Based on the idea (see publicgk's answer, option C) that one can create a converter which contains a collection of converters which internally are used in sequence i wrote a shoddy implementation which fits my needs. i.e. i can use a MultiValueConverter at the beginning and pipe the output into a list of normal converters:
[ContentProperty("Converters")]
public class GroupConverter : IValueConverter, IMultiValueConverter
{
private IMultiValueConverter _multiValueConverter;
public IMultiValueConverter MultiValueConverter
{
get { return _multiValueConverter; }
set { _multiValueConverter = value; }
}
private List<IValueConverter> _converters = new List<IValueConverter>();
public List<IValueConverter> Converters
{
get { return _converters; }
set { _converters = value; }
}
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return GroupConvert(value, Converters);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return GroupConvertBack(value, Converters.ToArray().Reverse());
}
private static object GroupConvert(object value, IEnumerable<IValueConverter> converters)
{
return converters.Aggregate(value, (acc, conv) => { return conv.Convert(acc, typeof(object), null, null); });
}
private static object GroupConvertBack(object value, IEnumerable<IValueConverter> converters)
{
return converters.Aggregate(value, (acc, conv) => { return conv.ConvertBack(acc, typeof(object), null, null); });
}
#endregion
#region IMultiValueConverter Members
private InvalidOperationException _multiValueConverterUnsetException =
new InvalidOperationException("To use the converter as a MultiValueConverter the MultiValueConverter property needs to be set.");
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (MultiValueConverter == null) throw _multiValueConverterUnsetException;
var firstConvertedValue = MultiValueConverter.Convert(values, targetType, parameter, culture);
return GroupConvert(firstConvertedValue, Converters);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
if (MultiValueConverter == null) throw _multiValueConverterUnsetException;
var tailConverted = GroupConvertBack(value, Converters.ToArray().Reverse());
return MultiValueConverter.ConvertBack(tailConverted, targetTypes, parameter, culture);
}
#endregion
}
(As you can see i pretty much completely disregard the ConverterParameters
, TargetTypes
and CultureInfo
parameters, further the ConvertBack
methods are untested so i do not advise anyone to actually use this.)
XAML usage:
<vc:GroupConverter MultiValueConverter="{StaticResource EqualityComparisonConv}">
<StaticResource ResourceKey="BoolToVisibilityConv"/>
</vc:GroupConverter>
来源:https://stackoverflow.com/questions/5524885/nested-binding-and-piped-conversion