Как я могу сделать форматированные данные ширины TextBlock привязанными к локализуемым?

В моем приложении WPF я хотел бы отобразить что-то похожее на это:

Пользователь Боб вышел из системы 22:17.

Где "Боб" и "22:17" - значения, привязанные к данным.

Очевидным способом сделать это будет использование StackPanel с несколькими дочерними элементами TextBlock, некоторые из которых связаны с данными:

<StackPanel Orientation="Horizontal">
   <TextBlock Text="The user"/>
   <TextBlock Text="{Binding Path=Username}" TextBlock.FontWeight="Bold" />
   <TextBlock Text="has logged off at"/>
   <TextBlock Text="{Binding Path=LogoffTime}" TextBlock.FontWeight="Bold" />
</StackPanel/>

Это работает, но это уродливо. Предполагается, что программа должна быть локализована на разных языках, а отдельные строки для "Пользователь" и "вышла из системы" - это рецепт для локализации.

В идеале я хотел бы сделать что-то вроде этого:

<TextBlock>
     <TextBlock.Text>
         <MultiBinding StringFormat="{}The user <Bold>{0}</Bold> has logged off at <Bold>{1}</Bold>">
             <Binding Path="Username" />
             <Binding Path="LogoffTime" />
         </MultiBinding>
</TextBlock>

Итак, переводчик увидит полное предложение The user <Bold>{0}</Bold> has logged off at <Bold>{1}</Bold>. Но это не работает, конечно.

Это должна быть общая проблема, какое правильное решение для этого?

Ответы

Ответ 1

Я никогда не пытался делать что-то подобное раньше, но если бы мне пришлось, я бы, вероятно, попытался использовать конвертер, который берет MultiBinding и разрывает его и возвращает StackPanel частей

Например, привязка будет выглядеть примерно так:

<Label>
     <Label.Content>
             <MultiBinding Converter={StaticResource TextWithBoldParametersConverter}>
                 <Binding Source="The user {0} has logged off at {1}" />
                 <Binding Path="Username" />
                 <Binding Path="LogoffTime" />
             </MultiBinding>
    </Label.Content>
</Label>

И конвертер сделает что-то вроде

public class TextWithBoldParametersConverter: IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        // Create a StackPanel to hold the content
        // Set StackPanel Orientation to Horizontal 

        // Take values[0] and split it by the {X} tags

        // Go through array of values parts and create a TextBlock object for each part
            // If the part is an {X} piece, use values[X+1] for Text and make TextBlock bold
            // Add TextBlock to StackPanel

        // return StackPanel
    }
}

Ответ 2

Проблема, которую я вижу, заключается в том, что вам нужен еще один String с другим присутствием пользовательского интерфейса в String.

Один из вариантов может состоять в том, чтобы отменить необходимость выделения жирным шрифтом и просто разместить одиночный String в файле Resources.resx. Тогда String будет ссылаться на свойство, которому привязан TextBlock, возвращая значения по мере необходимости; {0} и {1}, где это применимо.

Другим вариантом может быть возвращение набора значений Run, которые должны быть сохранены в TextBlock. Вы не можете привязываться к Run из окна в 3.5, но я считаю, что вы можете в 4.

            <TextBlock>
                  <Run Text="The user "/><Run FontWeight="Bold" Text="{Binding User}"/><Run Text="has logged at "/><Run FontWeight="Bold" Text="{Binding LogoffTime}"/>
            </TextBlock>

Последний вариант можно найти здесь и включает в себя создание более динамичного подхода к концепции Run, позволяющий привязывать ваши значения и затем привяжите их обратно к String.

Ответ 3

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

Если вы Microsoft, решение состоит в том, чтобы поместить все ваши шаблоны в словарь ресурсов, создать проект, который может использовать Expression Blend, чтобы представить их, а затем заставить ваших переводчиков работать с Blend (и, возможно, с кем-то, кто может помочь они его используют), чтобы перевести текст в словарь ресурсов, переупорядочивая элементы в шаблоне, где есть идиоматические различия в порядке слов. Это, конечно же, самое дорогое решение, но у него есть преимущество в получении проблем с форматированием (например, кто-то забыл, что французский текст занимает примерно на 20% больше места, чем текст на английском языке), в то же время, когда пользовательский интерфейс переводится. Это также имеет то преимущество, что он обрабатывает каждую презентацию текста в пользовательском интерфейсе, а не только презентации, созданные путем объединения текстовых блоков.

Если вам действительно нужно исправить стопки текстовых блоков, вы можете создать простое XML-представление помеченного текста и использовать XSLT для создания вашего XAML из файла в этом формате. Например, что-то вроде:

<div id="LogoutTime" class="StackPanel">
   The user 
   <strong><span class="User">Bob</span><strong>
   logged out at
   <strong><span class="Time">22:17</span></strong>
   .
</div>

По удивительному совпадению этот формат разметки является тем, который также можно просматривать в веб-браузере, поэтому его можно редактировать с помощью очень широкого спектра инструментов и корректуры без использования, скажем, Expression Blend. И это относительно просто перевести обратно в XAML:

<xsl:template match="div[@class='StackPanel']">
   <DataTemplate x:Key="{@id}">
      <StackPanel Orientation="Horizontal">
         <xsl:apply-templates select="node()"/>
      </StackPanel>
   </DataTemplate>
</xsl:template>

<xsl:template match="div/text()">
   <TextBlock Text="{.}"/>
</xsl:template>

<xsl:template match="strong/span">
   <TextBlock FontWeight="Bold">
      <xsl:attribute name="Text">
         <xsl:text>{Binding </xsl:text>
         <xsl:value-of select="@class"/>
         <xsl:text>}</xsl:text>
      </xsl:attribute>
   </TextBlock>
</xsl:template>