我正在做一個計算器。我的觀點包含 16 Buttons 和 1 TextBox:

TextBox的Text屬性系結到 中的UserInput屬性ViewModel:
<TextBox x:Name="InputTextBox"
Grid.Row="0"
Background="#ECDBBA"
Foreground="#191919"
FontSize="30"
Text="{Binding UserInput, UpdateSourceTrigger=PropertyChanged}"
IsReadOnly="True"
VerticalContentAlignment="Center"/>
public class ViewModel : INotifyPropertyChanged
{
public AddTextCommand AddTextCmnd { get; set; }
public ClearTextCommand ClearTextCmnd { get; set; }
public ShowResultCommand ShowResultCmnd { get; set; }
private string _userInput = "0";
public string UserInput
{
get { return _userInput; }
set
{
if (UserInput != value)
{
_userInput = value;
OnPropertyChanged("UserInput");
}
}
}
public ViewModel()
{
AddTextCmnd = new AddTextCommand(this);
ClearTextCmnd = new ClearTextCommand(this);
ShowResultCmnd = new ShowResultCommand(this);
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
我有 3 個命令:一個用于添加文本TextBox,另一個用于顯示結果,最后一個用于清除TextBox. AC和“=”按鈕(見截圖)系結到ClearTextCommand和ShowResultCommand。
public class ShowResultCommand : ICommand
{
private ViewModel _viewModel;
public ShowResultCommand(ViewModel viewModel)
{
_viewModel = viewModel;
}
public bool CanExecute(object parameter)
{
if (_viewModel.UserInput != "0")
return true;
else
return false;
}
public void Execute(object parameter)
{
DataTable calculator = new DataTable();
_viewModel.UserInput = calculator.Compute(_viewModel.UserInput, null).ToString();
}
public event EventHandler CanExecuteChanged
{
add
{
CommandManager.RequerySuggested = value;
}
remove
{
CommandManager.RequerySuggested -= value;
}
}
}
}
public class ClearTextCommand : ICommand
{
private ViewModel _viewModel;
public ClearTextCommand(ViewModel viewModel)
{
_viewModel = viewModel;
}
public bool CanExecute(object parameter)
{
if (_viewModel.UserInput != "0")
return true;
else
return false;
}
public void Execute(object parameter)
{
_viewModel.UserInput = "0";
}
public event EventHandler CanExecuteChanged
{
add
{
CommandManager.RequerySuggested = value;
}
remove
{
CommandManager.RequerySuggested -= value;
}
}
}
該程式運行良好,但 2 個命令共享一個問題。我不知道這是否是我的無知,但我在谷歌中沒有找到任何東西。我認為我的CanExecute方法只有在UserInput更改(屬性更改)時才有效,但是我在CanExecute命令的方法上設定了一個斷點,并看到它們在除錯中永遠有效,就像曾經UserInput改變過一樣,ShowResultCommand而ClearTextCommand's 的CanExecute方法永遠有效,它們被再次呼叫并再次回圈。我想知道他們是否應該這樣作業?不應該在UserInput更改時呼叫它們嗎?這不會導致我的程式出現任何錯誤,但我認為我的命令有問題。
所以基本上問題是:
應該CanExecute在我的應用程式運行時回圈作業,還是應該在與方法相關的屬性發生更改時作業?如果它應該回圈作業,那么一切都很好,但如果不是,我的命令有什么問題?
uj5u.com熱心網友回復:
我認為我的
CanExecute方法僅在 UserInput 更改(屬性更改)時才有效。
是和不是。您的命令將CanExecuteChanged事件委托RequerySuggested給CommandManager. 這是一種常見的方法。這CommandManager是一個 WPF 框架型別,負責:
提供與命令相關的實用方法,為類所有者和命令注冊
CommandBinding和InputBinding物件,添加和洗掉命令事件處理程式,并提供查詢命令狀態的服務。
該RequerySuggested事件僅在一些沒有充分記錄的情況下引發:
在確定命令目標何時發生變化時,
CommandManager僅注意某些條件,例如鍵盤焦點的變化。
如您所見,這是非常模糊的,并且在某些情況下CommandManager根本無法知道:
在
CommandManager無法充分確定導致命令無法執行的條件變化的情況下,InvalidateRequerySuggested可以呼叫以強制CommandManager引發RequerySuggested事件。
總而言之,是的,RequerySuggested當用戶輸入發生變化時以及其他與輸入相關的事件(例如,在 aTextBox或系結屬性中發生變化)時會引發該事件,但并非在所有情況下都會引發該事件。從不同的角度來看,它CommandManager決定何時只需要在一般觸發器上引發事件,因此它通常會使所有可以執行的狀態無效,盡管大多數甚至沒有命令可能會受到影響。它不是針對特定情況的,例如您想要觀察變化的獨特屬性,而是像水壺。這當然會對性能產生影響,盡管在大多數應用程式中可以忽略不計。
我在命令的方法上設定了斷點,
CanExecute并看到它們在除錯中永遠有效
是的,現在你知道為什么了。在除錯模式下,當斷點被命中時,除錯器或 IDE 將進入前臺,這意味著鍵盤焦點發生了變化。當您切換回正在除錯的應用程式時,鍵盤焦點會再次更改……一次又一次……一次又一次。由于在鍵盤焦點上CommandManager引發事件,您將不斷觸發并命中斷點。視窗激活也可能發生同樣的情況。RequerySuggestedCanExecute
不同的指揮方式
還有另一種非常常見的方法來通知可以執行更改。在您的示例中,您依靠CommandManager為所有命令盡力而為。但是,您也可以自己承擔責任,并且可以通過另一個公共方法執行顯式無效。
public class RelayCommand<T> : ICommand
{
private readonly Predicate<T> _canExecute;
private readonly Action<T> _execute;
public RelayCommand(Action<T> execute) : this(execute, null)
{
_execute = execute;
}
public RelayCommand(Action<T> execute, Predicate<T> canExecute)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExecute == null || _canExecute((T)parameter);
}
public void Execute(object parameter)
{
_execute((T)parameter);
}
public event EventHandler CanExecuteChanged;
public void RaiseCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
}
在這里,您假設您確切知道何時更新您自己的命令。與其問“親愛的命令管理員,請告訴我何時更新”,不如說“立即更新這些特定命令”。為此,您必須呼叫屬性中RaiseCanExecuteChanged的每個受影響的命令。
public string UserInput
{
get { return _userInput; }
set
{
if (UserInput != value)
{
_userInput = value;
OnPropertyChanged("UserInput");
AddTextCmnd.RaiseCanExecuteChanged();
ClearTextCmnd.RaiseCanExecuteChanged();
ShowResultCmnd.RaiseCanExecuteChanged();
}
}
}
RequerySuggested與事件相比,這種機制有一些優勢。
- 您可以準確決定何時更新命令。
- 只有真正受影響的命令才會更新。
- 理解視圖模型中的依賴關系要容易得多。
- 減少不必要的更新帶來的潛在性能優勢。
- 不依賴外部組件和隱藏的魔法或 vage 假設。
關于 DRY 和可重用性的提示
截至目前,您所有的命令都包含重復的邏輯,違反了不要重復自己的原則,簡稱 DRY。這使得維護更加困難。相反,您至少應該提取一個通用的、可重用的命令型別,如上所示,以改進您的代碼。您甚至不必自己實作,已經有大量可用的框架、庫和 NuGet 包,例如Microsoft.Toolkit.Mvvm,請在此處查看其檔案。
以下是明文命令的示例:
public ViewModel()
{
ClearTextCmnd = new RelayCommand(ExecuteClearText, CanExecuteClearText);
// ...other commands.
}
public ICommand ClearTextCmnd { get; set; }
public bool CanExecuteClearText()
{
return UserInput != "0";
}
public void ExecuteClearText()
{
UserInput = "0";
}
為簡單起見,我使用了RelayCommand沒有額外引數的實作,因為您目前不使用它。它與上面的命令相同,只是沒有每個方法的引數。
正如評論中所問的,這將是RelayCommand沒有引數的實作。
public class RelayCommand : ICommand
{
private readonly Func<bool> _canExecute;
private readonly Action _execute;
public RelayCommand(Action execute) : this(execute, null)
{
_execute = execute;
}
public RelayCommand(Action execute, Func<bool> canExecute)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExecute == null || _canExecute();
}
public void Execute(object parameter)
{
_execute();
}
public event EventHandler CanExecuteChanged;
public void RaiseCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/504358.html
