4
votes

I have a Xamarin.Forms ListView with a custom ViewCell that contains a StackLayout with an Entry and Label. I'd like the Entry to get focus any time that the ViewCell is selected.

ItemSelected provides the item that is selected, but not the ViewCell. I can create a custom class for the ViewCell, but I don't see how it can know when it is selected.

How can I focus the Entry upon ListView item selection?

2
How about you to ignore the selection and handle a TapGesture on the stacklayout, redirecting the action to focus your entry?Diego Rafael Souza
The selection may not result from a tap. For example, when a new item is added, it is selected automatically, and its Entry should be focused automatically.Edward Brey
Could you get the index of the item and then grab the ViewCell manually based on the index?GBreen12

2 Answers

3
votes

Move the Entry into the view model, and bind the Entry to a ContentView in the ViewCell. In the ListView.ItemSelected event handler, SelectedItem now contains the Entry, on which you can call Focus.

The XAML looks like this: (In this example, the Entry holds a quantity.)

<ViewCell>
    <StackLayout Orientation="Horizontal">
        <ContentView Content="{Binding QuantityView}" />
        <Label ... />
    </StackLayout>
</ViewCell>

The view model looks like this:

public class MyViewModel {
    public string Quantity {get; set;}
    public Entry QuantityView { get; } = new Entry {...};

    public MyViewModel() {
        QuantityView.SetBinding(Entry.TextProperty, nameof(Quantity));
    }
}

The ItemSelected event handler looks like this:

void FocusQuantity(object sender, SelectedItemChangedEventArgs e) {
    if (e.SelectedItem == null) return;
    ((MyViewModel)e.SelectedItem).QuantityView.Focus();
}
0
votes

The way I did this was create a class - Viewholder - that held the 2 views you want. Then use that for a ListView ItemSource, and focused on the SelectedItem Property Change.

Viewholder:

public class Viewholder 
{
    public Viewholder (){
    }

    public void OnFocus(){
        Entry.Focus();
    }

    private Entry _entry ;   
    public Entry Entry{
        get { return _entry;}
        set{
            if (value != null && 
            _entry!= value){
            _entry= value;
            OnPropertyChanged();
        }
    }

    private Label _label    
    public Label Label{
        get { return _label;}
        set{
            if (value != null && 
            _label!= value) {
            _label= value;
            OnPropertyChanged();
        }
    }   

    public event PropertyChangedEventHandler PropertyChanged;
    public void OnPropertyChanged([CallerMemberName] string propertyName = null){
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

MainPage (ListView Parent):

public class MainPage : ContentPage
{
    public MainPage () {
        SetUpControls();
    }

    private ObservableCollection<ViewHolder> _viewHolders;
    public ObservableCollection<ViewHolder> ViewHolders
    {
        get { return _viewHolders;}
        set{
            if (value != null && 
            _viewHolders!= value){
            _viewHolders= value;
            OnPropertyChanged();
        }
    }

    private ViewHolder _selectedViewHolder;
    public ViewHolder SelectedViewHolder{
        get { return _selectedViewHolder;}
        set{
            if (value != null && 
                _selectedViewHolder != value){
                    _selectedViewHolder= value;
                    OnPropertyChanged();
                    _selectedViewHolder.OnFocus();
        }
    }

    private void SetUpControls(List<string> l){
        foreach(var s in l){
            ViewHolder v = new ViewHolder{
                Label = new Label{
                    Text = s,
                };    
                Entry = new Entry();
            };

            ViewHolders.Add(v);
        }

        if(l.Count > 0){
            SelectedViewHolder = ViewHolders.ElementAt(0);
        }
    }    
}

XAML:

<ListView ItemsSource="{Binding ViewHolders, Mode=TwoWay}" 
          SelectedItem="{Binding SelectedViewHolder, Mode=TwoWay}">
    <ListView.ItemTemplate>
        <DataTemplate>
            <ViewCell>
                <ContentView Content="{Binding Label, Mode="TwoWay"}/>
                <ContentView Content="{Binding Entry, Mode="TwoWay"}/>
            </ViewCell>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

This isn't the nicest option, but it worked for me.

Also, if you add in OnSelection event which is triggered by focusing directly on the Entry, you can set the SelectedItem as the ViewHolder containing the Entry. But it's pretty ugly code (and that's saying something when you see what I've done above!) Involved looking for the control parent (and parents parents) until the parent was the listview (making the control a viewcell), and setting focus to it.