четверг, 8 апреля 2010 г.

WPF, DataBinding и ToolTip

WPF - интересная технология! Интересная, прежде всего, своими странностями и непонятностями, которые, вкупе с мощью и развесистостью библиотек, не перестают удивлять. :о)) В этот раз "порадовал" датабаиндинг внутри комплексного тултипа (всплывающей подсказки). Примитивнейший, казалось бы, пример:

<Window
  x:Class="WpfApplication2.Window1"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:local="clr-namespace:WpfApplication2"
  Width="300" Height="300"
>
  <Window.Resources>
    <XmlDataProvider x:Key="MyData" XPath="//test:Items/test:Item">
      <XmlDataProvider.XmlNamespaceManager>
        <XmlNamespaceMappingCollection>
          <XmlNamespaceMapping Uri="urn:test" Prefix="test" />
        </XmlNamespaceMappingCollection>
      </XmlDataProvider.XmlNamespaceManager>
      <x:XData>
        <Items xmlns="urn:test">
          <Item Text="Text 1" Description="Description 1" />
          <Item Text="Text 2" Description="Description 2" />
          <Item Text="Text 3" Description="Description 3" />
        </Items>
      </x:XData>
    </XmlDataProvider>
 
    <DataTemplate x:Key="MyTemplate">
      <TextBlock Text="{Binding XPath='@Text'}">
        <TextBlock.ToolTip>
          <StackPanel Orientation="Horizontal">
            <TextBlock Text="{Binding XPath='@Text'}" />
            <TextBlock Text=" : " />
            <TextBlock Text="{Binding XPath='@Description'}" />
          </StackPanel>
        </TextBlock.ToolTip>
      </TextBlock>
    </DataTemplate>
  </Window.Resources>
 
  <ItemsControl ItemTemplate="{StaticResource MyTemplate}" ItemsSource="{Binding Source={StaticResource MyData}}" />
</Window>

Но если включить вывод отладочной информации из источника System.Windows.Data (см. Trace sources in WPF или недавний пост про Отключение PresentationTraceSources в WPF) то в окошке Output студии можно видеть:

System.Windows.Data Information: 10 : Cannot retrieve value using the binding and no valid fallback value exists; using default instead. BindingExpression:XPath=@Description; DataItem=null; target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')
System.Windows.Data Information: 10 : Cannot retrieve value using the binding and no valid fallback value exists; using default instead. BindingExpression:XPath=@Text; DataItem=null; target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')
System.Windows.Data Information: 10 : Cannot retrieve value using the binding and no valid fallback value exists; using default instead. BindingExpression:XPath=@Description; DataItem=null; target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')
System.Windows.Data Information: 40 : BindingExpression path error: 'InnerText' property not found for 'current item of collection' because data item is null. This could happen because the data provider has not produced any data yet. BindingExpression:Path=/InnerText; DataItem=null; target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')
System.Windows.Data Information: 19 : BindingExpression cannot retrieve value due to missing information. BindingExpression:Path=/InnerText; DataItem=null; target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')
System.Windows.Data Information: 20 : BindingExpression cannot retrieve value from null data item. This could happen when binding is detached or when binding to a Nullable type that has no value. BindingExpression:Path=/InnerText; DataItem=null; target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')
System.Windows.Data Information: 10 : Cannot retrieve value using the binding and no valid fallback value exists; using default instead. BindingExpression:Path=/InnerText; DataItem=null; target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')
System.Windows.Data Information: 40 : BindingExpression path error: 'InnerText' property not found for 'current item of collection' because data item is null. This could happen because the data provider has not produced any data yet. BindingExpression:Path=/InnerText; DataItem=null; target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')

Мне это кажется непорядком: а почему бы и не выполнить предписания и не сделать баиндинг только тогда, когда данные действительно появятся? При этом получилось вот что:

<DataTemplate x:Key="MyTemplate">
  <TextBlock Text="{Binding XPath='@Text'}">
    <TextBlock.ToolTip>
      <ToolTip>
        <StackPanel Name="ToolTip" Orientation="Horizontal">
          <TextBlock>
            <TextBlock.Style>
              <Style TargetType="{x:Type TextBlock}">
                <Setter Property="Text" Value="{Binding XPath='@Text'}" />
                <Style.Triggers>
                  <Trigger Property="DataContext" Value="{x:Null}">
                    <Setter Property="Text" Value="" />
                  </Trigger>
                </Style.Triggers>
              </Style>
            </TextBlock.Style>
          </TextBlock>
          <TextBlock Text=" : " />
          <TextBlock>
            <TextBlock.Style>
              <Style TargetType="{x:Type TextBlock}">
                <Setter Property="Text" Value="{Binding XPath='@Description'}" />
                <Style.Triggers>
                  <Trigger Property="DataContext" Value="{x:Null}">
                    <Setter Property="Text" Value="" />
                  </Trigger>
                </Style.Triggers>
              </Style>
            </TextBlock.Style>
          </TextBlock>
        </StackPanel>
      </ToolTip>
    </TextBlock.ToolTip>
  </TextBlock>
</DataTemplate>

То есть баиндинг выставляется через стиль, а затем в DataTrigger-е сбрасывается в случае, когда данных нет.

Я ещё не до конца уверен, что подобными оптимизациями вообще стоит заниматься, но всё-таки лучше, когда удаётся обойти максимально возможное количество предупреждений. В данном случае обойти предупреждения совсем не тяжело, хотя и кода получилось заметно больше.