In my WPF application, I would like to display something that looks like this:
User Bob has logged off at 22:17.
Where "Bob" and "22:17" are data-bound values.
The obvious way to do this would be to use a StackPanel with multiple TextBlock children, some of them data bound:
<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/>
This works, but it's ugly. The program is supposed to be localized to different languages, and having separate strings for "The user" and "has logged off at" is a recipie for localization disaster.
Ideally, I would like to do something like this:
<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>
So the translator would see a complete sentence The user <Bold>{0}</Bold> has logged off at <Bold>{1}</Bold>. But that doesn't work, of course.
This has to be a common problem, what's the right solution for this?
The issue I see is that you want a single String yet with a different UI presence across the String.
One option could be to dismiss the need for bold and simply place the single String within the Resources.resx file. That String will then be referenced in the property which the TextBlock is bound to, returning the values as needed; {0} and {1} where applicable.
Another option could be to returning a set of Run values to be held within the TextBlock. You can not bind to a Run out of the box in 3.5 however I believe you can in 4.
<TextBlock>
<Run Text="The user "/><Run FontWeight="Bold" Text="{Binding User}"/><Run Text="has logged at "/><Run FontWeight="Bold" Text="{Binding LogoffTime}"/>
</TextBlock>
The last option can be found here and involves creating a more dynamic approach to the Run concept, allowing you to bind your values and then tie them back to a String.
I've never tried to do something like this before, but if I had to I would probably try and use a Converter that takes the MultiBinding and breaks it up and returns a StackPanel of the pieces
For example, the binding would be something like:
<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>
And the Converter would do something like
public class TextWithBoldParametersConverter: IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
// Create a StackPanel to hold the content
// Set StackPanel's 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
}
}
There's unlikely to be a single solution to this problem because there are so many different ways it can manifest itself, and there are so many different ways to solve it.
If you're Microsoft, the solution is to put all of your templates in a resource dictionary, create a project that can use Expression Blend to present them, and then have your translators work with Blend (and probably someone who can help them use it) to translate the text in the resource dictionary, reordering the elements in the template where there are idiomatic differences in word order. This is of course the most expensive solution, but it has the advantage of getting formatting problems (like someone forgot that French text occupies about 20% more space than English text) solved at the same time that the UI is being translated. It also has the advantage that it handles every presentation of text in the UI, not just the presentations that are created by stacking together text blocks.
If you're really only going to need to fix stacks of text blocks, you can create a simple XML representation of marked-up text, and use XSLT to create your XAML from a file in that format. For instance, something like:
<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>
By an amazing coincidence, that markup format is one that can also be viewed in a web browser, so it can be edited with a really wide range of tools and proofread without using, say, Expression Blend. And it's relatively straightforward to translate back into 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>
Related
From a control in a WPF XAML view, I need to access the properties of another control that I can reach in the Visual Tree only when walking up to a common parent control and then down from there.
As an example:
<PageUserControl>
<Grid>
<TextBlock Text="Some example text" />
</Grid>
<TextBlock Text="{Binding Source={RelativeSource Mode=FindAncestor, AncestorType=PageUserControl, Path=??? I want to access the TextBlock}" />
</PageUserControl>
I want to access the text property of the first text block from the second text block (this is just an example).
What I would need is a way to combine relative sources, first one to go up the visual tree and find the PageUserControl, second one to go down the visual tree from there and find the grid and then finally a third one to find the text block within the grid.
Am I missing something here or is it just not possible?
I cannot add control IDs or something like this, it has to work with control types only.
I was thinking about something like a relative source that takes a XPath syntax, but it seems as if this was meant for another purpose (binding XML documents).
Maybe another idea?
Thank you!
I found a solution for my problem. It is possible using this approach:
<PageUserControl>
<Grid>
<TextBlock Text="Some example text" />
</Grid>
<TextBlock Text="{Binding Path=Children[0].Children[0].Text,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=PageUserControl}}" />
</PageUserControl>
While not very flexible, it is good enough for me.
In this case I am looking to use strings declared in a resource dictionary as part of a binding on a Text property. Binding just a single dynamic resource string is not a problem:
<TextBlock Text="{DynamicResource keyToMyString}" />
But you quickly run into problems if you need to use a StringFormat on a MultiBinding because you need to insert dynamic text or want to combine several strings. For example, if my MultiBinding looks like this:
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} {1} some more text">
<Binding Source="{x:Static Resources:Strings.string1}" />
<Binding Source="{x:Static Resources:Strings.string2}" />
</MultiBinding>
<TextBlock.Text>
I can inject string1 and string2 from the specified resource file into the bound text, no problems there. But I cannot find a way to use strings from a dynamic resource in the same way. (I'm using this method to inject company and product names into text from a merged resource dictionary).
With a TextBlock I can circumvent this issue by using several Run items for the TextBlock content (reference):
<TextBlock >
<Run Text="{DynamicResource CompanyName}" />
<Run Text="{DynamicResource ProductName}" />
<Run Text="{DynamicResource MajorVersion}" />
</TextBlock>
but this is of no help when needing to bind the dynamic resource to the Window Title property. Is there anyway to accomplish this with (creative, if necessary) use of the existing markup extensions (like x:Static, etc)? Or do we have to write our own markup extension to achieve this?
Dynamic resource references have some notable restrictions. At least one of the following must be true:
The property being set must be a property on a FrameworkElement or FrameworkContentElement. That property must be backed by a DependencyProperty.
The reference is for a value within a Style Setter.
The property being set must be a property on a Freezable that is provided as a value of either a FrameworkElement or FrameworkContentElement property, or a Setter value.
Source: XAML Resources, MSDN.
So, in case of using the Binding, all the statements are violated.
As was shown, the DynamicResourceExtension works just fine for an instance of the Run class because the Run class (at least) is derived from the FrameworkContentElement class.
Additional references
Resources section: Wha' Happened Part Two: More Property Changes in WPF.
WPF: Dependency Properties & Resources.
When would I use the Text attribute of the <TextBlock> and when should I put my text in the content of the <TextBlock>?
<TextBlock Text="Example Text" />
vs.
<TextBlock>Example Text</TextBlock>
The former can be bound, whilst the latter is particularly useful when combining Runs:
<TextBlock Text="{Binding SomeProperty}"/>
<TextBlock>
<Run>You have </Run>
<Run Text="{Binding Count}"/>
<Run>items.</Run>
</TextBlock>
The use of the Text property has become common as a result of previous versions of the Xaml parser but the placing the text as content is more natural especially if you have a background in HTML.
The fact the many TextBlocks either have simple short chunks of literal text in or are bound. Would tip the balance IMO to using the Text property. In addition any globalisation that may come along latter may end with those literals being replaced by bindings as well.
What exactly I want to do is that there are 2 tables,ie, user and userprofile and both of them have almost identical fields. I shall take example of the email field. There is a textbox and the User table email field value is displayed in it. What I want to do is, have a context menu such that when the user right clicks on the textbox, the menu displays both the User and UserProfile email field values. – developer 1 hour ago
Whatever value one selects from the context menu the textbox then displays that value. You can use Binding Email1 and Binding Email2, as I have no problems getting those two values from database so I shall change my code accordingly. As I am new to WPF and .NET framework itself, I am not sure how to achieve this. Please let me know if I have made myself clear this time.
I am not sure how to handle commands and events. Can anybody show me the code to accomalish this..
<TextBox Style="{StaticResource FieldStyle}" Text="{Binding Email1, UpdateSourceTrigger=PropertyChanged}">
<TextBox.BorderBrush>
<MultiBinding Converter="{StaticResource TextBoxBorderConverter}">
<Binding Path="Email1"/>
<Binding Path="Email2"/>
</MultiBinding>
</TextBox.BorderBrush>
</TextBox>
Thanks in advance
At risk of giving you a WPF/MVVM noob answer and getting flamed, here goes. I can't advise you on databinding with databases since I've never done it, so I will just give you the XAML and it's up to you to work on the database end.
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<TextBox Height="28" Text={Binding PreferredEmail}">
<TextBox.ContextMenu>
<ContextMenu>
<MenuItem Header="{Binding Email1}" Command="{Binding Email1Command}" />
<MenuItem Header="{Binding Email2}" Command="{Binding Email2Command}" />
</ContextMenu>
</TextBox.ContextMenu>
</TextBox>
</Grid>
</Page>
In the databinding to objects case, PreferredEmail, Email1, and Email2 would bind to a dependency property or a property that raises the PropertyChanged event. This is how your ViewModel (or whatever you want to call the lower-level code) will update the data. If you change those values in code-behind, ultimately it'll get reflected in the context menu automagically. Then you have to implement two ICommand-based classes to handle the setting of PreferredEmail.
I think it's super lame to implement two command handlers, and it certainly won't scale well if you have to add more email sources. I think a better solution would be to use one command handler and a CommandParameter that is the selected MenuItem header, but I don't know how to do that. But in any case, the two command handler solution will still work if you're in a bind.
I'd like an advice to the following problem: I want to embed a Button into a text flow, but when I embed a Button and Label (or TextBlock) into the WrapPanel, I get the first figure:
alt text http://sklad.tomaskafka.com/files/wpf-wrappanel-problem.png
I think that one of solutions could be FlowDocument, but I feel that this is far too heavy for a control simple like this (which could be used in several hundred instances). Do you have some other ideas about how to implement this? Thank you!
EDIT:
One solution could be the following (I didn't know it was possible to put more stuff into TextBlock), but I would lose the ability to bind (which I need):
<TextBlock TextWrapping="Wrap">
<Span>
<Button x:Name="MyButton" Command="{Binding Path=MyCommand}" Content="+" />
<Run x:Name="MyLabel" Text="{Binding Path=Subject}" />
<!--
Problem: binding makes following error:
A 'Binding' cannot be set on the 'Text' property of type 'Run'.
A 'Binding' can only be set on a DependencyProperty of a DependencyObject.
-->
</Span>
</TextBlock>
To bind to Run.Text, checkout the BindableRun class by Fortes. Simple to implement, I use it all over my projects.
I found that implementing BindableRun correctly is pretty tricky - and almost all other available implementations will cause an exception from wpf layouting engine when the bound content changes from null to something non-null - see this problem, keyword "Collection was modified; enumeration operation may not execute."
Corrrect implementation from Microsoft is here - it shows how tricky this really is.
Solution: BindableRun class + the following markup:
<TextBlock>
<Button x:Name="MyButton" Command="{Binding Path=MyCommand}" Content="+" />
<common:BindableRun x:Name="Subject" BindableText="{Binding Path=Subject}"/>
</TextBlock>
Funny thing it works on the designer of a UserControl...
In that case, using the Property Change of your control to set the value to the Run is enough. I mean, if you had something like:
<TextBlock>
<Run Text="{Binding ElementName=thisCtrl, Path=Description}" />
</TextBlock>
Then just name the run, and on your property change handler of your UserControl DependencyProperty get/set the value.