简体   繁体   中英

How to force re-selection of DataTemplateSelector

I am using a DataTemplateSelector in UWP on a ListBox and I want to force a re-selection of the DataTemplate for an existing ListBoxItem. The only way I can get it to work is to remove the item from the bound collection and then re-add the item. This really seems like a kluge. I would simply like to invalidate the layout of the list box item and get it to call the data template selector again. Cannot seem to do this.

Here is the page...

<Page.Resources>
    <local:MyTemplateSelector x:Key="TemplateSelector"/>
</Page.Resources>
<Page.DataContext>
    <local:MyViewModel/>
</Page.DataContext>

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <StackPanel>
        <ListBox x:Name="listBox1" Margin="5" ItemsSource="{Binding QuantityRows}"
             ItemTemplateSelector="{StaticResource TemplateSelector}">
            <ListBox.Resources>
                <DataTemplate x:Key="Detail">
                    <StackPanel Orientation="Horizontal">
                        <Button Click="OnShowSummary">Show Summary</Button>
                        <TextBlock Text="Name "/>
                        <TextBox Width="40" Text="{Binding Name}"/>
                    </StackPanel>
                </DataTemplate>
                <DataTemplate x:Key="Summary">
                    <StackPanel Orientation="Horizontal">
                        <Button Click="OnShowDetail">Show Detail</Button>
                        <TextBlock Text="{Binding Summary}"/>
                    </StackPanel>
                </DataTemplate>
            </ListBox.Resources>
        </ListBox>
    </StackPanel>

</Grid>

Here is the template selector...

public class MyTemplateSelector : DataTemplateSelector
{
    protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
    {
        QuantityRow row = item as QuantityRow;
        ListBox lb = UWPUtilities.GetParent<ListBox>(container);
        if (row != null && lb != null)
        {
            if (row.IsDetail)
            {
                return lb.Resources["Detail"] as DataTemplate;
            }
            else
            {
                return lb.Resources["Summary"] as DataTemplate;
            }
        }
        return base.SelectTemplateCore(item, container);
    }
}

Here is the view model...

public class QuantityRow
{
    public bool IsDetail { get; set; }
    public int ID { get; set; }
    public string Name { get; set; }
    public string Summary
    {
        get
        {
            return "Name = " + Name;
        }
    }
}
public class MyViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    public ObservableCollection<QuantityRow> QuantityRows { get; set; }
    public MyViewModel()
    {
        QuantityRows = new ObservableCollection<QuantityRow>();
        QuantityRows.Add(new QuantityRow() { IsDetail = false, ID = 1, Name = "Length" });
        QuantityRows.Add(new QuantityRow() { IsDetail = true, ID = 2, Name = "Diameter" });
        QuantityRows.Add(new QuantityRow() { IsDetail = false, ID = 3, Name = "Temperature" });
        QuantityRows.Add(new QuantityRow() { IsDetail = false, ID = 4, Name = "Pressure" });
        QuantityRows.Add(new QuantityRow() { IsDetail = true, ID = 5, Name = "Angle" });
    }
}

Here is the code behind...

    public MyViewModel ViewModel
    {
        get
        {
            return DataContext as MyViewModel;
        }
    }

    private void OnShowSummary(object sender, RoutedEventArgs e)
    {
        QuantityRow row = (sender as Button).DataContext as QuantityRow;
        row.IsDetail = false;
    //            UpdateLayout1(row);
        UpdateLayout2(sender);
    }
    private void OnShowDetail(object sender, RoutedEventArgs e)
    {
        QuantityRow row = (sender as Button).DataContext as QuantityRow;
        row.IsDetail = true;
    //           UpdateLayout1(row);
        UpdateLayout2(sender);
    }
    private void UpdateLayout1(QuantityRow row)
    {
        int index = ViewModel.QuantityRows.IndexOf(row);
        ViewModel.QuantityRows.RemoveAt(index);
        ViewModel.QuantityRows.Insert(index, row);
    }
    private void UpdateLayout2(object sender)
    {
        ListBoxItem lbi = UWPUtilities.GetParent<ListBoxItem>(sender as DependencyObject);
        lbi.InvalidateArrange();
    }

Finally here is a utility function...

public static class UWPUtilities
{
    public static T GetParent<T>(DependencyObject d) where T : class
    {
        while (d != null && !(d is T))
        {
            d = VisualTreeHelper.GetParent(d);
        }
        return d as T;
    }
}

There is a button on each list item that toggles between the summary and detail templates. The UpdateLayout1 works fine, at the cost of a bit of churn in the bound list. UpdateLayout2 does not work. This is the one would be a cleaner implementation in my view. Why doesn't InvalidateArrange() on the ListBoxItem force a reselection of the template?

Thanks very much to @Justin XL for this idea. In order to get the template selector to fire again you have to set it to null and then set it back to the the same reference. This is indeed another kluge but I like it slightly better than my first kluge. No elegance to be found here.

    private void UpdateLayout2(object sender)
    {
        ListBoxItem lbi = UWPUtilities.GetParent<ListBoxItem>(sender as DependencyObject);
        DataTemplateSelector dts = lbi.ContentTemplateSelector;
        lbi.ContentTemplateSelector = null;
        lbi.ContentTemplateSelector = dts;
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM