我正在使用MVVMLight来创建调查表,并且在渲染InkCanvas控件时遇到内存问题。这是我正在使用的示例的简化示例:

问题病毒

public Question Question { get; set; }
public HandwritingControl HandwritingControl { get; set; }


问卷调查表

public List<QuestionVm> currentQuestions;
public List<QuestionVm> CurrentQuestions
{
    get { return currentQuestions; }
    set
    {
        currentQuestions = value;
        RaisePropertyChanged();
    }
}


问卷调查表

//Clear form & iterate questions
questionnaireForm.Children.Clear();
foreach (var questionVm in questionnaireVm.CurrentQuestions)
{
  questionnaireForm.Children.Add(questionVm.Question);
  if(questionVm.HandwritingControl != null)
    questionnaireForm.Children.Add(new InkCanvas());
}


RAM在每次页面加载时都会增加,很明显分配给InkCanvas的内存永远不会被释放。在第三页左右的页面上,当大约呈现125个InkCanvas控件时,该应用程序将引发System.OutOfMemoryException。

我的问题是,为什么不取消分配这些控件?以及如何手动释放内存?如果我将InkCanvas注释掉,则问卷很好,而Children.Clear()似乎正在清理TextBlocks或任何其他控件而没有问题。

更新

因此,在使用@Grace Feng之后,我尝试重构我的方法,并使用带有数据模板的ListView,而不是从xaml.cs创建网格。

问卷

                <ListView Name="questionnaireListView" ItemsSource="{Binding CurrentQuestions, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
                <ListView.ItemTemplate>
                    <DataTemplate>
                        <StackPanel>
                            <TextBlock Text="{Binding Question.Text}" />
                            <TextBlock Text="{Binding Question.Description}" />
                            <InkCanvas/>
                        </StackPanel>
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>


问卷调查表

    private void buttonNext_Click(object sender, RoutedEventArgs e)
    {
        //Validate & goto next page
        if (questionnaireVm.CurrentPageIsValid())
        {
            questionnaireVm.CurrentQuestions.Clear();
            questionnaireVm.LoadNextPage();
        }
    }


不幸的是,即使使用ListView数据模板方法,我仍然遇到相同的内存不足错误。有什么想法吗?

最佳答案

在第三页左右的页面上,当大约呈现125个InkCanvas控件时,该应用程序将引发System.OutOfMemoryException。


我只是重现了这个问题,是的,您是正确的。


  我的问题是,为什么不取消分配这些控件?


从Questionnaire.xaml.cs中的代码中,我认为您正在动态地将questionVm.QuestionInkCanvas的新实例动态添加到名为“ questionnaireForm”的父控件中,并且在执行此操作之前,请清除此父控件的子级。在加载数据期间没有“取消分配”操作,因此这些控件都不会被释放。


  以及如何手动释放内存?如果我将InkCanvas注释掉,则问卷很好,而Children.Clear()似乎正在清理TextBlocks或任何其他控件而没有问题。


如果您手动释放内存,则需要删除其中一些InkCanvas,或者我认为您可以针对这种情况做的正确的事情是使用UI虚拟化来减少加载数据时的内存丢失。

在UWP APP中,已经有两个具有UI虚拟化功能的控件ListView and GridView。我只是用超过125个空InkCnavas实例测试这两个控件。 Item的大小GridView适应于其在项目中的布局,因此,当InkCanvas为空时,它仍将立即加载所有数据,仍然会发生内存不足错误。但是默认情况下,ListView控件将占用一行内容,UI虚拟化在这里可以正常工作。

而且我看到您在InkCanvas的基础上添加了questionVm.HandwritingControl != null,所以这是一种解决方法,例如,您可以像这样设计Questionnaire.xaml:

<Page.Resources>
    <DataTemplate x:Key="NoInkCanvasDataTemplate">
        <TextBlock Text="{Binding Questions}" />
    </DataTemplate>
    <DataTemplate x:Key="InkCanvasDataTemplate">
        <StackPanel>
            <TextBlock Text="{Binding Questions}" />
            <InkCanvas></InkCanvas>
        </StackPanel>
    </DataTemplate>
    <local:CustomDataTemplateSelector x:Key="InkCanvasDataTemplateSelector" NoInkCanvas="{StaticResource NoInkCanvasDataTemplate}" InkCanvas="{StaticResource InkCanvasDataTemplate}"></local:CustomDataTemplateSelector>
</Page.Resources>

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <ListView x:Name="listView" ItemTemplateSelector="{StaticResource InkCanvasDataTemplateSelector}" />
</Grid>


并在后面的代码中例如:

private ObservableCollection<CurrentQuestions> questions = new ObservableCollection<CurrentQuestions>();

public MainPage()
{
    this.InitializeComponent();
    listView.ItemsSource = questions;
}

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    questions.Clear();
    foreach (var questionVm in questionnaireVm.CurrentQuestions)
    {
        //Add your data here
    }
}


并创建一个CustomDataTemplateSelector类,如下所示:

public class CustomDataTemplateSelector : DataTemplateSelector
{
    public DataTemplate NoInkCanvas
    {
        get;
        set;
    }

    public DataTemplate InkCanvas
    {
        get;
        set;
    }

    protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
    {
        var canvas = item as HandwritingControl;
        if (canvas == null)
        {
            return this.NoInkCanvas;
        }
        else
        {
            return InkCanvas;
        }
    }
}


简而言之,您可以使用ListView控件及其ItemTemplateSelector来完成此操作,因为我没有您的所有代码,因此上述代码只是示例,并非100%正确地适合您的情况。

关于c# - UWP InkCanvas内存不足,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/36407655/

10-11 23:51