How would I databind a Paragraph to a TextBlock? - wpf

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?

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.

Variable Binding-Path

I need to display a memory dump for a technical application. Each Byte (Cell) should be defined via a DataTemplate to show additional information (highlight via setting Background color, individual Tooltip etc). I made the following attempt:
<DataTemplate x:Key="HexNumberTemplate">
<Grid>
[...]
<TextBlock>
<TextBlock.Text>
<Binding Path="Cell[0].Value">
<Binding.Converter>
[...]
</Binding.Converter>
</Binding>
</TextBlock.Text>
</TextBlock>
</Grid>
</DataTemplate>
The final result should look like this:
My problem is the fix coded Binding path. 'Cell' is a list of objects that holds all necessary information to display the cell. Using this approach, I need to define 16 times the same DataTemplate with Cell[0] to Cell[15]. I definitely want to avoid this!
I read an approach defining the DataTemplate in source code where I assemble the XAML in a string and call Markup.XamlReader.Load(MemoryStreamOfTheString). But here I lose the comfort of the Visual Studio IDE.
Is it possible to define the DataTemplate in XAML and make the indexer of the Cell-Object a parameter?
You should do like you have read: create templates dynamically, by loading them with XamlReader. In order to have comfort of XAML editor, you can define your template in a separate xaml file like this:
<DataTemplate
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid DataContext="{Binding Current_Cell}">
<!--Your template controls goes here.-->
</Grid>
</DataTemplate>
Then set type of this file to Resource, and load it into string and simply replace Current_Cell with each individual cell numbers before you load template from string when you construct your view.
By setting DataContext of Grid you help yourself to use other binding inside the template (context is already set to the current cell, and you don't need to replace it everywhere).
I was in a the same situation recently, only difference was, that my grid had totally dynamic columns (loaded from server), so I didn't even have the opportunity to create 16 templates :)
Try it with ListBoxes.
The outer ListBox includes the rows which are ListBoxes too, each of them binded to a List object. And you can create the DataTemplate of the ListBoxItems.
<DataTemplate x:Key="innerListBoxItem">
[...]
<TextBlock Text="{Binding Value}" />
[...]
<DataTemplate>
<DataTemplate x:key="outerListBoxItem">
<Grid>
<ListBox ItemTemplate="{StaticResource innerListBoxItem}" ItemCollection="{Binding Cells}"/>
</Grid>
<DataTemplate>
and whereever you want to put this control:
<ListBox ItemTemplate="{StaticResource outerListBoxItem}" ItemCollection={Binding CellsList}"/>
code behind:
public class ListOfCells { public List<Cell> Cells {get; set; } }
public List<ListOfCells> CellsList {get; private set; }
You can try and use the Attached Behavior pattern. You can bind an attached property to the column number, and the attached behavior will bind the text to the required cell given the column number.
I would suggest to use a single column DataGrid with custom Header and Cell templates.
You grid won't benefit from indivudual cells resizing, will it? Your header is going to have a fixed number of columns, you cell template can be implemented as a subclass of ListControl - we just need to change StackPanel's orientation from vertical to horizontal. Then, your bound object will be a collection of bytes, which is easy as your cell control is derived from ListControl.
Please let us know if that makes sense.

Basic WPF databinding question

I have another WPF databinding question... one that I haven't found an answer to anywhere, and this surprises me since it seems like it is very basic.
Essentially, I have a string in code behind that I would like to establish a two-way binding with with a textbox in my GUI. I thought it was a simple matter of creating a DependencyProperty in the code behind, and then tying it to the TextBox via a Source binding. The problem is, I can't get one or both parts right.
Here is my DependencyProperty definition from the code behind:
public static readonly DependencyProperty FilePathProperty = DependencyProperty.Register( "FilePath", typeof(string), typeof(Window1));
public string FilePath
{
get { return (string)GetValue(FilePathProperty); }
set { SetValue( FilePathProperty, value); }
}
And here is my XAML:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ReportingInterface Test Application" Height="300" Width="536">
<Menu DockPanel.Dock="Top">
<MenuItem Name="menu_plugins" Header="File">
<MenuItem Header="Open">
<StackPanel Orientation="Horizontal">
<Label>File location:</Label>
<TextBox Name="text_filepath" Width="100" Text="{Binding Source=FilePath, Path=FilePath, Mode=TwoWay}"></TextBox>
<Button Margin="3" Width="20">...</Button>
</StackPanel>
</MenuItem>
</MenuItem>
</Menu>
The part I know is obviously wrong is the Binding part... I hate to waste people's time here with this question, but I honestly have come up short with every search (but now at least this request will populate subsequent google searches). :)
Thank you!
When you defined a binding in XAML, it binds to whatever is set as the DataContext for the object (or it's parent).
This typically means you'd set the DataContext of the Window to some class, and then the binding will work:
<TextBox Name="text_filepath" Width="100" Text="{Binding Path=FilePath, Mode=TwoWay}" />
You can fix this by adding, in the Window's constructor:
this.DataContext = this;
That will make the binding work against the window itself.
Alternatively, you can setup the binding to bind against a specific source object. If, in this case, you wanted to be able to use something else as the DataContext, but still want to bind to a Dependency Property defined in your Window, you could do:
<TextBox Name="text_filepath" Width="100" Text="{Binding Path=FilePath, RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}"></TextBox>
This works by telling the binding to find the first ancestor of type "Window", and bind it the "FilePath" property on that object.
For what it's worth, I would recommend looking into the M-V-VM pattern (Model, View, ViewModel)- essentially, what you do is have this class that serves as the DataContext for your XAML, and all your fun exposed properties/commands/what have you are exposed as public members of that class (called a ViewModel).
Here's a good overview webcast:
MVVM video
And here's another from MSDN mag:
http://msdn.microsoft.com/en-us/magazine/dd419663.aspx

Silverlight 3: Using list of UserControls as ItemsSource for TreeView

I want to populate a TreeView with UserControls, but I only want the Name property to show up, not the entire UserControl. The following code gives me weird crashes as soon as I add something to myUCs:
C#:
var myUCs = new ObservableCollection<UserControl>();
MyTreeView.ItemsSource = myUCs;
XAML:
<controls:TreeView x:Name="MyTreeView">
<controls:TreeView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</controls:TreeView.ItemTemplate>
</controls:TreeView>
Does anyone know how to use a list of UserControls as an ItemSource for TreeViews?
I found one not so convenient workaround: instead of a List of UserControls, use a Dictionary, and change the XAML to:
<controls:TreeView x:Name="MyTreeView">
<controls:TreeView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Key.Name}"/>
</DataTemplate>
</controls:TreeView.ItemTemplate>
</controls:TreeView>
The same bug(?) exists in ListBox, a solution is provided here:
Use UIElements as ItemsSource of ListBox in Silverlight
That particular fix does not work for TreeView
You may have to create your own class that extends UserControl and override the ToString() method so that it returns the name property.

If you were going to build an org chart builder in Silverlight, what base class would you use for creating the boxes of the chart?

For this question, let us assume that we will want to show the face of the employee, title, department, and whether they like PiƱa coladas/getting caught in the rain.
Perhaps it might look something like the following:
http://www.edrawsoft.com/images/examples/Photo-Org-Chart-Full.png
Would you use a...
System.Windows.Control.UserControl?
FrameworkElement?
UIElement?
Canvas
Why? As always, thank you for your advise! I greatly appreciate it!
If I had to create a org chart control with advanced layout I would probably derive from Control, and create a "real" templated control in a similar manner as e.g. the TreeView control. This is probably the most advanced route to create a new control, but also the most powerful.
You may also be able to modify the control template of a TreeView, and make it grow downwards from the center instead of left and down from the upper left corner, but it will probably be difficult or impossible to customize the layout of the various levels as the TreeViewItem doesn't carry any extra information to describe the layout of a particular node.
In fact I did recently some experiments modifying the TreeView control template, but I stumbled upon something I didn't understand. Luckily I figured out what I did wrong, and you can see how it is possible to change the orientation of TreeView child items from vertical to horizontal in my question here on Stack Overflow.
I've seen a website that uses TreeViewItem and ControlTemplates, but I can't find it at the moment, I think it was on CodeProject.
Another idea I was playing with recently is use 2 usercontrols, itemcontrols and stackpanels.
Here's an example of a an OrgBar rectangle with text under it and it renders it's children in OrgGroup control by setting the ItemSource to it's children collection recursively. You can put the root orgbar on a canvas and play around with paths for the arrows. I tried to point out the basics but if you need more I can fill in the blanks.
Public Class OrgBarDataNode
Public Property BarColor as New SolidColorBrush(Colors.Red)
Public Property BarName As String
Public Property Children as New ObservableCollection(Of OrgBarDataNode)
End Class
Class MainPage
...
Public Sub Loaded
Dim Root as New OrgBarDataNode With {.BarName = "Root"}
Dim Child1 as New OrgBarDataNode With {.Barname = "Child1"}
Root.Children.Add(Child1)
LayoutRoot.Children.Add(Root)
End Sub
...
End Class
<UserControl x:Class="OrgBar">
<Grid>
<StackPanel ToolTipService.ToolTip="{Binding BarName}" Cursor="Hand">
<Rectangle Fill="{Binding BarColor}" Style="{StaticResource RecStyle}"/>
<TextBlock Text="{Binding BarName}" HorizontalAlignment="Center"
Margin="0,10,0,0" />
<local:OrgGroup Margin="0,20" HorizontalAlignment="Center"
DataContext="{Binding Children}" />
</StackPanel>
</Grid>
</UserControl>
<UserControl x:Class="OrgGroup">
<Grid>
<!-- this {Binding} to nothing means bind to DataContext}-->
<ItemsControl ItemsSource="{Binding}" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:OrgBar Style="{StaticResource OrgBarStyle}"
DataContext="{Binding}" />
<!-- this {Binding} refers to the the child node this time} -->
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</UserControl>
That's essentially a tree structure, so like Paully suggested, I would start with a TreeView (Silverlight Toolkit) and customize the control template and treeview itself.

Resources