将通用对象(List <t>)绑定到自定义Xamarin控件

时间:2019-05-30 15:13:06

标签: c# generics xamarin.forms pagination

我正在开发一个移动应用程序,该应用程序具有可以调用以获取数据的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
        };
    }
}

我建立了一个可以选择分页结果的控件,该控件显示当前页面项目的列表,并允许用户在其他页面之间移动(如下图所示)。

The pagination control I built

我已经为此控件建立了概念证明,但是我遇到了一个很小但很烦人的绊脚石。

我的PagedResult<T>是通用的,但我的列表视图的PagedItemSource不是通用的。我将控件构建为具有可绑定属性:DataTemplatePagedItemSourceLoadPageCommand。控件本身确定要加载的页面并自行执行该命令(我可能稍后会公开此值,但现在不需要)。

在构建控件时,我必须初始化PagedResult<T>,但是不必担心泛型,我只是将其放入PagedResult<object>,并在视图模型中进行了相同的操作。现在,我要使该列表通用,这是我构建此控件的部分原因是通用和抽象,以及对控件分页的责任。但是由于我的API模型使用泛型,因此我被卡住了!

我无法使用通用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>类型,它将起作用,但这没用,因为现在我需要在视图模型中跟踪数据的类型... < / p>

我想出了一个解决办法,以便可以破解该应用程序,但是它很愚蠢,感觉很脏,我讨厌它...

我的技巧是在视图模型中拥有第二个属性,该属性仅采用当前的PagedResult<T>并将其转换为PagedResult<object>,并将此属性绑定到控件。 (对不起,您必须阅读此书)

我去了Xamarin.Forms源代码拖网,以查看他们如何为ListView完成此操作,因为这本质上是相同的问题,并且它们的ListView接受任何通用对象。不幸的是,我发现src令人迷惑不解,没有学到任何东西可以帮助解决我的问题(我在ItemView<Cell>中看到ItemSourceIEnumerable,但我不知道他们是如何设置{{ 1}} ...

任何帮助将不胜感激,我可能是用错误的方式看待这个问题!

1 个答案:

答案 0 :(得分:2)

正如Ed Plunkett所说,您正在考虑错误的方式。

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)
);

从此可绑定属性中,您可以访问数据列表以及分页信息。

希望这会有所帮助。