Как я могу сделать форматированные данные ширины 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>