2

I have the Following Collection: ObservableCollection<CheckedFileObject> Files

A CheckedFileObject contains a FileObject and a bool isChecked. Now when a user selects a File the isChecked Value of this file gets changed to true.

If at least one CheckedFileObject contains IsChecked = true, i want to make the Delete Button Visible.

So my Question is, is it possible to toggle the visibility based on an item in an ObservableCollection and how? I doesn't matter how many items there are, but if one is checked the button should be visible.

Maybe something like:

 Visibility="{Binding Files[i].IsChecked, Mode=OneWay, Converter={StaticResource BooleanAndToVisibilityConverter}}"

Or perhaps binding the ObservablCollection to a bool in the viewmodel which gets updated through something like this:

var isVisible = Files.Any(x => x.IsChecked == true);

when the collection changes. Is this even possible?

3 Answers 3

2

you can get the feature of items tracking from ListCollectionView class and change Button.Visibility based on item count in that collection view:

public ObservableCollection<CheckedFileObject> Files { get; } = new ObservableCollection<CheckedFileObject>();

private ICollectionView _filteredFiles;
public ICollectionView FilteredFiles 
{
    get
    {
        if (_filteredFiles == null)
        {
            _filteredFiles = new ListCollectionView(Files)
            {
                IsLiveFiltering = true,
                LiveFilteringProperties = { nameof(CheckedFileObject.IsChecked) },
                Filter = (o) => o is CheckedFileObject file && file.IsChecked
            };
        }
        return _filteredFiles;
    }
}

Items are displayed from the original collection. Button is bound to the filtered collection:

<ItemsControl ItemsSource="{Binding Files}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <CheckBox IsChecked="{Binding Path=IsChecked}"/>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

<Button Content="Delete">
    <Button.Style>
        <Style TargetType="Button">
            <Style.Triggers>
                <DataTrigger Binding="{Binding Path=FilteredFiles.Count}" Value="0">
                    <Setter Property="Visibility" Value="Collapsed"/>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </Button.Style>
</Button>

CheckedFileObject class must have property change notifications (implement INotifyPropertyChanged):

public class CheckedFileObject : INotifyPropertyChanged
{
    private bool isChecked;

    public bool IsChecked 
    { 
        get => isChecked; 
        set 
        {
            isChecked = value; 
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsChecked)));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}
5
  • How should the CollectionView get notified when a property of an object in the ObservableCollection gets changed
    – KSler
    Commented Jul 20, 2022 at 13:00
  • @KSler, by subscribing to PropertyChanged event on items. you can configure that using LiveFilteringProperties , Filter and IsLiveFiltering = true. ListCollectionView does the rest, so you don't have subscribe/unsubscribe PropertChanged event on each item manually
    – ASh
    Commented Jul 20, 2022 at 13:02
  • AHH okey now igot it, and everyone in the office is like: HOW? hahah But instead of INotifyPropertyChanged i used BindableBase's SetProperty() function
    – KSler
    Commented Jul 20, 2022 at 13:15
  • @KSler, BindableBase implements INotifyPropertyChanged too
    – ASh
    Commented Jul 20, 2022 at 13:18
  • Yeah know that but it's shorter haha
    – KSler
    Commented Jul 20, 2022 at 13:19
1

You could try with a converter like the following:

public class CheckedFileObjectToVisibilityConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (value is ObservableCollection<CheckedFileObject> files)
        {
            if (files.Any(x => x.IsChecked == true))
                return Visibility.Visible;
        }
        return Visibility.Collapsed;
    }

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

Note that if you have this control repeated multiple times in your view, this code might need to be improved in terms of performance.

3
  • how that converter is supposed to trigger when user changes IsChecked property on any object inside collection? wpf doesn't provide such notifications
    – ASh
    Commented Jul 20, 2022 at 12:35
  • The converter should work yes, but likie @ASh mentioned above it doesn't get triggered
    – KSler
    Commented Jul 20, 2022 at 12:48
  • 1
    @KSler, try other solutions which don't have such roadblock
    – ASh
    Commented Jul 20, 2022 at 12:52
0
public class SomeVm
{
    public SomeVm()
    {
        Files = new ObservableCollection<CheckedFileObject>();
        Files.CollectionChanged += Files_CollectionChanged;

        StartCommand = new DelegateCommand(ExecuteStart, () => Files.Any(f => f.IsChecked));
    }

    private void Files_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        if (e.Action == NotifyCollectionChangedAction.Remove || e.Action == NotifyCollectionChangedAction.Replace)
        {
            foreach (INotifyPropertyChanged checkedFileObject in e.OldItems)
            {
                checkedFileObject.PropertyChanged -= FileObject_PropertyChanged;
            }
        }
        if (e.Action == NotifyCollectionChangedAction.Add || e.Action == NotifyCollectionChangedAction.Replace)
        {
            foreach (INotifyPropertyChanged checkedFileObject in e.NewItems)
            {
                checkedFileObject.PropertyChanged += FileObject_PropertyChanged;
            }
        }
    }

    private void FileObject_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == nameof(CheckedFileObject.IsChecked))
            StartCommand.RaiseCanExecuteChanged();
    }

    public ObservableCollection<CheckedFileObject> Files { get; }
    public DelegateCommand StartCommand { get; }

    private void ExecuteStart() { throw new NotImplementedException(); }
}

public class CheckedFileObject : ObservableObject
{
    public bool IsChecked { get; set; }
    public FileInfo FileInfo { get; }
}

You could also implement it with multiselection

Not the answer you're looking for? Browse other questions tagged or ask your own question.