1

我有一个很大的对象,它在ListView很大程度上实现了数据虚拟化,以根据视图中的可见项目“巧妙地”卸载和加载视图中的项目。这样做的问题是,很多时候缓存项目,当添加一个新项目时,它实际上复制了已经添加到视图中的项目。所以在我的例子中,如果用户向 Inkcanvas 添加一个笔触,然后向 ListView 添加一个新的 InkCanvas,则新的画布包含来自前一个画布的笔触。正如这里所报道的,这是因为数据虚拟化。我的 ListView 实现如下:InkCanvasListViewListView

    <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但项目仍然重复。

4

1 回答 1

1

要解决这个问题,首先要了解为什么会出现这个问题。

ListView内部有一个重用容器,它不会无休止地创建新的列表项,而是会循环使用。

在大多数情况下,这样的回收不是问题。但它是特殊的InkCanvasInkCanvas是一个有状态的控件。当您在 上绘图时InkCanvas,笔迹会保留并显示在 UI 上。

如果你的控件是一个TextBlock,就不会出现这个问题,因为我们可以直接将值绑定到TextBlock.Text,但是对于 的 Stroke 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

xml

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

xml.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;
        }
    }
}

这样,我们可以保持我们的条目稳定。

于 2020-02-10T02:24:56.613 回答