【问题标题】:Updating a ListView with Xamarin.Forms使用 Xamarin.Forms 更新 ListView
【发布时间】:2024-06-05 14:00:02
【问题描述】:

我的几个 Xamarin Forms 应用程序中的列表视图存在问题。一种形式是在标签页设置中,另一种是普通内容页面(不同的应用程序)

我有这样的课

public class SomeClass
{
    public string StringOne {get;set;}
    public string StringTwo {get;set;}
    public int IntOne {get;set;}
}

在我的内容页面中,我设置了一个 ObservableCollection 并添加了一些数据。然后我告诉列表 SomeClass 是我的 ItemSource。这会在我的所有设备上正确生成 ListView。

问题是,当我更改其中一个属性时,ListView 上没有任何变化(因此,如果说我在 Observable 中有 3 个对象并删除了一个,列表仍然显示 3 - 或者如果我在第二个中更改了一个属性对象,ListView 上的第二项也不会改变)。

我还尝试通过使用标准 List 并在类中实现 INotifyChanged 来解决问题。不过,ListView 在 List 更改时也不会改变。

我知道数据发生了变化,就好像我对对象进行了更改一样,出来再进去,数据在 UI 中发生了变化。

是我做错了什么还是我需要将其放入 Bugzilla 中?

【问题讨论】:

  • ObservableCollection 应该在添加或删除项目时更新 - 但如果项目被修改,它不会强制更新,除非该项目实现 INotifyPropertyChanged。
  • 所以我需要 observable 和 inotifypropertychanged 才能更新列表视图?

标签: xamarin.ios xamarin.android xamarin.forms


【解决方案1】:

不绑定并实现INotifyPropertyChanged接口不会改变。

示例代码:

public class ObservableProperty : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string propertyName)
    {
        var handler = PropertyChanged;
        if (handler != null)
            handler(this, new PropertyChangedEventArgs(propertyName));
    }
}

public class SomeClass:ObservableProperty
{
    string stringOne;
    string stringTwo;
    int intOne;
    public string StringOne 
    {
        get{return stringOne;}
        set
        {
            stringOne = value;
            OnPropertyChanged("StringOne");
        }
    }

    public string StringTwo 
    {
        get{ return stringTwo;}
        set
        {
            stringTwo = value;
            OnPropertyChanged("StringTwo");
        }
    }

    public int IntOne 
    {
        get{ return intOne;}
        set
        {
            intOne = value;
            OnPropertyChanged("IntOne");
        }
    }
}

public class MainVM:ObservableProperty
{
    ObservableCollection<SomeClass> items;

    public ObservableCollection<SomeClass> items
    {
        get{return items;}
        set
        {
            items = value;
            OnPropertyChanged("Items");
        }
    }

    public MainVM()
    {
        Items = new ObservableCollection<SomeClass>();

        Items.Add(new SomeClass(){StringOne = "123", StringTwo = "test", IntOne =12});
    }

    public void CallMeForChangingProperty()
    {
        SomeClass item = Items[0];
        item.StringOne = "Test1";
    }
}


public class MainView
{
    public MainView()
    {
        this.BindingContext=  new MainVM()
    }
} 


< ListView ItemsSource="{Binding Items}" RowHeight="120">
 < ListView.ItemTemplate>
   < DataTemplate>
     < ViewCell>
       < ViewCell.View>
        < StackLayout>
            < Label Text= "StringOne" />
            < Label Text= "StringTwo" />
            < Label Text= "IntOne" />
        </ StackLayout>
       </ ViewCell.View>
     </ ViewCell>
   </ DataTemplate>
 </ ListView.ItemTemplate>
</ ListView>

【讨论】:

  • 这几乎是我已经拥有的,并且没有看到 UI 有任何变化 :(
【解决方案2】:

@eakgul 给出的答案对我来说就像是一种魅力。 我将在此处附上我已实现的内容,也许它可以帮助某人。

您必须将 INotifyPropertyChanged 设置为 ObservableColection 和它的 itens。

我有一个带有 INotifyPropertyChanged 的​​ BaseViewModel,如下所示:

public class BaseViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    public void OnPropertyChanged([CallerMemberName] string propertyName = "")
    {
        if (PropertyChanged != null)
        {
            PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    protected void SetProperty<T>(ref T backingField, T value, [CallerMemberName] string propertyName = null)
    {
        if (EqualityComparer<T>.Default.Equals( backingField, value)) return;
        backingField = value;
        OnPropertyChanged(propertyName);
    }
}

在我的 BluetoothPage.xaml 上,首先我将 bindincontext 设置为我的 BluetoothPageViewModel.cs 并设置 ListView ItemsSource 和它的绑定标签:

<ContentPage.BindingContext>
    <viewmodel:BluetoothPageViewModel/>
</ContentPage.BindingContext>

<ContentPage.Content>
    <StackLayout Padding="5,10">

        <Button x:Name="Scan_Devices_Button"
                Command="{Binding SearchNew_Button_Clicked}"/>
        
        <ListView x:Name="DevicesList"
                  ItemsSource="{Binding BluetoothDevices}"
                  SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
                  IsPullToRefreshEnabled="True">
            
            <ListView.ItemTemplate>
                <DataTemplate>
                    <ViewCell>
                        <StackLayout>
                            <Grid>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="*"/>
                                    <ColumnDefinition Width="*"/>
                                </Grid.ColumnDefinitions>
                                <Label Grid.Column="0"
                                       Text="{Binding device.Device.NativeDevice.Name}"/>
                                <Label Grid.Column="1"
                                       Text="{Binding device.Device.NativeDevice.Address, StringFormat='ID: {0}'}"/>
                            </Grid>
                            
                            <Grid>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="*"/>
                                    <ColumnDefinition Width="*"/>
                                </Grid.ColumnDefinitions>
                                <Label Grid.Column="0"
                                       Text="{Binding device.Rssi, StringFormat='Power: {0:F2}dbm'}"/> 
                                <Label Grid.Column="1"
                                       Text="{Binding distance, StringFormat='Distance: {0:F2}m'}"/>
                            </Grid>
                        </StackLayout>
                    </ViewCell>
                    
                </DataTemplate>
            </ListView.ItemTemplate>
            
        </ListView>
    </StackLayout>
</ContentPage.Content>
然后,在我的 BluetoothPageViewModel.cs 中,我使用 BaseViewModel 对其进行扩展,并使用 INotifyPropertyChanged 声明 ItemsSource BluetoothDevices。此时,每次我更改 ObservableCollection BluetoothDevices 上的项目时,ListView 都会更新。但是,如果我对 ObservableCollection 中的某个项目进行了更改,则不会发生任何事情! 因此,您必须将 INotifyPropertyChanged 设置为它的 itens。 下面是我的BluetoothPageViewModel,它在PageModel BluetoothPageModel.cs中使用了一个BluetoothPageModel类

BluetoothPageViewModel:

public class BluetoothPageViewModel : BaseViewModel
{
    public BluetoothPageViewModel()
    {
        SearchNew_Button_Clicked = new Command(NewDevices_Button_Clicked_Event);
        Scan_Devices_Button_BgColor = "#D6D7D7";
        Scan_Devices_Button_Text = "Scan nearby devices";
    }


    #region Declarations

    public List<IDevice> iDeviceList = new List<IDevice>();
    public ObservableCollection<BluetoothPageModel> _bluetoothDevices = new ObservableCollection<BluetoothPageModel>();
    public BluetoothPageModel _selectedItem;

    public ObservableCollection<BluetoothPageModel> BluetoothDevices
    {
        get { return _bluetoothDevices; }
        set { SetProperty(ref _bluetoothDevices, value); }
    }
    public BluetoothPageModel SelectedItem
    {
        get { return _selectedItem; }
        set { SetProperty(ref _selectedItem, value); }
    }

    public ICommand SearchNew_Button_Clicked { get; private set; }

    #endregion

    #region Functions

    private void NewDevices_Button_Clicked_Event(object obj)
    {
        // discover some devices
        if (!CrossBleAdapter.Current.IsScanning)
        {
            BluetoothDevices.Clear();
            iDeviceList.Clear();
            var scanner = CrossBleAdapter.Current.Scan().Subscribe(scanResult =>
            {
                if (!iDeviceList.Contains(scanResult.Device))
                {
                    iDeviceList.Add(scanResult.Device);
                    Device.BeginInvokeOnMainThread(() =>
                    {
                        BluetoothDevices.Add(new BluetoothPageModel
                        {
                            device = scanResult,
                            distance = Math.Pow(10, ((-68 - scanResult.Rssi) / 31.1474))
                        });
                    });
                }
                else
                {
                    int ind = iDeviceList.IndexOf(scanResult.Device);


                    Device.BeginInvokeOnMainThread(() =>
                    {
                        BluetoothDevices[ind].device = scanResult;
                        BluetoothDevices[ind].distance = Math.Pow(10, ((-68 - scanResult.Rssi) / 31.1474));
                    });
                }
            });
        }
        else
        {
            CrossBleAdapter.Current.StopScan(); //When you want to stop scanning
        }
    }
    #endregion
}

最后,当您更改 BluetoothPageModel 类的属性时能够更新数据:

public class BluetoothPageModel:BaseViewModel
{
    public IScanResult _device;
    public double _distance;
    public IScanResult device 
    {
        get { return _device; }
        set { SetProperty(ref _device, value); }
    }
    public double distance
    {
        get { return _distance; }
        set { SetProperty(ref _distance, value); }
    }
}

感谢 eakgul 的回答,我可以让它工作。希望它可以帮助别人。

【讨论】: