Advanced multibinding - c#

I have something like this:
<Controls:ToggleRectangleButton.Visibility>
<MultiBinding Converter="{StaticResource MultiButtonCheckedToVisibilityConverter}">
<Binding ElementName="btDayAndNightsLinesTickets" Path="IsButtonChecked" />
<Binding ElementName="btSchoolSemester" Path="IsButtonChecked" />
</MultiBinding>
</Controls:ToggleRectangleButton.Visibility>
MultiButtonCheckedToButtonEnabledConverter's convert method
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
bool visible = false;
foreach (object value in values)
{
if (value is bool)
{
if ((bool)value == true) visible = true;
}
}
if (visible)
{
return System.Windows.Visibility.Visible;
}
else
{
return System.Windows.Visibility.Hidden;
}
}
So it mean that if at least one of buttons passed as parameters has IsButtonChecked property set to true -> show control. Otherwise hide it.
I want to add some functionality, that is condition:
if ( otherButton.IsChecked ) return System.Windows.Visibility.Hidden;
So if otherButton is checked hide control (independently of the other conditions). I want to be able to set more "otherButtons" than 1 (if at least one of "otherButtons" is checked -> Hide).

Try this:
public class MultiButtonCheckedToVisibilityConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
bool visible = false;
int trueCount = (int)parameter;
for (int i = 0; i < trueCount; i++)
{
if ((bool)values[i])
{
visible = true;
break;
}
}
if (visible)
{
for (int i = trueCount; i < values.Length; i++)
{
if (!(bool)values[i])
{
visible = false;
break;
}
}
}
if (visible)
{
return System.Windows.Visibility.Visible;
}
else
{
return System.Windows.Visibility.Hidden;
}
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
XAML:
<Button Content="Test">
<Button.Visibility>
<MultiBinding Converter="{StaticResource MultiButtonCheckedToVisibilityConverter}">
<MultiBinding.ConverterParameter>
<sys:Int32>2</sys:Int32>
</MultiBinding.ConverterParameter>
<Binding ElementName="btDayAndNightsLinesTickets" Path="IsChecked" />
<Binding ElementName="btSchoolSemester" Path="IsChecked" />
<Binding ElementName="btOther1" Path="IsChecked" />
<Binding ElementName="btOther2" Path="IsChecked" />
</MultiBinding>
</Button.Visibility>
</Button>
<ToggleButton Name="btDayAndNightsLinesTickets">btDayAndNightsLinesTickets</ToggleButton>
<ToggleButton Name="btSchoolSemester">btSchoolSemester</ToggleButton>
<ToggleButton Name="btOther1">btOther1</ToggleButton>
<ToggleButton Name="btOther2">btOther2</ToggleButton>
The idea is to tell to converter how many buttons shows the control. If this count is not a constant you can refactor converter to receive count as a first binding.

the order of the binding will be kept in the converter code.
you can check the object[] values using an Index and implement your logic according to it.
for example :
if((values[0] is bool) && ((bool)values[0]))
{
//DoSomething
}

Related

Multiple Converter based on same property updating undelrying value

In the code below I'm trying to display "RateValue" as both a decimal and a percentage using a converter.
The below GridColumn code is within a ComboBoxEdit popout template.
What I'm seeing is that when all GridColumns are added the underlying "RateValue" ends up being the same in both cases. However when I only have one or the other they are showing the right values.
So having both appears to be changing the underlying source value.
Am I missing something obvious here?
Thanks
<dxg:GridColumn MinWidth="80" Header="Rate (%)">
<dxg:GridColumn.Binding>
<Binding Path="RateValue" Converter="{StaticResource DecimalToFourDecimalPlacesPercentageConverter}" Mode="OneWay" UpdateSourceTrigger="PropertyChanged"/>
</dxg:GridColumn.Binding>
</dxg:GridColumn>
<dxg:GridColumn Header="Rate (Decimal)">
<dxg:GridColumn.Binding>
<Binding Path="RateValue" Converter="{StaticResource DecimalToFourDecimalPlacesConverter}" Mode="OneWay" UpdateSourceTrigger="PropertyChanged"/>
</dxg:GridColumn.Binding>
</dxg:GridColumn>
<converters1:NumericToStringConverter x:Key="DecimalToFourDecimalPlacesPercentageConverter" Format="0:N4" Multiplier="100"/>
<converters1:NumericToStringConverter x:Key="DecimalToFourDecimalPlacesConverter" Format="0:N4" Multiplier="1"/>
public class NumericToStringConverter : IValueConverter
{
private static readonly ILog Logger = LogManager.GetLogger(typeof(NumericToStringConverter));
public string Format { get; set; }
public int Multiplier { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (!(value is decimal)) return Binding.DoNothing;
try
{
var v = (decimal?) value;
return string.Format("{" + Format + "}", Multiplier*v);
}
catch (FormatException ex)
{
Logger.Error(string.Format("Failed to format '{0}'", value), ex);
}
return Binding.DoNothing;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
I'm not familiar with DevExpress's WPF control, but assume they work like official one, there's no need for converter, StringFormat should be enough.
<dxg:GridColumn MinWidth="80" Header="Rate (%)">
<dxg:GridColumn.Binding>
<Binding Path="RateValue" StringFormat="P4"/>
</dxg:GridColumn.Binding>
</dxg:GridColumn>
<dxg:GridColumn Header="Rate (Decimal)">
<dxg:GridColumn.Binding>
<Binding Path="RateValue" StringFormat="N4"/>
</dxg:GridColumn.Binding>
</dxg:GridColumn>
If that doesn't work, change your converter to
public class NumericToStringConverter : IValueConverter
{
private static readonly ILog Logger = LogManager.GetLogger(typeof(NumericToStringConverter));
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
try
{
return string.Format(parameter.ToString(), value);
}
catch (FormatException ex)
{
Logger.Error($"Failed to format {value}", ex);
}
return Binding.DoNothing;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
then change other parts to
<dxg:GridColumn MinWidth="80" Header="Rate (%)">
<dxg:GridColumn.Binding>
<Binding Path="RateValue" Converter="{StaticResource MyDecimalToStringConverter}"
ConverterParameter="P4"/>
</dxg:GridColumn.Binding>
</dxg:GridColumn>
<dxg:GridColumn Header="Rate (Decimal)">
<dxg:GridColumn.Binding>
<Binding Path="RateValue" Converter="{StaticResource MyDecimalToStringConverter}"
ConverterParameter="N4"/>
</dxg:GridColumn.Binding>
</dxg:GridColumn>
<converters1:NumericToStringConverter x:Key="MyDecimalToStringConverter"/>

Not controlled exception in WPF XAML: The specified conversion is not valid

I have a MVVM WPF application. I have below converter:
public class PrintIconVisibilityValueConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values[0] == null || values[1] == null) return Visibility.Collapsed;
int item1 = (int)values[0];
string item2 = (string)values[1];
if (item1 > 0 || !string.IsNullOrEmpty(item2))
{
return Visibility.Visible;
}
else
{
return Visibility.Collapsed;
}
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
From my view I do:
<Window.Resources>
<classes:PrintIconVisibilityValueConverter x:Key="PrintIconVisibilityValueConverter"/>
</Window.Resources>
then I have an image in this view:
<Image Source="/MyImages;component/Images/PrintIco.png"
Height="15" Margin="20 0 5 0">
<Image.Visibility>
<MultiBinding Converter="{StaticResource PrintIconVisibilityValueConverter}">
<Binding Path="Item1" />
<Binding Path="Item2" />
</MultiBinding>
</Image.Visibility>
</Image>
Item1 and Item2 are public properties in view model:
private string _item2 = string.Empty;
public string Item2
{
get
{
return _item2;
}
set
{
if (_item2 == value) return;
_item2 = value;
OnPropertyChanged("Item2");
}
}
private int _item1;
public int Item1
{
get
{
return _item1;
}
set
{
if (_item1 == value) return;
_item1 = value;
OnPropertyChanged("Item1");
}
}
It compiles correctly and I can execute the application without problems but in design time, the view is not show, an error says Not controlled exception and points to the line:
int item1 = (int)values[0];
within PrintIconVisibilityValueConverter class.
Below the screenshots of the exception shown on view:
Some suggestions;
Call the GetIsInDesignMode method in your converter and return immediately if it returns true:
public class PrintIconVisibilityValueConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (DesignerProperties.GetIsInDesignMode(new DependencyObject()))
return Visibility.Visible;
...
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Set the DataContext in XAML:
<Window.DataContext>
<local:ViewModel />
</Window.DataContext>
Set the design time data context:
<Window ... d:DataContext ="{d:DesignInstance {x:Type local:ViewModel}, IsDesignTimeCreatable=True}">
Or Disable XAML UI designer

How to get in ConvertBack parameters from Convert?

The problem:
I am using MultiBinding with converter to pass (x,y) coordinates into method.
And I can't make it working in back direction:
public class MyConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var x = (int)values[0];
var y = (int)values[1];
return Model.Get(x, y);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
Model.Set(x, y, value); // how to get x, y here?
return new object[] { Binding.DoNothing, Binding.DoNothing };
}
}
Additional info:
The data will be visualized in a form of table. Here is cell template:
<TextBox>
<TextBox.Text>
<MultiBinding Converter="{StaticResource converter}" Mode="TwoWay">
<Binding Path="X" Mode="OneWay" />
<Binding Path="Y" Mode="OneWay" RelativeSource="..." />
</MultiBinding>
</TextBox.Text>
</TextBox>
The idea is to use converter, which receive x (from cell view model) and y (from parent column view model, notice RelativeSource) and calls Get(x,y) to display value.
However, when user entered something, ConvertBack is called and I need to call Set(x, y, value) method.
How do I pass x and y into ConvertBack?
There might be more-or-less dirty workarounds to get such a multivalue converter working. But I'd suggest you keep your multivalue converter one-way, but return a container object that wraps the actual text property.
Instead of directly binding to the TextBox.Text property, bind to some other property (eg. DataContext or Tag) and then bind the text to the container value.
Small example:
<TextBox Text="{Binding Value}">
<TextBox.DataContext>
<MultiBinding Converter="{StaticResource cMyConverter}">
<Binding Path="X"/>
<Binding Path="Y"/>
</MultiBinding>
</TextBox.DataContext>
</TextBox>
With container and converter:
public class ValueProxy
{
public int X { get; set; }
public int Y { get; set; }
public string Value
{
get { return Model.Get(X, Y); }
set { Model.Set(X, Y, value); }
}
}
public class MyConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var x = (int)values[0];
var y = (int)values[1];
return new ValueProxy { X = x, Y = y };
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
return new object[] { Binding.DoNothing, Binding.DoNothing };
}
}
The short answer is that you can't directly get the values of xand y inside your ConvertBack method. The IMultiValueConverter Convert multiple values into a single value. So, the ConvertBack method will do the opposite: convert a single value into multiple values.
It all depends on what your Model.Get(x, y) method returns. It needs to return a value that is unique enough for you to get the separate values of x and y from it.
Example: create unique strings for each pair of (x,y).
It seems hard to pass parameters into ConvertBack. It might be possible, but there is a workaround, which makes ConvertBack unnecessary. Thanks to #XAMlMAX for an idea.
One possibility to achieve it (there could be a better way) is to use data templates. Instead of multi-binding TextBlock.Text with string we can bind ContentControl.Content with some viewmodel, and this viewmodel should do the rest, including Set(x, y, value) call.
Here is code:
public class ViewModel
{
public int X { get; set; }
public int Y { get; set; }
string _text;
public string Text
{
get { return _text; }
set
{
// this should be only called by the view
_text = value;
Model.Set(X, Y, value);
}
}
public ViewModel(string text)
{
_text = text;
}
}
public class MyConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var x = (int)values[0];
var y = (int)values[1];
return new ViewModel(Model.Get(x, y)) { X = x, Y = y };
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
and the xaml will become
<ContentControl Focusable="False">
<ContentControl.Content>
<MultiBinding Converter="{StaticResource converter}">
<Binding Path="X" />
<Binding Path="Y" RelativeSource="..."/>
</MultiBinding>
</ContentControl.Content>
</ContentControl>
where data template is
<DataTemplate DataType="{x:Type local:ViewModel}">
<TextBox Text="{Binding Text}" />
</DataTemplate>

XAML can't find the converter class

I'm displaying a popup with the following code:
<Popup PlacementTarget="{Binding ElementName=categoryTagEditorControl}"
Placement="Bottom">
<Popup.IsOpen>
<MultiBinding Mode="OneWay" Converter="{StaticResource BooleanOrConverter}">
<Binding Mode="OneWay" ElementName="categoryTagEditorControl" Path="IsMouseOver"/>
<Binding RelativeSource="{RelativeSource Self}" Path="IsMouseOver" />
</MultiBinding>
</Popup.IsOpen>
<StackPanel>
<TextBox Text="Some Text.."/>
<DatePicker/>
</StackPanel>
</Popup>
Here's the code of BooleanOrConverter:
public class BooleanOrConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
foreach (object booleanValue in values)
{
if (booleanValue is bool == false)
{
throw new ApplicationException("BooleanOrConverter only accepts boolean as datatype");
}
if ((bool)booleanValue == true)
{
return true;
}
}
return false;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
}
and its placed into PopupTest.InfoPanels.Windows namespace
when I run this, I'm getting following exception:
Cannot find resource named 'BooleanOrConverter'. Resource names are case sensitive.
What should I change for this to work?
It sounds like your Multibinding doesn't know where to look for the converter. Have you defined the converter as a staticresource? You can either specify the converter in the control's resources or in the included ResourceDictionary. Add a reference to the converter's namespace and then define a ResourceKey for it. Something like:
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converters="clr-namespace:MyConverters">
<UserControl.Resources>
<converters:BooleanOrConverter x:Key="BoolOrConverter"/>
</UserControl.Resources>
... // use converter as you were before
</UserControl>

How to bind the IsEnabled property to an OR of two values?

currently when I have to make an OR of two values on the IsEnabled property of a control I end using an invisible container control (I use a Border) and setting the IsEnabled of the control and the one of the container.
Is there a better approach? If not, what is the most lightweight control for doing this?
Thanks in advance.
If IsEnabled is set via binding, you may use MultiBinding in conjunction with a multi-value converter.
You can use a converter like this:
public class BooleanOrConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
foreach (object value in values)
{
if ((value is bool) && (bool)value == true)
{
return true;
}
}
return false;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotSupportedException("BooleanOrConverter is a OneWay converter.");
}
}
And this is how you would use it:
<myConverters:BooleanOrConverter x:Key="BooleanOrConverter" />
...
<ComboBox Name="MyComboBox">
<ComboBox.IsEnabled>
<MultiBinding Converter="{StaticResource BooleanOrConverter}">
<Binding ElementName="SomeCheckBox" Path="IsChecked" />
<Binding ElementName="AnotherCheckbox" Path="IsChecked" />
</MultiBinding>
</ComboBox.IsEnabled>
</ComboBox>
Could use a MultiBinding with a converter which or's the values passed in.

Categories