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.
Related
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>
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.
Suppose I have this MultiBinding:
<MultiBinding Converter="{StaticResource FooBarConverter}>
<Binding Path="Foo" Converter="{StaticResource FooConverter}" />
<Binding Path="Bar" Converter="{StaticResource BarConverter}" />
</MultiBinding>
This doesn't seem to work: the values array passed to FooBarConverter contains DependencyProperty.UnsetValue for each value (two, in this case). Removing the converters on the child bindings (FooConverter and BarConverter) gives me the actual values. By the way: those converters are properly invoked, it just looks like their result is discarded.
Is this intended behavior? I want to bind 2 properties by I need to convert at least one of them before throwing them into the MultiValueConverter...
The developers in Kishore's shared link came to the conclusion that to make such a MultiBinding, the child Bindings must return the same type of result as the parent MultiBinding. So, in my case, if I wanted the parent MultiBinding to return a value of type Visibility, the child Bindings must also return Visibility values. Not doing so will pass UnsetValues to your converter method, likely giving you undesireable results.
Here is a snippet of code that works for me. Note that Converters "VisibleIfTrue" and "EnumToVisibility" both return type Visibility values:
<Grid.Visibility>
<MultiBinding Converter="{StaticResource MultiVisibilityConverter}">
<Binding Path="JobHasData" Converter="{StaticResource VisibleIfTrue}" />
<Binding Path="CurrentMode" Converter="{StaticResource EnumToVisibility}" ConverterParameter="{x:Static Mode.Setup}" />
</MultiBinding>
</Grid.Visibility>
It is annoying that you can't pass it different value types to process and give you the result you want. (I initially tried to pass bools to the converter.)
Hope this helps anyone who waited the seven years for the answer. ;)
you have mention converter in the Multibinding tag like this
<TextBlock Grid.Row="3" Grid.Column="1" Padding="5">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource sumConverter}">
<Binding Path="FirstNum" />
<Binding Path="SecondNum" />
<Binding Path="ThirdNum" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
If WPF was prior to 4.0 then it is a known and fixed bug that have workaround.
Here located a sample implementation of the workaround for poor man that are forced to work with elder versions.
To say shortly, old wpf versions are trying to convert values from multibinding child bindings that have converters directly into type of target dependency property. Workaround is to create hidden label, move multibinding or its child binding converter to label.content as it expects object and then bind desired property to it.
public class DataClass
{
public string FirstName { get; set; }
public string Surname { get; set; }
}
public class NameMultiValueConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
return String.Format("{0} {1}", values[0], values[1]);
}
}
The XAML looks basically like this
<Window xmlns:local="clr-namespace:BlogIMultiValueConverter">
<Window.Resources>
<local:NameMultiValueConverter x:Key="NameMultiValueConverter" />
</Window.Resources>
<Grid>
<TextBox Text="{Binding Path=FirstName, UpdateSourceTrigger=PropertyChanged}" />
<TextBox Text="{Binding Path=Surname, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource MultiValueConverter}">
<Binding Path="FirstName" />
<Binding Path="Surname" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Grid>
I'm currently using the TextBlock below to bind the value of a property named Name:
<TextBlock Text="{Binding Name}" />
Now, I want to bind another property named ID to the same TextBlock.
Is it possible to bind two or more values to the same TextBlock? Can it be done with simple concatenation, like Name + ID and, if not, how else could this be approached?
You can use a MultiBinding combined with the StringFormat property. Usage would resemble the following:
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} + {1}">
<Binding Path="Name" />
<Binding Path="ID" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
Giving Name a value of Foo and ID a value of 1, your output in the TextBlock would then be Foo + 1.
Note: This is only supported in .NET 3.5 SP1 and 3.0 SP2 or later.
I know this is a way late, but I thought I'd add yet another way of doing this.
You can take advantage of the fact that the Text property can be set using "Runs", so you can set up multiple bindings using a Run for each one. This is useful if you don't have access to MultiBinding (which I didn't find when developing for Windows Phone)
<TextBlock>
<Run Text="Name = "/>
<Run Text="{Binding Name}"/>
<Run Text=", Id ="/>
<Run Text="{Binding Id}"/>
</TextBlock>
If these are just going to be textblocks (and thus one way binding), and you just want to concatenate values, just bind two textblocks and put them in a horizontal stackpanel.
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="{Binding ID}"/>
</StackPanel>
That will display the text (which is all Textblocks do) without having to do any more coding. You might put a small margin on them to make them look right though.
Use a ValueConverter
[ValueConversion(typeof(string), typeof(String))]
public class MyConverter: IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return string.Format("{0}:{1}", (string) value, (string) parameter);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return DependencyProperty.UnsetValue;
}
}
and in the markup
<src:MyConverter x:Key="MyConverter"/>
.
.
.
<TextBlock Text="{Binding Name, Converter={StaticResource MyConverter Parameter=ID}}" />
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.