Search code examples
c#xamllistviewuwpwindows-10-universal

ListView ignoring drag and drop when setting a ItemTemplate


I have set up a ListView to manage tabs on my app. So users can drag and drop tabs to new instances of the app and reorder tabs. All goes great until I set up a ControlTemplate for the Itens at the ListView.

I used ControlTemplate because when I add a ListView.ItemTemplate directly it won`t work (it does not affect the listview item). So before adding a new item I use this:

tab.Template = (ControlTemplate)this.Resources["listViewItemTemplate"];

So the tabs is looking great, but all the functions that used to work (like drag and drop) isn't working anymore. When I remove the ControlTemplate the ListView show only one little string.

This is what is happening This is what is happening

My ListView Code:

<ListView x:Name="TabsListView" 
     CanDragItems="True"
     AllowDrop="True"
     DragEnter="TabsListView_DragEnter"
     Drop="TabsListView_Drop"
     ScrollViewer.VerticalScrollBarVisibility="Disabled
     CanReorderItems="True"
     DragItemsStarting="TabsListView_DragItemsStarting"
     ItemClick="TabsListView_ItemClick" >

I know for sure that the issue isn't on the drag and drop method.

To create a new item I just use Add. Of course I checked every single item inside ControlTemplate to know that it is not blocking anything.

The control template is:

<ControlTemplate x:Key="listViewItemTemplate" >
    <ListViewItem>
        <Grid Tapped="Grid_Tapped" Width="180" Margin="-12,-12,-12,0">
            <TextBlock Text="{Binding Name}" Margin="5,6,0,0"></TextBlock>
        </Grid>
    </ListViewItem>
</ControlTemplate>

Solution

  • The reason is that you are using ControlTemplate and embedding a ListViewItem in there. ListViewItems are created automatically by the control to contain the items, and ItemTemplate is a template of the contents of this container. So it should look like this:

    <DataTemplate x:Key="listViewItemTemplate" >
        <Grid Tapped="Grid_Tapped" Width="180" Margin="-12,-12,-12,0">
            <TextBlock Text="{Binding Name}" Margin="5,6,0,0"></TextBlock>
        </Grid>
    </ControlTemplate>
    
    <ListView ... 
          ItemTemplate="{ThemeResource listViewItemTemplate}">
            ...
    </ListView>
    

    Furthermore, if you want to style the ListViewItem (the container), you can create a style with TargetType="ListViewItem" and set it as ListView.ItemContainerStyle.

    <Style TargetType="ListViewItem" x:Key="TabListViewItemContainerStyle">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="ListViewItem">
                    <Grid x:Name="ContentBorder">
                        ... some template with ContentPresenter:
                        <ContentPresenter x:Name="ContentPresenter" ... />
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    
    <ListView ...
        ItemContainerStyle="{StaticResource TabListViewItemContainerStyle}">
         ...
    </ListView>
    

    You can find the default style in: C:\Program Files (x86)\Windows Kits\10\DesignTime\CommonConfiguration\Neutral\UAP\10.0.15063.0\Generic

    Update

    I have checked the code and found the problem. What you are actually doing is adding ListViewItems directly in code:

    ListViewItem tab = new ListViewItem();
    DataModel model = new DataModel();
    model.Name = $"NewTab {counter}";
    tab.DataContext = model;
    tab.Content = model.ToString();
    TabsListView.Items.Add(tab);
    

    Unfortunately this does not work, because when ListView sees an item that is of type ListViewItem, it just adds it to the list, but ItemTemplate doesn't apply to it, because that is applied only when the items are not UIElement but rather a custom class. If you use DataModel directly, ItemTemplate will be applied as expected:

    DataModel model = new DataModel();
    model.Name = $"NewTab {counter}";
    TabsListView.Items.Add(model);
    

    ListView encounters DataModel, knows that just a class not a UIElement, so it internally creates a ListViewItem, applies ItemContainerStyle to it and then uses the ItemTemplate to create the UI that will be displayed inside the ListViewItem's ContentPresenter. It is better to use this approach as it gives you better decoupling as you don't have to create UI-based classes in the code-behind and you get more control (as you can change both the style of the item as well as the container).