Bind an element to two sources - wpf

I currently have two text boxes which accept any number. I have a text block that takes the two numbers entered and calculates the average.
I was wondering if there was a way I could bind this text block to both text boxes and utilize a custom converter to calculate the average? I currently am catching the text changed events on both text boxes and calculating the average that way, but I am under the assumption data binding would be more efficient and easier.

You're looking for MultiBinding.
Your XAML will look something like this:
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource myConverter}">
<Binding Path="myFirst.Value" />
<Binding Path="mySecond.Value" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
With reasonable replacements for myConverter, myFirst.Value, and mySecond.Value.

Create a converter that implements IMultiValueConverter. It might look something like this:
class AverageConverter : IMultiValueConverter
{
#region IMultiValueConverter Members
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
int total = 0;
int number = 0;
foreach (object o in values)
{
int i;
bool parsed = int.TryParse(o.ToString(), out i);
if (parsed)
{
total += i;
number++;
}
}
if (number == 0) return 0;
return (total/number).ToString();
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
A multivalue converter receives an object array, one for each of the bindings. You can process these however you need, depending on whether you're intending it for double or int or whatever.
If the two textboxes are databound, you can use the same bindings in the multibinding for your textblock (remembering to notify when the property changes so that your average is updated), or you can get the text value by referring to the textboxes by ElementName.
<TextBox Text="{Binding Value1}" x:Name="TextBox1" />
<TextBox Text="{Binding Value2}" x:Name="TextBox2" />
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource AverageConverter}">
<Binding ElementName="TextBox1" Path="Text" />
<Binding ElementName="TextBox2" Path="Text" />
<!-- OR -->
<!-- <Binding Path="Value1" /> -->
<!-- <Binding Path="Value2" /> -->
</MultiBinding>
</TextBlock.Text>
</TextBlock>

Or, you could make a property in code behind, and bind the TextBlock to that ... I do that all the time, and it's a little simpler than making a converter, then doing that same code there.
Example: (in your code behind of the xaml):
public double AvgValue
{
get { return (valueA + valueB) / 2.0; }
}
And then, in your XAML:
<TextBlock Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=AvgValue}" />
That's a LOT simpler than a custom converter.

Just to add step-by-step procedure to Timothy's answer:
Setup the View.TextBlock.Text property to bind to the ViewModel.AvgValue property.
Catch the TextChanged event of the TextBox control, then set the AvgValue in the handler of that TextChanged event.
As part of that handler in step 2, make sure to raise a property change so that the TextBlock is updated.

Related

How can i string format double value to relative to other binding value?

I have a View Model which contain a collection. Each collection item have instrument and price properties. Instrument object has Name and decimal place numbers property.
When i bind collection to my View's DataGrid control, i want to format price column relative to item's instrument decimal place numbers property like below;
AAPL 100.89 decimal places number is 2 for AAPL
EURUSD 1.12345 decimal places number is 5 for EURUSD
How can i write this type dynamic converter? Or is there a better solution for this?
Before answer a question, i want to give a detail about problem. I have a WatchListItem class that have Instrument, Last Price, Bid Price, Ask Price properties. In my View Model there is WatchListItems property which is list of WatchListItem and i'm binding it to my View's DataGrid Control. DataGrid control have 3 columns for each property. At the runtime i want to show Last, Ask and Bid prices with format according to instrument's tick size.
After search a little bit i see this problem can be solved both Binding and Multi Binding.
If you want to use Binding you have to bind column to WatchListItem. Then you can write class that implement IValueConverter and use it in xaml via static resource and also for converter know that which price want to format you have to give convert parameter "Last" or "Bid" or "Ask". I don't like this solution because open for misspellings about convert parameter.
I used MultiBinding and IMultiValueConverter like below.
Converter
[ValueConversion(typeof(double), typeof(string))]
public class PriceToStringConverter : IMultiValueConverter
{
#region Implementation of IMultiValueConverter
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (targetType != typeof(string))
throw new ArgumentException("The target type must be a string.");
var instrument = (IInstrument) values[0];
var instrumentPrice = (double) values[1];
var instrumentTickSizeParts = instrument.TickSize.ToString("0.#####", culture).Split('.');
if (instrumentTickSizeParts.Length == 1)
return instrumentPrice.ToString(culture);
var format = $"0.{string.Empty.PadRight(instrumentTickSizeParts[1].Length, '0')}";
return instrumentPrice.ToString(format, culture);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
XAML
<DataGrid ItemsSource="{Binding CurrentWatchList.WatchListItems}">
<DataGrid.Resources>
<converters:PriceToStringConverter x:Key="PriceToStringConverter"/>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTextColumn Header="Instrument" Binding="{Binding Instrument.Name}"/>
<DataGridTextColumn Header="Time" Binding="{Binding Time, StringFormat={}{0:HH:mm:ss}}"/>
<DataGridTextColumn Header="Last">
<DataGridTextColumn.Binding>
<MultiBinding Converter="{StaticResource PriceToStringConverter}">
<Binding Path="Instrument"/>
<Binding Path="Last"/>
</MultiBinding>
</DataGridTextColumn.Binding>
</DataGridTextColumn>
<DataGridTextColumn Header="Bid">
<DataGridTextColumn.Binding>
<MultiBinding Converter="{StaticResource PriceToStringConverter}">
<Binding Path="Instrument"/>
<Binding Path="Bid"/>
</MultiBinding>
</DataGridTextColumn.Binding>
</DataGridTextColumn>
<DataGridTextColumn Header="Ask">
<DataGridTextColumn.Binding>
<MultiBinding Converter="{StaticResource PriceToStringConverter}">
<Binding Path="Instrument"/>
<Binding Path="Ask"/>
</MultiBinding>
</DataGridTextColumn.Binding>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>

WPF DataBinding where the Path is recovered from the object?

I have an object with several properties. Two of these are used to control the width and height of the target text box. Here is a simple example...
<DataTemplate DataType="{x:Type proj:SourceObject}">
<TextBox Width="{Binding ObjWidth}" Height="{Binding ObjHeight}"/>
</DataTemplate>
I also want to bind the Text property of the TextBox. The actual property to bind against is not fixed but instead is named in a field of the SourceObject. So ideally I would want to do this...
<DataTemplate DataType="{x:Type proj:SourceObject}">
<TextBox Width="{Binding ObjWidth}" Height="{Binding ObjHeight}"
Text="{Binding Path={Binding ObjPath}"/>
</DataTemplate>
Here the ObjPath is a string that returns a path that would be perfectly valid for the binding. But this does not work because you cannot use a binding against the Binding.Path. Any ideas how I can achieve the same thing?
For more context I will point out that the SourceObject is user customizable and hence the ObjPath can be updated over time and hence I cannot simply put a fixed path in the data template.
You could implement an IMultiValueConverter and use this one as BindingConverter for your Text Property. But then you have the problem, that the value of the Textbox is only updated if your ObjPath property changes (the path itself), not the value where the path is pointing to. If that's, okay you can go with a BindingConverter which returns the value of your binding Path using Reflection.
class BindingPathToValue : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value[0] is string && value[1] != null)
{
// value[0] is the path
// value[1] is SourceObject
// you can use reflection to get the value and return it
return value[1].GetType().GetProperty(value.ToString()).GetValue(value[1], null).ToString();
}
return null;
}
public object[] ConvertBack(object value, Type[], object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Have the converter in your Resources:
<proj:BindingPathToValue x:Key="BindingPathToValue" />
and use it in XAML:
<DataTemplate DataType="{x:Type proj:SourceObject}">
<TextBox Width="{Binding ObjWidth}" Height="{Binding ObjHeight}">
<TextBox.Text>
<MultiBinding Mode="OneWay" Converter="{StaticResource BindingPathToValue}">
<Binding Mode="OneWay" Path="ObjPath" />
<Binding Mode="OneWay" Path="." />
</MultiBinding>
</TextBox.Text>
</TextBox>
</DataTemplate>

How to boolean && two visibility converters

I have two separate converters for visibility, one based on whether a field has been updated and one based on whether a field is allowed to be seen. I use the updatedField one for each text item on my page so that a star shows up next to an updated field. But some text items only are visible to some users based on permission levels.
For example:
<Image Visibility="{Binding ElementName=MyObject, Path=UpdatedFields, Mode=OneWay, Converter={StaticResource updatedFieldConverter}, ConverterParameter=FieldToTest}" Source="Properties:Resources.star_yellow" />
and
<TextBlock FontSize="21" Foreground="{DynamicResource LabelBrush}" Text="{x:Static Properties:Resources.Some_Text}" Visibility="{Binding Source={StaticResource allowedFields}, Path=Some_Text_Field, Converter={StaticResource visibilityConverter}}" />
My problem is that for the case of the permission-required fields I need to run both converters to determine if the star shows up. Is there a way to do a boolean "And" on the results of two converters?
I looked at this post but it doesn't seem to allow for different sets of parameters to be passed into to the two different converters.
-------Update--------
I also tried to create a MultiValueConverter with this xaml
<Image Grid.Row="4" Grid.Column="0" Source="star_yellow.png">
<Image.Visibility>
<MultiBinding Converter="{StaticResource combinedVisibilityConverter}" ConverterParameter="FieldToTest" >
<Binding ElementName="allowedFieldsModel" Path="Some_Text_Field" Mode="OneWay" />
<Binding ElementName="MyObject" Path="UpdatedFields" Mode="OneWay" />
</MultiBinding>
</Image.Visibility>
</Image>
But when it enters the converter both values are "DependencyProperty.UnsetValue". So I'm apparently doing something wrong here.
--------Solution---------
I had to modify to this, but then it worked.
<Image.Visibility>
<MultiBinding Converter="{StaticResource combinedVisibilityConverter}" ConverterParameter="FieldToTest">
<Binding Source="{StaticResource allowedFieldsModel}" Path="Some_Text_Field" />
<Binding Path="MyObject.UpdatedFields" />
</MultiBinding>
</Image.Visibility>
You could use a MultiBinding together with a short, hand made IMultiValueConverter.
Example:
<StackPanel>
<StackPanel.Resources>
<local:MultiBooleanToVisibilityConverter x:Key="Converter" />
</StackPanel.Resources>
<CheckBox x:Name="Box1" />
<CheckBox x:Name="Box2" />
<TextBlock Text="Hidden Text">
<TextBlock.Visibility>
<MultiBinding Converter="{StaticResource Converter}">
<Binding ElementName="Box1"
Path="IsChecked" />
<Binding ElementName="Box2"
Path="IsChecked" />
</MultiBinding>
</TextBlock.Visibility>
</TextBlock>
</StackPanel>
... and the converter ...
class MultiBooleanToVisibilityConverter : IMultiValueConverter
{
public object Convert(object[] values,
Type targetType,
object parameter,
System.Globalization.CultureInfo culture)
{
bool visible = true;
foreach (object value in values)
if (value is bool)
visible = visible && (bool)value;
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();
}
}
Late to the party here but an easier solution is to just wrap the control in another control. I prefer this to having lots of Converters that do different things.
<Border Visibility="{Binding Value1, Converter={convertersDF:Converter_ValueToVisibility}}">
<ComboBox Visibility="{Binding Value2, Converter={convertersDF:Converter_ValueToVisibility}}"/>
</Border>
One thing that came to mind is, perhaps, instead of two different boolean fields, a single bit field created by ORing together updatedField and allowedField. Then you can have three value converters, all operating on the same field.
Or just calculate another field in your data model that does the ANDing there. That's probably more efficient (in terms of runtime).
You could pass an array of two objects to the converter in the ConverterParameter - constructing the array in XAML.

Binding StringFormat

I have a collection of textblocks that I'm going to be showing and I'm needing the text of each textblock to be displayed differently. I'm currently saving the format string in the tag property and I'm needing to display the text in this format. How do I bind the StringFormat section?
Something like the section below:
<TextBlock Tag="{Binding MyFormatString}" Text="{Binding MyProperty, StringFormat='{}{0:MyTag}'}" />
Since BindingBase.StringFormat is not a dependency property, I do not think that you can bind it. If the formatting string varies, I'm afraid you will have to resort to something like this
<TextBlock Text="{Binding MyFormattedProperty}" />
and do the formatting in your view model. Alternatively, you could use a MultiBinding and a converter (example code untested):
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource myStringFormatter}">
<Binding Path="MyProperty" />
<Binding Path="MyFormatString" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
public class StringFormatter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return string.Format((string)values[1], values[0]);
}
...
}
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}{0},{1}">
<Binding Path="MyProperty" />
<Binding Path="MyFormatString" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
The String Formatting is a display setting and therefore should live close to the UI layer, you can either declare it in the Xaml or have formatted string properties on a ViewModel and perform the formatting in the Get of those properties and bind the TextBlock to it the ViewModel properties. It would source its data from the original datasource.

WPF - Converter hides dependency on a DependencyProperty

I have a TextBlock (acutally a whole bunch of TextBlocks) where I set the Text to "" if a DependencyProperty in my ViewModel is set to Visiblity.Hidden. I do this via a converter as follows:
<TextBlock Margin="0,0,5,0">
<TextBlock.Text>
<Binding Converter="{StaticResource GetVisibilityOfColumnTitles}"
Path="Name" />
</TextBlock.Text>
</TextBlock>
The converter looks like this:
public object Convert(object value, Type targetType,
object parameter,System.Globalization.CultureInfo culture)
{
if (MainMediator.Instance.VisibilityOfWorkItemColumnTitles
== Visibility.Visible)
return value;
else
return "";
}
I admit that this is a bit convoluted way to do this, but I have my reasons (DataContext complications and spacing for the TextBlock)
The problem I have is that when VisibilityOfWorkItemColumnTitles is changed, even though it is a dependency property, TextBlock.Text does not realize there is a dependency there (because it's used in the converter).
Is there a way in the code behind (preferably in the converter) to say, this TextBlock wants to update this binding when VisibilityOfWorkItemColumnTitles changes?
Since your converter depends on both the Text property from the TextBox and the VisibilityOfWorkItemColumnTitles property on your MainMediator class, you'll probably need to use a MultiBinding and include both properties back in the XAML.
<TextBlock Margin="0,0,5,0">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource GetVisibilityOfColumnTitles}">
<Binding Path="Name" />
<Binding Path="VisibilityOfWorkItemColumnTitles" Source="{x:Static my:MainMediator.Instance}" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
(I've used "my" as the XML namespace for your MainMediator class in that code sample.)
Then change your converter to an IMultiValueConverter, and reference values[0] for the text and values1 for the "visibility" property. Now the binding will know if either property changes, and call off to the converter appropriately.

Resources