MultiBinding and IMultiValueConverter Convert() called only once - c#

Q: Why does my custom TextBox UserControl using a MultiBinding and IMultiValueConverter gets its Convert() method called only once (during instanciation) ??
I have defined a UserControl that requires a MultiBinding and a IMultiValueConverter in order to change its behavior/presentation upon 2 indenpendant DependencyProperty.
<proj:MyControl Value="10" Digits="1" />
UserControl:
<UserControl x:Class="MyControl"
x:Name="uc"
...>
<UserControl.Resources>
<conv:DecimalToStringMultiConverter x:Key="DecToString" />
</UserControl.Resources>
[...]
<Grid>
<ctrl:VTextBox x:Name="vTb" Grid.Column="0" Margin="0,0,2,0">
<ctrl:VTextBox.Text>
<MultiBinding Converter="{StaticResource DecToString}" UpdateSourceTrigger="LostFocus" Mode="TwoWay">
<Binding ElementName="uc" Path="Value" Mode="TwoWay" />
<Binding ElementName="uc" Path="Digits" Mode="TwoWay" />
</MultiBinding>
</ctrl:VTextBox.Text>
</ctrl:VTextBox>
</Grid>
</UserControl>
When executing the application, the UserControls are all correctly instanciated. However, the IMultiValueConverter.Convert() method gets called only once.
Using an simple Binding + IValueConverter with a constant ConvertParameter worked great: the converter's Convert() method would get called everytime the TextBox contained inside the UserControl had its Text property changed.
Design changed and I had to resort to using a MultiBinding + IMultiValueConverter, and now the Convert() method only gets called once, and the TextBox.Text property is never updated upon the LostFocus() event.
What gives?
The MultiValueConverter is defined as below. I just wrap the IMultiValueConverter upon the IValueConverter to reuse existing code.
[ValueConversion(/*sourceType*/ typeof(Decimal), /*targetType*/ typeof(string))]
public class DecimalToStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null)
return "0.00";
int? digits = parameter as int?;
if(digits == null)
digits = 2;
NumberFormatInfo nfi = (NumberFormatInfo) CultureInfo.InvariantCulture.NumberFormat.Clone();
nfi.NumberGroupSeparator = " ";
nfi.CurrencyDecimalSeparator = ".";
nfi.NumberDecimalDigits = (int)digits;
return ((decimal)value).ToString("n", nfi);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null)
return 0.00m;
decimal d;
return decimal.TryParse((string)value, out d) ? d : 0.00m;
}
}
[ValueConversion(/*sourceType*/ typeof(Decimal), /*targetType*/ typeof(string))]
public class DecimalToStringMultiConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
DecimalToStringConverter conv = new DecimalToStringConverter();
return conv.Convert(values[0], targetType, values.Length > 1 ? values[1] : null, culture);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
DecimalToStringConverter conv = new DecimalToStringConverter();
return new[] { conv.ConvertBack(value, targetTypes[0], null, culture) };
}
}

It seems like you have some conflicting expectations about the updating behavior of the Binding and TextBox. The only reason Convert will be called multiple times is if the values of Digits or Value change multiple times, and there is nothing in your posted code to indicate that will happen. Changes to TextBox.Text won't cause calls to Convert, but should instead be calling ConvertBack on every change+LostFocus. Are you seeing that when you run your code?
You also need to return two values, instead of the one there now, from your ConvertBack method in order to supply both of the Bindings used in the MultiBinding with values.

Related

StringFormat freezes UI

I am making a ProgressBar with a TextBlock on top of it to notify the user about the download, I need the TextBlock to be bound to the value of the ProgressBar and formatted through XAML.
Like this:
<TextBlock x:Name="TxtBlock_Download" Grid.Row="5" Grid.Column="1" Grid.ColumnSpan="3"
TextAlignment="Center"
Foreground="White" Padding="0,2,0,0">
<!--Updates the textbox by using multibinding-->
<TextBlock.Text>
<!--TODO fix the StringFormat -->
<MultiBinding Converter="{StaticResource ResourceKey=kk}" StringFormat="{}{}">
<Binding ElementName="ProgressBar_Download" Path="Value"/>
<Binding ElementName="ProgressBar_Download" Path="Maximum"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
I am also using a separate class to connect the two value and maximum
Like this:
class Binding : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return values[0] + "/" + values[1].ToString();
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
return (value as string).Split('/');
}
}
But every time I try to format the string using StringFormat="" it freezes the UI until the download is complete (The download still works in the background but the program is frozen).
Since you can't format numbers that have already been converted to string I recommend you simply rewrite your converter (since you already have one) to take care of all the formatting you need:
class MyAwesomeProgressConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return ((double)values[0]).ToString("f2") + "/" + ((double)values[1]).ToString("f2");
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
Enumerable.Repeat(DependencyProperty.UnsetValue, targetTypes.Length).ToArray()
}
}
Also:
You don't need ConvertBack. Throw an Exception or return an Array of DependencyProperty.UnsetValue or Binding.DoNothing.
Don't name your Converter Binding - that might cause ambiguities

Get line count in textblock- windows phone 8

I have a textblock in a Data template in which I am displaying data through binding. Initially I need to show data upto three lines in textblock. For more data there is See more option tapping which expands the textblock.
Upto this things are done. The main problem I am facing is if the data size is not more than three lines, I don't have to shoe see more option.
How do I get to know that my data consumes just 1 or 2 lines of textblock
Thanks in advance
Consider you have view model bound to your template
public class ViewModel : INotifyPropertyChanged
{
public string MyBoundText { get { .. }; set { .. }; }
]
You can just create another property:
public int LinesNo => this.MyBoundText.Split('\n').Length;
//remember about null-check and other edge-cases
I assume you have in your xaml somewhere:
<TextBox Text="{Binding MyBoundText}"></TextBox>
SO, create own converter:
class Button1VisibilityConverter : IValueConverter
{
public object Convert(object value, Type targettype, object parameter, System.Globalization.CultureInfo culture)
{
int mode = (int)value;
if (mode <= (int)parameter)
return System.Windows.Visibility.Collapsed;
else
return System.Windows.Visibility.Visible;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
}
Then bind it with your button:
<Button Content="Show more...">
<Button.Visibillity>
<Binding Path="LinesNo "
Converter="{StaticResource Button1VisibilityConverter }">
<Binding.ConverterParameter>
<sys:Int32>3</sys:Int32>
</Binding.ConverterParameter>
</Binding>
</Button.Visibillity>
</Button>
(Remember to place converter as static resoruce before).
Warning: not tested, it is only idea / hint.

Is there an IValueConverter that does if-then

What I am trying to do:
<Grid>
<Grid.RowDefinitions>
...
<!--The next line is pseudo code for what I am trying to achieve-->
<RowDefintion Height="if(EditEnabled) { 10* } else { 0 }" />
...
</Grid.RowDefinition>
...
<DockPanel Visibility="{Binding EditEnabled, Converter={StaticResource InverseBooleanToVisibilityConverter}}" ...>
...
I am trying to change the visibility of the DockPanel depending on whether editing is enabled, while keeping he ability to resize and have fixed heights and relative heights.
The question:
Is there an IValueConverter (System.Windows.Data.IValueConverter) that can take a boolean, and two numbers and choose one of the GridLengths based on the boolean? From just inspecting the interface of IValueConverter it doesn't look like this is quite the right type to use.
Or is there a better way to inject the GridLength that I want?
What I have tried:
Looking through the inheritors of IValueConverter - nothing obvious to me
Moving the Height="10*" inside the DockPanel tag and changing the RowDefinition to be Auto - this created an conversion exception
Searching here
Unfortunately, there is no IValueConverter that does if-then.
(and to be more specific: you can not do if-then logic with the XAML)
But you can do the if-then logic in the C# code.
here is the solution
public class HeightConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
bool enableEdit = (bool)value;
double param = System.Convert.ToDouble(parameter);
if (enableEdit)
return new GridLength(param, GridUnitType.Star);
else
return new GridLength(0);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
and the window like this.
<Window.Resources>
<local:HeightConverter x:Key="heightConverter"/>
<sys:Int32 x:Key="param">10</sys:Int32>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="{Binding Path=EditEnabled, Converter={StaticResource heightConverter}, ConverterParameter={StaticResource param}}" />
</Grid.RowDefinitions>
</Grid>
please consider also define the required namespace that you will use, like the following
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:local="clr-namespace:[your namespace]"
Update the same result could be achieved by using IMutliValueConverter
public class HeightMultiConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
bool enableEdit = (bool)values[0];
double param = System.Convert.ToDouble(values[1]);
if (enableEdit)
return new GridLength(param, GridUnitType.Star);
else
return new GridLength(0);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
and the window like this
<Window.Resources>
<local:HeightMultiConverter x:Key="heightMutliConverter"/>
<sys:Int32 x:Key="param">10</sys:Int32>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition >
<RowDefinition.Height>
<MultiBinding Converter="{StaticResource heightMutliConverter}">
<Binding Path="EditEnabled"/>
<Binding Source="{StaticResource param}"/>
</MultiBinding>
</RowDefinition.Height>
</RowDefinition>
</Grid.RowDefinitions>
</Grid>
NOTE: just do not forget, you have to take care of the Source by setting the DataContext property.
There is a built-in converter you may be able to use: AlternationConverter. You specify a list of values (of arbitrary type), bind to an integer, and the converter looks up the integer in the list of values (modulo the value count).
If you specify two values for this AlternationConverter, and you're able to provide your EditEnabled property as an integer 0 or 1, then you can map that 0 and 1 to any value you want.
If you feel it doesn't make sense to convert your bool to an integer first (something I can sympathise with), you could still use AlternationConverter as inspiration for a custom converter that doesn't require the model value to be of type int.
Create a BooleanConverter<T> base class as described in http://stackoverflow.com/a/5182660/469708
public class BooleanConverter<T> : IValueConverter
{
public BooleanConverter(T trueValue, T falseValue)
{
True = trueValue;
False = falseValue;
}
public T True { get; set; }
public T False { get; set; }
public virtual object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value is bool && ((bool) value) ? True : False;
}
public virtual object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value is T && EqualityComparer<T>.Default.Equals((T) value, True);
}
}
Then write
public class BooleanToGridLengthConverter : BooleanConverter<System.Windows.GridLength>
{
public BooleanToGridLengthConverter() : base(
new System.Windows.GridLength(1, System.Windows.GridUnitType.Star),
new System.Windows.GridLength(0))
{
}
}
The values for true and false can be set directly, no need for a MultiValueConverter (as long as only the boolean parameter needs to be bindable).
<convert:BooleanToGridLengthConverter x:Key="Converter" True="10*" False="0" />
Alternatively, you can derive the converter from MarkupExtension and directly use it like this:
<RowDefinition Height="{Binding EditEnabled, Converter={convert:BooleanToGridLengthConverter True=10*, False=0}" />

IValueConverter with Bound Dependency Properties

I need to determine the StringFormat of some bound TextBlocks at runtime based on the unit system identified in the object to be bound.
I Have a converter with a Dependency Property that I would like to Bind to. The Bound value is used in determining the conversion process.
public class UnitConverter : DependencyObject, IValueConverter
{
public static readonly DependencyProperty IsMetricProperty =
DependencyProperty.Register("IsMetric", typeof(bool), typeof(UnitConverter), new PropertyMetadata(true, ValueChanged));
private static void ValueChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
((UnitConverter)source).IsMetric = (bool)e.NewValue;
}
public bool IsMetric
{
get { return (bool)this.GetValue(IsMetricProperty); }
set { this.SetValue(IsMetricProperty, value); }
}
object IValueConverter.Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (IsMetric)
return string.Format("{0:0.0}", value);
else
return string.Format("{0:0.000}", value);
}
object IValueConverter.ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
I declare the converter
<my:UnitConverter x:Key="Units" IsMetric="{Binding Path=IsMetric}"/>
and bind the TextBlock
<TextBlock Text="{Binding Path=Breadth, Converter={StaticResource Units}}" Style="{StaticResource Values}"/>
Never the less, I get the following error:
System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=IsMetric; DataItem=null; target element is 'UnitConverter' (HashCode=62641008); target property is 'IsMetric' (type 'Boolean')
I guess this is initialising before I set the datacontext and therefore there is nothing to bind the IsMetric property to. How can I achieve the desired result?
Provided that Breadthand IsMetric are properties of the same data object, you might use a MultiBinding in conjunction with a multi value converter:
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource UnitMultiValueConverter}">
<Binding Path="Breadth" />
<Binding Path="IsMetric" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
with a converter like this:
public class UnitMultiValueConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
double value = (double)values[0];
bool isMetric = (bool)values[1];
string format = isMetric ? "{0:0.0}" : "{0:0.000}";
return string.Format(format, value);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
The problem with your approach is that when the UnitConverter is declared as resource it does not have a DataContext, and it will never get one later on.
And one more important thing: the ValueChanged callback for UnitConverter.IsMetric is nonsense. It sets the same property again which was just changed.

Is possible to get a index from a item in a list?

I mean, I've got a listBox, and I'm putting in itemsSource property the list. And I want to show also the index in the binding of it.
I have no idea if this is possible in WPF. Thanks.
There are a few methods for doing this including some workarounds using the AlternationIndex.
However, since I've used the AlternationIndex for other purposes I like to get a binding for the element index with the following:
<MultiBinding Converter="{StaticResource indexOfConverter}">
<Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type ItemsControl}}" />
<Binding Path="."/>
</MultiBinding>
Where the converter is defined as:
public class IndexOfConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (Designer.IsInDesignMode) return false;
var itemsControl = values[0] as ItemsControl;
var item = values[1];
var itemContainer = itemsControl.ItemContainerGenerator.ContainerFromItem(item);
// It may not yet be in the collection...
if (itemContainer == null)
{
return Binding.DoNothing;
}
var itemIndex = itemsControl.ItemContainerGenerator.IndexFromContainer(itemContainer);
return itemIndex;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
return targetTypes.Select(t => Binding.DoNothing).ToArray();
}
}

Categories

Resources