【问题标题】:How to set color only for the selected frame in collection view using MVVM in Xamarin forms?如何在 Xamarin 表单中使用 MVVM 仅为集合视图中的选定框架设置颜色?
【发布时间】: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。我不想在后面使用代码。我不知道如何解决这个问题有什么建议吗?

【问题讨论】:

    标签: c# xamarin xamarin.forms xamarin.android frame


    【解决方案1】:

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

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


    解决问题的一种方法是:

    1. 在您的 ViewModel 中定义一个名为 SelectedItem 的属性:这将跟踪当前选定的项目。
    2. 然后您将 FrameBackgroundColor 绑定到新属性:SelectedItem,为此您需要一个接受 SelectedItemValueConverterConverterParameter:当前Frame
    3. Frame 内,在 StackLayout 中有 good old TapGestureRecognizer,其处理程序在调用时将设置所选项目
    4. 当设置了 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】:

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

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


      实现目标的一种方法是:

      1. 将名为 Selected 或类似名称的属性添加到 Object 中,该属性正在填充绑定到您的 CollectionView 的 Collection (samplelist)。
      2. FrameBackgroundColor 属性绑定到 Selected 属性,并为其设置一个 Converter,该 Converterboolean 值(被选择?)到 Color选择颜色)。
      3. 然后,当点击集合中的一个项目并触发 TapGestureRecognizer 时,您可以将所选项目作为 CommandParameter 传递给 Command
      4. 在命令处理程序中,将传递的项目的Selected 属性设置为true
      5. 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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2021-09-05
        • 1970-01-01
        • 2018-12-10
        • 1970-01-01
        • 2016-09-29
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多