locked
Dynamic ViewCell in ListView RRS feed

  • Question

  • User24644 posted

    Right now I have a listview like this which basically hides half of the cell based on what type it is :

        <ListView.ItemTemplate>
            <DataTemplate>
              <ViewCell>
                <ViewCell.View>
                  <StackLayout IsVisible="{Binding IsType1}">
                            Show if Type1
                  </StackLayout>
              <StackLayout IsVisible="{Binding IsType2}">
                            Show if Type2
                  </StackLayout>
                </ViewCell.View>
              </ViewCell>
            </DataTemplate>
          </ListView.ItemTemplate>
    

    Is this the right way to dynamically pick which viewcell to display? or is there a better way? and if this is the best way, do I suffer performance because the template is so large?

    Wednesday, July 23, 2014 2:46 PM

All replies

  • User8759 posted

    Hi,

    I wrote an TemplateSelector for a ListView.

    1. First I added a DependencyProperty to the ListView

          public class ScsListView : ListView
              {
                  public ScsListView()
                  {
                      ItemTemplate = new DataTemplate(GetHookedCell);
                  }
      
                  Cell GetHookedCell()
                  {
                      var content = new ViewCell();
                      content.BindingContextChanged += OnBindingContextChanged;
                      return content;
                  }
      
                  public static readonly BindableProperty TemplateSelectorProperty = BindableProperty.Create<ScsListView, IDataTemplateSelector>(p => p.TemplateSelector, null);
      
                  public IDataTemplateSelector TemplateSelector
                  {
                      get { return (IDataTemplateSelector)GetValue(TemplateSelectorProperty); }
                      set { SetValue(TemplateSelectorProperty, value); }
                  }
      
      
                  private void OnBindingContextChanged(object sender, EventArgs e)
                  {
                      var cell = (ViewCell)sender;
                      if (TemplateSelector != null)
                      {
                          var template = TemplateSelector.SelectTemplate(cell, cell.BindingContext);
                          cell.View = ((ViewCell)template.CreateContent()).View;
                      }
                  }
      
              }
      
    2. Every TemplateSelector mus implement an Interface:

      public interface IDataTemplateSelector
          {
              DataTemplate SelectTemplate(object view, object dataItem);
          }
      
    3. My sample TemplateSelector.

          public class TableDataTemplateSelector : IDataTemplateSelector
              {
                  public DataTemplate MainTemplate { get; set; }
                  public DataTemplate SubTemplate { get; set; }
      
                  public DataTemplate SelectTemplate(object view, object dataItem)
                  {
                      var data = dataItem as CompanyIncomeItem;
                      if (data == null) return null;
      
                      if (data.IsMainCategory)
                      {
                          return MainTemplate;
                      }
                      else
                      {
                          return SubTemplate;
                      }
                  }
              }
      
    4. In Xaml define the Template Selectors.

          <ContentPage.Resources>
              <ResourceDictionary>
      
                <DataTemplate x:Key="MainCategoryItemTemplate">
                  <ViewCell>
                    <ViewCell.View>
                      <Grid BackgroundColor="Blue">
                        <Grid.RowDefinitions>
                          <RowDefinition Height="*" />
                          <RowDefinition Height="0" />
                        </Grid.RowDefinitions>
      
                        <Label Text="{Binding IncomeName}" VerticalOptions="Center"/>
                      </Grid>
                    </ViewCell.View>
                  </ViewCell>
                </DataTemplate>
      
                <DataTemplate x:Key="SubCategoryItemTemplate">
                  <ViewCell>
                    <ViewCell.View>
                      <Grid BackgroundColor="Green">
                        <Grid.RowDefinitions>
                          <RowDefinition Height="*" />
                          <RowDefinition Height="0" />
                        </Grid.RowDefinitions>
      
                        <Label Text="{Binding IncomeName}" VerticalOptions="Center"/>
                      </Grid>
                    </ViewCell.View>
                  </ViewCell>
                </DataTemplate>
      
                <local:TableDataTemplateSelector x:Key="TableTemplateSelector" MainTemplate="{StaticResource MainCategoryItemTemplate}" SubTemplate="{StaticResource SubCategoryItemTemplate}"/>
      
              </ResourceDictionary>
            </ContentPage.Resources>
      
    5. At the end use it in Xaml.

          …
          <controls:ScsListView Grid.Row="1" ItemsSource="{Binding CompanyIncomeDetails}" TemplateSelector="{StaticResource TableTemplateSelector}" VerticalOptions="Fill" HorizontalOptions="Fill"/>
            </Grid>
          </ContentPage>
      

    Your TemplateSelector can have as many DataTemplates as you wish. You mast have in the ViewModel any property, which you use to distinguish between the DataTemplates (in my case data.IsMainCategory). I use the TemplateSelector on iPad and it works fine.

    Wednesday, July 23, 2014 3:54 PM
  • User24644 posted

    @MarianGrzesik? So you still have both templates defined in xaml. I guess I was looking for a way in which each datatemplate could be its own xaml file and the listview could dynamically choose one or the other.

    Wednesday, July 23, 2014 4:02 PM
  • User39542 posted

    @ChristopherGozdziewski?

    You could use a set of ContentView objects (Add File > Forms > ContentView) and then use the DataTemplateSelector approach outlined above or here. Then your view definitions are in separate XAML files. Is that what you want?

    Wednesday, July 23, 2014 4:52 PM
  • User69657 posted

    If you add ContextActions to your ViewCell it won´t work. My solution to this is to make this change in private void OnBindingContextChanged:

    var content = (ViewCell) template.CreateContent(); cell.View = content.View; foreach (var action in content.ContextActions) { cell.ContextActions.Add(action); }

    Wednesday, June 24, 2015 12:10 PM
  • User214546 posted

    @"MarkSmith.8123" would either of those Template Selectors allow you to change the data once it had already been displayed? If not how could I change a Data Template to another Template after it had been displayed? Because the Data Template will not let you access the Data in the Template while it is on the screen, I need a way to change the Template and have the screen repaint with the new Template. Unless you know a way I could change the data that is in the template, because that would work also. I an using the CarouselView and I need to be able to change what is displayed when either of two buttons are clicked. Now that I think about it while changing the template could work it would be better to be able to access the data on the screen and change it there dynamically. Anyone know how this could be achieved? Thanks to anyone that could help.

    Tuesday, September 6, 2016 7:46 PM
  • User46883 posted

    @MarkDail, have you found any solution to this?

    All solutions that I've came across (either via OnBindingContextChanged, or TemplateSelector) work fine at ListView's creation time.

    I am looking for a solution that would work when the ListView has already been created and them items displayed.

    Think of the following use case: - initial ContextAction with one MenuItem "Select" - swipe and Tap on the MenuItem would trigger the specified Command and set the Item's "is_selected" property to true (which is false by default of course) - then the ContextAction menu needs to close (just as intended) - I swiped again, it would now show "Unselect" as the items is indeed selected

    My findings are that: - OnBindingContextChanged is only called at ListView's creation time - If I do change the MenuItem's Text in code in the Clicked event or via a bindable property, the texts does change but it won't close after having been tapped - moreover this is not the wanted behavior as I don't want to show the "Unselect" text before swiping it back again

    I'm stuck for now. If you have any brillant ideas, I would love to hear them.

    Saturday, April 1, 2017 10:17 AM
  • User263 posted

    DataTemplateSelector and a view with MVVM bindings inside the custom ViewCell. This will work nicely on all platforms and bindings will fire correctly, even after list view has been displayed.

    Saturday, April 1, 2017 11:13 PM
  • User72595 posted

    A DataTemplateSelector did the trick for me even if have different ContextActions for each kind of cell:

    ```xml

        <DataTemplate x:Key="CanNotBeDeletedTemplate">
            <ViewCell>
                <ViewCell.ContextActions>
                    <MenuItem Command="{Binding Source={x:Reference myPage}, Path=BindingContext.DuplicateCommand}" 
                                  CommandParameter="{Binding .}" Text='Duplicate &amp; Edit' />
                </ViewCell.ContextActions>
                <local:MyCell></local:MyCell>
            </ViewCell>
       </DataTemplate>
    
       <local:MyTemplateSelector x:Key="TimeEntryTemplateSelector"
                                             TemplateOne="{StaticResource TemplateOne}"
                                             CanNotBeDeletedTemplate="{StaticResource CanNotBeDeletedTemplate}"></local:MyTemplateSelector>
    

    ```

    And then use it in your ListView:

    xml <ListView ItemTemplate="{StaticResource MyTemplateSelector}" />

    Wednesday, April 1, 2020 9:26 AM