WPF TextBlock binding with <LineBreak/> - wpf

I have a TextBlock binding as follows in my ControlTemplate.
<TextBlock Grid.Column="1" VerticalAlignment="Center"
FontSize="16" FontFamily="Arial" FontWeight="Bold"
Text="{Binding RelativeSource={RelativeSource TemplatedParent},Path=ButtonText}">
</TextBlock>
When I set ButtonText as follows with , it doesn't work. It doesn't display in separate line.
ButtonText="Change<LineBreak/> Casette"
How to fix this? Appreciate your help, please provide me with sample code.

A TextBlock displays the contents of its Inlines property. The Text property exists only as a convenience (though it's a significant one): if you set the Text property, the TextBlock will create a Run, set its content to the string you've provided, and save it in the Inlines collection.
When you set the content of a TextBlock element in XAML, the XamlReader populates the Inlines collection directly rather than through the Text property. It parses text nodes into Run objects, and elements as usual for XAML. So this:
<TextBlock>
Line1<LineBreak/>Line2
</TextBlock>
is treated as though it were actually this:
<TextBlock>
<Run>Line1</Run>
<LineBreak/>
<Run>Line2</Run>
</TextBlock>
Note, by the way, that if you try to set the Text property explicitly:
<TextBlock>
<TextBlock.Text>
Line1<LineBreak/>Line2
</TextBlock.Text>
</TextBlock>
you'll get an exception, because the XamlReader will try to create a LineBreak object, and the Text property can only contain a string.
Your binding isn't working the way you want it to because it's explicitly setting the Text property to a string. This doesn't get parsed as XAML (and good thing, too). And so what's displaying in the TextBlock is the content of that string.
So there are basically two ways to accomplish what you're trying to accomplish. In your case, you probably can just get away with embedding a newline into the string.
But this is trickier than it looks if you're doing it from XAML. Because XAML is XML, and XML does some funny things to whitespace. You're OK if you set it explicitly in an attribute using XML character entities, e.g.:
<TextBlock Text="Line 1
Line 2"/>
But that won't work if you do it this way:
<TextBlock>
<TextBlock.Text>
Line 1
Line 2
</TextBlock.Text>
</TextBlock>
because the XML parser normalizes whitespace in element content. That CR/LF pair gets turned into a single space, and that's what gets into the text property.
If you're using binding, you don't need to worry about any of this XML stuff (unless you're binding to the contents of an XML document!). You can just put \r\n into the property value.
The other way to do this is to directly populate the TextBlock's Inlines property. But you can't do this via binding, since Inlines isn't a dependency property - in fact, it's read-only, and you can only populate it by calling its Add or AddRange methods.

I used this code to obtain what you want. This is the XAML:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="1" VerticalAlignment="Center"
FontSize="16" FontFamily="Arial" FontWeight="Bold"
Text="{Binding Path=ButtonText}">
</TextBlock>
</Grid>
and this is the code behind. To make the example simplier, I don't create a ViewModel class:
public Window1()
{
InitializeComponent();
DataContext = this;
ButtonText = "Change\r\nCasette";
}
public string ButtonText
{
get { return (string)GetValue(ButtonTextProperty); }
set { SetValue(ButtonTextProperty, value); }
}
// Using a DependencyProperty as the backing store for ButtonText. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ButtonTextProperty =
DependencyProperty.Register("ButtonText", typeof(string), typeof(Window1), new UIPropertyMetadata(""));

Related

Expander-like WPF control that only hides empty children

In my WPF view, I need something similar to an Expander or a TreeView, but instead of completely hiding the content, I only want to hide empty parts, i.e. TextBoxes with null or empty text, or empty ItemCollections.
I thought about using a style with a DataTrigger or set Visibility with a converter, but how would I link that to the parent's setting (e.g. IsExpanded)?
I would like to avoid doing this in the ViewModel, as that would need a property for each section (and I need lots of them), but it's purely visual and therefore IMHO it only belongs to the View.
So I guess the way to go is to use DependencyProperties or write some CustomControls, but I don't have an idea where to start. The XAML of the end result could look something like this:
<CustomExpander Header="Main" CollapseContentIfEmpty="True">
<CustomExpander Header="Section1" CollapseContentIfEmpty="True">
<StackPanel>
<TextBox Text="{Binding SomeString}" />
<TextBox Text="{Binding SomeEmptyString}" />
</StackPanel>
</CustomExpander>
<CustomExpander Header="Section2" CollapseContentIfEmpty="True">
<ListView ItemsSource="{Binding SomeCollectionView}" />
</CustomExpander>
</CustomExpander>
In this example, if CollapseContentIfEmpty is set to true and the CollectionView shows no elements (e.g. due to filters), only the content of SomeString should be visible, along with all the headers. If SomeString is empty, only "Main" should be visible, as now all child CustomExpanders are empty as well.
Setting CollapseContentIfEmpty to false (e.g. via a Button like in Expander) would show all Children again, regardless if they are empty or not.
I thought about using a style with a DataTrigger or set Visibility with a converter, but how would I link that to the parent's setting (e.g. IsExpanded)?
Use a binding with a {RelativeSource}.
In the following example, the TextBlock is invisible unless you set the Tag property of the parent UserControl to true:
<UserControl xmlns:sys="clr-namespace:System;assembly=mscorlib">
<UserControl.Tag>
<sys:Boolean>false</sys:Boolean>
</UserControl.Tag>
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
</UserControl.Resources>
<StackPanel>
<TextBlock Text="text..."
Visibility="{Binding Tag,
RelativeSource={RelativeSource AncestorType=UserControl},
Converter={StaticResource BooleanToVisibilityConverter}}" />
</StackPanel>
</UserControl>
You can of course replace the UserControl with a custom control with a custom bool property.
An Expander collapses its entire Content which is different from hiding specific controls in that content.

Binding datacontext of usercontrol to mainwindow

I have a UserControl positioned inside of the MainWindow. The UserControl runs a query and populates certain TextBlocks within it. I also want to populate TextBlock in the MainWindow from the same returned data.
How do I bind the MainWindow data to the UserControl? I have tried this:
<MainWindow DataContext="{Binding Path=DataContext, ElementName=UserControlName}">
Any help would be appreciated. Thanks!
Here is a simple working example.
The UserControl XAML contains just a two-way bound text box. The relative source stuff is more verbose than you need, you could have a data context set above that, but it's just to make it clear where the property is coming from:
<TextBox x:Name="ucTextBox"
Text="{Binding Path=UcText,
RelativeSource={RelativeSource AncestorType={x:Type local:UserControl1}},
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}" />
User Control code behind declares either a dependency property or, as shown here, a regular property implementing INotifyPropertyChanged:
private string _ucText;
public string UcText
{
get { return _ucText; }
set
{
_ucText = value;
OnPropertyChanged("UcText");
}
}
The MainWindow XAML then sets it's own text block to the text property from the textbox in the user control, like so:
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Path=UcText, ElementName=uc1}"/>
<local:UserControl1 x:Name="uc1" />
</StackPanel>
Nothing extra is required in the MainWindow codebehind.
What this results in is a text box (in the user control) which - as you type inside it - updates the text block on the main window.

XAML text binding

I would bind a string property to text property like this: Text="{Binding propertyName}.
I also want to append a hardcoded string to this like Text="{Binding propertyName} appendedName. How to do this?
Text="{Binding propertyName,StringFormat='Your property is: {}{0}'}"
You could use Run Text:
<TextBlock>
<Run Text="{Binding YourBinding}"/>
<Run Text="Suffix"/>
</TextBlock>
If you want to use it like this several times I would recommend a TemplatedControl where you have a Suffix DependencyProperty and a Text DependencyProperty.
You should create new property that returns text + appendedName.
Another way is to use several text blocks.

Silverlight Label content binding problems

I'll preface this and say that I'm new to Silverlight development by about week so I'm most likely doing it wrong...
Anyway I have a Label and a TextBox done up thusly in XAML:
<dataInput:Label Target="{Binding ElementName=JobCode}" Height="18" HorizontalAlignment="Left" Margin="15,7,0,0" Name="lableJobCode" VerticalAlignment="Top" Width="250" FontWeight="Bold" Grid.Column="1" />
<TextBox Height="23" Text="{Binding SelectedRole.Job_Code}" HorizontalAlignment="Left" Margin="15,31,0,0" Name="JobCode" VerticalAlignment="Top" Width="277" Grid.Column="1" IsReadOnly="{Binding IsNotAdmin}" />
Everything works great, the only issue I have is that the binding I'm doing on the IsReadOnly attribute which goes to a boolean in my ViewModel which is set based on a call to an authentication service, is now overriding the label Content to the name of my ViewModel property: IsNotAdmin. I can't seem to find a way to specify which data binding source to pull the label content MetaData from. Maybe I'm missing something on how to manipulate control editablity/visibility from my ViewModel.
--Update: The data source class that the TextBox is bound to is as follows (for the relevant parts):
public class RoleSummary {
[Display(Name= "Job Code (To be Completed by HR):")]
public string Job_Code { get; set; }
Without the binding to the IsReadOnly attribute the Label displays the text from the data annotation just fine. When I add the binding it displays "IsNotAdmin"
can you post more of your code? I'm not entirely sure what it is that you're trying to make happen so it's hard to propose a solution.
I assume you're trying to create a text entry element that has validation performed on it (hence the label) -- but what exactly is the label supposed to be showing for it's content?
EDIT: I figured this out. The label control by default looks through all the properties in its datacontext looking for metadata it can use. For whatever reason it decided to use the metadata for the IsNotAdmin property in your code (even though you didn't set it manually, I assume that the Display metadata gets a default value of the property name), and so you get that for the text of the label.
Microsoft put in a property specifier into the data controls so you can tell it which property it should use for the metadata lookup: PropertyPath
Try it like this:
<dataInput:Label Target="{Binding ElementName=JobCode}" PropertyPath="SelectedRole.Job_Code" Height="18" HorizontalAlignment="Left" Margin="15,7,0,0" Name="lableJobCode" VerticalAlignment="Top" Width="250" FontWeight="Bold" Grid.Column="1" />
<TextBox Height="23" Text="{Binding SelectedRole.Job_Code}" HorizontalAlignment="Left" Margin="15,31,0,0" Name="JobCode" VerticalAlignment="Top" Width="277" Grid.Column="1" IsReadOnly="{Binding IsNotAdmin}" />
As long as your datacontext is right (which it should be) this should work for you -- it worked in my sample I reconstructed from your code.

How would I databind a Paragraph to a TextBlock?

How would I take a Paragraph object and databind them to the TextBlock for use in a DataTemplate?
A plain bind does nothing, just a ToString() of the Paragraph object.
The InLines property would let me add a list of TextRun's that make up the Paragraph manually, but that can't be bound to and I could really do with a binding based solution.
Edited question to focus on what I really need to do.
Here's an example using a nested ItemsControl. Unfortunately, it will make one TextBlock per Inline instead of putting the whole Paragraph into one TextBlock:
<Grid xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid.Resources>
<FlowDocument x:Key="document">
<Paragraph><Run xml:space="preserve">This is the first paragraph. </Run><Run>The quick brown fox jumps over the lazy dog.</Run></Paragraph>
<Paragraph><Run xml:space="preserve">This is the second paragraph. </Run><Run>Two driven jocks help fax my big quiz.</Run></Paragraph>
<Paragraph><Run xml:space="preserve">This is the third paragraph. </Run><Run>Sphinx of black quartz, judge my vow!</Run></Paragraph>
</FlowDocument>
<DataTemplate DataType="{x:Type Paragraph}">
<ItemsControl ItemsSource="{Binding Inlines}" IsHitTestVisible="False">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</DataTemplate>
</Grid.Resources>
<ListBox ItemsSource="{Binding Blocks, Source={StaticResource document}}"/>
</Grid>
If you want one Paragraph per element you should probably do as suggested and use a read-only RichTextBox, or do what this person did and derive from TextBlock so that the Inlines property can be bound.
I had a similar need and solved it along the lines of Andy's answer... I created a BindableTextBlock:
class BindableTextBlock : TextBlock
{
public Inline BoundInline
{
get { return (Inline)GetValue(BoundInlineProperty); }
set { SetValue(BoundInlineProperty, value); }
}
public static readonly DependencyProperty BoundInlineProperty =
DependencyProperty.Register("BoundInline", typeof(Inline), typeof(BindableTextBlock),
new UIPropertyMetadata((PropertyChangedCallback)((d, e) => { ((BindableTextBlock)d).Inlines.Clear(); ((BindableTextBlock)d).Inlines.Add(e.NewValue as Inline); })));
}
Then in my XAML I can bind to the BoundInline dependency property:
<DataTemplate x:Key="TempTemplate">
<t:BindableTextBlock TextWrapping="Wrap" BoundInline="{Binding Path=TextInlines}" Grid.Column="1" Grid.Row="1" Grid.ColumnSpan="2" />
</DataTemplate>
The one drawback to this is that you can only bind a single root Inline to the textblock, which worked fine for my situation as my content is all wrapped in a top-level Span.
I'm not sure if you can bind a Paragraph directly to a TextBlock's inlines. However, I was able to find the class BindableRun that lets you bind to the Run's Text property. Would that work for you instead?
EDIT: Modified my answer to reflect the edited question.
You could try to create your own DataTemplate for Paragraph objects that wraps each one in its own FlowDocument, which is then presented via a RichTextBox (readonly, of course)
I had almost the same problem and answered it in a similar way to joshperry, sub-classing TextBlock to make the inlines Bindable. In addition I wrote a convertor between a String of xaml markup and an InlineCollection.
How to bind a TextBlock to a resource containing formatted text?

Resources