Привязка универсального объекта (List<T>) к пользовательскому элементу управления Xamarin

Я разрабатываю мобильное приложение с API, которое я могу вызывать для получения данных, большинство конечных точек выгружаются и возвращают следующий объект:

public class PagedResult<T>
{
    public List<T> Data { get; private set; }

    public PagingInfo Paging { get; private set; }

    public PagedResult(IEnumerable<T> items, int pageNo, int pageSize, long totalRecordCount)
    {
        Data = new List<T>(items);
        Paging = new PagingInfo
        {
            PageNo = pageNo,
            PageSize = pageSize,
            TotalRecordCount = totalRecordCount,
            PageCount = totalRecordCount > 0
                ? (int)Math.Ceiling(totalRecordCount / (double)pageSize)
                : 0
        };
    }
}

Я создал элемент управления, который может выбирать постраничные результаты, элемент управления представляет список текущих элементов страницы и позволяет пользователю перемещаться по другим страницам (на фото ниже).

Привязка универсального объекта (List&lt;T&gt;) к пользовательскому элементу управления Xamarin

Я построил доказательство концепции для этого элемента управления, однако я столкнулся с небольшим, но очень раздражающим камнем преткновения.

Мой PagedResult<T> является общим, а PagedItemSource для моего списка — нет. Я создал элемент управления с привязываемыми свойствами: DataTemplate, PagedItemSource и LoadPageCommand. Сам элемент управления определяет, какая страница будет загружена, и сам выполнит эту команду (я могу показать это значение позже, но сейчас оно не нужно).

При создании элемента управления мне пришлось инициализировать PagedResult<T>, однако вместо того, чтобы беспокоиться об дженериках, я просто превратил его в PagedResult<object> и сделал то же самое в своей модели представления. Теперь я нахожусь в том месте, где я хочу, чтобы этот список был универсальным, отчасти причина, по которой я создал этот элемент управления, заключалась в том, чтобы быть универсальным и абстрагировать большую часть ответственности за подкачку к элементу управления. Однако из-за того, что моя модель API использует универсальную модель, я застрял!

Я не могу инициализировать свой элемент управления с помощью Generic T:

public partial class PaginationControl<T>: ContentView
{
    ...
}

Будет означать, что компилятор C# начнет кричать на меня, фактически весь класс сходит с ума. Мои связанные свойства отображают предупреждение: Static field in generic type. Вот почему я просто использовал <object> в качестве типа своего списка, к сожалению, привязка не будет работать в следующем сценарии:

Посмотреть модель

public PagedResult<MyObject> PagedItemSource { get; set; }

Контроль

public static readonly BindableProperty PagedItemSourceProperty = BindableProperty.Create(
    nameof(PagedItemSource),
    typeof(PagedResult<object>),
    typeof(PaginationControl),
    defaultValue: null,
    propertyChanged: (bindable, oldVal, newVal) => ((PaginationControl)bindable).OnPagedItemSourceChanged((PagedResult<object>)newVal)
);

Это будет работать, если данные моей модели представления также имеют тип <object>, однако это бесполезно, потому что теперь мне нужно отслеживать тип моих данных в моей модели представления...

Я придумал хакерский обходной путь, чтобы я мог взломать приложение, но это глупо и кажется грязным, и я ненавижу его...

Мой хак состоит в том, чтобы иметь второе свойство в моей модели представления, которое просто берет текущее PagedResult<T> и преобразует его в PagedResult<object>, а также привязывает это свойство к элементу управления. (Извините, что вам пришлось это прочитать)

Я пошел и просмотрел исходный код Xamarin.Forms, чтобы увидеть, как они сделали это для ListView, поскольку это по сути та же проблема, и их ListView принимает любой общий объект. К сожалению, я нашел src невероятно запутанным и не узнал ничего, что помогло бы решить мою проблему (я видел в ItemView<Cell>, что ItemSource является IEnumerable, но я не мог понять, как они устанавливали T этого...

Любая помощь будет высоко оценена, я, вероятно, смотрю на это неправильно!

Зачем вам когда-либо делать общий элемент управления? ItemsSource свойства всегда необобщенные System.Collections.IEnumerable; там нет параметра типа. Все общее должно быть в модели представления. Привязки XAML имеют «утиный тип»: ItemsSource = "{Binding MyItems}" означает «Мне все равно, какой тип DataContext, просто попробуйте найти в нем любое случайное свойство с именем MyItems. Если оно есть и если значение, возвращаемое этим свойством, реализует System.Collections.IEnumerable либо неявно или явно заполнить себя любыми случайными объектами, которые дает вам перечислитель».

15ee8f99-57ff-4f92-890c-b56153 30.05.2019 17:53

В этом красота и магия XAML.

15ee8f99-57ff-4f92-890c-b56153 30.05.2019 17:54

Привет @ЭдПланкетт. Я понимаю вашу точку зрения, мой элемент управления xaml должен заботиться о элементах, которые к нему привязаны. Моя проблема в том, что, поскольку я абстрагировал свою привязку к PagedResult<T>, я не могу понять, как я могу рекламировать это как действительное связываемое свойство. Используя PagedResult<object> в качестве свойства моего элемента управления, я не могу привязать PagedResult<UserDetails> к элементам управления, мне пришлось бы сначала преобразовать тип в object, что добавляет тот самый шаблон в мою модель представления, которого я пытался избежать, создавая этот элемент управления, который обработал львиную долю пейджинга :/

Axemasta 30.05.2019 18:13

Итак, общие свойства/методы, которые вы добавили в свой элемент управления? Определите их все и удалите из исходного файла. Затем перезапустите Visual Studio, чтобы не было соблазна нажать кнопку «Отменить». Затем напишите общую модель представления, которая делает то, что вам нужно, и сделайте ее DataContext элемента управления. Я делал это довольно много раз. Вы что-то поняли в своем вопросе, когда задались вопросом, смотрите ли вы на это неправильно. Поддерживает ли Xamarin неявные шаблоны данных? Если да, и вы хотите их использовать (они удобны), я покажу вам, как это сделать. Это быстро.

15ee8f99-57ff-4f92-890c-b56153 30.05.2019 18:21
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
4
1 694
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Как уже упоминал Эд Планкетт, вы думаете не о том.

ItemsSource относится к типу IEnumerable, здесь нет общего аргумента, поскольку ListView не заботится о конкретном типе элементов. Это должно относиться и к вашему элементу управления, иначе вы бы не сделали его универсальным.

Так почему бы просто не следовать тому же дизайну, который они реализуют для большинства коллекций, например, List<T> также реализует IList<T> и IList в качестве интерфейсов. последний интерфейс можно использовать, когда вас не интересует общий аргумент, а просто нужен доступ к списку. Следуя этой настройке, мы можем сделать что-то вроде этого:

public interface IPagedResult
{
    IEnumerable Data { get; }

    PagingInfo Paging { get; }

    //any other properties you might need
}

Тогда мы можем объявить PagedResult следующим образом:

public class PagedResult<T> : IPagedResult
{
    public List<T> List { get; private set; }
    public IEnumerable Data => List;

    public PagingInfo Paging { get; private set; }

    public PagedResult(IEnumerable<T> items, int pageNo, int pageSize, long totalRecordCount)
    {
        List = new List<T>(items);
        Paging = new PagingInfo
        {
            PageNo = pageNo,
            PageSize = pageSize,
            TotalRecordCount = totalRecordCount,
            PageCount = totalRecordCount > 0
                ? (int)Math.Ceiling(totalRecordCount / (double)pageSize)
                : 0
        };
    }
}

И теперь вы должны просто объявить свой BindableProperty следующим образом:

public static readonly BindableProperty PagedItemSourceProperty = BindableProperty.Create(
    nameof(PagedItemSource),
    typeof(IPagedResult),
    typeof(PaginationControl),
    defaultValue: null,
    propertyChanged: (bindable, oldVal, newVal) => ((PaginationControl)bindable).OnPagedItemSourceChanged((IPagedResult)newVal)
);

И из этого привязываемого свойства вы можете получить доступ к списку данных, а также к информации о подкачке.

Надеюсь это поможет.

Вау, отличное объяснение. Я просмотрел некоторые элементы управления, которые я сделал в другом проекте, и я использовал IList там, полный идиотский момент от меня! Я реализовал ваши предложения, и все работает именно так, как я задумал! Большое спасибо!

Axemasta 31.05.2019 17:04

Пожалуйста, рад, что помогло. Удачи с проектом!

user10608418 31.05.2019 18:04

Другие вопросы по теме