Question

I have a simple button:

<Button Content="Print" Command="{Binding PrintCommand}"/>

...with a command:

private RelayCommand _printCommand;
public ICommand PrintCommand
{
    get
    {
        if (_printCommand == null)
        {
            _printCommand = new RelayCommand(param => Print(),
                                             () => (Files != null && Files.Count > 0));
        }

        return _printCommand;
    }
}

It is enabled only when the Files collection is either not null or has some items in it. Here's the collection:

private ObservableCollection<RecordModel> _files;
public ObservableCollection<RecordModel> Files
{
    get { return _files; }
    set
    {
        if (_files == value)
        {
            return;
        }

        _files = value;
        OnPropertyChanged("Files");
    }
}

The collection is bound to a ListView in the window. Thus far, nothing special... and here's where the odd behavior comes in...

If I have enough items in the collection to have ListView's ScrollBar to show, then my button is shown as Enabled, which is good. If I have no items, then it's Disabled, which is also good. However, if I have just enough items to fill a portion of the visible ListView, without triggering the appearance of the ScrollBar, then my button shows as Disabled. If I focus on any control, including the button itself, then it pops-up as Enabled. I've no idea what's going on. At first, I thought it might have been a button template I was using, so I got rid of it and kept the button with the default setup, yet the odd behavior remained.

Any ideas what's going on?

Here's the RelayCommand class. I'm not sure if a problem could be within it, but that's what I've been using for a while:

public class RelayCommand : ICommand
{
    readonly Action<object> _execute; 
    readonly Func<bool> _canExecute; 

    public RelayCommand(Action<object> execute) : this(execute, null) { } 
    public RelayCommand(Action<object> execute, Func<bool> canExecute) 
    {
        if (execute == null)
        {
            throw new ArgumentNullException("execute");
        }
        _execute = execute; 
        _canExecute = canExecute; 
    } 

    public bool CanExecute(object parameter) 
    { 
        return _canExecute == null ? true : _canExecute(); 
    } 

    public event EventHandler CanExecuteChanged 
    { 
        add 
        { 
            CommandManager.RequerySuggested += value; 
        } 
        remove 
        { 
            CommandManager.RequerySuggested -= value; 
        } 
    } 

    public void Execute(object parameter) 
    { 
        _execute(parameter); 
    } 
}

EDIT:

Here's how I populate my collection.

public FileManagerViewModel()
{
    LoadCollection();
}

private void LoadCollection()
{
    Task task = new Task(() =>
    {
        Files = DbWorker.GetFiles();
    });
    task.Start();
}

Here's how I bind the collection to the ListView:

<Window.DataContext>
    <vm:FileManagerViewModel/>
</Window.DataContext>

<Window.Resources>
    <CollectionViewSource Source="{Binding Files}" x:Key="GroupedFiles">
        <CollectionViewSource.GroupDescriptions>
            <PropertyGroupDescription PropertyName="RepNum"/>
        </CollectionViewSource.GroupDescriptions>
    </CollectionViewSource>
</Window.Resources>

<ListView ItemsSource="{Binding Source={StaticResource GroupedFiles}}">
    ...
</ListView

EDIT:

Hmm, I don't know if this is it or how it could be causing it (especially with the whole ScrollBar situation), but when I don't use the Task to update my collection, I don't experience this behavior. Of course, I then have to deal with hanging due to a lengthy operation. Not sure how I can remedy this, considering I don't want to block the UI thread. I even tried this and it didn't change anything:

var temp = new ObservableCollection<RecordModel>();
Task task = new Task(() =>
{
    temp = DbWorker.GetFiles();
});
task.ContinueWith((result) =>
{
    Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal,
        new Action(() =>
        {
            Files = temp;
        }));
});
task.Start();

But I don't see an issue with properties not being updated. I've checked and all the properties update as needed. It's just something with that little hang in the status update of CanExecute that only updates by focus change (in this scenario).

Just based on what I know, it seems as if there's an issue between threads and Commands... hmm. Every time I manually give UI element a focus by clicking on, Commands update (or so it appears). This also happens if ScrollBar appears or dissapears. But then other UI elements don't do anything, such as text.

Was it helpful?

Solution

Well, as I see it your command just doesn`t know that it is related to Files collection. So Files is changed, it`s setter is called, your ListView is updated because it is bound to that collection and reacts to PropertyChanged. But command is not tied directly to it so it just sits there undisturbed, drinking coffee or whatever. Only when UI starts changing the system calls CanExecute and it starts working. So I think the simple solution here can be to tie Files to command.

You can do it directly it the setter (because you know that command is dependent on collection):

public ObservableCollection<RecordModel> Files
{
    get { return _files; }
    set
    {
        if (_files == value) return;

        _files = value;
        OnPropertyChanged("Files");
        CommandManager.InvalidateRequerySuggested();
    }
}

Or you can do it right when you load the collection (for example if you know that`s the only operation that affects collection size):

task.ContinueWith((result) =>
{
    Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal,
        new Action(() =>
        {
            Files = temp;
            CommandManager.InvalidateRequerySuggested();
        }));
});


Note: I used InvalidateRequerySuggested as simplest way to invalidate command, but it`s a bit of overkill. Other ICommand implementations use different techniques to do that (for example Telerik`s DelegateCommand has custom InvalidateCanExecute method). So this part can be further improved.

OTHER TIPS

I will not be able to comment on why this design is not working because it is not clear from the code provided how your command is handling the enabling of the button. But the way I would do this is the following: In my viewmodel class I would have a public property IsPrintAllowed.

private bool _isPrintAllowed;

public bool IsPrintAllowed{
get{ return _isPrintAllowed;}
set{_isPrintAllowed = value;
RaisePropertyChanged(() => IsPrintAllowed)}

The CollectionChanged event of the Files collection would evaluate IsPrintAllowed Property.

Files.CollectionChanged += EvaluateIsPrintAllowed;


private void EvaluateIsPrintAllowed()
{
     IsPrintAllowed = Files != null && Files.Count > 0;
}

And in the xaml I would bind the IsEnabled property of the button to IsPrintAllowed.

<Button Content="Print" Command="{Binding PrintCommand}" IsEnabled = {Binding IsPrintAllowed}/>
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top