Я разрабатываю мобильное приложение с 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
};
}
}
Я создал элемент управления, который может выбирать постраничные результаты, элемент управления представляет список текущих элементов страницы и позволяет пользователю перемещаться по другим страницам (на фото ниже).
Я построил доказательство концепции для этого элемента управления, однако я столкнулся с небольшим, но очень раздражающим камнем преткновения.
Мой 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
этого...
Любая помощь будет высоко оценена, я, вероятно, смотрю на это неправильно!
В этом красота и магия XAML.
Привет @ЭдПланкетт. Я понимаю вашу точку зрения, мой элемент управления xaml должен заботиться о элементах, которые к нему привязаны. Моя проблема в том, что, поскольку я абстрагировал свою привязку к PagedResult<T>
, я не могу понять, как я могу рекламировать это как действительное связываемое свойство. Используя PagedResult<object>
в качестве свойства моего элемента управления, я не могу привязать PagedResult<UserDetails>
к элементам управления, мне пришлось бы сначала преобразовать тип в object
, что добавляет тот самый шаблон в мою модель представления, которого я пытался избежать, создавая этот элемент управления, который обработал львиную долю пейджинга :/
Итак, общие свойства/методы, которые вы добавили в свой элемент управления? Определите их все и удалите из исходного файла. Затем перезапустите Visual Studio, чтобы не было соблазна нажать кнопку «Отменить». Затем напишите общую модель представления, которая делает то, что вам нужно, и сделайте ее DataContext элемента управления. Я делал это довольно много раз. Вы что-то поняли в своем вопросе, когда задались вопросом, смотрите ли вы на это неправильно. Поддерживает ли Xamarin неявные шаблоны данных? Если да, и вы хотите их использовать (они удобны), я покажу вам, как это сделать. Это быстро.
Как уже упоминал Эд Планкетт, вы думаете не о том.
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
там, полный идиотский момент от меня! Я реализовал ваши предложения, и все работает именно так, как я задумал! Большое спасибо!
Пожалуйста, рад, что помогло. Удачи с проектом!
Зачем вам когда-либо делать общий элемент управления?
ItemsSource
свойства всегда необобщенныеSystem.Collections.IEnumerable
; там нет параметра типа. Все общее должно быть в модели представления. Привязки XAML имеют «утиный тип»:ItemsSource = "{Binding MyItems}"
означает «Мне все равно, какой тип DataContext, просто попробуйте найти в нем любое случайное свойство с именем MyItems. Если оно есть и если значение, возвращаемое этим свойством, реализуетSystem.Collections.IEnumerable
либо неявно или явно заполнить себя любыми случайными объектами, которые дает вам перечислитель».