WPF Textblock in Listbox not clipping properly - wpf

Here's what I want: A ListBox whose items consist of a StackPanel with two TextBlocks. The textblocks need to support wrapping, the listbox should not expand, and there should be no horizontal scrollbar. Here's the code I have so far. Copy and paste it into XamlPad and you'll see what I'm talking about:
<ListBox Height="300" Width="300" x:Name="tvShows">
<ListBox.Items>
<ListBoxItem>
<StackPanel>
<TextBlock Width="{Binding ElementName=tvShows, Path=ActualWidth}" TextWrapping="Wrap">Lost is an American live-action television series. It follows the lives of plane crash survivors on a mysterious tropical island.</TextBlock>
<TextBlock Width="{Binding ElementName=tvShows, Path=ActualWidth}" TextWrapping="Wrap">Lost is an American live-action television series. It follows the lives of plane crash survivors on a mysterious tropical island.</TextBlock>
</StackPanel>
</ListBoxItem>
<ListBoxItem>
<StackPanel>
<TextBlock Width="{Binding ElementName=tvShows, Path=ActualWidth}" TextWrapping="Wrap">Lost is an American live-action television series. It follows the lives of plane crash survivors on a mysterious tropical island.</TextBlock>
<TextBlock Width="{Binding ElementName=tvShows, Path=ActualWidth}" TextWrapping="Wrap">Lost is an American live-action television series. It follows the lives of plane crash survivors on a mysterious tropical island.</TextBlock>
</StackPanel>
</ListBoxItem>
</ListBox.Items>
</ListBox>
This seems to be doing the job of keeping the textblocks from growing, but there's one problem. The textblocks seem to be slightly larger than the listbox, causing the horizontal scrollbar to appear. This is strange because their widths are bound to the lisbox's ActualWidth. Also, if you add a few more items to the listbox (just cut and paste in XamlPad) causing the vertical scrollbar to appear, the width of the textblocks do not resize to the vertical scrollbar.
How do I keep the TextBlocks inside the ListBox, with or without the vertical scrollbar?

There are two ways of doing this, but I think what you really want is to disable the horizontal scrollbar, which is done with an attached property:
<ListBox ScrollViewer.HorizontalScrollBarVisibility="Disabled">
...
You can then remove the width bindings on the TextBlocks.
Your other option is to bind the TextBlocks widths to the ScrollContentPresenter's ActualWidth via RelativeSource bindings:
<ListBox Height="300" Width="300" x:Name="tvShows" ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListBox.Items>
<ListBoxItem>
<StackPanel>
<TextBlock Width="{Binding ActualWidth, RelativeSource={RelativeSource AncestorType={x:Type ScrollContentPresenter}}}" TextWrapping="Wrap">Lost is an American live-action television series. It follows the lives of plane crash survivors on a mysterious tropical island.</TextBlock>
<TextBlock Width="{Binding ActualWidth, RelativeSource={RelativeSource AncestorType={x:Type ScrollContentPresenter}}}" TextWrapping="Wrap">Lost is an American live-action television series. It follows the lives of plane crash survivors on a mysterious tropical island.</TextBlock>
</StackPanel>
</ListBoxItem>
<ListBoxItem>
<StackPanel>
<TextBlock Width="{Binding ActualWidth, RelativeSource={RelativeSource AncestorType={x:Type ScrollContentPresenter}}}" TextWrapping="Wrap">Lost is an American live-action television series. It follows the lives of plane crash survivors on a mysterious tropical island.</TextBlock>
<TextBlock Width="{Binding ActualWidth, RelativeSource={RelativeSource AncestorType={x:Type ScrollContentPresenter}}}" TextWrapping="Wrap">Lost is an American live-action television series. It follows the lives of plane crash survivors on a mysterious tropical island.</TextBlock>
</StackPanel>
</ListBoxItem>
</ListBox.Items>
</ListBox>

You could work around the problem like this:
<ListBox.Resources>
<Style TargetType="TextBlock">
<Setter Property="Margin" Value="0 0 -6 0" />
<Setter Property="Padding" Value="0 0 6 0" />
</Style>
</ListBox.Resources>
This might not work well if the fontsize changes.
Another (and probably better) way is to disable the scrollbar completely:
<ListBox x:Name="tvShows" ScrollViewer.HorizontalScrollBarVisibility="Disabled">

Related

ActualHeight Grows But Doesn't Shrink

I'm experiencing an odd behavior with WPF.
My goal here is to have an ItemsControl with a vertical line on each item's side going from the top to the bottom of the item. Being that the items may vary in height, I'm binding the Line's Y2 property to the ActualHeight of the StackPanel belonging to the Grid.
Here's the XAML:
<ItemsControl Grid.Row="1" BorderThickness="0" ItemsSource="{Binding ShipmentActivity}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Name="ListBox">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Line Stroke="{StaticResource ButtonOutlineBrush}" X1="8" X2="8" Y1="0"
Y2="{Binding ActualHeight, ElementName=ShipmentActivity}"
StrokeThickness="1" />
<StackPanel Grid.Column="1" Margin=".1" x:Name="ShipmentActivity">
<StackPanel.Resources>
<Style TargetType="TextBlock">
<Setter Property="TextWrapping" Value="Wrap"/>
</Style>
</StackPanel.Resources>
<TextBlock Text="{Binding Status}" FontWeight="SemiBold" FontSize="13" />
<TextBlock Text="{Binding Location}" Foreground="Gray"/>
<TextBlock Text="Wed, Sep 13, 2017, 8:29 PM (2 days ago)" Foreground="Gray"/>
</StackPanel>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Running this at first seems perfect. The problem is noticeable once I start to narrow the width of the window, causing the TextBlocks to wrap, and in effect
causing the items to grow in height, and then resizing the window back to its original width (and beyond). Although the TextBlocks are back to its original state (and height), the height of the items remain the same - stuck at its highest point, leaving huge gaps below the text, while the Line's height doesn't shrink. The Line is definitely the one to blame here, because removing the line removes the issue.
What is even stranger yet, is adding a margin to the StackPanel even the slightest (.1) fixes the issue, and the items shrink back to its intended height.
Am I doing something wrong, or is this a bug?
I would use a Border element to decorate the StackPanel, rather than using a Line. Then just set the BorderThickness property of the Border accordingly.
Hence your XAML would be like this:
<ItemsControl ...>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderThickness="1,0,0,0"
BorderBrush="{StaticResource ButtonOutlineBrush}">
<StackPanel ...>
You can use the Padding property of the Border and/or the Margin property of the StackPanel to space-out the two elements.

Drawing lines and ellipses using ObservableCollection and ItemsControl

I'm trying to draw a set of lines between Ellipses on a canvas. I do this in two steps:
The first section in the XAML below with the ItemsControl bound to 'SingleCL' visualizes the lines using the nested ItemsControl, bound to 'Points'.
The second section, bound tot he same collections, visualizes the ellipses.
'SingleCL' and 'Points' are both ObservableCollection< T >
The Point-class have four properties: VisualHorPos, VisualVerPos, CenterHorPos and CenterVerPos and implements INotifyPropertyChanged. The properties represents X,Y, with the 'Visual'-properties adjusted to represent the upper left corner of the view when placed on a canvas so that when moved around with the mouse, the center is placed where the mouse pointer is.
The points are sorted by their Y-values in their respective collection so that the lines are drawn from bottom to top.
To avoid (hide) the first line that would be drawn from 0,0 to x2,y2 for the very first point in the Point-collection, I'm using a PriorityBinding so that it gets a length of 0, thus in practice hiding it.
The result is the red zig-zag line shown in this image
Adding points below (image wise) - first in the collection - only the ellipses appears, see the blue dots in the image above. If I save the data and reload it from scratch, the lines appear as expected.
Adding points above (image wise) - after another point in the collection - lines are drawn correctly, but the existing lines are not updated to take the new point into account.
I have verified that the points are correctly sorted, both when adding new and when they are moved along the Y-axis.
Perhaps I am asking too much from WPF, but I expected it to draw the lines from point to point, based on the order of the items in the ObservableCollection< T > 'Points' when adding new points, not just the when the binding happens. Also, when moving the points, I expected the lines to refresh so that they always are drawn from bottom to top based on the order in the collection.
The way I see it, the problem is that once the Lines have been created, they are not rebound when objects are moved or added in the collection. Is there a way to make the ItemsControl reevaluate all its items on each Moved/Added/Removed action in the underlying ObservableCollection< T > ?
The "drawing code"
<ItemsControl ItemsSource="{Binding SingleCL}">
<ItemsControl.LayoutTransform>
<TransformGroup>
<ScaleTransform ScaleX="{Binding ElementName=imageZoom, Path=Value}" ScaleY="{Binding ElementName=imageZoom, Path=Value}"/>
</TransformGroup>
</ItemsControl.LayoutTransform>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ItemsControl ItemsSource="{Binding Points}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Line X2="{Binding CenterHorPos, Mode=OneWay}" Y2="{Binding CenterVerPos, Mode=OneWay}" Stroke="{Binding Color}" StrokeThickness="2">
<!-- Bind X1,Y1 to the previous point, or if not available (as for the first data item) to the same as X2,Y2 -->
<Line.X1>
<PriorityBinding>
<Binding Path="CenterHorPos" RelativeSource="{RelativeSource PreviousData}"/>
<!-- This is the value used of the first binding doesn't work -->
<Binding Path="CenterHorPos"/>
</PriorityBinding>
</Line.X1>
<Line.Y1>
<PriorityBinding>
<Binding Path="CenterVerPos" RelativeSource="{RelativeSource PreviousData}"/>
<!-- This is the value used of the first binding doesn't work -->
<Binding Path="CenterVerPos"/>
</PriorityBinding>
</Line.Y1>
</Line>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!-- Display markers for all centerlines -->
<ItemsControl ItemsSource="{Binding SingleCL}">
<ItemsControl.LayoutTransform>
<TransformGroup>
<ScaleTransform ScaleX="{Binding ElementName=imageZoom, Path=Value}" ScaleY="{Binding ElementName=imageZoom, Path=Value}"/>
</TransformGroup>
</ItemsControl.LayoutTransform>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ItemsControl ItemsSource="{Binding Points}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Canvas.Left" Value="{Binding VisualHorPos}" />
<Setter Property="Canvas.Top" Value="{Binding VisualVerPos}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
And the code for the ellipses:
<UserControl x:Class="SMT.View.AutoCalibration.AutoCalibrationMarkerView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300" MouseDown="MarkerMouseDown"
MouseUp="MarkerMouseUp"
MouseMove="MarkerMouseMove" Width="10" Height="10"
Background="Transparent">
<Grid Background="Transparent">
<Line X1="0" Y1="5" X2="10" Y2="5" Fill="{Binding Color}" Stroke="{Binding Color}" StrokeThickness="0.5"/>
<Line X1="5" Y1="0" X2="5" Y2="10" Fill="{Binding Color}" Stroke="{Binding Color}" StrokeThickness="0.5"/>
<Ellipse Width="10" Height="10" Stroke="{Binding Color}" StrokeThickness="0.5" Fill="Transparent"/>
</Grid>
Workaround/Solution
As also proposed by #Juan Pablo Garcia Coello in the comments, I ended up with to different collections for the ellipses and lines, the latter one being cleared and refilled as needed.

wfp charting toolkit: show data point values above all columns

total emergency here. Just saw WPF for the first time and need this quick, so forgive me: if I don't provide enough info first time around I promise to edit the question.
In a charting object, defined with namespace:
xmlns:charting="clr-namespace:System.Windows.Controls.DataVisualization.Charting;assembly=System.Windows.Controls.DataVisualization.Toolkit"
I am drawing a simple bar chart.
<charting:Chart Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="3"
Visibility="{Binding Path=MyCurrentResultsView, Converter={StaticResource ResourceKey=NullObjectToVisibilityConverter}}"
Background="Transparent" Foreground="White"
Margin="50,0,50,0" Height="350"
HorizontalAlignment="Stretch" Title="{Binding Path=MyCurrentResultsView.Name}">
<charting:ColumnSeries Height="350" Foreground="Black"
ItemsSource="{Binding Path=MyCurrentResultsView.ResultsView}"
IndependentValueBinding="{Binding Key}"
DependentValueBinding="{Binding Value}">
</charting:ColumnSeries>
</charting:Chart>
What I'd like to do is to show the value of each column above the column (or even inside the column rectangle if possible: these are percentage values and the idea is to make them more visible on the bar chart).
I have been looking at the styling information, but here this is more than just style. I see two possibilities. Either:
For each column item in the series, define a transformation that positions a frame above each column, creates a text box whose label is set to the dependent value, then draws the text box inside the frame.
Find some kind of property on "ColumnSeries" or "? ColumnItem ?" that "enables" the display of the bound value above the column.
Total shot in the dark here. Thanks.
I would try to change the ColumnDatapointTemplate like this:
<charting:ColumnSeries Height="350" Foreground="Black"
ItemsSource="{Binding Path=MyCurrentResultsView.ResultsView}"
IndependentValueBinding="{Binding Key}"
DependentValueBinding="{Binding Value}">
<charting:ColumnSeries.DataPointStyle>
<Style TargetType="charting:ColumnDataPoint">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="charting:ColumnDataPoint">
<Grid>
<Rectangle Fill="{TemplateBinding Background}" Stroke="Black"/>
<Grid Margin="0 -20 0 0" HorizontalAlignment="Center" VerticalAlignment="Top">
<TextBlock Text="{TemplateBinding FormattedDependentValue}" Margin="2"/>
</Grid>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</charting:ColumnSeries.DataPointStyle>
</charting:ColumnSeries>
Play a bit with vertical alignments and/or margins and you will be able to get infos into the columns and other.
Hope this help!

silverlight 4 treeview slow on expanding when many items

In XAML I have:
<sdk:TreeView x:Name="navigationTreeView" Grid.Column="0" Grid.Row="1" SelectedItemChanged="TreeView_SelectedItemChanged">
<sdk:TreeView.ItemContainerStyle>
<Style TargetType="sdk:TreeViewItem">
<Setter Property="IsExpanded" Value="True"/>
</Style>
</sdk:TreeView.ItemContainerStyle>
<sdk:TreeView.ItemTemplate>
<sdk:HierarchicalDataTemplate ItemsSource="{Binding Path=Nodes}">
<StackPanel Orientation="Horizontal">
<!--<Image Source="{Binding Path=ImageUri}" />-->
<TextBlock Text="{Binding Path=Title}" ToolTipService.ToolTip="{Binding Path=Title}"/>
</StackPanel>
</sdk:HierarchicalDataTemplate>
</sdk:TreeView.ItemTemplate>
</sdk:TreeView>
In code behind:
this.navigationTreeView.ItemsSource = nodes;
navigationTreeView.ExpandAll();
There are 1000 items as children of one node. If I'm not expanding elements everything is fine. But when I expand that node it's pretty slow (10 sec maybe). What could I do to speed it up?
Silverlight 4, 2010 april toolkit.
Unlike WPF, Silverlight (until Silverlight 4, not sure about 5) does NOT support UI virtualization for hierarchical data, and this is why when you expand the node, the 1000 items which are inside the HierarchicalDataTemplate are not virtualized and take more than 10 seconds to load.
I believe Telerik's RadTreeView has its built-in UI virtualization, but the control is not free.
The best solution I have found so far is from this site. Please note it is still a ListBox solution, however, because it doesn't use HierarchicalDataTemplate, it is fully virtualized and looks exactly like a TreeView. Also by looking at the source code, it is quite easy to implement.
I know it is not the perfect answer for this question but at least give you some alternatives. :)
This is how the TreeView works. It's virtualized by default: it won't create TreeViewItems until they're needed. However, you're expanding the entire tree which forces all items to be created. That's just inherently slow. If you really need this sort of behavior (where all choices are expanded), I'd suggest something else like a ListBox. Anything that is scrolled off the screen won't be created until it's needed (but see this caveat.)
My bad stack panel is not in the right place...
Ok try this instead:
<sdk:TreeView x:Name="navigationTreeView" Grid.Column="0" Grid.Row="1" SelectedItemChanged="TreeView_SelectedItemChanged">
<sdk:TreeView.ItemContainerStyle>
<Style TargetType="sdk:TreeViewItem">
<Setter Property="IsExpanded" Value="True"/>
</Style>
</sdk:TreeView.ItemContainerStyle>
<sdk:TreeView.ItemTemplate>
<sdk:HierarchicalDataTemplate ItemsSource="{Binding Path=Nodes}">
<StackPanel Orientation="Horizontal">
<!--<Image Source="{Binding Path=ImageUri}" />-->
<TextBlock Text="{Binding Path=Title}" ToolTipService.ToolTip="{Binding Path=Title}"/>
</StackPanel>
</sdk:HierarchicalDataTemplate>
</sdk:TreeView.ItemTemplate>
<sdk:TreeView.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</sdk:TreeView.ItemsPanel>
</sdk:TreeView>
Adding:
<sdk:TreeView.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</sdk:TreeView.ItemsPanel>
Let me know the results :D
try this:
<sdk:TreeView.ItemTemplate>
<sdk:HierarchicalDataTemplate ItemsSource="{Binding Path=Nodes}">
<VirtualizingStackPanel Orientation="Horizontal">
<!--<Image Source="{Binding Path=ImageUri}" />-->
<TextBlock Text="{Binding Path=Title}" ToolTipService.ToolTip="{Binding Path=Title}"/>
</VirtualizingStackPanel>
</sdk:HierarchicalDataTemplate>
</sdk:TreeView.ItemTemplate>
Replace the normal StackPanel with one VirtualizingStackPanel...

Force TextBlock to wrap in WPF ListBox

I have a WPF listbox which displays messages. It contains an avatar on the left side and the username and message stacked vertically to the right of the avatar. The layout is fine until the message text should word wrap, but instead I get a horizontal scroll bar on the listbox.
I've Googled and found solutions to similar issues, but none of them worked.
<ListBox HorizontalContentAlignment="Stretch" ItemsSource="{Binding Path=FriendsTimeline}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Border BorderBrush="DarkBlue" BorderThickness="3" CornerRadius="2" Margin="3" >
<Image Height="32" Width="32" Source="{Binding Path=User.ProfileImageUrl}"/>
</Border>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Path=User.UserName}"/>
<TextBlock Text="{Binding Path=Text}" TextWrapping="WrapWithOverflow"/> <!-- This is the textblock I'm having issues with. -->
</StackPanel>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Contents of the TextBlock can be wrapped using property TextWrapping.
Instead of StackPanel, use DockPanel/Grid.
One more thing - set ScrollViewer.HorizontalScrollBarVisibility property to Disabled value for the ListBox.
Updated Hidden to Disabled based on comment from Matt. Thanks Matt.
The problem might not be located in the ListBox. The TextBlock won't wrap, if one of the parent controls provides enough space, so that it hasn't the need to wrap. This might be caused by a ScrollViewer control.
If you want to prevent TextBlock to grow, and you want it to just fit in the size of the listbox, you should set the width of it explicitly.
In order to change it dynamically, it means not a fix value, but you need to bind it to its proper parent element in the visual tree. You can have something like this:
<ListBox ItemsSource="{Binding MyItems}" Name="MyListBox">
<ListBox.Resources>
<Style TargetType="ListBoxItem">
<Setter Property="Width"
Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ScrollContentPresenter}, Path=ActualWidth}" />
</Style>
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Title}" TextWrapping="Wrap" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
If it does not work, try to find the proper elements (which has to be binded to what) with the Live Visual Tree in Visual Studio.

Resources