在 CollectionView 中滚动不会保持 Switch 切换选择

Scrolling in CollectionView does not maintain Switch toggle selection

我有以下 CollectionView -

<CollectionView x:Name="PCollection" ItemsSource="{Binding P.data}" Margin="0,10,2,10">
    <CollectionView.ItemTemplate>
        <DataTemplate>
            <StackLayout>
                <Image Source="p" HeightRequest="60" WidthRequest="60" />
                <StackLayout Orientation="Vertical" >
                    <Label Text="{Binding id}" VerticalOptions="Center" IsVisible="False"/>
                    <StackLayout Orientation="Horizontal" >+
                    <Switch IsToggled="{Binding IsOwned, Mode=TwoWay}" HorizontalOptions="Start" Toggled="Switch_Toggled_Place" />
                </StackLayout>
            </StackLayout>
        </DataTemplate>
    </CollectionView.ItemTemplate>
</CollectionView>

我的代码在后面 -

bool _isOwned;
public bool IsOwned
{
    get
    {
        return _isOwned;
    }
    set
    {
        _isOwned = value;

    }
}

private void Switch_Toggled_Place(object sender, ToggledEventArgs e)
{

}

我的问题是,当我在集合中切换开关时,一切都按预期工作,我进入 Switch_Toggled_Place。但是假设集合中有 20 个项目,当我向上滚动并且切换开关不在视图中时,出于某种原因它再次触发 Switch_Toggled_Place 并取消选中我的开关!

我试过从绑定中删除 Mode=TwoWay 但没有效果。我还尝试确定切换事件是从用户输入发生的还是从代码本身触发但再次无效。我该如何解决这个问题,我相信这很简单。

您需要实现接口INotifyPropertyChanged

在您的模型中

public partial class MyModel: INotifyPropertyChanged
{
   public event PropertyChangedEventHandler PropertyChanged;

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

   bool _isOwned;
   public bool IsOwned
   { 
     get
     {
        return _isOwned;
     }
     set
     {
        if(_isOwned!=value)
            {
                _isOwned= value;
                OnPropertyChanged(nameof(IsOwned));

            }

      }
}


并且由于您使用了 MVVM ,因此您应该在 ViewModel 而不是 Event 中处理逻辑。否则会和数据绑定冲突。

在您的 ViewMNode 中

public class MyViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

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

    // MySource here is the ItemsSource of CollectionView
    public ObservableCollection<MyModel> MySource { get; set; }


    public MyViewModel()
    {
        MySource = new ObservableCollection<MyModel>() {

       //...
        };

        foreach(MyModel model in MySource)
        {
            model.PropertyChanged += Model_PropertyChanged;
        }

    }

    private void Model_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if(e.PropertyName== "IsOwned")
        {
          // do some thing you want here .
        }
    }
}

根据这个GitHub Issue,建议在 Switch 中添加 OnClick 事件,人们遇到了同样的问题。

话虽如此,作为一种解决方法,您可以在带有 TapGesture 的开关顶部添加 StackLayout 以注册水龙头,然后 Toggle/UnToggle 开关,无需切换事件

例如:

<Grid>
    <Switch IsToggled="{Binding IsOwned}"></Switch>
    <StackLayout>
        <StackLayout.GestureRecognizers>
            <TapGestureRecognizer 
                Tapped="OnSwitchTapped" 
                NumberOfTapsRequired="1">
        </StackLayout.GestureRecognizers>
    </StackLayout>
</Grid>

...

public void OnSwitchTapped(object sender, EventArgs e)
{
    //Change Switch IsToggle
    //add code here
}

这是一个非常简单的示例,但您知道需要做什么。这绝不是一个好的解决方案,这是一个变通办法,你的做法是正确的