2013-09-23 3 views
4

Я принимаю шаблон MVVM в WPF и узнал об использовании Command. Но в моей реализации всегда вызывается делегат, назначенный мне для реализации CanExecute. Я имею в виду, если бы я поставил точку останова внутри функции делегата, это показывает, что эта функция продолжает получать вызовы. К моему пониманию (и естественному мышлению, но, конечно, я могу ошибаться), этот делегат вызван только тогда, когда я как-то уведомляю об изменении состояния, и тогда CommandManager (re) проверяет свойство CanExecute и модифицирует IsEnabled свойство элемента интерфейса.Обновлено название: Почему ICommand.CanExecute получает вызов все время, вместо того, чтобы работать как событие?

Вот моя реализация VB.NET, которую я получил из версии C#. Я заметил, что мне нужно внести некоторые изменения в портированный код, чтобы он мог компилироваться. Может ли быть основой C# и VB.NET отличается? Так может ли кто-нибудь предоставить мне оригинальную реализацию VB.NET или указать мне, что не так, или если я правильно понимаю поведение команды?

Вот моя версия VB.NET:

Public Class CommandBase 
    Implements ICommand 

    Public Property ExecuteDelegate() As Action(Of Object) 

    Public Property CanExecuteDelegate() As Predicate(Of Object) 

    Public Sub New() 
    End Sub 

    Public Sub New(execute As Action(Of Object)) 
     Me.New(execute, Nothing) 
    End Sub 

    Public Sub New(execute As Action(Of Object), canExecute As Predicate(Of Object)) 
     If execute Is Nothing Then 
      Throw New ArgumentNullException("execute") 
     End If 
     ExecuteDelegate = execute 
     CanExecuteDelegate = canExecute 
    End Sub 

    Public Function CanExecute(parameter As Object) As Boolean Implements ICommand.CanExecute 
     Return If(CanExecuteDelegate Is Nothing, True, CanExecuteDelegate(parameter)) 
    End Function 

    Public Custom Event CanExecuteChanged As EventHandler Implements ICommand.CanExecuteChanged 
     AddHandler(ByVal value As EventHandler) 

      If CanExecuteDelegate IsNot Nothing Then 
       AddHandler CommandManager.RequerySuggested, value 
      End If 

     End AddHandler 
     RemoveHandler(ByVal value As EventHandler) 
      If CanExecuteDelegate IsNot Nothing Then 
       RemoveHandler CommandManager.RequerySuggested, value 
      End If 
     End RemoveHandler 

     RaiseEvent(ByVal sender As Object, ByVal e As System.EventArgs) 
      CommandManager.InvalidateRequerySuggested() 
     End RaiseEvent 
    End Event 

    Public Sub Execute(parameter As Object) Implements ICommand.Execute 
     If ExecuteDelegate IsNot Nothing Then ExecuteDelegate.Invoke(parameter) 
    End Sub 

    Public Sub RaiseCanExecuteChanged() 
     CommandManager.InvalidateRequerySuggested() 
    End Sub 

End Class 

И как я создаю экземпляр объекта что-то вроде этого:

MyCommand = New CommandBase(AddressOf CommandExec, AddressOf CanExecuteExec) 

где CanExecuteExec, конечно, имеет подпись, как это:

Private Function CanExecuteExec(obj As Object) As Boolean 

Как я уже упоминал, CanExecuteExec все время звонит. Я думаю, что это неэффективно, представьте, что у меня есть сотни Command объектов, и большинство из них CanExecute не меняются большую часть времени.

UPDATE:

Кто-то говорит, что CanExecute действительно вызывается все время, в то время как другие говорят об обратном. Я не эксперт в этом, но я должен сказать, что второе мнение звучит более естественно и имеет больше смысла для меня. Хотя мне все еще нужно выяснить, верно ли это, почему WPF постоянно обнаруживает изменение, так что он продолжает проверять CanExecute

+1

Что вы хотите сказать? 'CanExecute' * does * вызывается все время - ваше понимание не совсем корректно, но вы на самом деле не задали вопрос здесь. –

+0

@DanPuzey прав, вы не понимаете правильно, делегаты can-execute всегда вызываются. –

+0

@tete: вы разместили свое обновление, но мой вопрос: когда вы думаете, что ваша команда 'CanExecute' должна« естественно »быть вызвана? Как вы это делаете логично - помните, что кто-то должен писать код, который будет функционировать для всех сценариев, для всех приложений, для всех команд, для всех возможных источников данных и сложности интерфейса? Не зная содержимого метода CanExecute, единственным вариантом является запрос * много *. –

ответ

7

В вашем CanExecuteDelegate у вас есть крючок на CommandManager.RequerySuggested.

Итак, всякий раз, когда вызывается CommandManager.RequerySuggested, ваш CanExecuteDelegate будет называться.

CommandManager.RequerySuggested событие возникает всякий раз, когда изменения в источник команд будут обнаружены с помощью команды менеджера, который находится в диапазоне от Keyboard.KeyUpEvent, Mouse.ClickEvent т.д.

Кроме того, CommandManager имеет статический метод - InvalidateRequerySuggested, который заставляет CommandManager поднять RequerySovedEvent. Таким образом, вы можете вызвать это, чтобы проверить свои команды слишком вручную.

Если вы хотите взять под контроль поднять CanExecute, вы можете использовать Delegate Command, предоставленный PRISM. CanExecute Делегат будет вызван только тогда, когда вы явно вызываете метод RaiseCanExecuteChanged(), выставленный командой делегирования.

Включения комментариев ответить

Breakpoint бьет каждый раз при повороте к VS, так как CommandManager RequerySuggested события вызывается на потерянном фокусе окна и активацию собственности изменилась окно. Вот почему вы замечаете, что точка останова нажимает время от времени, когда вы переходите на VS, поскольку фокус перемещается из окна WPF в Visual Studio.

+1

Спасибо за ваш вопрос. Наконец кто-то понял, о чем я спрашиваю. На самом деле, если вы посмотрите на мой код, я вызвал CommandManager.InvalidateRequerySposed в событии CanExecuteChanged. Это правильное место для вызова этой функции? Потому что, если я правильно помню, делегат CanExecute все равно вызывается все время. Но я проверю это завтра – tete

+0

И что касается использования Command from Prism, значит ли это, что использование должно быть более осторожным, потому что теперь полностью ответственность разработчика заключается в том, чтобы напрямую поддерживать состояние CanExecute? В настоящее время я все еще не могу убедить нашу команду использовать Призм. И я действительно не знаю, должен ли я ставить эту точку в Pro или Con :) – tete

+0

В вашей реализации вам не нужно вызывать 'CommandManager.InvalidateRequerySposed', так как CommandManager достаточно умен, чтобы обнаруживать условия, когда это нужно поднимать. Кроме того, ваша точка прерывания срабатывает каждый раз, поскольку 'CanExecute' выполняется даже при потерянном фокусе Windows. –

0

Может быть по неизвестной причине пользовательский интерфейс может обновляться (измерение, упорядочивание, а затем визуализация звонки). И если у вас есть точка останова, которая может выполнить метод, она будет повторяться. Другими словами, вы не можете пройти эту точку останова, каждый раз, когда вы будете делать F5, точка останова будет снова.

Чтобы исследовать, вы должны поместить операторы logging/output в свой метод выполнения и сколько раз и когда он будет вызван.

+1

События 'MouseOver' часто являются причиной такой проблемы круговой отладки. –

2

Когда вы настраиваете свою команду, нет надежного способа для среды выполнения, чтобы знать, на какие данные ваш CanExecute будет полагаться, чтобы принять решение. Итак, когда у вас есть команды, которые связаны с вашим пользовательским интерфейсом и зарегистрированы в CommandManager, поведение заключается в том, что команда CanExecute для всех команд переоценивается всякий раз, когда изменяется состояние вашего приложения. Способ WPF знает об этом, когда обновляется связанное свойство или происходит событие пользовательского интерфейса.

Обычно вы увидите CanExecute вызывается всякий раз, переплеты обновление или при наступлении определенных событий управления (например, когда выделен текст текстового поля, тем CanExecute из встроенных Cut и Copy команд будет меняться, и поэтому событие изюминкой вызывает переоценка, которая, как я полагаю, связана с событием MouseUp).

+0

Спасибо за ваш ответ. Итак, в основном, что вы говорите, WPF будет называть этого делегата всякий раз, когда какие-либо изменения происходят с ViewModel, даже если такое изменение может быть неактуальным для состояния самой команды? Я думаю, ваше описание действительно соответствует моему наблюдению. Но не могли бы вы дать мне ссылку на такое поведение, чтобы я мог копать, если бы я сделал свою ViewModel плохой, что WPF думает, что состояние продолжает меняться? – tete

+0

Как сказал Рохит в своем ответе, событие 'CommandManager.RequerySposed' будет управлять этим для большинства приложенных команд« RoutedCommands »или« CommandBinding ».Это единственный статический метод, который разделяется всеми командами, и поэтому да: это вызовет вызов вашего делегата, когда изменение состояния может быть неактуальным для конкретной команды. Но тогда у него не было бы никакого способа узнать, какое изменение состояния * имеет значение для вашей команды, поэтому ему пришлось бы запустить событие для многих возможных изменений. –

+0

Дэн, хороший момент. Я думаю, что в настоящее время я буду придерживаться решения по умолчанию, позволяющего CommandManager решить, следует ли повторять проверку CanExecute. Просто потому, что я не хочу иметь в виду, что я должен поднять событие CanExecuteChanged во всех возможных сценариях. Поэтому я буду использовать его, пока не найду его неэффективным. В конце концов, это не так глупо, как я думал, это было: как я упоминал в принятом ответе, только потому, что я прыгал между приложением и VS, что вызвало изменение LostFocus/MouseOver, чтобы делегат был повторно вызван. – tete