如何在 Xamarin 表单中使用 MVVM 仅为集合视图中的选定框架设置颜色?

Posted

技术标签:

【中文标题】如何在 Xamarin 表单中使用 MVVM 仅为集合视图中的选定框架设置颜色?【英文标题】:How to set color only for the selected frame in collection view using MVVM in Xamarin forms? 【发布时间】:2021-11-22 01:54:44 【问题描述】:

我正在使用 RelativeSource 绑定 为集合视图中的框架绑定 背景颜色。但是集合视图中所有框架的背景颜色都在变化。我需要仅为我选择的框架设置背景颜色

这是我的 xaml 代码

<StackLayout Padding="10">
    <CollectionView x:Name="list" ItemsSource="Binding samplelist">
        <CollectionView.ItemsLayout>
            <GridItemsLayout Orientation="Vertical" Span="2" HorizontalItemSpacing="10" VerticalItemSpacing="10" />
        </CollectionView.ItemsLayout>
        <CollectionView.ItemTemplate>
            <DataTemplate>
                <StackLayout>
                    <VisualStateManager.VisualStateGroups>
                        <VisualStateGroup Name="CommonStates">                        
                            <VisualState Name="Selected">
                                <VisualState.Setters>
                                    <Setter Property="BackgroundColor" Value="Green" />
                                </VisualState.Setters>
                            </VisualState>
                        </VisualStateGroup>
                    </VisualStateManager.VisualStateGroups>
                    <Frame  CornerRadius="10"  HasShadow="False" BackgroundColor="Binding BackgroundTest,Mode=TwoWay, Converter=StaticResource colorConverter" HeightRequest="75" Margin="5,0,0,0" >
                        <StackLayout Orientation="Vertical">
                            <StackLayout.GestureRecognizers>
                                <TapGestureRecognizer Command="Binding Source=x:Reference test, Path=BindingContext.TriggerScene"
                                                              CommandParameter="Binding ."/>
                            </StackLayout.GestureRecognizers>

这是我在 Viewmodel

中的代码
public bool FrameColorChange=true;
private Color _backgroundTest;
public Color BackgroundTest

    get  return _backgroundTest;        
    set
    
        if (value == _backgroundTest)
            return;
    
        _backgroundTest = value;
        OnPropertyChanged(nameof(BackgroundTest));
    

private async void TriggerScene(Scene scene)


    if (FrameColorChange==true)
    
        BackgroundTest = Color.Gray;
        FrameColorChange = false;
    
    else
    
        BackgroundTest = Color.White;
        FrameColorChange = true;
    

我已经完成了一些修复,例如

how to access child elements in a collection view?

但没有任何帮助。我也尝试了 SelectionChanged 事件。但是 SelectionChanged 的​​问题是它没有正确触发,因为我的框架中有 TapGestureRecognizer。我想在我的 TriggerScene 命令 中为所选帧设置颜色绑定 我的 viewmodel 中的 TapGestureRecognizer。我不想在后面使用代码。我不知道如何解决这个问题有什么建议吗?

【问题讨论】:

【参考方案1】:

我已经在另一个答案中发布了解决您问题的一种方法,现在我想提供一个更简单的解决方案(尽管我不会声称它是最好的)。

注意,在这个解决方案中,与我的其他答案相反,您不需要向集合视图中的对象添加多余的属性,但新属性直接定义在ViewModel。


解决问题的一种方法是:

    在您的 ViewModel 中定义一个名为 SelectedItem 的属性:这将跟踪当前选定的项目。 然后您将 FrameBackgroundColor 绑定到新属性:SelectedItem,为此您需要一个接受 SelectedItemValueConverterConverterParameter:当前Frame。 在 Frame 内,在 StackLayout 中有 good old TapGestureRecognizer,其处理程序在调用时将设置所选项目。 当设置了 SelectedItem 时,将调用 OnPropertyChanged 并为 CollectionView 中的每个项目调用 ValueConverter。转换器然后检查 Frame 的 BindingContext(它绑定到的项目!)是否与 SelectedItem 相同,如果是,则将其颜色设置为 Gray(选中!)

当然,下面我添加了一个完整的最小工作示例。随意复制粘贴并使用它。

Page1.xaml

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="App1.Page1"
             x:Name="test"
             xmlns:local="clr-namespace:App1">
    
    <ContentPage.BindingContext>
        <local:ViewModel/>
    </ContentPage.BindingContext>

    <ContentPage.Resources>
        <ResourceDictionary>
            <local:SelectedToColorConverter x:Key="selectedToColorConverter"/>
        </ResourceDictionary>
    </ContentPage.Resources>
    
    <ContentPage.Content>
        <StackLayout Padding="10">
            <CollectionView ItemsSource="Binding samplelist">
                
                <CollectionView.ItemsLayout>
                    <GridItemsLayout Orientation="Vertical" Span="2" HorizontalItemSpacing="10" VerticalItemSpacing="10" />
                </CollectionView.ItemsLayout>
                
                <CollectionView.ItemTemplate>
                    <DataTemplate>
                        <StackLayout>

                            <Frame x:Name="frame" CornerRadius="10"  
                                    HasShadow="False" 
                                    BackgroundColor="Binding Source=x:Reference test, Path=BindingContext.SelectedItem, Converter=x:StaticResource selectedToColorConverter, ConverterParameter=x:Reference frame" 
                                    HeightRequest="75" 
                                    Margin="5,0,0,0" >
                                <StackLayout Orientation="Vertical">
                                    <StackLayout.GestureRecognizers>
                                        <TapGestureRecognizer Command="Binding Source=x:Reference test, Path=BindingContext.TriggerSceneCommand" CommandParameter="Binding ."/>
                                    </StackLayout.GestureRecognizers>
                                    <Label Text="Binding Text"/>
                                    <Label Text="Binding Description"/>
                                </StackLayout>
                            </Frame>
                            
                        </StackLayout>
                    </DataTemplate>
                </CollectionView.ItemTemplate>
            </CollectionView>
        </StackLayout>

    </ContentPage.Content>
</ContentPage>

ViewModel.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Runtime.CompilerServices;
using Xamarin.Forms;

namespace App1

    public class ViewModel : INotifyPropertyChanged
    

        public ViewModel()
        
            samplelist = new List<item> 
             
                new item  Text = "Uno", Description = "Uno Description bla bla" ,
                new item  Text = "Dos", Description = "Dos Description bla bla" ,
                new item  Text = "Tres", Description = "Tres Description bla bla" 
            ;

            TriggerSceneCommand = new Command<item>(TriggerScene);
        

        public List<item> samplelist  get; set; 


        private item _selectedItem;
        public item SelectedItem
        
            get => _selectedItem;
            set
            
                _selectedItem = value;
                OnPropertyChanged();
            
        



        public Command TriggerSceneCommand  get; set; 
        private void TriggerScene(item newSelectedItem)
        
           SelectedItem = newSelectedItem;
        


        public event PropertyChangedEventHandler PropertyChanged;
        public void OnPropertyChanged([CallerMemberName] string name = "")
        
            var propertyChanged = PropertyChanged;

            propertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        

    

    public class item
    

        public String Text  get; set; 

        public String Description  get; set; 

    


    public class SelectedToColorConverter : IValueConverter
    
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        

            Color result = Color.White;


            if (value != null && parameter != null && ((Frame)parameter).BindingContext != null && (item)value == (item)((Frame)parameter).BindingContext)
            
                result = Color.Gray;
            


            return result;

        

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        
            return null;
        
    


为什么不,让我们在这里放一个奖金

您可以在 TapGestureRecognizer 的事件处理程序中添加两行代码,以便在一段时间(可能是三秒?)后返回原始颜色。

只需将 ViewModel 中的TriggerScene 方法更改如下(参见代码中的 cmets):

private void TriggerScene(item newSelectedItem)

    // Highlight selection!
    SelectedItem = newSelectedItem;

    // Sit and wait...
    await Task.Delay(3000);

    // Go back to normal!
    SelectedItem = null;

【讨论】:

【参考方案2】:

你可以试试下面的代码。

Xaml:

     <StackLayout Padding="10">
        <CollectionView x:Name="list" ItemsSource="Binding samplelist"  SelectionMode="Single"  SelectionChanged="list_SelectionChanged" >
            <CollectionView.ItemsLayout>
                <GridItemsLayout Orientation="Vertical" Span="2" HorizontalItemSpacing="10" VerticalItemSpacing="10" />
            </CollectionView.ItemsLayout>
            <CollectionView.ItemTemplate>
                <DataTemplate>

                    <Frame  CornerRadius="10"  HasShadow="False" BackgroundColor="Binding BackgroundTest" HeightRequest="75" Margin="5,0,0,0" >
                        <StackLayout Orientation="Vertical">
                            <Label Text="Binding str"></Label>
                         
                        </StackLayout>
                    </Frame>

                </DataTemplate>
            </CollectionView.ItemTemplate>
        </CollectionView>
    </StackLayout>

后面的代码:

   public Page2()
    
        InitializeComponent();
        this.BindingContext = new MyViewModel();
    

    private void list_SelectionChanged(object sender, SelectionChangedEventArgs e)
    
        MyModel previous = e.PreviousSelection.FirstOrDefault() as MyModel;
        MyModel current = e.CurrentSelection.FirstOrDefault() as MyModel;

        //Set the current to the color you want
        current.BackgroundTest = "Red";


        if (previous != null)
        
            //Reset the previous to defaulr color
            previous.BackgroundTest = "Gray";
          
    

视图模型:

public class MyViewModel

    public ObservableCollection<MyModel> samplelist  get; set; 
    public MyViewModel()
    
        samplelist = new ObservableCollection<MyModel>()
        
            new MyModel() BackgroundTest="Gray", str="hello1",
            new MyModel() BackgroundTest="Gray", str="hello2",
            new MyModel() BackgroundTest="Gray", str="hello3",
            new MyModel() BackgroundTest="Gray", str="hello4",
            new MyModel() BackgroundTest="Gray", str="hello5",
            new MyModel() BackgroundTest="Gray", str="hello6",
            new MyModel() BackgroundTest="Gray", str="hello7",
            new MyModel() BackgroundTest="Gray", str="hello8",
        ;
    

型号:

public class MyModel : INotifyPropertyChanged


    public string str  get; set; 
    private string _backgroundTest;
    public string BackgroundTest
    
        get  return _backgroundTest; 
        set
        
            _backgroundTest = value;
            OnPropertyChanged("BackgroundTest");
        
    

    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = "")
    
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    

更新:

如果您在 DataTemplate 中有 TapGestureRecognizer,则可以使用 VisualState 而不是 CollectionView 的 SelectionChanged

   <ContentPage.Resources>
    <ResourceDictionary>
        <Style TargetType="StackLayout">
            <Setter Property="VisualStateManager.VisualStateGroups">
                <VisualStateGroupList>
                    <VisualStateGroup>
                        <VisualState x:Name="Selected">
                            <VisualState.Setters>
                                <Setter Property="BackgroundColor" Value="Accent" />
                            </VisualState.Setters>
                        </VisualState>
                        <VisualState x:Name="UnSelected">
                            <VisualState.Setters>
                                <Setter Property="BackgroundColor" Value="Blue" />
                            </VisualState.Setters>
                        </VisualState>
                    </VisualStateGroup>
                </VisualStateGroupList>
            </Setter>
        </Style>
    </ResourceDictionary>
</ContentPage.Resources>

Xaml:

   <StackLayout.GestureRecognizers>
                                <TapGestureRecognizer Tapped="TapGestureRecognizer_Tapped"></TapGestureRecognizer>
                            </StackLayout.GestureRecognizers>

后面的代码:

    StackLayout lastElementSelected;
    private void TapGestureRecognizer_Tapped(object sender, EventArgs e)
    

        if (lastElementSelected != null)
            VisualStateManager.GoToState(lastElementSelected, "UnSelected");

        VisualStateManager.GoToState((StackLayout)sender, "Selected");

        lastElementSelected = (StackLayout)sender;

    

【讨论】:

SelectionChanged 的​​问题是它没有正确触发,因为我的框架中有 TapGestureRecognizer。我想要 TapGestureRecognizer 的 textselected 命令中所选框架的颜色绑定。【参考方案3】:

可能有很多方法可以解决您的问题,我不会声称找到了最好的方法,但这仍然是一种(简单)方法。

我将为您的下方添加一个完整的工作最小示例,它完全符合您的要求,因此请随意复制粘贴并根据您的需要进行调整。


实现目标的一种方法是:

    将名为 Selected 或类似名称的属性添加到 Object 中,该属性正在填充绑定到您的 CollectionView 的 Collection (samplelist)。 将 FrameBackgroundColor 属性绑定到 Selected 属性,并为其设置一个 Converter,该 Converterboolean 值(被选择?)到 Color选择颜色)。 然后,当点击集合中的一个项目并触发 TapGestureRecognizer 时,您可以将所选项目作为 CommandParameter 传递给 Command 在命令处理程序中,将传递的项目的Selected 属性设置为true。 当Selected 属性改变时,Converter 被调用,BackgroundColor 属性被更新。

以下示例说明了这一点:

Page1.xaml

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="App1.Page1"
             x:Name="test"
             xmlns:local="clr-namespace:App1">
    
    <ContentPage.BindingContext>
        <local:ViewModel/>
    </ContentPage.BindingContext>

    <ContentPage.Resources>
        <ResourceDictionary>
            <local:SelectedToColorConverter x:Key="selectedToColorConverter"/>
        </ResourceDictionary>
    </ContentPage.Resources>
    
    <ContentPage.Content>
        <StackLayout Padding="10">
            <CollectionView ItemsSource="Binding samplelist">
                
                <CollectionView.ItemsLayout>
                    <GridItemsLayout Orientation="Vertical" Span="2" HorizontalItemSpacing="10" VerticalItemSpacing="10" />
                </CollectionView.ItemsLayout>
                
                <CollectionView.ItemTemplate>
                    <DataTemplate>
                        <StackLayout>

                            <Frame  CornerRadius="10"  
                                    HasShadow="False" 
                                    BackgroundColor="Binding Selected, Converter=x:StaticResource selectedToColorConverter" 
                                    HeightRequest="75" 
                                    Margin="5,0,0,0" >
                                <StackLayout Orientation="Vertical">
                                    <StackLayout.GestureRecognizers>
                                        <TapGestureRecognizer Command="Binding Source=x:Reference test, Path=BindingContext.TriggerSceneCommand" CommandParameter="Binding ."/>
                                    </StackLayout.GestureRecognizers>
                                    <Label Text="Binding Text"/>
                                    <Label Text="Binding Description"/>
                                </StackLayout>
                            </Frame>
                            
                        </StackLayout>
                    </DataTemplate>
                </CollectionView.ItemTemplate>
            </CollectionView>
        </StackLayout>

    </ContentPage.Content>
</ContentPage>

ViewModel.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Runtime.CompilerServices;
using Xamarin.Forms;

namespace App1

    public class ViewModel
    

        public ViewModel()
        
            samplelist = new List<item> 
             
                new item  Text = "Uno", Description = "Uno Description bla bla" ,
                new item  Text = "Dos", Description = "Dos Description bla bla" ,
                new item  Text = "Tres", Description = "Tres Description bla bla" 
            ;

            TriggerSceneCommand = new Command<object>(TriggerScene);
        

        public List<item> samplelist  get; set; 


        public Boolean isMultiSelect = false;
        

        public Command TriggerSceneCommand  get; set; 
        private void TriggerScene(object selectedItem)
        
            ((item)selectedItem).Selected = !((item)selectedItem).Selected;

            if (!isMultiSelect)
            
                foreach (item otherItem in samplelist)
                
                    if (otherItem != selectedItem)
                    
                        otherItem.Selected = false;
                    
                
            
        

    

    public class item : INotifyPropertyChanged
    

        public Boolean _selected;
        public Boolean Selected 
        
            get 
            
                return _selected;
            
            set
            
                _selected = value;
                OnPropertyChanged();
            
        

        public String Text  get; set; 

        public String Description  get; set; 

        public event PropertyChangedEventHandler PropertyChanged;
        public void OnPropertyChanged([CallerMemberName] string name = "")
        
            var propertyChanged = PropertyChanged;

            propertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        

    


    public class SelectedToColorConverter : IValueConverter
    
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        
            return ((Boolean)value) ? Color.Gray : Color.White;
        

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


请注意,作为 奖励,有一个名为 isMultiSelect 的属性,如果为 true,则允许标记/着色多个项目,如果为 false,则当一个项目被选中,所有其他人将其Selected 属性设置为false

【讨论】:

以上是关于如何在 Xamarin 表单中使用 MVVM 仅为集合视图中的选定框架设置颜色?的主要内容,如果未能解决你的问题,请参考以下文章

如何将相同的viewmodel设置为xamarin表单中的新mvvm中的两个视图

使用 mvvm 在 xamarin 表单中的视图之间传递数据

使用mvvm在xamarin表单中的视图之间传递数据

Xamarin表单 - 如何使用标题生成iOS ListView本机分组样式?

关于xamarin.forms在MVVM情况下如何DisplayActionSheet

Xamarin MVVM 从另一个页面删除 Listview 项目