4

I'm binding an ObservableCollection to a control which has a converter to change its visibility depending on if the collection has any values or not:

Simplified example:

XAML:

<Window.Resources>
    <local:MyConverter x:Key="converter"/>
</Window.Resources>

<Grid x:Name="grid">
    <Rectangle Height="100" Width="200" Fill="CornflowerBlue"
                Visibility="{Binding Converter={StaticResource converter}}"/>
    <Button Content="click" 
            HorizontalAlignment="Left" VerticalAlignment="Top" 
            Click="Button_Click"/>
</Grid>

C#:

ObservableCollection<string> strings;

public MainWindow()
{
    InitializeComponent();

    strings = new ObservableCollection<string>();
    grid.DataContext = strings;
}

private void Button_Click(object sender, RoutedEventArgs e)
{
    strings.Add("new value");
}

When the collection is bound, the Rectangle is visible when there are values and not when the collection is empty. However, if the collection is empty and I add a value at runtime, the Rectangle does not appear (the converter's Convert method isn't even fired). Am I missing something or just trying to ask too much of IValueConverter?

CatBusStop
  • 3,347
  • 7
  • 42
  • 54
  • So... binding to ObservableCollection.Count makes the converter work as expected so I'm guessing that adding a value to the collection doesn't fire a PropertyChanged event... A temporary solution I guess would be to multibind the collection and it's .Count method, but this sounds nasty... – CatBusStop Jun 27 '11 at 13:46

3 Answers3

3

OK, so here's how I got around the problem using a MultiValueConverter

The converter now looks like:

public object Convert(
    object[] values, 
    Type targetType, 
    object parameter, 
    System.Globalization.CultureInfo culture)
{
    ObservableCollection<string> strings = 
        values[0] as ObservableCollection<string>;

    if (strings == null || !strings.Any())
        return Visibility.Collapsed;
    else
        return Visibility.Visible;
}

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

And the XAML now looks like:

<Rectangle Height="100" Width="200" Fill="CornflowerBlue">
    <Rectangle.Visibility>
        <MultiBinding Converter="{StaticResource converter}">
            <Binding Path="."/>
            <Binding Path="Count"/>
        </MultiBinding>
    </Rectangle.Visibility>
</Rectangle>

The C# remains the same :)

Luis Filipe
  • 8,488
  • 7
  • 48
  • 76
CatBusStop
  • 3,347
  • 7
  • 42
  • 54
  • 6
    The reason this works is that "Count" property is changed and it triggers the Converter. The "Multi" converter is useless in this case, the same thing you obtain using `` where CountTovisibilityConverter implements IValueConverter and you just have to compare the value (type int) to 0 to return visibility. again, best regards – daniell Jun 28 '11 at 08:22
  • 1
    Following up on what daniell said, this won't work if you use ObservableCollection.SetItem, which does not update the count. – hypehuman Aug 30 '16 at 19:12
  • 1
    The what to visibility converter @daniell!? – Matt Searles Dec 15 '16 at 03:28
2

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...

best regards,

daniell
  • 222
  • 4
  • 12
  • 3
    Yes, I've already read the post you appear to have plagiarised this from (http://stackoverflow.com/questions/2816163/when-will-the-valueconverters-convert-method-be-called-in-wpf). The MultiValueConverter does the job in any case – CatBusStop Jun 27 '11 at 15:48
  • "plagiarised" ?! happy you find your solution .. best regards – daniell Jun 28 '11 at 07:58
0

You must set the DataContext after creating the collection; it is probable that you initialize the "strings"collection to "null", you set the DataContext in the constructor to that value(e.g. null), then you actually create the collection--this way, the DataContext remains null.

You must set the DataContext again after creating the collection.

Andy
  • 3,631
  • 2
  • 23
  • 32
  • Setting the DataContext after creating the collection only means it works once, it still doesn't change if you add values at run time. I've got a solution using MultiValueConverters that I'll post up in a minute :) – CatBusStop Jun 27 '11 at 14:04