如何在C#中向TableView添加一个包含Grid的ViewCell

13

我正在构建一个动态的TableView。到目前为止,我的代码是这样的:

var section = new TableSection("Available Categories");
foreach (var category in categoryGroups)
{
   var name = (string)category.Name;
   var cell = new TextCell { Text = name };
   section.Add(cell);
}
tableView.Root.Add(section);

这个方法是可行的,但是我希望使用一个ViewCell,并且该ViewCell包含与我现在在XAML中使用的网格相同的结构:

< ViewCell >
   < Grid VerticalOptions = "CenterAndExpand" Padding = "20, 0" >
      < Grid.ColumnDefinitions >
         < ColumnDefinition Width = "*" />
         < ColumnDefinition Width = "Auto" />
         < ColumnDefinition Width = "20" />
      </ Grid.ColumnDefinitions >
      < Label Style = "{DynamicResource ListItemTextStyle}" Grid.Column = "0" HorizontalOptions = "StartAndExpand" Text = "{Binding Name}" />
      < Label Style = "{DynamicResource ListItemTextStyle}" Grid.Column = "1" HorizontalOptions = "End" XAlign = "End" Text = "{Binding TotalWordCount}" VerticalOptions = "Center" TextColor = "Gray" />
      < Label Grid.Column = "2" Text = "{x:Static local:FontAwesome.FACheck}" HorizontalTextAlignment = "End" HorizontalOptions = "End" FontFamily = "FontAwesome" XAlign = "Center" FontSize = "13" IsVisible = "{Binding IsToggled}" TextColor = "#1E90FF" />
   </ Grid >
</ ViewCell >

有人能给我建议如何将此添加到我的C#代码中。 我只知道如何在XAML中实现它。

注意

这是我了解动态样式的地方:

https://developer.xamarin.com/guides/xamarin-forms/user-interface/styles/device/


1
这句话的意思是什么:“ListView.Header look like the header areas on the settings pages”?你只需要改变颜色还是还有其他的要求?你尝试过改变Stack Layout的颜色吗? - Yuri S
嗨Yuri,如果可能的话,我想用iOS渲染器自定义它。我想改变标题而不是在其中添加新的stackLayout。 - user1464139
如果其他答案不足够,请告诉我,或者更好的是在这里查看 https://developer.xamarin.com/guides/xamarin-forms/application-fundamentals/custom-renderer/listview/ 这个渲染器并不简单,我会尽量避免使用它。 - Yuri S
1个回答

8

选项1:在C#中定义自定义ViewCell

以下是您分享的XAML模板的C#等效代码:

public class CustomViewCell : ViewCell
{
    public CustomViewCell()
    {
        var label1 = new Label
        {
            HorizontalOptions = LayoutOptions.StartAndExpand
        };

        //or, label1.Style = Device.Styles.ListItemTextStyle;
        label1.SetDynamicResource(VisualElement.StyleProperty, "ListItemTextStyle");
        Grid.SetColumn(label1, 0);
        label1.SetBinding(Label.TextProperty, "Name");

        var label2 = new Label
        {
            HorizontalOptions = LayoutOptions.End,
            //XAlign = TextAlignment.End, //not needed
            VerticalOptions = LayoutOptions.Center,
            TextColor = Color.Gray
        };

        //or, label2.Style = Device.Styles.ListItemTextStyle;
        label2.SetDynamicResource(VisualElement.StyleProperty, "ListItemTextStyle");
        Grid.SetColumn(label2, 1);
        label2.SetBinding(Label.TextProperty, "TotalWordCount");

        var label3 = new Label
        {
            HorizontalOptions = LayoutOptions.End,
            HorizontalTextAlignment = TextAlignment.End,
            VerticalOptions = LayoutOptions.Center,
            //XAlign = TextAlignment.Start, //not needed
            FontFamily = "FontAwesome",
            FontSize = 13,
            TextColor = Color.FromHex("#1E90FF"),
            Text = FontAwesome.FACheck,
        };
        Grid.SetColumn(label3, 2);
        label3.SetBinding(VisualElement.IsVisibleProperty, "IsToggled");

        var grid = new Grid
        {
            VerticalOptions = LayoutOptions.CenterAndExpand,
            Padding = new Thickness(20, 0),
            ColumnDefinitions = new ColumnDefinitionCollection()
            {
                new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star) },
                new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Auto) },
                new ColumnDefinition() { Width = new GridLength(20) },
            },
            Children = {
                label1,
                label2,
                label3
            }
        };

        View = grid;
    }
}

选项二:在XAML中定义自定义的ViewCell

即使您是动态创建TableView,您仍然可以使用基于XAML的方法。只需按以下方式创建一个新的XAML控件:

示例ViewCell XAML

<ViewCell 
    xmlns="http://xamarin.com/schemas/2014/forms" 
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
    x:Class="AppNamespace.MyViewCell">
    <Grid VerticalOptions="CenterAndExpand" Padding = "20, 0" >
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="75" />
        </Grid.ColumnDefinitions>
        <Label Grid.Column = "0" HorizontalOptions = "StartAndExpand" Text = "{Binding Name}" />
        <Label Grid.Column = "1" HorizontalOptions = "End" XAlign = "End" Text = "{Binding TotalWordCount}" VerticalOptions = "Center" TextColor = "Gray" />
        <Switch Grid.Column = "2" HorizontalOptions = "End"  IsToggled = "{Binding IsToggled}"  />
    </Grid>
</ViewCell>

代码后置

public partial class MyViewCell : ViewCell
{
    public MyViewCell()
    {
        InitializeComponent();
    }
}

您可以按照以下方式创建您的TableView

var section = new TableSection("Available Categories");
foreach (var category in categoryGroups)
{
   var cell = new MyViewCell { BindingContext = category };
   section.Add(cell);
}
tableView.Root.Add(section);

选项3. 创建自定义的TableView,支持ItemSource,类似于ListView
public class DynamicTableView : TableView
{
    /// <summary>
    /// Bindable property for the data source
    /// </summary>
    public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create(
        "ItemsSource", typeof(IDictionary), typeof(DynamicTableView), propertyChanging: OnItemsSourceChanged);

    /// <summary>
    /// Gets or sets the items source - can be any collection of elements.
    /// </summary>
    /// <value>The items source.</value>
    public IDictionary ItemsSource
    {
        get { return (IDictionary)GetValue(ItemsSourceProperty); }
        set { SetValue(ItemsSourceProperty, value); }
    }

    /// <summary>
    /// Bindable property for the data template to visually represent each item.
    /// </summary>
    public static readonly BindableProperty ItemTemplateProperty = BindableProperty.Create(
        "ItemTemplate", typeof(DataTemplate), typeof(DynamicTableView));

    /// <summary>
    /// Gets or sets the item template used to generate the visuals for a single item.
    /// </summary>
    /// <value>The item template.</value>
    public DataTemplate ItemTemplate
    {
        get { return (DataTemplate)GetValue(ItemTemplateProperty); }
        set { SetValue(ItemTemplateProperty, value); }
    }

    /// <summary>
    /// Initializes an ItemsControl.
    /// </summary>
    public DynamicTableView()
    {

    }

    /// <summary>
    /// This is called when the underlying data source is changed.
    /// </summary>
    /// <param name="bindable">ItemsSource</param>
    /// <param name="oldValue">Old value.</param>
    /// <param name="newValue">New value.</param>
    static void OnItemsSourceChanged(BindableObject bindable, object oldValue, object newValue)
    {
        ((DynamicTableView)bindable).OnItemsSourceChangedImpl((IDictionary)oldValue, (IDictionary)newValue);
    }

    /// <summary>
    /// Instance method called when the underlying data source is changed through the
    /// <see cref="ItemsSource"/> property. This re-generates the list based on the 
    /// new collection.
    /// </summary>
    /// <param name="oldValue">Old value.</param>
    /// <param name="newValue">New value.</param>
    void OnItemsSourceChangedImpl(IDictionary oldValue, IDictionary newValue)
    {
        Root.Clear();
        if(newValue != null)
        {
            FillContainer(newValue);
        }
    }

    /// <summary>
    /// This method takes our items source and generates visuals for
    /// each item in the collection; it can reuse visuals which were created
    /// previously and simply changes the binding context.
    /// </summary>
    /// <param name="newValue">New items to display</param>
    void FillContainer(IDictionary newValue)
    {
        Root.Clear();

        var template = ItemTemplate;

        foreach(var key in newValue.Keys)
        {
            var tableSection = new TableSection() { Title = key.ToString() };
            var innerList = newValue[key] as IList;
            if (innerList == null)
                innerList = Enumerable.Repeat(newValue[key], 1).ToList();

            foreach(var dataItem in innerList)
            {
                if (template != null)
                {
                    var view = InflateTemplate(template, dataItem);
                    if (view != null)
                        tableSection.Add(view);
                }
                else
                {
                    var label = new TextCell { Text = dataItem.ToString() };
                    tableSection.Add(label);
                }
            }

            Root.Add(tableSection);
        }
    }

    /// <summary>
    /// Inflates the visuals for a data template or template selector
    /// and adds it to our StackLayout.
    /// </summary>
    /// <param name="template">Template.</param>
    /// <param name="item">Item.</param>
    ViewCell InflateTemplate(DataTemplate template, object item)
    {
        // Pull real template from selector if necessary.
        var dSelector = template as DataTemplateSelector;
        if (dSelector != null)
            template = dSelector.SelectTemplate(item, this);

        var view = template.CreateContent() as ViewCell;
        if (view != null)
        {
            view.BindingContext = item;
            return view;
        }

        return null;
    }
}

使用方法如下:

<local:DynamicTableView ItemsSource="{Binding AllCategories}">
    <local:DynamicTableView.ItemTemplate>
        <DataTemplate>
            <ViewCell>
                <Grid VerticalOptions="CenterAndExpand" Padding = "20, 0" >
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="*" />
                        <ColumnDefinition Width="Auto" />
                        <ColumnDefinition Width="75" />
                    </Grid.ColumnDefinitions>
                    <Label Grid.Column = "0" HorizontalOptions = "StartAndExpand" Text = "{Binding Name}" />
                    <Label Grid.Column = "1" HorizontalOptions = "End" XAlign = "End" Text = "{Binding TotalWordCount}" VerticalOptions = "Center" TextColor = "Gray" />
                    <Switch Grid.Column = "2" HorizontalOptions = "End"  IsToggled = "{Binding IsToggled}"  />
                </Grid>
            </ViewCell>
        </DataTemplate>
    </local:DynamicTableView.ItemTemplate>
</local:DynamicTableView>

并提供样本数据集:

public class SettingsViewModel {
    public Categories AllCategories => new Categories();
}

public class Category {
    public string Name { get; set; }
    public int TotalWordCount { get; set; }
    public bool IsToggled { get; set; }
}
public class Categories : Dictionary<string, List<Category>>
{
    public Categories()
    {
        this.Add("Available Categories", new List<Category>(new []{
            new Category(){ Name = "Test1", TotalWordCount = 10, IsToggled = true },
            new Category(){ Name = "Test2", TotalWordCount = 25, IsToggled = true },
            new Category(){ Name = "Test3", TotalWordCount = 20, IsToggled = false }
        }));

        this.Add("Other Categories", new List<Category>(new[]{
            new Category(){ Name = "Test-N1", TotalWordCount = 30, IsToggled = true },
            new Category(){ Name = "Test-N2", TotalWordCount = 50, IsToggled = false }
        }));
    }
}

基于旧问题的旧回答(不再有效)

如果您只需要为特定平台指定BackgroundColorFontSize,可以使用OnPlatform来实现,无需使用自定义渲染器。

<ListView.Header>
    <!-- don't forget to override spacing and padding properties to avoid default spacing -->
   <StackLayout Spacing="0" Padding="0" VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand">
      <StackLayout.BackgroundColor>
         <OnPlatform x:TypeArguments="Color"
             Android=""
             WinPhone=""                                         
             iOS="#000000">
      </StackLayout.BackgroundColor>
      <StackLayout Padding="10,35,10,10" Orientation="Horizontal" HorizontalOptions="FillAndExpand">
         <local:ExtLabel ExtStyleId="Body" Text="Custom body Label"></local:ExtLabel>
         <local:ExtLabel ExtStyleId="Header" Text="Custom hdr Label"></local:ExtLabel>
      </StackLayout>
   </StackLayout>
</ListView.Header>

此外,在参考listviewrenderer的源代码时,特别是此处 - 看起来头部/header-template属性只是在ListView中作为一组控件的占位符。如果您在header中提供自定义控件,则框架将实例化并使用其渲染器。因此,如果您确实需要基于自定义渲染器的方法,则可以创建一个自定义控件(例如CustomListViewHeader)并为其实现iOS渲染器。
然后,您可以在您的ListView头部或head-template中使用此控件。
<ListView.Header>
   <local:CustomListViewHeader />
</ListView.Header>

选项3对于MVVM来说是一个不错的选择,但在运行时它无法工作。当我在运行时向表中添加一行时,它并没有添加这一行。有什么解决办法? - Behzad
我在绑定类的表页中向AllCategories列表添加了一行。那么我该如何告诉表格ItemSource已更改? - Behzad
你需要修改 OnItemsSourceChangedImpl 方法,检查集合是否支持 INotifyCollectionChanged 接口并订阅其事件。你可以使用 Observable 集合作为项源。 - Sharada Gururaj

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接