I have an app which uses two sliders to generate a product used elsewhere in the code. What I would like is to have the product value bound to a textblock or tooltip, for example, to look something like "10 x 15 = 150".
The first part is easy, and looks like this:
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} x {1}">
<Binding ElementName="amount_slider" Path="Value" />
<Binding ElementName="frequency_slider" Path="Value"/>
</MultiBinding>
</TextBlock.Text>
But what's a nice easy way to get the product in there as well?
Using Pavlo Glazkov's solution, I modified it to look like this:
public class MultiplyFormulaStringConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var doubleValues = values.Cast<double>().ToArray();
double x = doubleValues[0];
double y = doubleValues[1];
var leftPart = x.ToString() + " x " + y.ToString();
var rightPart = (x * y).ToString();
var result = string.Format("{0} = {1}", leftPart, rightPart);
return result;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
And the all-important
<Window.Resources>
<local:MultiplyFormulaStringConverter x:Key="MultiplyFormulaStringConverter"/>
</Window.Resources>
Thanks!
Instead of using StringFormat create a converter. Something like this:
public class MultiplyFormulaStringConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var doubleValues = values.Cast<double>().ToArray();
var leftPart = string.Join(" x ", doubleValues);
var rightPart = doubleValues.Sum().ToString();
var result = string.Format("{0} = {1}", leftPart, rightPart);
return result;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
<TextBlock.Text>
<MultiBinding Converter="{StaticResource MultiplyFormulaStringConverter}">
<Binding ElementName="amount_slider" Path="Value" />
<Binding ElementName="frequency_slider" Path="Value"/>
</MultiBinding>
</TextBlock.Text>
You could use a converter and pass as a parameter the two values that you would like to calculate. The converter would do the calculation and then return the string result.
(Converter example here)
Related
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
My program has this kind of a multi bind
<MultiBinding Converter="{StaticResource myConverter}" Mode="OneWay">
<Binding Path="SelectedItems.Count"/>
<Binding Path="EffectiveStyleContext.Selection"/>
</MultiBinding>
IS there anyway to get the current enable disable status in the Convert method
class myConverter: IMultiValueConverter
{
public object Convert(object[] values, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
//I need to get current status here
}
}
You have to pass control itself into a ValueConverter.
Your modified Xaml will be
<MultiBinding Converter="{StaticResource myConverter}" Mode="OneWay">
<Binding RelativeSource="{RelativeSource Self}"/>
<Binding Path="SelectedItems.Count"/>
<Binding Path="EffectiveStyleContext.Selection"/>
</MultiBinding>
Now in your coverter code you will be able to access control.
public class MyConverter : IMultiValueConverter
{
public object Convert(object[] values, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var control = values[0] as FrameworkElement;
var value1 = values[1] as int;
// write your logic here.
}
public object[] ConvertBack(object value, System.Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
return throw new System.NotImplementedException();;
}
}
You can also bind direct to the property itself:
<Binding Path="IsEnabled" RelativeSource="{RelativeSource Self}" />
I would actually argue that this is preferable because the MultiBinding won't get updated when the enable state changes otherwise.
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();
}
}
I need to print in a textBlock repeat if a number is less than 80 and paint color red, and more or equal than 80 print successful with green color.
How can I do that in XAML?
Converters.
Sadly there are no inequality-triggers or the like, so using a converter should do.
<TextBlock>
<TextBlock.Foreground>
<Binding Path="TestDouble">
<Binding.Converter>
<vc:ThresholdConverter BelowValue="{x:Static Brushes.Red}"
AboveValue="{x:Static Brushes.Green}"
Threshold="80" />
</Binding.Converter>
</Binding>
</TextBlock.Foreground>
<TextBlock.Text>
<Binding Path="TestDouble">
<Binding.Converter>
<vc:ThresholdConverter BelowValue="Repeat"
AboveValue="Successful"
Threshold="80" />
</Binding.Converter>
</Binding>
</TextBlock.Text>
</TextBlock>
public class ThresholdConverter : IValueConverter
{
public double Threshold { get; set; }
public object AboveValue { get; set; }
public object BelowValue { get; set; }
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
double input;
if (value is double)
{
input = (double)value;
}
else
{
var converter = new DoubleConverter();
input = (double)converter.ConvertFrom(value);
}
return input < Threshold ? BelowValue : AboveValue;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
}
<local:NumberToBrushConverter x:Key="numberToBrushConverter" />
<local:NumberToTextConverter x:Key="numberToTextConverter" />
<TextBlock Background="{Binding Number, Converter={StaticResource numberToBrushConverter}}"
Text="{Binding Number, Converter={StaticResource numberToTextConverter}"/>
class NumberToBrushConverter: IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
int number = (int)value;
return number < 80 ? Brushes.Red : Brushes.Green;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return Binding.DoNothing;
}
#endregion
}
The other converter would look similar to the brush converter, but return "Successful" or "Repeat".
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.