c# - Xamarin ListView MVVM 数据绑定(bind)

标签 c# xamarin data-binding

我在将数据绑定(bind)到 ListView 时遇到一点问题。

因为我想要一个带有 MultiSelection 的 ListView ,所以我需要实现一个名为 GenericSelectableItem 的自定义类,它存储数据,如果单元格IsSelected.

首先,这是 MainPage 的 View 模型:

public class MainPageViewModel : BaseViewModel, INotifyPropertyChanged
{
    private ObservableCollection<GenericSelectableItem<AudioFile>> _audiofiles = new ObservableCollection<GenericSelectableItem<AudioFile>>();

    public ObservableCollection<GenericSelectableItem<AudioFile>> AudioFiles
    {
        get => _audiofiles ?? new ObservableCollection<GenericSelectableItem<AudioFile>>();
        set
        {
            _audiofiles = value;
            PropertyChanged(this, new PropertyChangedEventArgs(nameof(AudioFiles)));
        }
    }
}

MainPage 的 Xaml:

            <!-- The Content -->
            <ListView x:Name="listView" Grid.Row="1" HasUnevenRows="true" RowHeight="-1" ItemsSource="{Binding AudioFiles}" ItemSelected="ListView_OnItemSelected">
                <ListView.ItemTemplate>
                    <DataTemplate>
                        <local:AudioViewCell Audiofile="{Binding Data}"/>
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>

2 个帮助类,用于制作可多选的 ListView:

public class GenericSelectableItem<T> : SelectableItem
{
    public GenericSelectableItem(T data)
        : base(data)
    {
    }

    public GenericSelectableItem(T data, bool isSelected)
        : base(data, isSelected)
    {
    }

    // this is safe as we are just returning the base value
    public new T Data
    {
        get => (T)base.Data;
        set => base.Data = value;
    }
}

public class SelectableItem : BindableObject
{
    public static readonly BindableProperty DataProperty =
        BindableProperty.Create(
            nameof(Data),
            typeof(object),
            typeof(SelectableItem),
            (object) null);

    public static readonly BindableProperty IsSelectedProperty =
        BindableProperty.Create(
            nameof(IsSelected),
            typeof(object),
            typeof(SelectableItem),
            (object)false);

    public SelectableItem(object data)
    {
        Data = data;
        IsSelected = false;
    }

    public SelectableItem(object data, bool isSelected)
    {
        Data = data;
        IsSelected = isSelected;
    }

    public object Data
    {
        get => (object)GetValue(DataProperty);
        set => SetValue(DataProperty, value);
    }

    public bool IsSelected
    {
        get => (bool)GetValue(IsSelectedProperty);
        set => SetValue(IsSelectedProperty, value);
    }
}

AudioViewCell.Xaml 中的绑定(bind)示例:

<Label x:Name="LblFilename" Text="{Binding Filename}"
                               VerticalTextAlignment="Center"
                               Style="{StaticResource CellLabel}"/>

AudioViewCell.cs

public partial class AudioViewCell : ViewCell
{
    public static BindableProperty AudiofileProperty = BindableProperty.Create(
        propertyName: nameof(Audiofile),
        returnType: typeof(AudioFile),
        declaringType: typeof(AudioViewCell),
        defaultValue: null,
        defaultBindingMode: BindingMode.OneWay);

    public AudioFile Audiofile
    {
        get => (AudioFile) GetValue(AudiofileProperty);
        set
        {
            Debug.WriteLine("Audiofile changed");
            SetValue(AudiofileProperty, value);
            ((MenuItemViewModel) BindingContext).Audiofile = value;
        }
    }

    public AudioViewCell()
    {
        InitializeComponent();

        this.BindingContext = new MenuItemViewModel(SlAdditionalData, AwvWaveView);
    }
}

最后是 MenuItemViewModel:

public class MenuItemViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged = delegate { };

    private AudioFile _audioFile;
    public AudioFile Audiofile
    {
        get => _audioFile;
        set
        {
            Debug.WriteLine("Setting Audiofile");
            _audioFile = value;
            PropertyChanged(this, new PropertyChangedEventArgs(nameof(Audiofile)));
            PropertyChanged(this, new PropertyChangedEventArgs(nameof(Filename)));
        }
    }

    public string Filename => Audiofile?.Filename;
}

GenericSelectableItem 中的 Field Data 似乎从未设置,所以我认为绑定(bind)有问题

有没有人知道更好的方法或者为什么这不起作用? 非常感谢您的帮助!!

最佳答案

TL;DR 版本:深入了解您的 cell的和'cellViewModel' s 源代码 我注意到您的代码的绑定(bind)句柄存在混淆。您正在处理您在 AudioViewCell 的构造函数中设置的一个 BindingContext,但它被 ListView(在构造函数之后运行)自动设置的一个覆盖。所以你站在一个没有数据渲染的 ViewCell 上。

在这张图片上,我试图展示你的模型发生了什么:

sample Diagram

请注意左侧的黄色圆形箭头,这是您在构造函数中定义绑定(bind)上下文。稍后会被红色箭头覆盖(在 ListView 呈现后设置)。

要使其按照您编码的方式工作,请按照下列步骤操作:

  1. 去掉 AudiofileViewCell 的 AudiofileProperty,你将不需要它;
  2. MenuItemViewModel 的构造函数创建一个重载以接收“AudioFile”(MenuItemViewModel 类是您真正的 BindingContext);
  3. 重写 OnBindingContextChanged 方法以提取新的 Data 字段并将其作为参数发送给 MenuItemViewModel 的新实例的构造函数;
  4. MenuItemViewModel 的这个新实例设置为内部 View 的 BindingContext(根据您的源代码,这是一个名为 slRoot 的 StackLayout)

这是步骤代码:

2:

public class MenuItemViewModel : INotifyPropertyChanged
{
    // ...
    public void SetAudiofile(AudioFile data)
    {
        Audiofile = data;
    }
    // ...
}

3 和 4:

public partial class AudioViewCell : ViewCell
{
    // ...
    protected override void OnBindingContextChanged()
    {
        base.OnBindingContextChanged();
        // * I'm not sure if it's ok create a new instance to your binding context here, the old one ca be kept on memory due it's subscription. Think about create a method to set just the Audiofile property
        slRoot.BindingContext = new MenuItemViewModel( thing, thing, ((GenericSelectableItem<AudioFile>)BindingContext).Data); 
    }
    // ...
}

我已经测试过并且它有效,但它远非理想的清洁解决方案。

如果您的意图是重用此单元格,我认为您应该公开可以绑定(bind)或不绑定(bind)的属性,让它说明将显示什么。 View 单元应仅处理视觉布局/行为,与其上的数据无关。

P.S.:对不起,我的英语不好,我希望它能被理解。

关于c# - Xamarin ListView MVVM 数据绑定(bind),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49128660/

相关文章:

c# - 最新版本的Xamarin.Forms Button.CornerRadius错误

c# - 使用 Canvas 作为 ItemsPanelTemplate 和绑定(bind) Canvas.Top 不起作用

c# - ListCollectionView 是否泄漏内存?

c# - AesManaged.KeySize 属性的默认值是多少?

java - 无法访问可移植类库中的类

c# - 在 <Editor> 中检查用户是否在键入单词后键入了特定单词

jquery - knockout 三个级联下拉菜单

c# - MVVM:强制组合框标签刷新

c# - 如何为 Web API 2 异步操作中的异常获取正确的堆栈跟踪?

c# - 如何修改 web 服务代理以获取原始 XML