【问题标题】:Xamarin.Forms: IValueConverter only works onceXamarin.Forms:IValueConverter 只工作一次
【发布时间】:2018-06-22 02:45:11
【问题描述】:

我正在尝试通过更改所选标签的颜色来控制标签的背景颜色。我遵循 MVVM 模式,我实现的方式是这样的:

  1. 在模型中,我创建了一个带有 get 和 set 的布尔值,它必须检测是否选择了我的列表视图中的项目。 public boolean Selected {get; set;}

  2. 在我看来,我将背景颜色属性绑定到布尔值,并将 IValueConverter 设置为转换器

  3. 在 ViewModel 中,我实现了 get 和 set

它似乎只检查一次,因为背景颜色总是白色的。我已经在 Converter 中使用断点对其进行了检查,它仅在启动列表时调用,而不是在更新项目时调用。

IValue 转换器:

public class SelectedItemColorConverter : IValueConverter
    {

        #region IValueConverter implementation

        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            if (value is bool)
            {
                if ((Boolean)value)
                    return Color.Red;
                else
                    return Color.White;
            }
            return Color.White;
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }

        #endregion
    }
}

这是列表视图:

<StackLayout x:Name="standingsStackLayout" IsVisible="False">
                <ListView x:Name="standingsList" SeparatorColor="Black" ItemsSource="{Binding StandingsListSource}" SelectedItem="{Binding SelectedItem}">
                    <ListView.ItemTemplate>
                        <DataTemplate>
                            <ViewCell>
                                <Label x:Name="TournamentNameLabel" Text="{Binding TournamentName}" 
                                       TextColor="{StaticResource textColor}" HorizontalTextAlignment="Center" 
                                       VerticalTextAlignment="Center" 
                                       BackgroundColor="{Binding Selected, Converter={StaticResource colorConvert}}"/>
                            </ViewCell>
                        </DataTemplate>
                    </ListView.ItemTemplate>
                </ListView>
            </StackLayout>

ViewModel 代码:

public HistoricalStandingsData _selectedItem;
    public HistoricalStandingsData SelectedItem
    {
        get { return _selectedItem; }
        set
        {
            if (_selectedItem != value)
            { 
                if(_selectedItem != null)
                    _selectedItem.Selected = false;

                _selectedItem = value;

                if (_selectedItem != null)
                    _selectedItem.Selected = true;


                TournamentLabelName = _selectedItem.TournamentName;

                OnPropertyChanged(nameof(SelectedItem));
                //OnPropertyChanged(nameof(_selectedItem.Selected));
            }
        }
    }

我已经为转换器添加了&lt;ContentPage.Resources&gt;

【问题讨论】:

  • Collection 的选择跟踪通常是通过 CollectionView 完成的。请参阅我写的旧 MVVM 介绍中的第 8 点:social.msdn.microsoft.com/Forums/vstudio/en-US/…
  • 我假设您的数据源需要实现“INotifyPropertyChanged”,以便在属性更新时通知 burbs。
  • @orhtej2 :感谢您的建议,但这不起作用:(
  • @Hudhud 的事情是,您所做的只是将绑定源更新为整个 ListView,您需要做的是在 HistoricalStandingsData 的各个属性上实现 INotifyPropertyChanged,因为这就是您的转换器是必然的。
  • 你应该使用一个baseViewModel,它为你的所有viewModels实现INotifyPropertyChanged,因为orthej2是正确的。这是使用 baseViewModel 的最简单方法 :)

标签: c# xaml xamarin mvvm xamarin.forms


【解决方案1】:

让我们看看你的观点

<StackLayout x:Name="standingsStackLayout" IsVisible="False">
    <ListView x:Name="standingsList" SeparatorColor="Black" ItemsSource="{Binding StandingsListSource}" SelectedItem="{Binding SelectedItem}">
        <ListView.ItemTemplate>
            <DataTemplate>
                <ViewCell>
                    <Label x:Name="TournamentNameLabel" Text="{Binding TournamentName}" 
                           TextColor="{StaticResource textColor}" HorizontalTextAlignment="Center" 
                           VerticalTextAlignment="Center" 
                           BackgroundColor="{Binding Selected, Converter={StaticResource colorConvert}}"/>
                </ViewCell>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>
</StackLayout>

我们可以看到这里发生了两个主要的数据绑定。首先,ListViewItemsSource 属性绑定到您的视图模型的StandingsListSource 属性。有两件事可以改变这一点:StandingsListSource 整体指向的对象或集合内容。

绑定的官方文档有following to say regarding binding ListView.ItemsSource:

ListView 在处理可能发生的更改方面非常复杂 动态发生在基础数据中,但前提是采取某些 脚步。如果集合项分配给ItemsSource 属性 ListView 在运行时发生变化——也就是说,如果可以添加项目 到集合或从集合中移除——使用ObservableCollection 类 对于这些项目。 ObservableCollection 实现 INotifyCollectionChanged 接口,ListView 将安装一个 CollectionChanged 事件的处理程序。

让我们这样做(DataSource 类的完整实现,我在后面的表单中用作BindingContext):

public ObservableCollection<HistoricalStandingsData> StandingsListSource { get; } = new ObservableCollection<HistoricalStandingsData>();

为简单起见,我将StandingsListSource 设为C# 6.0 readonly auto property 以消除跟踪其重新分配的需要。

现在,由于 ListView.SelectedItem 也已绑定,我们需要某种方式来通知 ListView 所选项目已从后面的代码更新。输入前面提到的文档中的第二条建议:

如果项目本身的属性在运行时发生变化,则 集合中的项目应实现INotifyPropertyChanged 使用接口和信号更改属性值 PropertyChanged 事件。

这有两个含义:

  • HistoricalStandingsData 应在其属性更改时通知,因为ListView 中的每一行都根据DataTemplate 绑定到此属性:

    public class HistoricalStandingsData : INotifyPropertyChanged
    {
        public HistoricalStandingsData(string name)
        {
            this.TournamentName = name;
        }
    
        private bool selected;
    
        public bool Selected
        {
            get
            {
                return selected;
            }
    
            set
            {
                selected = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Selected)));
            }
        }
    
        public string TournamentName { get; }
    
        // From INotifyPropertyChanged
        public event PropertyChangedEventHandler PropertyChanged;
    }
    
  • 视图模型类应该实现INotifyPropertyChanged 来通知属性,在这种情况下SelectedItem 会发生变化。

    class DataSource : INotifyPropertyChanged
    {
        public ObservableCollection<HistoricalStandingsData> Items { get; } = new ObservableCollection<HistoricalStandingsData>();
    
        public HistoricalStandingsData SelectedItem
        {
            // Information on selection is stored in items themselves, use Linq to find the single matching item
            get => Items.Where(x => x.Selected).SingleOrDefault();
            set
            {
                // Reset previous selection
                var item = SelectedItem;
                if (item != null)
                    item.Selected = false;
    
                // Mark new item as selected, raising HistoricalStandingItem.PropertyChanged
                if (value != null)
                    value.Selected = true;
    
                // Notify observers that SelectedItem changed
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedItem)));
            }
        }
    
        // From INotifyPropertyChanged
        public event PropertyChangedEventHandler PropertyChanged;
    
        public DataSource()
        {
            // Helper ICommand used for appending new items to HistoricalStandingsData
            AddNew = new Command(() =>
            {
                var item2 = new HistoricalStandingsData(DateTime.Now.ToString());
                // Append, notifies observers that collection has changed.
                Items.Add(item2);
                // Set as selected, resetting previous selection
                SelectedItem = item2;
            });
        }
    
        public ICommand AddNew { get; } 
    }
    

AddNew 命令是可选的,我添加它是为了测试目的。

【讨论】: