我有一个很大ListView
,它主要InkCanvas
对象组成,事实证明,ListView
实现了数据虚拟化,以根据视图中的可见项目"巧妙地"卸载和加载视图中的项目。这样做的问题是,很多时候ListView
会缓存项目,并且在添加新项目时,它基本上会复制视图中已添加的项目。因此,就我而言,如果用户向 Inkcanvas 添加笔画,然后将新的 InkCanvas 添加到 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
但项目仍然重复。
要解决这个问题,我们必须首先了解为什么会出现这个问题。
ListView
里面有一个重用容器,它不会无休止地创建新的列表项,而是会回收。
在大多数情况下,这种回收不是问题。但这对InkCanvas
来说很特别.InkCanvas
是有状态控件。在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;
}
}
}
通过这种方式,我们可以保持我们的条目稳定。