一个由不合理布局导致Windows 8.1 store应用失去响应的例子

         如果我们忽视了Windows store应用的界面设计,通常你会得到一个糟糕的用户界面,然而你有没有想过在某种情况下却会导致应用程序失去响应甚至闪退,这里就有这样一个活生生的例子。

         这个例子首先要从Windows 8 store应用的应用视图(application view)说起,Windows 8 Store应用有三种视图状态:full screen(程序填满整个屏幕), snapped(应用程序只占据了整个屏幕的一小部分),Fill(应用程序占据了snapped宽度剩余的区域)。所以在windows 8上设计Windows Store应用时,开发者通常会设计三种布局对应于这三种不同的视图。但是在Windows 8.1上取消了这三种视图状态,取而代之的是,应用商店程序可以在一定范围内任意调节应用的宽度,这个范围指的是应用程序有一个最小宽度设定,这通常是500个像素,您的Windows store应用至少要达到这个宽度,或者剩下空间至少有这个宽度。这样就给开发人员带来了挑战,我们不可能使用固定的布局,而是需要不同的宽度动态调整。如果不作调整的话,那么就有可能会发生下面这个问题。

        为了重现这个问题,我们可以在Visual Studio 2013上创建一个基于Hub应用模板的Windows store应用,然后在ItemPage添加如下XAML代码:

<Grid Grid.Row="1" x:Name="contentRegion">

<Grid.ColumnDefinitions>

     <ColumnDefinition x:Name="firstColumn" Width="600"/>

     <ColumnDefinition x:Name="secondColumn" Width="*"/>

</Grid.ColumnDefinitions>

     <ScrollViewer Grid.Column="1" >

          <TextBlock Margin="40" Text="{Binding Content}" Style="{StaticResource BaseTextBlockStyle}" />

     </ScrollViewer>

</Grid>

        当你点击HubPage页面的项目时,应用就会跳转到ItemPage页面。在这个ItemPage页面会显示相应的文字。在全屏状态下,应用工作得很好很流畅。可是,如果你把应用的宽度调整到很小,比如500个像素,这时显示ItemPage页面时就会长时间失去响应,在一些极端的情况下甚至会应为失去响应时间过长而闪退。

 

       实际上这个问题是由于Grid的第二列宽度太小而导致的。在这个页面里,你已经设置了第一个列的宽度为600,而出问题的时候应用的总宽度也不过500,这样留给第二列中的ScrollView里的TextBlock的宽度几乎就没有了。而在第二列中的这个TextBlock通常会有以下属性设置:

            <Setter Property="TextTrimming" Value="CharacterEllipsis "/>

            <Setter Property="TextWrapping" Value="Wrap"/>

            <Setter Property="Typography.DiscretionaryLigatures" Value="True"/>

       这是因为BaseTextBlockStyle会包含以上属性设置,而系统内置的TextBlock Style都是继承自BaseTextBlockStyle的。只要你对TextBlock使用了系统的Style,那么就会包含以上设置。这几个设置定义了系统如何对TextBlock里的文本进行分行断句,在文本框宽度很窄甚至为0的情况下,系统就会一直忙于对TextBlock中文本的解析而无法响应用户的操作。这时候如果你使用Windbg跟踪一下应用程序,你可以看到应用实际上一直忙于RichTextServices中的格式化代码:

ChildEBP RetAddr 

049bcfbc 0f812084 Windows_UI_Xaml!Ptls6::LsCreateLineCore+0x363

049bcfec 0f811f3f Windows_UI_Xaml!Ptls6::LsCreateLine+0x28

049bd0bc 0f811e0e Windows_UI_Xaml!RichTextServices::Internal::LsTextLine::FormatCollapsed+0x102

049bd0e8 0f9cd747 Windows_UI_Xaml!RichTextServices::Internal::LsTextLine::Collapse+0xd1

049bd11c 0f8d8dc7 Windows_UI_Xaml!ParagraphNode::TrimLineIfNecessary+0x8e

049bd150 0f8a15e0 Windows_UI_Xaml!ParagraphNode::FormatLineAtIndex+0x78

049bd1c4 0f8a1355 Windows_UI_Xaml!ParagraphNode::ArrangeCore+0x114

049bd1e0 0f8a122e Windows_UI_Xaml!BlockNode::Arrange+0x73

049bd218 0f8a1438 Windows_UI_Xaml!ContainerNode::ArrangeCore+0x81

049bf5b8 0f880bc5 Windows_UI_Xaml!CUIElement::Arrange+0x7e1

049bf5f0 0f86218f Windows_UI_Xaml!CLayoutManager::UpdateLayout+0x136

049bf690 0f861de4 Windows_UI_Xaml!CCoreServices::NWDrawTree+0x371

049bf6d4 0f861675 Windows_UI_Xaml!CCoreServices::NWDrawMainTree+0x205

049bf6f8 0f861549 Windows_UI_Xaml!CWindowRenderTarget::Draw+0x4e

049bf730 0f861b85 Windows_UI_Xaml!CXcpBrowserHost::OnTick+0xd9

(Inline) -------- Windows_UI_Xaml!CXcpDispatcher::Tick+0x46

049bf750 0f8600b4 Windows_UI_Xaml!CXcpDispatcher::OnReentrancyProtectedWindowMessage+0x8b

 (Inline) -------- Windows_UI_Xaml!DirectUI::DXamlCore::RunMessageLoop+0x52

049bf9f4 5539f45e Windows_UI_Xaml!DirectUI::FrameworkView::Run+0x5e

049bfa00 5539f322 twinapi_appcore!Windows::ApplicationModel::Core::CoreApplicationView::Run+0x27

049bfa20 69cc008a twinapi_appcore!<lambda_f0454c86bc54370cf843d844d6c13e00>::operator()+0xb2

049bfaa4 7540495d shcore!_WrapperThreadProc+0xe2

049bfab0 774998ee KERNEL32!BaseThreadInitThunk+0xe

049bfaf4 774998c4 ntdll!__RtlUserThreadStart+0x20

049bfb04 00000000 ntdll!_RtlUserThreadStart+0x1b

 

     要解决这个问题其实很简单,这里你不应该将Grid的第一列设为一个固定值600,而是应该采用星号来确定两个列的比例,当在任何时候TextBlock都有足够宽度容纳文本时,这个问题就解决了。