Реализация виртуализации данных в ListView UWP без дублирования элементов - PullRequest
0 голосов
/ 07 февраля 2020

У меня есть большой ListView, который в основном состоит из InkCanvas объектов, получается, что ListView реализует виртуализацию данных для «умной» выгрузки и загрузки элементов в представлении в зависимости от видимых элементов в представлении. Проблема заключается в том, что ListView кэширует элементы много раз, и когда добавляется новый элемент, он по сути копирует элементы, уже добавленные в представление. Так что в моем случае, если пользователь добавляет обводку в Inkcanvas, а затем добавляет новый InkCanvas в ListView, новый холст содержит обводки из предыдущего холста. Как сообщается здесь , это связано с виртуализацией данных. Мой ListView реализован следующим образом:

    <Grid HorizontalAlignment="Stretch">
        <ListView x:Name="CanvasListView" IsTapEnabled="False"
                  IsItemClickEnabled="False"
                  ScrollViewer.ZoomMode="Enabled"
                  ScrollViewer.HorizontalScrollMode="Enabled"
                  ScrollViewer.VerticalScrollMode="Enabled"
                  ScrollViewer.HorizontalScrollBarVisibility="Auto"
                  ScrollViewer.VerticalScrollBarVisibility="Visible"
                  HorizontalAlignment="Stretch">

            <!-- Make sure that items are not clickable and centered-->
            <ListView.ItemContainerStyle>
                <Style TargetType="ListViewItem">
                    <Setter Property="HorizontalContentAlignment" Value="Center"/>
                </Style>
            </ListView.ItemContainerStyle>

            <ListView.ItemTemplate>
                <DataTemplate>
                    <StackPanel>
                        <local:CanvasControl Margin="0 2"
                                             VerticalAlignment="Stretch"
                                         HorizontalAlignment="Stretch" 
                                         MinWidth="1000" MinHeight="100" MaxHeight="400"
                                         Background="LightGreen"/>
                        <Grid HorizontalAlignment="Stretch" Background="Black" Height="2"></Grid>
                    </StackPanel>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
        <StackPanel Orientation="Horizontal" HorizontalAlignment="Stretch">
            <InkToolbar x:Name="inkToolbar" 
                    VerticalAlignment="Top"
                    Background="LightCoral"/>
            <StackPanel HorizontalAlignment="Right">
                <Button x:Name="AddButton" Content="Add Page" Click="Button_Click"/>
                <TextBlock x:Name="PageCountText" />
            </StackPanel>
        </StackPanel>
    </Grid>

Полный пример можно найти здесь , а вот видео о проблеме . Действительно, если я отключу виртуализацию данных (или переключусь на ItemsControl), все будет работать великолепно. Проблема, однако, заключается в том, что при очень большом списке этот подход сильно влияет на производительность (при более чем 60 элементах управления InkCanvas приложение просто падает). Так есть ли способ сохранить виртуализацию данных, избегая дублирования элементов? Я пробовал с VirtualizationMode.Standard, но элементы все еще дублируются.

1 Ответ

1 голос
/ 10 февраля 2020

Чтобы решить эту проблему, мы должны сначала понять, почему эта проблема возникает.

ListView имеет контейнер для повторного использования внутри, он не будет бесконечно создавать новые элементы списка, но будет перерабатывать.

В большинстве случаев такая переработка не является проблемой. Но это специально для InkCanvas. InkCanvas - это контроль состояния. Когда вы рисуете InkCanvas, почерк сохраняется и отображается в пользовательском интерфейсе.

Если ваш элемент управления - TextBlock, эта проблема не возникает, поскольку мы можем напрямую связать значение с TextBlock.Text , но для инсульта InkCanvas мы не можем напрямую связать, что вызовет так называемый остаток .

Поэтому, чтобы избежать этого, нам нужно очистить state, то есть каждый раз, когда InkCanvas создается или перезагружается, штрихи в InkCanvas перерисовываются.

1. Создайте список для сохранения информации о ходе в ViewModel

public class ViewModel : INotifyPropertyChanged
{
    // ... other code
    public List<InkStroke> Strokes { get; set; }
    public ViewModel()
    {
        Strokes = new List<InkStroke>();
    }
}

2. Измените внутреннюю структуру CanvasControl

xaml

<Grid>
    <InkCanvas x:Name="inkCanvas" 
               Margin="0 2"
               MinWidth="1000" 
               MinHeight="300"
               HorizontalAlignment="Stretch" >
    </InkCanvas>
</Grid>

xaml.cs

public sealed partial class CanvasControl : UserControl
{
    public CanvasControl()
    {
        this.InitializeComponent();
        // Set supported inking device types.
        inkCanvas.InkPresenter.InputDeviceTypes =
            Windows.UI.Core.CoreInputDeviceTypes.Mouse |
            Windows.UI.Core.CoreInputDeviceTypes.Pen;

    }

    private void StrokesCollected(InkPresenter sender, InkStrokesCollectedEventArgs args)
    {
        if (Data != null)
        {
            var strokes = inkCanvas.InkPresenter.StrokeContainer.GetStrokes().ToList();
            Data.Strokes = strokes.Select(p => p.Clone()).ToList();
        }
    }

    public ViewModel Data
    {
        get { return (ViewModel)GetValue(DataProperty); }
        set { SetValue(DataProperty, value); }
    }

    public static readonly DependencyProperty DataProperty =
        DependencyProperty.Register("Data", typeof(ViewModel), typeof(CanvasControl), new PropertyMetadata(null,new PropertyChangedCallback(Data_Changed)));

    private static void Data_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if(e.NewValue!=null && e.NewValue is ViewModel vm)
        {
            var strokes = vm.Strokes.Select(p=>p.Clone());
            var instance = d as CanvasControl;
            instance.inkCanvas.InkPresenter.StrokesCollected -= instance.StrokesCollected;
            instance.inkCanvas.InkPresenter.StrokeContainer.Clear();
            try
            {
                instance.inkCanvas.InkPresenter.StrokeContainer.AddStrokes(strokes);
            }
            catch (Exception)
            {

            }

            instance.inkCanvas.InkPresenter.StrokesCollected += instance.StrokesCollected;
        }
    }
}

Таким образом, мы можем сохранить наши записи стабильными.

...