简体   繁体   中英

WPF : Bound IsEnabled MenuItem with ObservableCollection<T> Items.Any()

I have an ObservableCollection<T> in a class and i want to bind the IsEnabled property of a MenuItem Element with ObservableCollection<T>

Items.Any()

XAML =>

<MenuItem x:Name="MyMenu" 
        IsEnabled="{Binding ????}" 
        Header="MENU">
</MenuItem>

C# =>

public class MyClass
{
    public static MyClass Current = new MyClass();

    public ObservableCollection<Object> Items { get; } = new ObservableCollection<Object>();

}

Idiomatic Solution: Commands

The idiomatic way to do this would be to bind your menu item to a command and have the command's CanExecute handler check whether your Items collection is empty. The MenuItem will automatically be disabled if CanExecute returns false .

public class MyViewModel : INotifyPropertyChanged
{
    public ObservableCollection<Object> Items { get; }

    public DelegateCommand MenuItemCommand { get; }

    public MyViewModel()
    {
        Items = new ObservableCollection<object>();

        MenuItemCommand = new DelegateCommand(
            () => { /* Do Something */ },
            () => Items.Count > 0);

        Items.CollectionChanged += (s, e) => MenuItemCommand.RaiseCanExecuteChanged();
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged(string propertyName)
    {
        this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

As per the MVVM pattern, you should set the DataContext of your screen (eg, your UserControl , Page , or Window ) to an instance of the view model.

To hook up your menu item to the command, modify your Xaml as follows:

<MenuItem Command="{Binding MenuItemCommand}"
          Header="MENU" />

There are countless examples of DelegateCommand implementations out there (sometimes called RelayCommand ), but I'll include one here for completeness:

public class DelegateCommand : ICommand
{
    private readonly Action _execute;
    private readonly Func<bool> _canExecute;

    public DelegateCommand(Action execute, Func<bool> canExecute = null)
    {
        _execute = execute ?? throw new ArgumentNullException(nameof(execute));
        _canExecute = canExecute;
    }

    public virtual bool CanExecute() => _canExecute?.Invoke() ?? true;

    public virtual void Execute() => _execute();

    public void RaiseCanExecuteChanged()
    {
        OnCanExecuteChanged();
    }

    public event EventHandler CanExecuteChanged;

    protected virtual void OnCanExecuteChanged()
    {
        this.CanExecuteChanged?.Invoke(this, EventArgs.Empty);
    }

    bool ICommand.CanExecute(object parameter)
    {
        return this.CanExecute();
    }

    void ICommand.Execute(object parameter)
    {
        this.Execute();
    }
}

Alternate Solution: Binding IsEnabled

If you insist on not using commands, you could do the following:

public class MyViewModel : INotifyPropertyChanged
{
    public ObservableCollection<Object> Items { get; }

    public bool HasItems => Items.Count > 0;

    public MyViewModel()
    {
        Items = new ObservableCollection<object>();

        Items.CollectionChanged += (s, e) => OnPropertyChanged(nameof(HasItems));
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged(string propertyName)
    {
        this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

And update your Xaml as follows:

<MenuItem IsEnabled="{Binding HasItems}"
          Header="MENU" />

You could use a Style and and bind to the Count property directly:

<MenuItem x:Name="MyMenu" Header="MENU">
    <MenuItem.Style>
        <Style TargetType="MenuItem">
            <Style.Triggers>
                <DataTrigger Binding="{Binding Items.Count}" Value="0">
                    <Setter Property="IsEnabled" Value="False" />
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </MenuItem.Style>
</MenuItem>

The ObservableCollection<T> raises a change notification each time the Count property changes.

You can bind to ObservableCollection<T>.Count property and create a value converter which converts 0 to false and non-zero to true:

public class HasItemsConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return (value as int?) > 0;
    }

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

Then add your converter to resources and use in binding:

<MenuItem x:Name="MyMenu" 
        IsEnabled="{Binding Items.Count, Converter={StaticResource HasItemsConverter}}" 
        Header="MENU">
</MenuItem>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM