WP7:ContextMenu使用中碰到的问题
这两天发现项目占用内存过高,突然就象检查一下各个页面的回收是否正常,最后定位到Silverlight Toolkit中的ContextMenu控件使得我的一个页面在离开的时候无法被正常的回收,这里记录一下定位的过程:
首先,重载页面的OnRemovedFromJournal函数,我们知道当以GoBack的方式回退到回退栈中的前一个页面时,这个函数会被首先执行,然后才是OnNavigatedFrom函数。
1 protected override void OnRemovedFromJournal(JournalEntryRemovedEventArgs e)
2 {
3 this.DataContext = null;
4
5 base.OnRemovedFromJournal(e);
6 }
在OnRemovedFromJournal函数中把页面绑定的DataContext置为null,防止和页面绑定的ViewModel导致页面无法回收。
然后,给页面加上析构函数,好让我知道在离开页面时,是否页面是否会被析构。
~MyTestPage()
{
System.Diagnostics.Debug.WriteLine("Finalizing " + this.GetType().FullName);
}
就是在析构函数中加上个输出语句,便于观察。
再然后就是加上断点,调试项目,跳转到MyTestPage,然后按BackKey进行回退,预料之中首先执行OnRemovedFromJournal函数,但是输出控制台并没有输出那句“Finalizing”,显然页面没有被销毁,它被阻碍了。
可以上网或者在MSDN上查,什么样的情况会导致页面无法回收。我了解的三个基本情况是:
- 页面的DataContext为置空,也就是说页面和ViewModel还关联着,这会导致页面无法回收。
- 页面中的部分控件会导致页面无法回收(原因有很多,后面会提到)。
- 页面中注册的一些事件,没有退订也会导致页面无法回收。
估摸着我碰到的情况应该在2.3中产生,我先尝试着能不能把情况3给排除掉。怎么排除呢,我把xaml中和cs中注册的事件在OnNavigatedFrom函数中全部退订一边,然后在调试程序,和上面一样的步骤看能不能在输出控制台中看见那句“Finalizing ”,然后答案是没有看见,很好,情况3就被排除了。
其实我也认为页面中的控件导致页面无法得到回收最为可能,情况3已经排除了,我就开始把xaml文件中的控件一个个的试(注释、调试、查看结果),好在页面的布局不是很复杂啊,最着定位到这样一个DataTemplate:
1 <coding4fun:Tile Background="{Binding TileBackgroundBrush}" Margin="2,0,0,2" Click="Tile_Click" Loaded="Tile_Loaded">
2 <toolkit:ContextMenuService.ContextMenu>
3 <toolkit:ContextMenu IsZoomEnabled="False" Foreground="{StaticResource S_White}"
4 Background="{StaticResource PhoneAccentBrush}"
5 BorderBrush="{StaticResource PhoneAccentBrush}">
6 <toolkit:MenuItem Visibility="{Binding , Converter={StaticResource}}"
7 Header="{Binding, Source={StaticResource LocalizedStrings}}"
8 Command="{Binding}" Foreground="White" />
9 <toolkit:MenuItem Visibility="{Binding, Converter={StaticResource}}"
10 Header="{Binding, Source={StaticResource LocalizedStrings}}"
11 Command="{Binding}" Foreground="White" />
12 <toolkit:MenuItem Header="{Binding , Source={StaticResource LocalizedStrings}}"
13 Command="{Binding }" Foreground="White" />
14 </toolkit:ContextMenu>
15 </toolkit:ContextMenuService.ContextMenu>
16 </coding4fun:Tile>
再然后我就把这块代码给注释掉:
<toolkit:ContextMenuService.ContextMenu>
<toolkit:ContextMenu IsZoomEnabled="False" Foreground="{StaticResource S_White}"
Background="{StaticResource PhoneAccentBrush}"
BorderBrush="{StaticResource PhoneAccentBrush}">
<toolkit:MenuItem Visibility="{Binding , Converter={StaticResource}}"
Header="{Binding, Source={StaticResource LocalizedStrings}}"
Command="{Binding}" Foreground="White" />
<toolkit:MenuItem Visibility="{Binding, Converter={StaticResource}}"
Header="{Binding, Source={StaticResource LocalizedStrings}}"
Command="{Binding}" Foreground="White" />
<toolkit:MenuItem Header="{Binding , Source={StaticResource LocalizedStrings}}"
Command="{Binding }" Foreground="White" />
</toolkit:ContextMenu>
</toolkit:ContextMenuService.ContextMenu>
断点、调试,~MyTestPage函数执行了,也就是说页面被析构了,不过在这儿就能断定是ContextMenu的原因么?在MenuItem中还绑定了Command,Command对象虽然好用,不过也可能是元凶,先把它给删掉看是不是Command的原因,结果~MyTestPage木有执行,不是Command的原因,不过到这儿突然发现这个担心其实是多余的,我不是在OnRemovedFromJournal函数中把页面的DataContext置为null了么。
那结果就指向了toolkit:ContextMenu ,是它导致我的页面无法被析构和回收,但是我又不能把它删掉,因为他承载了我需要的功能。没办法只能旁敲侧击,抄小道消灭这个问题,我的解决方案是:
<coding4fun:Tile Background="{Binding TileBackgroundBrush}" Margin="2,0,0,2" Click="Tile_Click" Loaded="Tile_Loaded">
在Tile中注册个Tile_Loaded事件,把所有的Tile维护到一个List中,在页面的OnNavigatedFrom函数中这样写一下,就能把Tile中的ContextMenu给消除掉:
foreach (var item in Tiles)//移除所有的MenuItem否则该页面无法回收
{
var menuItem = ContextMenuService.GetContextMenu(item);
ContextMenuService.SetContextMenu(item,null);
}
再次断点、调试,这回~MyTestPage函数执行了,页面被析构了。
@me http://weibo.com/tianyutingxy