我需要以编程方式获取列表视图的所有视单元,以便可以更改视单元内特定子布局的背景颜色。 轻按时,更新视单元的颜色没有问题,但是,每当轻按其他视单元时,我都需要将所有视单元的颜色更改为默认颜色。
在寻找解决方案时,我经常会找到答案,通过列表视图的运行时属性访问视单元(请参见下面的代码或此处的第二个答案: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”视单元而不是仅返回标题。 有什么可能的方法可以更改此代码,以便获得预期的结果,还是必须为此使用自定义渲染器?
在这里您可以在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>
我弄清楚了如何使用绑定在模型中存储每个视单元的当前选定背景。目前,我面临的问题是绑定值更改后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()调用中正确存储了有界值。
伙计们,我尝试了一些事情,并陷入了更新模型的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;
}
}
非常感谢您提供的任何帮助,并感谢到目前为止发表评论的人。
答案 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>
。
演示项目
为了能够查看实际情况,我上传了一个小型演示项目,在其中我实现了两种解决方案:
答案 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;
}
}
}
}
}