【问题标题】:Binding Generic Object (List<T>) to Custom Xamarin Control将通用对象 (List<T>) 绑定到自定义 Xamarin 控件
【发布时间】:2019-05-31 22:39:47
【问题描述】:

我正在开发一个移动应用程序,它有一个 API,我可以调用该 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&lt;T&gt; 是通用的,但我的列表视图的PagedItemSource 不是。我构建了具有可绑定属性的控件:DataTemplatePagedItemSourceLoadPageCommand。控件本身决定加载哪个页面并自行执行该命令(稍后我可能会公开此值,但现在不需要)。

在构建控件时,我必须初始化PagedResult&lt;T&gt;,但是我不必担心泛型,而是将其设置为PagedResult&lt;object&gt;,并在我的视图模型中进行了同样的操作。现在,我希望此列表具有通用性,我构建此控件的部分原因是通用性和抽象性,与对控件的分页责任一样多。然而,由于我的 API 模型使用泛型,我陷入了困境!

我无法使用通用 T 初始化我的控件:

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

这意味着 C# 编译器开始对我大喊大叫,实际上整个班级都在发疯。我的绑定属性显示警告:Static field in generic type。这就是为什么我只使用&lt;object&gt; 作为列表类型的原因,不幸的是,绑定在以下情况下不起作用:

查看模型

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

如果我的视图模型的数据也是&lt;object&gt; 类型,它会起作用,但是那没用,因为现在我需要在我的视图模型中跟踪我的数据类型...

我想出了一个 hacky 解决方法,以便我可以继续使用该应用程序,但它很愚蠢,感觉很脏,我讨厌它...

我的技巧是在我的视图模型中有第二个属性,它只接受当前的PagedResult&lt;T&gt; 并将其转换为PagedResult&lt;object&gt;,并将此属性绑定到控件。 (抱歉,您必须阅读此内容)

我去搜索了 Xamarin.Forms 源代码,看看他们是如何为 ListView 做到这一点的,因为这基本上是相同的问题,并且他们的 ListView 接受任何通用对象。不幸的是,我发现 src 非常令人困惑,并且没有学到任何有助于解决我的问题的东西(我在 ItemView&lt;Cell&gt; 中看到 ItemSourceIEnumerable 但我无法弄清楚他们是如何设置 T 的...

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

【问题讨论】:

  • 您为什么要制作通用控件? ItemsSource 属性始终是非泛型的 System.Collections.IEnumerable;那里没有类型参数。任何通用的东西都应该放在视图模型中。 XAML 绑定是“鸭子类型”:ItemsSource="{Binding MyItems}" 表示“我不在乎 DataContext 是什么类型,只需尝试在其上查找名为 MyItems 的任何随机属性。如果有,并且该属性返回的值实现 @ 987654348@ 隐式或显式地用枚举器给你的任何随机对象填充自己”。
  • 这就是 XAML 的美丽和魔力。
  • 嗨@EdPlunkett。我理解你的意思,我的 xaml 控件应该需要关心绑定到它的项目。我的问题是,由于我已将绑定抽象为PagedResult&lt;T&gt;,我无法弄清楚如何将其宣传为有效的可绑定属性。通过使用PagedResult&lt;object&gt; 作为控件上的属性,我无法将PagedResult&lt;UserDetails&gt; 绑定到控件,我必须首先将类型转换为object,这会将样板文件添加到我尝试的视图模型中通过创建处理分页的最大份额的控件来避免:/
  • 好的,那么您添加到控件的通用属性/方法?识别所有这些并将它们从源文件中删除。然后重新启动 Visual Studio,这样您就不会想点击撤消。然后编写一个通用视图模型来满足您的需求,并使其成为控件的 DataContext。我已经这样做了好几次了。当您想知道您是否以错误的方式看待这个问题时,您的问题就出现了。 Xamarin 是否支持隐式数据模板?如果是这样,并且您想使用它们(它们很方便),我将向您展示如何使用它们。这是一个快照。

标签: c# generics xamarin.forms pagination


【解决方案1】:

正如 Ed Plunkett 已经提到的那样,您的想法是错误的。

ItemsSourceIEnumerable 类型,因为 ListView 不关心项目的特定类型,所以那里没有通用参数。您的控件也应该是这种情况,否则您不会使其通用。

那么为什么不遵循它们为大多数集合实现的相同设计,例如List&lt;T&gt; 也将IList&lt;T&gt;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,这真是愚蠢的时刻!我已经实施了您的建议,一切都完全按照我的预期进行!非常感谢!
  • 不客气,很高兴它有帮助。祝项目顺利!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-12-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多