简体   繁体   English

Button,Command和canExecute的奇怪行为

[英]Odd behavior with Button, Command and canExecute

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. 仅当Files集合不为null或其中包含某些项目时才启用它。 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. 该集合绑定到窗口中的ListView 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. 如果集合中有足够的项目可以显示ListViewScrollBar ,则我的按钮显示为Enabled ,这很好。 If I have no items, then it's Disabled , which is also good. 如果我没有任何物品,则为Disabled ,这也很好。 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 . 但是,如果我只有足够的项目来填充可见ListView ,而又不会触发ScrollBar的出现,那么我的按钮将显示为Disabled If I focus on any control, including the button itself, then it pops-up as Enabled . 如果我专注于任何控件,包括按钮本身,则将其弹出为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. 这是RelayCommand类。 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 : 这是我将集合绑定到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. 嗯,我不知道这是不是它或者它是怎么引起的(尤其是在整个ScrollBar情况下),但是当我不使用Task更新我的收藏集时,我不会遇到这种行为。 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. 考虑到我不想阻塞UI线程,因此不确定如何解决此问题。 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). CanExecute的状态更新中,只有一点点挂起的东西(在这种情况下)仅通过焦点更改来更新。

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). 每次我通过单击手动为UI元素赋予焦点时,“命令更新”(或出现)。 This also happens if ScrollBar appears or dissapears. 如果ScrollBar出现或消失,也会发生这种情况。 But then other UI elements don't do anything, such as text. 但是其他UI元素则什么也不做,例如文本。

Well, as I see it your command just doesn`t know that it is related to Files collection. 好吧,据我所知,您的命令只是不知道它与Files收集有关。 So Files is changed, it`s setter is called, your ListView is updated because it is bound to that collection and reacts to PropertyChanged. 因此, Files被更改,它的设置器被调用,您的ListView被更新,因为它绑定到该集合并对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. 仅当UI开始更改系统时,系统才会调用CanExecute并开始工作。 So I think the simple solution here can be to tie Files to command. 因此,我认为这里的简单解决方案可以是将Files绑定到命令。

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`sa bit of overkill. 注意 :我使用InvalidateRequerySuggested作为使命令InvalidateRequerySuggested最简单方法,但是有点过分。 Other ICommand implementations use different techniques to do that (for example Telerik`s DelegateCommand has custom InvalidateCanExecute method). 其他ICommand实现使用不同的技术来做到这一点(例如Telerik的DelegateCommand具有自定义的InvalidateCanExecute方法)。 So this part can be further improved. 因此,这部分可以进一步改进。

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. 但是,我的操作方式如下:在我的viewmodel类中,我将拥有一个公共属性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事件将评估IsPrintAllowed属性。

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. 在xaml中,我会将按钮的IsEnabled属性绑定到IsPrintAllowed。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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