如何在 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
的属性:这将跟踪当前选定的项目。
然后您将 Frame 的 BackgroundColor
绑定到新属性:SelectedItem
,为此您需要一个接受 SelectedItem
的 ValueConverter 和ConverterParameter
:当前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
)。
将 Frame 的 BackgroundColor 属性绑定到 Selected
属性,并为其设置一个 Converter,该 Converter 从 boolean 值(被选择?)到 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 表单中的视图之间传递数据
Xamarin表单 - 如何使用标题生成iOS ListView本机分组样式?