How to get a symmetrical TwoWay binding? - wpf

While debugging a problem in a WPF application, I have noticed that the TwoWay data binding does not seem to be symmetrical. Here is an example:
<Window x:Class="ConverterTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ConverterTest"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:TextConverter x:Key="textConverter"/>
</Window.Resources>
<Grid>
<TextBox x:Name="txt1" Height="24" Margin="0" VerticalAlignment="Top"/>
<TextBox x:Name="txt2" Height="24" Margin="0,40,0,0" VerticalAlignment="Top"
Text="{Binding ElementName=txt1, Path=Text,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged,
Converter={StaticResource textConverter}}"/>
</Grid>
</Window>
The converter looks like this:
public class TextConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, System.Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
return value.ToString().ToUpper();
}
public object ConvertBack(object value, System.Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
return value.ToString().ToLower();
}
#endregion
}
When editing inside txt1, only Convert() is called; but when editing inside txt2, first ConvertBack() and then Convert() is called.
In other words, TwoWay binding seems to work like this:
When the source property is updated, the target property is updated.
When the target property is updated, first the source property is updated, and then the target property is updated again.
The MSDN documentation merely says:
Causes changes to either the source property or the target property to automatically update the other.
From that sentence, I would have expected the following behavior:
When the source property is updated, the target property is updated.
When the target property is updated, the source property is updated.
Is there any way to achieve this symmetrical behavior (purely in XAML, without C# programming)?
EDIT: I just have discovered that in WPF 3.5, TwoWay bindings work symmetrical. In WPF 4.0, however, the behavior has changed (as described above).

Well, entirely symmetrical cannot be achieved because:
When the source is updated, the control just has to show the new value, period. Nothing fancy happens.
When the target is updated, before it is written to the source, the change of value can be canceled at a ridiculous amount of places.
For example: validation.
When the source is updated, say, from code, it passed all validation (unless you forgot to check, but that's another problem). The control just displays the new value.
When the target is updated, the value is first converted back in the converter to be manipulable, then coerced, validated and then at last, the source is set.
But as you see, the value could have been modified between the target and the source, so the value is converted again, and finally displayed.
Bonus: to understand where and in which order the value can change, see Dependency Property Value Precedence.

Related

Span in Contentpresenter not working properly

I have stumbled upon a small problem and I'm not sure how to avoid it or work around it and whether it's a bug or a "feature".
When rendering a span with text in it, it seems to be disconnected from the logical tree when using a content presenter to render it. It does not bubble IsMouseOver (or probably any event) and Hyperlinks inside the span also won't fire any associated code.
<ContentPresenter>
<ContentPresenter.Content>
<!--Normally this would be a binding, but it behaves the same.-->
<Span>
Test <Hyperlink Click="Hyperlink_OnClick">Testlink</Hyperlink>
</Span>
</ContentPresenter.Content>
</ContentPresenter>
Inspecting the visual tree with Snoop indeed shows that the TextBlock used to display the span does not receive IsMouseOver-Events from it's inline elements while they themselves do indeed register them correctly (when you expand the inline property and navigate to them; they just refuse to pass them on). Also when attaching a message box to the click handler, nothing happens when you click on the link.
<TextBlock Grid.Row="1">
<Span>
Test <Hyperlink Click="Hyperlink_OnClick">Testlink</Hyperlink>
</Span>
</TextBlock>
This one on the other hand works as expected. The IsMouseOver works fine and even the Link works.
The premise of my problem is, that I want to dynamically bind the text of the TextBlock to something. But I can't bind the text-property to a span directly so I'm using a content presenter which does the job (but is broken). Is this a bug or some feature/implication that I'm unaware of? And is there another way to bind a span to something to display it with working event handling & hyperlink clicks?
You could use a converter that returns a TextBlock with the Span added to its Inlines collection:
public class MyConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Span span = value as Span;
TextBlock textBlock = new TextBlock();
textBlock.Inlines.Add(span);
return textBlock;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
XAML:
<ContentPresenter Content="{Binding Span, Converter={StaticResource MyConverter}}" />

ObservableCollection Remove() not firing to Visibility binding

I have a strange issue with my WPF project. I have a ObservableCollection<T> bound to a ListBox. When I add and remove items, the binding works and the list displays the correct results.
The issue I have, is I'm also binding this same property to another XAML control, but it doesn't trigger the converter when I remove an item from the list. It works when I add items.
The relevant XAML is
<view:WelcomeView Visibility="{Binding Steps, Converter={StaticResource CollapseIfZero}}"/>
<ListBox ItemsSource="{Binding Steps}" />
And the converter is
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var col = value as ICollection;
return col.Count == 0 ? Visibility.Visible : Visibility.Collapsed;
}
I have a break point in the converter. When a new item is added, the break point is hit. When an existing item is removed, the break point is not hit.
Does WPF do something magical with the ListBox which I'm not aware of (which has led to this unexpected behavior)?
ObservableCollection implements INotifyCollectionChanged and ListBox (and other ItemsControls) listens when collection was modified.
Steps property itself doesn't change, it is the same ObservableCollection.
WelcomeView.Visibility is bound to Steps, and doesn't update because property value didn't change, it keeps the same object reference.
try create binding to Steps.Count property (converter should be modified to use int value)
<view:WelcomeView Visibility="{Binding Steps.Count, Converter={StaticResource CollapseIfZeroCount}}"/>
or
there is bool HasItems property in ItemsControl. I would make a binding with ElementName and BooleanToVisibilityConverter
<view:WelcomeView "{Binding ElementName=Lst, Path=HasItems, Converter={StaticResource Bool2Visibility}}"/>
<ListBox Name="Lst" ItemsSource="{Binding Steps}" />

WPF StringFormat on Label Content

I want to format my string binding as Amount is X where X is a property bound to a label.
I've seen many examples but the following doesn't work:
<Label Content="{Binding Path=MaxLevelofInvestment,
StringFormat='Amount is {0}'}" />
I've also tried these combinations:
StringFormat=Amount is {0}
StringFormat='Amount is {}{0}'
StringFormat='Amount is \{0\}'
I even tried changing the binding property's datatype to int, stringand double. Nothing seems to work. This is a very common use case but doesn't seem to be supported.
The reason this doesn't work is that the Label.Content property is of type Object, and Binding.StringFormat is only used when binding to a property of type String.
What is happening is:
The Binding is boxing your MaxLevelOfInvestment value and storing it the Label.Content property as a boxed decimal value.
The Label control has a template that includes a ContentPresenter.
Since ContentTemplate is not set, ContentPresenter looks for a DataTemplate defined for the Decimal type. When it finds none, it uses a default template.
The default template used by the ContentPresenter presents strings by using the label's ContentStringFormat property.
Two solutions are possible:
Use Label.ContentStringFormat instead of Binding.StringFormat, or
Use a String property such as TextBlock.Text instead of Label.Content
Here is how to use Label.ContentStringFormat:
<Label Content="{Binding Path=MaxLevelofInvestment}" ContentStringFormat="Amount is {0}" />
Here is how to use a TextBlock:
<TextBlock Text="{Binding Path=MaxLevelofInvestment, StringFormat='Amount is {0}'}" />
Note: For simplicity I omitted one detail in the above explanation: The ContentPresenter actually uses its own Template and StringFormat properties, but during loading these are automatically template-bound to the ContentTemplate and ContentStringFormat properties of the Label, so it seems as if the ContentPresenter is actually using the Label's properties.
Make a universal StringFormatConverter : IValueConverter. Pass your format string as ConverterParameter.
Label Content="{Binding Amount, Converter={...myConverter}, ConverterParameter='Amount is {0}'"
Also, make StringFormatMultiConverter : IMultiValueConverter when you need more than one object in format string, for instance, Completed {0} tasks out of {1}.
I just checked and for some reason it doesn't work with the Label, probably because it uses a ContentPresenter for the Content property internally. You can use a TextBlock instead and that will work. You could also put the TextBlock excerpt below in the content of a Label if you need to inherit styling, behaviour etc.
<TextBlock Text="{Binding Path=MaxLevelofInvestment, StringFormat='Amount is \{0\}'} />
Try using a converter....
<myconverters:MyConverter x:Key="MyConverter"/>
<Label Content="{Binding Path=MaxLevelofInvestment, Converter={StaticResource MyConverter"} />
public class MyConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return String.Format("Amount is {0}", value);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
}

Problem with binding in converter in silverlight custom control

i got some xaml here and what i m trying to do it's simply bind a property call Property (not the real name) on the width of a rectangle and to convert the value of this property with the converter name Conv and it's working perfectly with {TemplateBinding Property} or DataContext={TemplateBinding Property} or with a relative source (like in the code sample).
My problem is that the converterParameter should also be a binding property, but i m not able to bind any property in the converterParameter. So the 30 in the sample should be something like {Binding Path=SecondProperty}. If anyone got that problem or maybe if anyone got some other way to bind stuff in custom control thanks a lot ;)
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:RatingControl">
<Style TargetType="controls:Ctr">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="controls:Ctr">
<Grid>
<Grid.Resources>
<controls:Converter x:Name="Conv" />
</Grid.Resources>
<Rectangle x:Name="rect" Width="{Binding Path=Property, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource Conv}, ConverterParameter=30}" Height="20" />
It doesn't look like that's possible: http://msdn.microsoft.com/en-us/library/system.windows.data.binding.converterparameter(VS.95).aspx
You can add a property to the Converter class and bind to that.
You can't bind to a property of the Binding object, since it isn't a DependencyProperty in fact Binding isn't a DependencyObject. This is understandable can you imagine the complexity of managing dependency trees and the possiblity of recursive or circular bindings in bindings.
However you could use a Specialised converter for the task:-
public class MySpecialConverter: IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
Ctr obj = (Ctr)value;
var val = obj.Property;
var param = obj.SecondProperty;
// Do your intended code with val and param here.
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException("This converter only works for one way binding");
}
}
now your Xaml looks like:-
<Rectangle x:Name="rect" Height="20"
Width="{Binding RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource Conv}" />
It's a really good solution but it's not working bcs my first property must be bind (twoWay) because if i got any change on it the converter must convert again the value so i get the result back and show the real result.

Change other's property

Right now, I'm learning WPF. Can we change another WPF object's property when an WPF object's property is changed?
Below is simplified scenario.
I have a Window with a TextBox named m_Text and a ToggleButton named m_Button. I want to change the m_Text.Background property if m_Button is pressed, that is m_Button.IsChecked = true. I think it's possible using a Trigger but I don't know how to do it.
P.S. If possible, I want to do it only in XAML.
WPF makes this really easy - you can databind the TextBox's Background property directly to the IsChecked property on the ToggleButton. Of course, you will need to convert the IsChecked (boolean) to be a Brush, but WPF allows you to specify a Converter object right in the binding...
In code, you create an object that implements IValueConverter, and implement the Convert method, like
public class BoolToBrushConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
bool isChecked = (bool)value;
string[] colours = parameter.ToString().Split(':');
if (isChecked)
return new BrushConverter().ConvertFromString(colours[0]);
return new BrushConverter().ConvertFromString(colours[1]);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
then in xaml you need to add the namespace containing this class, declare an instance of the converter as a resource within the window, then use it in the Binding... it should look something like this:
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<local:BoolToBrushConverter x:Key="boolToBrushConverter" />
</Window.Resources>
<StackPanel Height="250">
<ToggleButton Name="toggleButton" Height="32" Content="Green" />
<TextBox
Background="{Binding ElementName=toggleButton, Path=IsChecked, Converter={StaticResource boolToBrushConverter}, ConverterParameter=Green:White}" />
</StackPanel>
</Window>
UPDATE: As per Ivan's excellent suggestion - have updated to show how you can pass parameters through to the Converter from the XAML...

Resources