在 Xamarin.Forms 上制作一组动态的 3 个选择器

Making a dynamic set of 3 pickers on Xamarin.Forms

我们正在 Xamarin.Forms 上开发跨平台应用程序。
在其中一个页面上,我们需要显示一组具有相同项目列表的 3 个选择器。这个想法是,当您 select 一个选择器上的项目时,它会从其他两个选择器的项目源中删除。
为此,我们开发了以下代码:
我们从一个名为 BaseList 的项目列表开始,它是我们从网络服务中获得的。我们还创建了 3 个单独的列表(ListAListBListC)和 3 个项目来存储每个选择器的 selected 项目(SelectedASelectedBSelectedC).

private List<Item> BaseList;
private List<Item> _ListA;
private Item _SelectedA;
private List<Item> _ListB;
private Item _SelectedB;
private List<Item> _ListC;
private Item _SelectedC;
…
//Api Calls
private void LoadData()
{
         …
    BaseList = new List<Item> (ListFromWebServices);

    _ListA = new List<Item>(BaseList);
    OnPropertyChanged(nameof(ListA));
    _ListB = new List<Item>(BaseList);
    OnPropertyChanged(nameof(ListB));
    _ListC = new List<Item>(BaseList);
    OnPropertyChanged(nameof(ListC));
}
…
//Public Fields
public List<Item> ListA
{
    get
    {
        return _ListA;
    }
}

public Item SelectedA
{
    get
    {
        return _SelectedA;
    }
    set
    {
        SetProperty(ref _SelectedA, value, nameof(SelectedA));
    }
}

public List<Item> ListB
{
    get
    {
        return _ListB;
    }
}

public Item SelectedB
{
    get
    {
        return _SelectedB;
    }
    set
    {
        SetProperty(ref _SelectedB, value, nameof(SelectedB));
    }
}

public List<Item> ListC
{
    get
    {
        return _ListC;
    }
}

public Item SelectedC
{
    get
    {
        return _SelectedC;
    }
    set
    {
        SetProperty(ref _SelectedC, value, nameof(SelectedC));
    }
}

此代码在我们的 ViewModel 上,因此我们使用 SetProperty 将引用的 属性 设置为值并从 INotifyPropertyChanged

调用 PropertyChangedEventArgs
public event PropertyChangedEventHandler PropertyChanged;

protected bool SetProperty<T>(ref T storage, T value,
                              [CallerMemberName] string propertyName = null)
{
    if (Equals(storage, value))
        return false;

    storage = value;
    OnPropertyChanged(propertyName);
    return true;
}

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

为了更新 ItemSource,每当更改 selected 项目时,我们都会从 SelectedASelectedB 的 setter 调用 OnSelectedItemChangedSelectedC。此方法接收指示哪个 Picker 触发它的索引:

private void OnSelectedItemChanged(int index)
{
Item CurrentA = SelectedA;
Item CurrentB = SelectedB;
Item CurrentC = SelectedC;
    int i;
    switch (index)
    {
        case 0:
            _ListB = new List<Item> (BaseList);
            _ListB.Remove(CurrentA);
            _ListB.Remove(CurrentC);
            OnPropertyChanged(nameof(ListB));
            _ListC = new List<Item>(BaseList);
            _ListC.Remove(CurrentA);
            _ListC.Remove(CurrentB);
            OnPropertyChanged(nameof(ListC));

            i = ListB.IndexOf(CurrentB);
            if (i > -1)
            {
                _SelectedB = ListB[i];
            }
            OnPropertyChanged(nameof(SelectedB));
            i = ListC.IndexOf(CurrentC);
            if (i > -1)
            {
                _SelectedC = ListC[i];
            }
            OnPropertyChanged(nameof(SelectedC));

            break; 
        case 1:
            _ListA = new List<Item>(BaseList);
            _ListA.Remove(CurrentB);
            _ListA.Remove(CurrentC);
            OnPropertyChanged(nameof(ListA));
            _ListC = new List<Item>(BaseList);
            _ListC.Remove(CurrentA);
            _ListC.Remove(CurrentB); 
            OnPropertyChanged(nameof(ListC));

            i = ListA.IndexOf(CurrentA);
            if (i > -1)
            {
                _SelectedA = ListA[i];
            }
            OnPropertyChanged(nameof(SelectedA));
            i = ListC.IndexOf(CurrentC);
            if (i > -1)
            {
                _SelectedC = ListC[i];
            }
            OnPropertyChanged(nameof(SelectedC));

            break;
         case 2:
            _ListA = new List<Item>(BaseList);
            _ListA.Remove(CurrentB);
            _ListA.Remove(CurrentC);
            OnPropertyChanged(nameof(ListA));
            _ListB = new List<Item>(BaseList);
            _ListB.Remove(CurrentA);
            _ListB.Remove(CurrentC); 
            OnPropertyChanged(nameof(ListB));

           i = ListA.IndexOf(CurrentA);
            if (i > -1)
            {
                _SelectedA = ListA[i];
            }
            OnPropertyChanged(nameof(SelectedA));
            i = ListB.IndexOf(CurrentB);
            if (i > -1)
            {
                _SelectedB = ListB[i];
            }
            OnPropertyChanged(nameof(SelectedB));

            break;
    }
}

我们在这里所做的基本上是将每个选择器的当前 selected 项目保存在一个单独的变量上,将 BaseList 复制到两个没有调用事件的选择器中,然后每个新列表删除其他选择器使用的所有选项,再次将每个新列表上的 selected Item 设置为最初 selected 的项目,最后调用 OnPropertyChanged() 到告知修改意见。

这里的问题是,当我们更改 Picker 上的 ItemSource 时,它会将 SelectedItem 设置为 null。在调用 OnSelectedItemChanged() 之后在 setter 上调用 OnPropertyChanged() 会导致一个 Picker 更新另一个的无限循环,并添加一个过滤器在设置之前检查该值是否不为空选择器显示没有 selected 项目,而值已经设置。

以防万一有人遇到同样的问题,我们找到了解决方案。如果你创建 CurrentACurrentBCurrentC 全局变量并在每个案例上添加一个 if ((CurrentA != SelectedA) && (!(SelectedA is null))) { ... (do all the stuff) } break; 并在最后设置

_SelectedA = CurrentA;
OnPropertyChanged(nameof(SelectedA));
_SelectedB = CurrentB;
OnPropertyChanged(nameof(SelectedB));
_SelectedC = CurrentC;
OnPropertyChanged(nameof(SelectedC));

有效。我们不知道为什么:)