如何知道在 ListView 中修改了哪个 Xamarin Forms 条目?

How to know which Xamarin Forms Entry has been modified in a ListView?

我正在 Xamarin.Forms 的一个 PCL 项目中工作。

我有一个 page/screen,其中有一个 ListView 控件。我为 ViewCell 创建了自定义 DataTemplate。

这个 ViewCell 有不同的控件:一些 Labels,一个 Button 和一个 Entry。

<ListView x:Name="lvProducts" >
  <ListView.ItemTemplate>
    <DataTemplate>
      <ViewCell>
        <StackLayout BackgroundColor="#FFFFFF" Orientation="Vertical">
          <Grid Padding="5">
            ...
            <Button Grid.Row="0" Grid.Column="1" Text="X" 
                    CommandParameter="{Binding MarkReference}"
                    Clicked="DeleteItemClicked" />
            ...
            <StackLayout Grid.Row="2" Grid.ColumnSpan="2" Orientation="Horizontal" >
              <Label Text="Ref.: " FontSize="24" FontAttributes="Bold" TextColor="#000000" />
              <Label Text="{Binding Reference}" FontSize="24" TextColor="#000000" />
            </StackLayout>
            ...
            <Entry Grid.Row="3" Grid.Column="1" Text="{Binding NumElements}"
                   Keyboard="Numeric" Placeholder="" FontSize="24"
                   HorizontalTextAlignment="Center"  Focused="OnItemFocus"    
                   Unfocused="OnItemUnfocus" />
         </Grid>
       </StackLayout>   
      </ViewCell>
    </DataTemplate>
  </ListView.ItemTemplate>
</ListView>

我想用这个 Entry 控件实现我无法实现的两件事:

首先,当我添加一个新项目时,我希望这个新项目有他的条目焦点,准备开始输入。

二、当用户结束向Entry写入一个值时,我想改变后面的值。我想知道 ListView 的哪个 Entry 被修改了。我尝试使用 Unfocused 事件,但是在启动的方法的参数中只有一个发送器参数,returns Entry 对象,没有关于已绑定模型的引用。

    public void OnItemUnfocus(object sender, EventArgs e)
    {
        Entry entry = (Entry)sender;
        //Here I would like to know the model object that's binded 
        //with this Entry / CellView item
    }

我怎样才能达到这两点?

我想建议你使用行为:

public class FocusBehavior : Behavior<Entry>
{
    private Entry _entry;

    public static readonly BindableProperty IsFocusedProperty =
        BindableProperty.Create("IsFocused",
                                typeof(bool),
                                typeof(FocusBehavior),
                                default(bool),
                                propertyChanged: OnIsFocusedChanged);

    public int IsFocused
    {
        get { return (int)GetValue(IsFocusedProperty); }
        set { SetValue(IsFocusedProperty, value); }
    }

    protected override void OnAttachedTo(Entry bindable)
    {
        base.OnAttachedTo(bindable);

        _entry = bindable;
    }

    private static void OnIsFocusedChanged(BindableObject bindable, object oldValue, object newValue)
    {
        var behavior = bindable as FocusBehavior;
        var isFocused = (bool)newValue;

        if (isFocused)
        {
            behavior._entry.Focus();
        }
    }
}

<ListView x:Name="TasksListView"
          ItemsSource={Binding Tasks}
          RowHeight="200">
  <ListView.ItemTemplate>
    <DataTemplate>
      <ViewCell x:Name="ViewCell">
        <Grid x:Name="RootGrid"
              Padding="10,10,10,0"
              BindingContext="{Binding}">
          <Entry>
              <Entry.Behaviors>
                <helpers:FocusBehavior IsFocused="{Binding BindingContext.IsFocused, Source={x:Reference RootGrid}}"/>
              </Entry.Behaviors>
          </Entry>
        </Grid>
      </ViewCell>
    </DataTemplate>
  </ListView.ItemTemplate>
</ListView>

还有我的模特:

public class TaskModel : INotifyPropertyChanged
{
    private bool _isFocused;

    public bool IsFocused
    {
        get { return _isFocused; }
        set
        {
            _isFocused = value;
            RaisePropertyChanged();
        }
    }

并且在 ViewModel 中,添加新项目后,将其 IsFocused 属性 设置为 true

您可以将 TextChanged 用于 Entry 的行为与此相同。

对于我的第一个问题,我找到了解决它的方法。不知道是不是最好的方案。

我已将 ListView 扩展为 CustomListView,并且添加了单元格字典:

private Dictionary<string, Cell> dicCells;

此外,我已经覆盖了 SetupContent 和 UnhookContent 方法。

SetupContent 在添加新单元格时触发,并且他的一个参数为我提供了我保存到字典中的新单元格。 (MarkReference 是我的关键值)

    //When a new cell item has added, we save it into a dictionary
    protected override void SetupContent(Cell content, int index)
    {
        base.SetupContent(content, index);

        ViewCell vc = (ViewCell)content;            

        if (vc != null)
        {
            BasketProduct bp = (BasketProduct)vc.BindingContext;
            if (bp != null)
            {
                this.dicCells.Add(bp.MarkReference, content);
            }                
        }

    }

UnhookContent 在单元格被移除时触发。我删除了字典中存在的项目。

    //When a new cell item has removed, we remove from the dictionary
    protected override void UnhookContent(Cell content)
    {
        base.UnhookContent(content);

        ViewCell vc = (ViewCell)content;

        if (vc != null)
        {
            BasketProduct bp = (BasketProduct)vc.BindingContext;
            if (bp != null)
            {
                this.dicCells.Remove(bp.MarkReference);
            }
        }

    }

然后,我创建了一个函数来检索包含对象(在我的例子中是 BasketProduct)的条目(在我的例子中是 CustomEntry)。

    //Retrieves a CustomEntry control that are into the collection and represents the BasketProduct that we have passed
    public CustomEntry GetEntry(BasketProduct bp)
    {
        CustomEntry ce = null;

        if (bp != null && this.dicCells.ContainsKey(bp.MarkReference))
        {
            ViewCell vc = (ViewCell)this.dicCells[bp.MarkReference];

            if (vc != null)
            {
                ce = (CustomEntry)((Grid)((StackLayout)vc.View).Children[0]).Children[4];
            }

        }

        return ce;
    }

当我想把焦点放在某个Entry上时,调用这个方法:

    //Put the focus on the CustomEntry control that represents de BasketProduct that they have passed
    public void SetSelected(BasketProduct bp, bool withDelay)
    {
        CustomEntry entry = null;

        entry = GetEntry(bp);

        if (entry != null)
        {
            if (withDelay)
            {
                FocusDelay(entry);
            } else
            {
                entry.Focus();
            }                                
        }            
    }

如果我从 ItemTapped 方法调用 SetSelected() 方法,工作正常,但如果我在然后集合中添加项目后调用 SetSelected() 方法,则 Entry 不会获得焦点。在这种情况下,我做了一个技巧。

    private async void FocusDelay(CustomEntry entry)
    {            
        await Task.Delay(500);
        entry.Focus();
    }

关于第二个问题,正如@markusian 所建议的,我已经扩展了 Entry (CustomEntry) 控件,并且在 Unfocused 事件中我已经这样做了:

    private void CustomEntry_Unfocused(object sender, FocusEventArgs e)
    {
        try
        {
            //If the user leaves the field empty, we set the last value
            BasketProduct bp = (BasketProduct)BindingContext;
            if (this.Text.Trim().Equals(string.Empty))
            {
                this.Text = bp.NumElements.ToString();
            }
        }
        catch (FormatException ex) { }
    }