Xamarin Forms 中数据模板视图单元格内的绑定上下文

Posted

技术标签:

【中文标题】Xamarin Forms 中数据模板视图单元格内的绑定上下文【英文标题】:Binding Context inside Data Template view cell in Xamarin Forms 【发布时间】:2019-03-13 03:16:35 【问题描述】:

我在列表视图中显示了一个自定义单元格。它应该工作,但我担心它工作,我不明白为什么。

让我为你安排一下,因为它有点复杂。

基本上,我会在顶部显示一个带有搜索字段的联系人列表。我还有其他各种超出此问题范围的内容要显示,但为了清楚起见,您会在整个代码中看到它们,特别是在 xaml 和数据模板选择器中。

我正在使用不同类型的自定义单元格来显示我的联系人列表的每个部分(有一个标题单元格、一个搜索单元格等等)。

这里,ContactsPage 持有列表视图和数据模板的声明。

<ContentPage>
    <ContentPage.Resources>
        <ResourceDictionary>


            <DataTemplate x:Key="HeaderTemplate">
                <ViewCell>
                    <StackLayout>
                        <local:HeaderView/>    
                    </StackLayout>
                </ViewCell>
            </DataTemplate>


            <DataTemplate x:Key="SearchTemplate">
                <local:SearchCell/>                 //<=== Important
            </DataTemplate>


            <DataTemplate x:Key="CategoryTemplate">
                <ViewCell
                    x:Name="CategoryCell">
                    <Label
                        Text="CategoryCell" ></Label>
                </ViewCell>
            </DataTemplate>


            <DataTemplate x:Key="SelectionTemplate">
                <ViewCell
                    x:Name="SelectionCell">
                    <Label
                        Text="Selection Cell" ></Label>
                </ViewCell>
            </DataTemplate>


            <DataTemplate x:Key="ContactTemplate">
                <ViewCell
                    x:Name="ContactCell">
                    <Label
                        Text="Binding FirstName" ></Label>
                </ViewCell>
            </DataTemplate>

            <local:ContactDataTemplateSelector x:Key="TemplateSelector"
                                              HeaderTemplate="StaticResource HeaderTemplate"
                                              SearchTemplate="StaticResource SearchTemplate"
                                              CategoryTemplate="StaticResource CategoryTemplate"
                                              SelectionTemplate="StaticResource SelectionTemplate"
                                              ContactTemplate="StaticResource ContactTemplate"/>
        </ResourceDictionary>
    </ContentPage.Resources>

你看我有各种各样的数据模板,每个都有自己的用途。标头正在工作,其余的正在进行中,我关心的唯一搜索实现。从单元格到视图模型,再到数据模板。

现在这只是资源,这是实际的页面 UI(仅相关代码)

<ContentPage.Content>
   ... Content of the page, including the actual listview
            <ListView 
                x:Name="ContactsListView"
                HasUnevenRows="True""
                ItemTemplate="StaticResource TemplateSelector"
                ItemsSource="Binding ListSource">
            </ListView>
    </ContentPage.Content>

让我在该视图的视图模型中带您了解这背后的逻辑。视图本身的代码隐藏不做任何事情,这里是该联系人列表的 ViewModel。

public class ContactsViewModel : BaseViewModel, IContactsViewModel
    
        readonly IContactsService _service;
        List<object> _listSource;

        public List<object> ListSource
        
            get => _listSource;
            private set
            
                _listSource = value; 
                OnPropertyChanged();
            
        

        public string CurrentText => "HelloX";             //<=== Important
        readonly ISearchViewModel _searchViewModel;
        readonly ICategoryFilterViewModel _categoryFilterViewModel;
        readonly ISelectionViewModel _selectionViewModel;

        public ContactsViewModel()
        
            _service = new();

            HeaderViewModel = new HeaderViewModel();

            _searchViewModel = new();
            _categoryFilterViewModel = new();
            _selectionViewModel = new();

            ListSource = GenerateDefaultList();
        

        public async Task LoadContacts()       //Called when UI appears.
        
            await _service.LoadContacts();

            var list = GenerateDefaultList();
            list.AddRange(_service.Contacts);

            ListSource = list;
        

        List<object> GenerateDefaultList()
        
            return new List<object>()
            
                HeaderViewModel,
                _searchViewModel,              //<===== important
                _categoryFilterViewModel,
                _selectionViewModel
            ;
        
    

为了清楚起见,我包含了大部分代码;我想强调的重要部分是ListSource 里面已经有一些视图模型。我正在使用该源来填充我的列表视图,对象的类型定义了我要使用的数据模板类型。这是在 DataTemplate 选择器中完成的,这里是:

public class ContactDataTemplateSelector : DataTemplateSelector
    
        public DataTemplate ContactTemplate  get; set; 
        public DataTemplate HeaderTemplate  get; set; 
        public DataTemplate SearchTemplate  get; set; 
        public DataTemplate CategoryTemplate  get; set; 
        public DataTemplate SelectionTemplate  get; set; 

        protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
                    
            switch (item)
            
                case HeaderViewModel _:
                    return HeaderTemplate;
                case SearchViewModel _:
                    return SearchTemplate;            //<==== important
                case CategoryFilterViewModel _:
                    return CategoryTemplate;
                case SelectionViewModel _:
                    return SelectionTemplate;
                default:
                    return ContactTemplate;
            
        
    

所以我确实有一个SearchViewModel 的实例(对我的问题来说唯一重要的一个),但它从来没有说过搜索数据模板的 ViewCell 实际上使用了 SearchViewModel。我只是将它用作我的if 语句的条件。

这是数据模板中使用的搜索单元格(它本身是通过数据模板选择器选择的)

<ViewCell x:Class="MYNAMESPACE.SearchCell">
    <AbsoluteLayout>
        <Frame>
            <StackLayout>
                <Entry
                    Placeholder="Binding PlaceHolderText"/>
                <Button
                    Text="Binding CurrentText"
                    Command="Binding SearchCommand"/>
            </StackLayout>
        </Frame>
    </AbsoluteLayout>
</ViewCell>

我在不冒掩盖上下文的风险的情况下尽可能多地删除。我知道这是一堵代码墙,但我相信如果您决定进行调查,它会很有用。

据我了解,我从不向我的自定义 ViewCell(搜索单元)提供绑定上下文。我里面确实有绑定,值得注意的是,我的工作示例是CurrentText。我在我的SearchViewModel 中有它作为文本@

public class SearchViewModel : ISearchViewModel
    
        public string CurrentText => "<TODO SEARCH>";     //<=== Important

        //NotifyPropertyChanged Implementation
    

我在ContactsViewModel 中有另一个CurrentText,但运行时显示的文本是来自SearchViewModel 的文本。我看到的是“”而不是“HelloX”。这就是我要的。我只是不明白单元格如何/为什么使用我的视图模型。

我只使用 viewmodel 来选择要显示的数据模板,没有将 viewmodel 设置为该数据模板的绑定上下文,也没有 viewcell。还是我?绑定上下文来自哪里?

【问题讨论】:

【参考方案1】:

感谢您提供详细的问题,我很确定我正在关注您所做的事情,所以希望这会有所帮助:

如果没有另外指定,页面上每个元素的 BindingContext 都是基于其父元素设置的。我相信您可能已经看到,当您为页面设置 BindingContext 时,它会向下流向该页面的所有元素,除非您明确覆盖它。 因此,对于列表,每个项目的 BindingContext 都会自动设置为其对应的 ItemsSource 对象。因此,当您从模板选择器返回 SearchTemplate 时,它​​的所有元素都将继承该项目的绑定上下文,在这种情况下,它是您在 GenerateDefaultList 期间创建并放入 ListSource 的 SearchViewModel 实例。

【讨论】:

好的,有道理!我从来没有意识到元素的绑定上下文是基于父级的;我通常手动指定所有内容,从未有过这么“复杂”的东西。我很高兴它以这种方式工作,为我节省了很多麻烦来弄清楚如何手动给这个坏男孩一个绑定上下文。 (感谢您的支持:P 我花了很多时间试图尽可能清楚!很高兴它有帮助) 谢谢帮我省了很多痛苦 :D 问题是如何引用GenerateDefaultList中没有的东西?你可以确定,因为它们在绑定上下文中。

以上是关于Xamarin Forms 中数据模板视图单元格内的绑定上下文的主要内容,如果未能解决你的问题,请参考以下文章

ListView 中突出显示的选定项 Xamarin.Forms

设置列表视图项模板的背景颜色和列表背景颜色时,Xamarin Forms Listview 选定项背景颜色丢失

将 UIDatePicker 添加到单元格内容视图

Xamarin.Forms 如何将数据从 CollectionView 传输到不同的视图?

Xamarin XAML语言教程模板视图TemplatedView

使用后台代码触发时,Xamarin.forms 将变量传递给点击手势