2

I'm using MVVMLight to create a questionnaire and running into memory issues when rendering the InkCanvas controls. Here's a watered down example of what I am working with:

QuestionVm

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

QuestionnaireVm

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

Questionnaire.xaml.cs

//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());
}

The RAM spikes on each page load and it's clear the memory allocated to the InkCanvas is never being deallocated. On the third or so page when roughly ~125 InkCanvas controls are rendered, the app throws a System.OutOfMemoryException.

My question is, why aren't these controls being deallocated? And how can I manually free up the memory? If I comment out the InkCanvas, the questionnaire is fine and Children.Clear() appears to be cleaning up the TextBlocks or any other controls without issue.

UPDATE

So after working with @Grace Feng I tried to refactor my approach and use a ListView with a data template rather than creating a grid from my xaml.cs.

Questionnaire.xaml

                <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>

Questionnaire.xaml.cs

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

Unfortunately I am still experiencing the same out of memory error even using the ListView data template method. Thoughts?

2 Answers2

1

On the third or so page when roughly ~125 InkCanvas controls are rendered, the app throws a System.OutOfMemoryException.

I just reproduced this problem, and yes you are right about this.

My question is, why aren't these controls being deallocated?

From the code in your Questionnaire.xaml.cs, I think you are dynamic adding questionVm.Question and a new instance of InkCanvas to the parent control named "questionnaireForm", and before doing this, you clear the children of this parent control. There is no "deallocating" action during you load your data, so none of these controls will be deallocated.

And how can I manually free up the memory? If I comment out the InkCanvas, the questionnaire is fine and Children.Clear() appears to be cleaning up the TextBlocks or any other controls without issue.

If you manually free up the memory, you will need to remove some of these InkCanvas, or I think what you can do right is for this scenario is using UI virtualization to reduce the memory loss when you load data.

In an UWP APP, there are two controls which have UI virtualization function already, ListView and GridView. I just test these two controls with over 125 instance of empty InkCnavas. The Item's size of GridView is adaptive to its layout in the item, so when the InkCanvas is empty, it will still load all data at once, the out of memory error will still happen. But the ListView control by default will take one row to hold its items, the UI Virtualization works fine here.

And I saw that you add InkCanvas based on the questionVm.HandwritingControl != null, so here is a workaround, you can for example design your Questionnaire.xaml like this:

<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>

And in the code behind for example:

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
    }
}

And create a CustomDataTemplateSelector class like this:

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

In a few words, you can do this with a ListView control and its ItemTemplateSelector, since I don't have all your code, the above code is just a sample, not 100% correctly for your case.

Grace Feng
  • 16,564
  • 2
  • 22
  • 45
  • Grace, this is a fantastic solution and will be marked as the answer. I had another question for you regarding this. Our data structure is much more complex where there is a large variety of questions / InkCanvas / other controls. Would the best solution be to create a DataTemplate for each unique combination? I feel as if there must be a way to make this more data driven. With the grid approach I simply render each row based on the data given for each question. How would you tackle a more complex scenario? – System Out Of Memory Apr 05 '16 at 14:35
  • @jagsrocknfl, you mean you have more than two styles for all questions? – Grace Feng Apr 05 '16 at 15:13
  • Yes, each question is essentially any combination of: Text / InkCanvas & Radio Button / Checkbox / etc.. This is all data driven and each question may have all controls or only text. So using data templates would we need a new template for each combination? Or just use a single template where they are all nullable? – System Out Of Memory Apr 05 '16 at 15:19
  • If that is so, I would try to create a `usercontrol` which use all needed controls and the most complex style, and use this control in the DataTemplate, together I will use converters to judge the children controls's visibilities inside the usercontrol based on the data. It means, creating a Usercontrol and binding the Visibility property of the children controls inside it with converters. Or forget about usercontrol, just design your layout in the datatemplate and use binding with converters to make controls visiable or not. – Grace Feng Apr 05 '16 at 15:21
  • I've updated the question above with what I've tried. I am still experiencing the same memory issues. – System Out Of Memory Apr 05 '16 at 16:31
  • @StillBahlman, I don't quite understand your code of Questionnaire.xaml.cs now, I didn't see how you add data to the itemsource, I think the problem should be here, and please, we've discussed too much in this answer, you've marked my answer then unmarked, then marked and again unmarked...If you have more questions about this, could you please mark this answer and ask a new question? – Grace Feng Apr 06 '16 at 01:36
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/108396/discussion-between-still-bahlman-and-grace-feng-msft). – System Out Of Memory Apr 06 '16 at 11:55
  • 1
    Could you please reopen the chat discussion, looks like this one has been closed – Franklin Chen - MSFT Apr 19 '16 at 08:52
0

The problem is in this line

foreach (var questionVm in questionnaireVm.CurrentQuestions)
{ 
  questionnaireForm.Children.Add(questionVm.Question);
  if(questionVm.HandwritingControl != null)
    questionnaireForm.Children.Add(new InkCanvas()); //everytime you are creating a new object                    
}

try to create one object of InkCanvas and use it everytime inside the foreach loop. You can create the object in the constructor or at the class level

Apoorv
  • 2,023
  • 1
  • 19
  • 42