1

私は大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たが、アイテムはまだ複製されています。

4

1 に答える 1

1

この問題を解決するには、まずこの問題が発生する理由を理解する必要があります。

ListView内部に再利用コンテナーがあり、新しいリスト項目を際限なく作成するのではなく、リサイクルします。

ほとんどの場合、そのようなリサイクルは問題になりません。しかし、それは特別ですInkCanvasInkCanvasステートフル コントロールです。に描画するInkCanvasと、手書きが保持され、UI に表示されます。

コントロールが の場合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;
        }
    }
}

このようにして、エントリを安定させることができます。

于 2020-02-10T02:24:56.613 に答える