5

I have an ObservableCollection bound to a list box and a boolean property bound to a button. I then defined two converters, one that operates on the collection and the other operates on the boolean property. Whenever I modify the boolean property, the converter's Convert method is called, where as the same is not called if I modify the observable collection. What am I missing??

Snippets for your reference,

xaml snipet,

<Window.Resources>
    <local:WrapPanelWidthConverter x:Key="WrapPanelWidthConverter" />
    <local:StateToColorConverter x:Key="StateToColorConverter" />
</Window.Resources>
<StackPanel>
    <ListBox x:Name="NamesListBox" ItemsSource="{Binding Path=Names}">
        <ListBox.ItemsPanel>
            <ItemsPanelTemplate>
                <WrapPanel x:Name="ItemWrapPanel" Width="500" Background="Gray">
                    <WrapPanel.RenderTransform>
                        <TranslateTransform x:Name="WrapPanelTranslatation" X="0" />
                    </WrapPanel.RenderTransform>
                    <WrapPanel.Triggers>
                        <EventTrigger RoutedEvent="WrapPanel.Loaded">
                            <BeginStoryboard>
                                <Storyboard>
                                    <DoubleAnimation Storyboard.TargetName="WrapPanelTranslatation" Storyboard.TargetProperty="X" To="{Binding Path=Names,Converter={StaticResource WrapPanelWidthConverter}}" From="525"  Duration="0:0:2" RepeatBehavior="100" />
                                </Storyboard>
                            </BeginStoryboard>
                        </EventTrigger>
                    </WrapPanel.Triggers>
                </WrapPanel>
            </ItemsPanelTemplate>
        </ListBox.ItemsPanel>
        <ListBox.ItemTemplate>
            <DataTemplate>
                <Grid>
                    <Label Content="{Binding}" Width="50" Background="LightGray" />
                </Grid>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
    <Button Content="{Binding Path=State}" Background="{Binding Path=State, Converter={StaticResource StateToColorConverter}}" Width="100" Height="100" Click="Button_Click" />
</StackPanel>   

code behind snippet

public class WrapPanelWidthConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        ObservableCollection<string> aNames = value as ObservableCollection<string>;
        return -(aNames.Count * 50);
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}


public class StateToColorConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        bool aState = (bool)value;
        if (aState)
            return Brushes.Green;
        else
            return Brushes.Red;
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}   
sudarsanyes
  • 3,156
  • 8
  • 42
  • 52

3 Answers3

17

A multibinding converter can be used to overcome this issue. You can then bind to the Collection.Count property and the collection at the same time. The count will trigger the binding to get re-evaluated and then you use the second binding to actually transform the values as required

<TextBlock IsHitTestVisible="false"
    Margin="5,0"
    TextTrimming="CharacterEllipsis"
    VerticalAlignment="Center"
    DockPanel.Dock="Left" >
    <TextBlock.Text>
        <MultiBinding Converter="{Resources:ListToStringConverter}">
            <Binding Path="List.Count" />
            <Binding Path="List" />
        </MultiBinding>
    </TextBlock.Text>
</TextBlock>
riQQ
  • 9,878
  • 7
  • 49
  • 66
Zorro
  • 171
  • 1
  • 3
  • Thank you for this idea! While the above answers did not work at all for me, this one does. I need to modify my converter a bit to implement IMultiValueConverter instead of IValueConverter, and I need to make clear at which index my collection will be, but then it's all fine. – ygoe Jul 30 '12 at 08:46
13

I think the converter in a Binding is always called if the Binding source has been updated and notifies about that update (as a DependencyProperty or using INotifyPropertyChanged). However, an ObservableCollection does not raise the PropertyChanged event if an item has been added or removed, but it raises the CollectionChanged event. It does not raise any event at all if an item in the collection is changed. Even if the item itself raises PropertyChanged, this will not update the Binding on the collection since the Binding source is not the item, but the collection.

I fear your approach will not work this way. You could bind directly to ObservableCollection.Count and add an appropriate math converter to it to perform the inversion and multiplication, but the Count property does not perform change notification, so this no option. I think you will have to provide another property in your ViewModel or code-behind which handles these cases...

gehho
  • 9,049
  • 3
  • 45
  • 59
  • Whenever an item is added to the collection, the ObservableCollection member is raising CollectionModified event. Where as the Converters are triggered only when the Property is modified. As a workaround, one can subscribe for the collection modified event and then raise PropertyChanged event with the ObservableCollection's property name. – sudarsanyes May 12 '10 at 07:53
  • Just now came across your reply and I too inferred the same. – sudarsanyes May 12 '10 at 07:55
  • Your workaround may work, but you should be aware that this workaround will update the complete binding, i.e. the collection is read anew and all the items in your collection are created once again. At least, that is what I would expect. I would rather recommend binding to a custom `Count` property, but it depends on your scenario whether it is worth the overhead. – gehho May 12 '10 at 09:15
2

A converter is called when the binding happens or the property changes. So your converter is called for your boolean whenever the value of the boolean changes. Your collection is set once and that is when the binding occurs and the converter is used. When the internals of the collection change (the collection is added to or removed from) the property does not change (i.e. you are not binding a new collection) so your converter does not fire again.

Use a view model and wrap your collection and add another property such as count that implements change notification. You can use this wrapper class from here which will wrap your collection and it will be easy to add a property there.

Aran Mulholland
  • 23,555
  • 29
  • 141
  • 228