WPF DataBinding where the Path is recovered from the object? - wpf

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>

Related

Bind DataContext Index to Slider Control Value

Is it possible to use the current value of a WPF Slider control (Slider.Value) as an input to a Indexing Binding on another control?
Use case: A collection of items is set as the DataContext for a control, and the slider is used to select which item from a collection is displayed.
<Slider x:Name="selector" Minimum="1" Maximum="{Binding Count}"/>
<!-- How to grab the value of selector and use as indexer?? -->
<StackPanel DataContext="{Binding [??????]}">
<TextBlock Text="{Binding name}"/>
<TextBlock Text="{Binding job}" />
</StackPanel>
Is it possible to use the current value of a WPF Slider control (Slider.Value) as an input to a Indexing Binding on another control?
No, not directly. ?????? in {Binding [??????]} has to be a compile-time constant.
You could bind to both the DataContext and the Value property of the Slider and use a converter to perform the lookup though:
public class MultiConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var yourDataContext = values[0] as IDictionary<double, object>; //cast to whatever the type of your DataContext is
double value = (double)values[1];
return yourDataContext[value];
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) =>
throw new NotSupportedException();
}
XAML:
<StackPanel>
<StackPanel.DataContext>
<MultiBinding>
<MultiBinding.Converter>
<local:MultiConverter />
</MultiBinding.Converter>
<Binding Path="." />
<Binding Path="Value" ElementName="selector" />
</MultiBinding>
</StackPanel.DataContext>
<TextBlock Text="{Binding name}"/>
<TextBlock Text="{Binding job}" />
</StackPanel>
Declare two dependency properties on your data context: SliderIndex and SelectedItem (you can name them whatever you want, but those are the names I'll use for my answer).
Bind Slider.Value to SliderIndex. Then use a PropertyChangedCallback to update the SelectedItem property based on the new value of SliderIndex. Finally, bind StackPanel.DataContext to SelectedItem.
This is the best way I know of to do this. There is no easy way to bind the two directly since you can't use a variable as an index for collection binding. The other option is to use an IValueConverter or IMultiValueConverter, but the above is cleaner in my opinion.

Bind IsChecked of a CheckBox to a method

Is it possible to bind the IsChecked property of a checkbox to a custom method?
I created a list of checkboxes bound to a collection of objects. I have a second collection of objects which is a subset of the first one. I'd like to bind the IsChecked porperty of the checkbox to a method that determines if the object is contained in the second list or not
EDIT:
<ListBox Height="auto" HorizontalAlignment="Stretch" Name="listBox" VerticalAlignment="Stretch" Width="auto" ItemsSource="{Binding DataSources}">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Name="CheckBoxZone"
Content="{Binding Name}"
Tag="{Binding Id}"
Margin="0,5,0,0"
/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
You can bind the checkbox Command property to a ICommand on your model. This means every time the check is changed the command will be invoked.
Example:
<CheckBox Name="CheckBoxZone"
Content="{Binding Name}"
Tag="{Binding Id}"
Margin="0,5,0,0"
Command={Binding CheckBoxChangedCommand}
/>
You may bind IsChecked to both the data object and the subset collection by means of a MultiBinding in conjunction with a multi-value converter that converts into a bool (or Nullable<bool> for IsChecked) value:
<CheckBox.IsChecked>
<MultiBinding Converter="{StaticResource ObjectInListConverter}" Mode="OneWay">
<Binding />
<Binding Source="{StaticResource SubsetCollection}" />
</MultiBinding>
</CheckBox.IsChecked>
The converter:
class ObjectInListConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
IList subset = values[1] as IList;
Nullable<bool> result = subset.Contains(values[0]);
return result;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
In addition to Pop Catalin's answer, you will want to bind IsChecked to a property in the VM and modify that VM property when command is executed.

Is it possible to add more characters after a binding in xaml?

I was wondering something, and couldn't find any relevant topics. I have following binding :
Content="{x:Static resx:Resource.Form_OtherOption_Description}"
This will place a string in a label. What i was asking myself is if i can add a ":" after that binding, not in code, just in xaml. The label represent something like "Name :". But adding the ":" as part of the binding is not an option.
Edit
I'm working in 3.5 version
Any suggestions.
Thanks in advance.
You could accomplish this with something like:
<TextBlock Text="{Binding Source={x:Static resx:Resource.Form_OtherOption_Description},
StringFormat={}{0}:}" />
Edit: <Label>s Content property does not respect the StringFormat property of a binding apparently. Which I've found has been moved to the ContentStringFormat property on the <Label>.
<Label Content="{x:Static resx:Resource.Form_OtherOption_Description}"
ContentStringFormat="{}{0}:" />
If you're using WPF 4.0, you could also do this:
<TextBlock>
<Run Text="{Binding SomeLabel}"/>
<Run Text=":"/>
</TextBlock>
This actually concatenates the two strings coming from two Run tag and copied into TextBlock.Text property!.
Using this approach you can even bind to different properties in presenter, and display it in a single TexBlock. See this excellent example:
Can we concat two properties in data binding?
You can also use MultiBinding with StringFormat e.g:
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="ID {0} Name: {1} Age: {2}">
<Binding Source="{x:Static resx:SomeResx.ID}"/>
<Binding Path="Name"/>
<Binding Path="Age"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
You can use this in a content control TextBlock TextBlock.Text (sorry I couldn't get the code to show up for this above)
Yes you can. Here I add "testing" after binding text(clouds.all) in windows phone.
<TextBlock Text="{Binding Path=clouds.all, StringFormat=\{0\}testing}"/>
Try Binding's property StringFormat - it can do very simply what you want.
if you use a label inside a progress bar you can use this way:
<Label x:Name="Progress" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" FontWeight="Bold" Foreground="White" Opacity=".7"
Content="{Binding Path=Value, RelativeSource={RelativeSource TemplatedParent}}" ContentStringFormat="{}{0}%">
in this way you can visualize the value of progressbar with a % added.
You can create a converter that takes the input string and adds the ":".
public class AddStringToStringConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
string input = value as string;
string suffix = parameter as string;
return input + suffix;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
Xaml:
<Window.Resources>
<local:AddStringToStringConverter x:Key="AddStringToStringConverter"/>
</Window.Resources>
...
<Label Text="{Binding Source={x:Static resx:Resource.Form_OtherOption_Description}, Converter={StaticResource AddStringToStringConverter}, ConverterParameter=:}"/>
Or something like that. Tried it and it worked for my source at least.
If you have whitespace and the like in you ConverterParameter you can use signle quotes to make sure it does not get disposed.
Edit: Oh right... yeah... there's also StringFormat which i have never needed before, ehehehe...

How to bind multiple values to a single WPF TextBlock?

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}}" />

Bind an element to two sources

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.

Resources