简体   繁体   中英

How to set tab order?

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.

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