Home > other >  WPF change visibility based on Items in Observable-Collection
WPF change visibility based on Items in Observable-Collection

Time:07-20

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

CodePudding user response:

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;
}

CodePudding user response:

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.

CodePudding user response:

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

  • Related