I've three Classes in my project Master
, Person
and Command
. Master
has two properties, a constructor and overridden the ToString
:
class Master {
public string FirstName { get; set; }
public string LastName { get; set; }
public Master(string FirstName, string LastName) {
this.FirstName = FirstName;
this.LastName = LastName;
}
public override string ToString() {
return FirstName + " " + LastName;
}
}
Command
is an implementation of ICommand
class Command : ICommand {
Func<object, bool> CanDo { get; set; }
Action<object> Do { get; set; }
public event EventHandler CanExecuteChanged;
public Command(Func<object, bool> CanDo, Action<object> Do) {
this.CanDo = CanDo;
this.Do = Do;
CommandManager.RequerySuggested += (o, e) => Evaluate();
}
public bool CanExecute(object parameter) => CanDo(parameter);
public void Execute(object parameter) => Do(parameter);
public void Evaluate() => CanExecuteChanged?.Invoke(null, EventArgs.Empty);
}
and Person
has two properties, implemented INotifyPropertyChanged
, is an ObservableCollection<Master>
and using Command
:
class Person : ObservableCollection<Master>, INotifyPropertyChanged {
string firstName, lastName;
public string FirstName {
get => firstName;
set { firstName = value; OnPropertyChanged(); }
}
public string LastName {
get => lastName;
set { lastName = value; OnPropertyChanged(); }
}
public Command AddToList { get; set; }
public new event PropertyChangedEventHandler PropertyChanged;
public Person() {
AddToList = new Command(CanDo, Do);
}
void OnPropertyChanged([CallerMemberName] string name = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
bool CanDo(object para) => !string.IsNullOrEmpty(firstName) && !string.IsNullOrEmpty(lastName);
void Do(object para) {
Add(new Master(firstName, firstName));
FirstName = LastName = null;
}
}
On xaml I've these:
<Window ...>
<Window.Resources>
<local:Person x:Key="Person"/>
</Window.Resources>
<Grid DataContext="{StaticResource Person}">
<StackPanel>
<TextBox Text="{Binding FirstName}"/>
<TextBox Text="{Binding LastName}"/>
<Button Content="Click" Command="{Binding AddToList}"/>
<ListView ItemsSource="{Binding}"/>
</StackPanel>
</Grid>
</Window>
I've to click on the first TextBox
, bound to FirstName
, after launching the app to type something there, by pressing Tab I can type in the second TextBox
and if I then hit Tab again, it instead of focusing the Button
goes back to first TextBox
so I've to hit Tab twice to get to the Button
and by hitting Enter or Space I can add the item in the ListView
.
At this point I'm not sure what's focused, I've to hit Tab once more to get to the first TextBox
. After typing some more text in first as well as second TextBoxes
if I hit Tab , it instead of focusing Button
or first TextBox
selects the ListView
so I've to hit Tab thrice to get to the Button
!
I want to give first TextBox
focus when the app launches and after hitting Tab on second TextBox
I want it to go to the Button
and exclude ListView
from focus. I've tried setting Focusable="False"
, KeyboardNavigation.IsTabStop="False"
, IsTabStop="False"
in ListView
but those don't work! I also have tried setting TabIndex
on TextBoxes
and Button
like this:
<TextBox Text="{Binding FirstName}" TabIndex="1"/>
<TextBox Text="{Binding LastName}" TabIndex="2"/>
<Button Content="Click" Command="{Binding AddToList}" TabIndex="3"/>
These don't work either!
The problem you're having is that at the point you tab away from the second text box it needs to decide where to place the focus. However, at that point in time, the command is still disabled because it cannot be executed because the value from the text box has not arrived in the view model yet. This value arrives after the focus has been moved.
One way to fix this would be to change the binding of the second textbox so that the ViewModel is updated on every change to the value. Add the following clause to that binding:
UpdateSourceTrigger=PropertyChanged
More details here... https://docs.microsoft.com/en-us/dotnet/framework/wpf/data/how-to-control-when-the-textbox-text-updates-the-source
Now whenever you type a character the command will reconsider whether it can be executed.
The short answer:
Set UpdateSourceTrigger=PropertyChanged
on your binding:
<TextBox Text="{Binding FirstName, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox Text="{Binding LastName, UpdateSourceTrigger=PropertyChanged}"/>
I suspect what is happening is that WPF is evaluating the next tab position before the command CanExecute is evaluated, so when it is working out where to tab to next the button is still disabled, hence the only place it has to go is back to the first text box.
UpdateSourceTrigger tells WPF to evaluate the bindings on each keypress instead of when the focus changes, which means the button is correctly enabled by the time it needs to work the tab stops out.
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.