Xamarin表单:TemplatedItems仅返回分组标题中的视单元

时间:2019-05-26 09:55:28

标签: c# android listview xamarin.forms

我需要以编程方式获取列表视图的所有视单元,以便可以更改视单元内特定子布局的背景颜色。 轻按时,更新视单元的颜色没有问题,但是,每当轻按其他视单元时,我都需要将所有视单元的颜色更改为默认颜色。

在寻找解决方案时,我经常会找到答案,通过列表视图的运行时属性访问视单元(请参见下面的代码或此处的第二个答案:Xamarin.Forms: Get all cells/items of a listview),在测试代码时,我意识到不适用于已启用分组的列表视图。

IEnumerable<PropertyInfo> pInfos = (connectionsListView as ItemsView<Cell>).GetType().GetRuntimeProperties();
var templatedItems = pInfos.FirstOrDefault(info => info.Name == "TemplatedItems");
if (templatedItems != null)
{
  var cells = templatedItems.GetValue(connectionsListView);
    foreach (ViewCell cell in cells as Xamarin.Forms.ITemplatedItemsList<Xamarin.Forms.Cell>)
    {
        if (cell.BindingContext != null && cell.BindingContext is MyModelClass)
        {
              // Change background color of viewcell
        }
    }
}

启用分组后,此代码仅返回分组的标头视单元。我找不到更改此代码的答案,因此返回了实际的“ body”视单元而不是仅返回标题。 有什么可能的方法可以更改此代码,以便获得预期的结果,还是必须为此使用自定义渲染器?

更新-Listview XAML代码

在这里您可以在XAML中看到我当前正在使用的列表视图。我尝试制定一种解决方案,可以将每个视单元的背景颜色绑定到模型(就我的情况而言,绑定到每个“文档”),但是目前我无法解决如何更改每个特定视单元的颜色。一个被窃听。 (我只需要更改当前所选视单元的背景色,因此所有其他视单元都具有默认背景色。)

            <ListView x:Name="DocumentListView"
                      ItemsSource="{Binding GroupedDocuments}"
                      BackgroundColor="WhiteSmoke"
                      HasUnevenRows="True"
                      RefreshCommand="{Binding LoadDocumentsCommand}"
                      IsPullToRefreshEnabled="True"
                      Refreshing="DocumentListView_OnRefreshing"
                      IsRefreshing="{Binding IsBusy, Mode=OneWay}"
                      CachingStrategy="RecycleElement"
                      IsGroupingEnabled="True"
                      GroupDisplayBinding="{Binding Key}"
                      GroupShortNameBinding="{Binding Key}"
                      VerticalOptions="StartAndExpand"
                      HorizontalOptions="StartAndExpand"
                      Margin="0, -20, 0, 0">
                <ListView.GroupHeaderTemplate>
                    <DataTemplate>
                        <ViewCell Height="25">
                            <Label x:Name="DocumentDate"
                                   FontSize="Medium"
                                   TextColor="#2E588C"
                                   VerticalOptions="Center"
                                   HorizontalTextAlignment="Center"
                                   Text="{Binding Key}"/>
                        </ViewCell>
                    </DataTemplate>
                </ListView.GroupHeaderTemplate>
                <ListView.ItemTemplate>
                    <DataTemplate>
                        <ViewCell Height="155" Tapped="DocumentViewCell_OnTapped">
                            <StackLayout Padding="10, 5, 10, 5">
                                <Frame Padding="0" HorizontalOptions="FillAndExpand" HasShadow="True">
                                    <StackLayout Padding="10" Orientation="Horizontal" HorizontalOptions="FillAndExpand" VerticalOptions="StartAndExpand">
                                        <StackLayout HorizontalOptions="StartAndExpand">
                                            <StackLayout Orientation="Horizontal" Spacing="15" Margin="10, 10, 10, 0" HorizontalOptions="StartAndExpand">
                                                <Label Text="{Binding Name}"
                                                       LineBreakMode="NoWrap"
                                                       FontSize="20"
                                                       TextColor="CornflowerBlue"
                                                       FontAttributes="Bold"/>
                                            </StackLayout>
                                            <StackLayout Orientation="Horizontal" Spacing="5" Margin="12, 0, 0, 0" HorizontalOptions="StartAndExpand">
                                                <Label Text="{Binding DocumentType.Name, StringFormat='Typ: {0}'}"
                                                       LineBreakMode="NoWrap"
                                                       FontSize="16"
                                                       TextColor="Black"/>
                                            </StackLayout>
                                            <StackLayout Orientation="Horizontal" Spacing="5" Margin="12, 3, 0, 0" HorizontalOptions="StartAndExpand">
                                                <Label Text="{Binding TotalValue, StringFormat='Gesamtwert: {0:F2} €'}"
                                                       LineBreakMode="NoWrap"
                                                       FontSize="16"
                                                       TextColor="Black"/>
                                            </StackLayout>
                                            <StackLayout Spacing="5" Margin="12, 3, 0, 0" Orientation="Horizontal" HorizontalOptions="StartAndExpand" VerticalOptions="Start">
                                                <Label Text="{Binding TagCollectionString, StringFormat='Tags: {0}'}"
                                                       LineBreakMode="WordWrap"
                                                       FontSize="14"
                                                       TextColor="Black" 
                                                       VerticalOptions="CenterAndExpand"/>
                                            </StackLayout>
                                        </StackLayout>
                                        <StackLayout HorizontalOptions="EndAndExpand" VerticalOptions="Start" Margin="0, 25, 25, 0">
                                            <ImageButton HeightRequest="85" MinimumWidthRequest="85" x:Name="ButtonEditDocument" Source="baseline_more_vert_black_48.png" Clicked="ButtonEditDocument_OnClicked" Margin="0, 0, 15, 0" BackgroundColor="Transparent" WidthRequest="25" />
                                        </StackLayout>
                                    </StackLayout>
                                </Frame>
                            </StackLayout>
                        </ViewCell>
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>

更新2-绑定的使用

我弄清楚了如何使用绑定在模型中存储每个视单元的当前选定背景。目前,我面临的问题是绑定值更改后UI无法正确更新。 这是我到目前为止编写的代码,以及在点击其他视单元时如何更新页面:

文档模型类:

public class Document 
{
        public bool HasDefaultColor { get; set; }

        public string CurrentlySelectedColorFromHex
        {
            get => ColorConverter.GetHexString(CurrentlySelectedColor);
        }

        [NotMapped]
        public Color CurrentlySelectedColor => HasDefaultColor ? DefaultColor : ActivatedColor;

        private static readonly Color DefaultColor = Color.WhiteSmoke;
        private static readonly Color ActivatedColor = Color.FromHex("#2E588C");
}

代码背后的OnTapped函数:

        private void DocumentViewCell_OnTapped(object sender, EventArgs e)
        {                    
                    var documents = documentRepository.GetAll();
                    foreach (var document in documents)
                        document.HasDefaultColor = true;

                    selectedDocument.HasDefaultColor = false;
                    unitOfWork.Complete();
                    UpdatePage();
}

在UpdatePage()中,我想在绑定集合更改后正确刷新listview:

                viewModel.LoadDocuments();
                DocumentListView.BeginRefresh();

很抱歉,如果这是一个初学者的问题,但我还没有找到答案,或者无法弄清楚如何正确更新UI,以便正确更新每个视单元的背景色。 至少在每个OnTapped()调用中正确存储了有界值。

更新3-添加了转换器

伙计们,我尝试了一些事情,并陷入了更新模型的bound属性的困境。 我也尝试过数据触发器,但无法正确更改这些数据触发器,因此它们没有按我预期的那样工作。

直到现在,我已经向颜色转换器添加了一个自定义布尔值以转换绑定属性:

    public class BoolToColorConverter : IValueConverter
    {
        private static readonly Color DefaultColor = Color.WhiteSmoke;
        private static readonly Color ActivatedColor = Color.FromHex("#2E588C");

        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value is bool activated)
                return activated ? ActivatedColor : DefaultColor;

            return DefaultColor;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value is Color color)
            {
                if (color == DefaultColor)
                    return true;
            }

            return false;
        }

Color转换器返回正确的值,但是我不知道如何在运行时在每个视单元的OnTapped()方法中更新模型属性。

当前,这是我的OnTapped()方法:

        private void DocumentViewCell_OnTapped(object sender, EventArgs e)
        {
                // Determine which document was selected
                if (sender.GetType() == typeof(ViewCell))
                {
                    ViewCell selectedViewCell = (ViewCell)sender;

                    if (selectedViewCell.BindingContext != null && selectedViewCell.BindingContext.GetType() == typeof(Document))
                    {
                        Document document = (Document)selectedViewCell.BindingContext;

                        // Update backing field selectedDocument for correct bindings and to show correct detail page
                        if (document != null)
                            selectedDocument = document;
                    }
                }

非常感谢您提供的任何帮助,并感谢到目前为止发表评论的人。

2 个答案:

答案 0 :(得分:1)

我终于有时间为您找到答案。

到现在为止,您已经找到了解决方法,但是由于它与Xamarin.Forms的设计哲学并不完全一致(它在代码背后进行了硬编码工作以获取所需的内容) Xamarin.Forms功能的使用。

无论如何,我认为有两种可能的解决方案,它们与更好的Xamarin.Forms设计相符。

选项1:

此选项跟踪当前选中的项目,并为列表中的每个项目使用一个值转换器,以检查它是否与所选项目相等,并根据该颜色返回颜色。

在我们的ViewModel中,我们需要为ListView的{​​{1}}属性设置集合,以通知我们属性更改,最后还要设置SelectedItem来更改我们的集合。 ItemTappedCommand

SelectedItem

然后,我们将需要一个private ObservableCollection<ItemGroup> _itemGroups; public ObservableCollection<ItemGroup> ItemGroups { get => _itemGroups; set => SetProperty(ref _itemGroups, value); } private Item _selectedItem; public Item SelectedItem { get => _selectedItem; set => SetProperty(ref _selectedItem, value); } private ICommand _itemTappedCommand; public ICommand ItemTappedCommand => _itemTappedCommand ?? (_itemTappedCommand = new Command<Item>((item) => { SelectedItem = item; })); 来检查相等性并返回正确的ValueConverter

Color

此转换器使用少量方法从public class EqualityToColorConverter : IValueConverter { public Color EqualityColor { get; set; } public Color InequalityColor { get; set; } public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { if (value == null || parameter == null) return InequalityColor; if (parameter is Binding binding && binding.Source is View view) { parameter = view.BindingContext; } return value == parameter ? EqualityColor : InequalityColor; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } } 中提取实际项目,因为由于某种原因,它一直向我返回parameter而不是实际的Binding

现在我们已经准备就绪,可以创建页面了:

Item

此解决方案的优点是您不必更改<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:converters="clr-namespace:ColorChangeDemo.Converters" x:Class="ColorChangeDemo.Views.SelectedItemPage"> <ContentPage.Resources> <ResourceDictionary> <converters:EqualityToColorConverter x:Key="equalityToColorConverter" EqualityColor="Green" InequalityColor="Gray" /> </ResourceDictionary> </ContentPage.Resources> <ContentPage.Content> <ListView x:Name="ListView" ItemsSource="{Binding ItemGroups}" GroupShortNameBinding="{Binding Key}" GroupDisplayBinding="{Binding Key}" IsGroupingEnabled="True"> <ListView.GroupHeaderTemplate> <DataTemplate> <ViewCell> <Label Text="{Binding Key}" /> </ViewCell> </DataTemplate> </ListView.GroupHeaderTemplate> <ListView.ItemTemplate> <DataTemplate> <ViewCell> <Button x:Name="Button" Text="{Binding Id}" BackgroundColor="{Binding Source={x:Reference ListView}, Path=BindingContext.SelectedItem, Converter={StaticResource equalityToColorConverter}, ConverterParameter={Binding Source={x:Reference Button}}}" Command="{Binding Source={x:Reference ListView}, Path=BindingContext.ItemTappedCommand}" CommandParameter="{Binding .}"/> </ViewCell> </DataTemplate> </ListView.ItemTemplate> </ListView> </ContentPage.Content> </ContentPage> 类(以防万一您无法对其进行控制),但是缺点是,每次选择更改时,所有项目都会做出反应更改后的值。因此,如果显示很多项目,则可能不是最佳选择。

选项2:

此选项将为Item类添加Selected属性,并跟踪先前选择的项目,以便在选择另一个项目时可以取消选择它。

再次从Item开始,首先是属性:

ViewModel

然后在构造函数中创建命令。为此,我们创建了一个局部变量,该变量可用于在命令中捕获,以便跟踪先前选择的项目:

private ObservableCollection<SelectableItemGroup> _selectableItemGroups;
public ObservableCollection<SelectableItemGroup> SelectableItemGroups
{
    get => _selectableItemGroups;
    set => SetProperty(ref _selectableItemGroups, value);
}

public ICommand ItemTappedCommand { get; }

现在我们需要一个SelectableItem previous = null; ItemTappedCommand = new Command<SelectableItem>((item) => { if (previous != null) previous.Selected = false; previous = item; item.Selected = true; }); ,可以将我们的ValueConverter属性转换为正确的Selected

Color

我们再次进行了所有设置来创建页面:

public class BoolToColorConverter: IValueConverter
{
    public Color TrueColor { get; set; }

    public Color FalseColor { get; set; }

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value != null && value is bool boolValue)
            return boolValue ? TrueColor : FalseColor;
        return FalseColor;
    }

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

此选项的好处是它不会遍历整个列表以确保所有内容都具有正确的<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:converters="clr-namespace:ColorChangeDemo.Converters" x:Class="ColorChangeDemo.Views.DeselectPage"> <ContentPage.Resources> <ResourceDictionary> <converters:BoolToColorConverter x:Key="boolToColorConverter" TrueColor="Green" FalseColor="Gray" /> </ResourceDictionary> </ContentPage.Resources> <ContentPage.Content> <ListView x:Name="ListView" ItemsSource="{Binding SelectableItemGroups}" GroupShortNameBinding="{Binding Key}" GroupDisplayBinding="{Binding Key}" IsGroupingEnabled="True"> <ListView.GroupHeaderTemplate> <DataTemplate> <ViewCell> <Label Text="{Binding Key}" /> </ViewCell> </DataTemplate> </ListView.GroupHeaderTemplate> <ListView.ItemTemplate> <DataTemplate> <ViewCell> <Button x:Name="Button" Text="{Binding Id}" BackgroundColor="{Binding Selected, Converter={StaticResource boolToColorConverter}}" Command="{Binding Source={x:Reference ListView}, Path=BindingContext.ItemTappedCommand}" CommandParameter="{Binding .}"/> </ViewCell> </DataTemplate> </ListView.ItemTemplate> </ListView> </ContentPage.Content> </ContentPage>

演示项目

为了能够查看实际情况,我上传了一个小型演示项目,在其中我实现了两种解决方案:

https://github.com/nknoop/ChangeColorDemo

答案 1 :(得分:0)

(请参阅@Knoop给出的正确答案,以正确的xamarin形式绑定和命令进行操作)

我可以完成我的最初目标,那就是在后面的代码中以编程方式更改每个视单元的背景色,我将发布结果作为答案。

起初,我了解到遍历视单元不是一个好主意,因为它违反了设计模式。因此,我使用了绑定以及自定义的BoolToColor Converter来动态更新视单元的背景色。

这是我编写的代码:

模型类(文档):

   public class Document : BaseModel<int>, IDocument, INotifyPropertyChanged
    {
        #region INotifyPropertyChanged

        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
        {
            var changed = PropertyChanged;

            changed?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        #endregion // INotifyPropertyChanged

        public bool HasDefaultColor
        {
            get => hasDefaultColor;
            set
            {
                hasDefaultColor = value;
                OnPropertyChanged();
            }
        }
    }

转换器

public class BoolToColorConverter : IValueConverter (see question for detailed code...)

xaml中转换器的实现:

<Frame BackgroundColor="{Binding HasDefaultColor, Converter={converters:BoolToColorConverter}}"  Padding="0" HorizontalOptions="FillAndExpand" HasShadow="True">

最后(这是我遇到的问题),在OnTapped()方法中正确更新绑定的viewmodel中的集合:

        private void DocumentViewCell_OnTapped(object sender, EventArgs e)
        {
            try
            {
                // Determine which document was selected
                if (sender.GetType() == typeof(ViewCell))
                {
                    ViewCell selectedViewCell = (ViewCell)sender;

                    if (selectedViewCell.BindingContext != null && selectedViewCell.BindingContext.GetType() == typeof(Document))
                    {
                        Document document = (Document)selectedViewCell.BindingContext;

                        if (document != null)
                        {
                            // Update default color (viewcell) for binded model
                            document.HasDefaultColor = !document.HasDefaultColor;

                            // Update backing field selectedDocument for correct bindings and to show correct detail page

                            ObservableCollection<Grouping<string, Document>> documents = viewModel.GroupedDocuments;
                            foreach (var group in documents)
                            {
                                foreach (var doc in group)
                                {
                                    if (doc.Name == document.Name)
                                    {
                                        doc.HasDefaultColor = document.HasDefaultColor;
                                    }
                                }
                            }

                            viewModel.GroupedDocuments = documents;
                            selectedDocument = document;
                        }
                    }
               }
           }
       }