Drawing lines and ellipses using ObservableCollection and ItemsControl - wpf

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.

Related

How to bind an ItemsSource in an AdornedControl to a property of its Adorner?

The below code correctly creates an adorner consisting of multiple WriteBoxes of InkCanvas and background lines.
It binds correctly to the properties in the view models.
Code by Josh Smith
<!--XAML that creates the adorned control and the adorner-->
<ac:AdornedControl IsAdornerVisible="{Binding TranscriptionLayer.IsAdornerVisible}" Grid.ColumnSpan="2">
<!--#region Adorned Element-->
<!--TranscriptionLayer will have an Adorner (inkcanvas) for handwriting recogntion.
The RichTextBox is the element being adorned and sets the size of the adornment object (i.e., the inkcanvas writing surface). The RichTextControl holds the actual transcript.-->
<RichTextBox x:Name="RichTextControl" Panel.ZIndex="{Binding TranscriptionLayer.ZIndex}" Cursor="IBeam"
AllowDrop="True" PreviewDragEnter="RichTextControl_PreviewDragEnter"
PreviewDragOver="RichTextControl_PreviewDragOver"
PreviewDrop="RichTextControl_PreviewDrop"
Height="{Binding VirtualPage.Height}"
Visibility="{Binding TranscriptionLayer.TranscriptIsVisible}"
SpellCheck.IsEnabled="True"
VerticalScrollBarVisibility="Auto"
AcceptsReturn="True" AcceptsTab="True">
<!--Remove blank line between paragraphs-->
<RichTextBox.Resources>
<Style TargetType="{x:Type Paragraph}">
<Setter Property="Margin" Value="0"/>
</Style>
</RichTextBox.Resources>
<i:Interaction.Behaviors>
<!--Update the menu and toolbar when a selection is made in the RichTextBox
The behavior is bound to the SelectionChanged of the RichTextBox.
The normal binding sends the binding source to the dependency property, the target, of the behavior.
BindsTwoWayByDefault sends the behavior value, the target, back to the Binding source.-->
<!--<b:RichTextBehavior AlignLeft ="{Binding TranscriptionLayer.AlignLeft}" /> -->
<b:RichTextBehavior
FontHeight="{Binding ElementName=Fontheight, Path=SelectedItem, Mode=TwoWay, Converter={c:NullToDoubleConverter}}"
TextFont="{Binding ElementName=Fonttype, Path=SelectedItem}"
TextBold="{Binding ElementName=ToggleBold, Path=IsChecked}"
Italic="{Binding ElementName=ToggleItalic, Path=IsChecked}"
Underline="{Binding ElementName=ToggleUnderline, Path=IsChecked}"
Strikethrough="{Binding ElementName=ToggleStrikethrough, Path=IsChecked}"
ParagraphTag ="{Binding ElementName=CurrentParagraph, Path=SelectedItem}"
IsFocused ="{Binding TranscriptionLayer.RichTextHasFocus}"
MoveText ="{Binding TranscriptionLayer.MoveText}"
SelectedText="{Binding TranscriptionLayer.SelectedText}"
IsImage="{Binding TranscriptionLayer.IsImage}"
DeleteImage="{Binding TranscriptionLayer.DeleteImage}"
/>
</i:Interaction.Behaviors>
</RichTextBox>
<!--#endregion-->
<ac:AdornedControl.AdornerContent>
<!--#region The Adorner-->
<!-- This is the framework element as the adorner content. It is always on top of the adorned element. There adorned elements
is the RichTextConttrol.
The ItemsControl will expand to the width of the parent adorner which takes its size from the adorned element-the
RichTextConttrol. The ItemsControl inherits from  System.Windows.FrameworkElement.
-->
<ItemsControl x:Name="WritingLayerControl" ItemsSource="{Binding TranscriptionLayer.WritingBoxes}" >
<!--
If the <ItemsControl.ItemsPanel> is not used, the ItemsControl will default to a vertical StackPanel.
-->
<ItemsControl.ItemTemplate>
<!--
DataTemplate and DataType point to a class, not a namespace!
-->
<DataTemplate DataType="{x:Type vm:WritingBoxViewModel}" >
<Grid>
<Grid.RowDefinitions>
<!-- 0 to be used for recogntion results-->
<RowDefinition Height="auto" />
<!-- 1 to be used for Ink -->
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="{Binding RecognitionResults}" Background="#100000FF"
Height="{Binding RecognitionResultsHeight}"/>
<!--
Binding Mode must be set to TwoWay on TheSelectedStrokes because by default binding works one way,
i.e. loading changes from the view model, but not updating the viewmodel back. So either use:
TheSelectedStrokes="{Binding SelectedStrokes, Mode=TwoWay}" or set it with
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault in TheSelectedStrokes dependency definition.
Background is of type Brush, so must use ImageBrush, DrawingBrush, VisualBrush.
-->
<InkCanvas x:Name="InkCanvas" Grid.Row="1"
Height="{Binding WritingBoxHeight}" Strokes="{Binding Strokes}"
EditingMode="{Binding EditingMode}"
DefaultDrawingAttributes="{Binding DefaultDrawingAttributes}" >
<i:Interaction.Behaviors>
<b:InkCanvasBehavior DeletedStrokes="{Binding DrawingLayer.DeleteStrokes}" />
</i:Interaction.Behaviors>
<InkCanvas.Background>
<!--
Note: Making the DrawingBrush a StaticResource makes the XAML much more efficient as only one object
is created. It also is only created on the first pass and no update from the Bindings will happen so
the lines will not change even when the TypeSize is changed.
Viewport (type Rect) gives the position, width, and height of the base tile
-->
<DrawingBrush Stretch="Uniform" TileMode="Tile" Viewport="{Binding ViewPortBaseTile}" ViewportUnits="Absolute" >
<DrawingBrush.Drawing>
<GeometryDrawing>
<GeometryDrawing.Geometry>
<GeometryGroup>
<!--
X is horizontal displacement from origin.
Y is veritcal displacyement from origin.
Origin O(0,0) is top-left of InkCanvas.
-->
<!-- Vertical Line
<LineGeometry StartPoint="0,0" EndPoint="{Binding TileBottom}"/>-->
<!-- Midline. Horizontal Line -->
<LineGeometry StartPoint="{Binding MidLineStartPoint}" EndPoint="{Binding MidLineEndPoint}"/>
<!-- BaseLine. Horizontal Line-->
<LineGeometry StartPoint="{Binding BaseLineStartPoint}" EndPoint="{Binding BaseLineEndPoint}"/>
</GeometryGroup>
</GeometryDrawing.Geometry>
<GeometryDrawing.Pen>
<Pen Thickness="1" Brush="Tomato"/>
</GeometryDrawing.Pen>
</GeometryDrawing>
</DrawingBrush.Drawing>
</DrawingBrush>
</InkCanvas.Background>
</InkCanvas>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!--#endregion-->
</ac:AdornedControl.AdornerContent>
</ac:AdornedControl>
Now, I would like to change:
<ItemsControl x:Name="WritingLayerControl"
ItemsSource="{Binding TranscriptionLayer.WritingBoxes}">
So that the WritingBoxes property is located in the Adorner class itself -- not in the ViewModel. In the ViewModel it is defined as:
private ObservableCollection<WritingBoxViewModel> _writingBoxes;
public ObservableCollection<WritingBoxViewModel> WritingBoxes
{
get { return _writingBoxes; }
set
{
if (_writingBoxes == value)
return;
_writingBoxes = value;
RaisePropertyChanged();
}
}
Keeping in mind, the Adorner may or may not be visible at any given time.
How can I bind the ItemsSource in the AdornedControl to a property in the Adorner?

wpf grid as itemspaneltemplate gives inconsistent heights

im trying to make a week scheduler
so for each day i have a listbox with a grid as its itemspaneltemplate , and each individual session gets its row and rowspan from the database and its working very well
the problem is when i have a short appointment so the rowspan is only 2 or 3 and the content is longer, in that case the rows containing the appointment grow to accommodate the larger content
i don't want that, id rather the appointments reflect their time span even if i don't see all data
i tried many combinations of answers that i found online
heres the whole markup
<ListBox Name="lsbTasks" Loaded="lsbTasks_Loaded_1" HorizontalContentAlignment="Stretch" ScrollViewer.HorizontalScrollBarVisibility="Disabled" VerticalContentAlignment="Stretch" >
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<Grid Initialized="Grid_Initialized_1" Background="Thistle" IsSharedSizeScope="True"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Grid.Row" Value="{Binding BusyFrom,Converter={StaticResource BusyFromConverter}}" />
<Setter Property="Grid.RowSpan" Value="{Binding BusyMinutes,Converter={StaticResource BusyMinutesConverter}}" />
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto" />
</Style>
</ItemsControl.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<ScrollViewer ScrollViewer.CanContentScroll="True" ScrollViewer.VerticalScrollBarVisibility="Auto" ScrollViewer.IsDeferredScrollingEnabled="True">
<StackPanel Background="Purple" PreviewMouseDown="DockPanel_PreviewMouseDown_1">
<TextBlock TextAlignment="Center" TextWrapping="Wrap" Text="{Binding SessionPlace}" Foreground="Thistle" DockPanel.Dock="Bottom" />
<TextBlock TextAlignment="Center" TextWrapping="Wrap" Text="{Binding ClientName}" Foreground="White" FontWeight="Bold" DockPanel.Dock="Top" />
<TextBlock TextAlignment="Center" TextWrapping="Wrap" Text="{Binding OppositionName}" Foreground="White" DockPanel.Dock="Top" />
<TextBlock TextAlignment="Center" TextWrapping="Wrap" Text="{Binding SubjectName}" Foreground="Thistle" />
</StackPanel>
</ScrollViewer>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
and heres the initialization code
Private Sub Grid_Initialized_1(sender As Grid, e As EventArgs)
Dim mnts = MyWorkDaySpan.TotalMinutes
For i = 1 To mnts / 10
Dim rd = New RowDefinition With {.Height = New GridLength(1, GridUnitType.Star)}
'rd.SharedSizeGroup = "RowGroup"
sender.RowDefinitions.Add(rd)
Next
End Sub
but still the rows are uneven, as proven by the gridlines
id appreciate any help or guidance
You should use a different Panel, perhaps a Canvas and bind the Canvas.Top and Height properties in the ListBoxItem style.
Another alternative would be to write a custom Panel class ("CalendarPanel"), which defines two attached properties BusyFrom and BusyMinutes and arranges its child elements according to the values of these properties.

TabControl DataTemplate Instance Creation Issues

I have a WPF issue that I don't understand - can anyone help?
The WPF below is used as the ContentTemplate for a standard TabControl and resides within a ResourceDictionary. MyElementItemsControl is a simple derivative of ItemsControl and MyDesignCanvas is a simple derivative of Canvas.
<DataTemplate x:Key="TabContent" x:Shared="False">
<Grid>
<Grid Grid.RowSpan="2">
<ScrollViewer x:Name="contentScrollViewer" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" >
<Grid>
<View:MyElementItemsControl BorderBrush="Transparent" x:Name="schedulePanel" ItemsSource="{Binding Path=Elements}" Background="White">
<View:MyElementItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<View:MyDesignCanvas Height="1000" Width="1000" HorizontalAlignment="Left" VerticalAlignment="Top"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
Background="Transparent">
</View:MyDesignCanvas>
</ItemsPanelTemplate>
</View:MyElementItemsControl.ItemsPanel>
</View:MyElementItemsControl>
<Grid.LayoutTransform>
<TransformGroup>
<ScaleTransform>
<ScaleTransform.ScaleX>
<Binding ElementName="SlideZoom" Path="Value" Mode="OneWay"/>
</ScaleTransform.ScaleX>
<ScaleTransform.ScaleY>
<Binding ElementName="SlideZoom" Path="Value" Mode="OneWay"/>
</ScaleTransform.ScaleY>
</ScaleTransform>
</TransformGroup>
</Grid.LayoutTransform>
</Grid>
</ScrollViewer>
</Grid>
<Slider Opacity="0.5" VerticalAlignment="Top" HorizontalAlignment="Left" Width="300" Grid.Row="1" Name="SlideZoom" Orientation="Horizontal" Minimum="0.1" Maximum="3" Value="1">
</Slider>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="30" />
</Grid.RowDefinitions>
</Grid>
</DataTemplate>
When I run the code I get two issues I don't understand:
I seem to only get a single ScrollViewer when I would expect a ScrollViewer per item. So if I add two tab items and make the canvas different sizes, the scroll bars are adjusted to the size of the biggest canvas only. I expected that the Shared=False attribute would create new instances of the template for each tab.
Maybe related to item 1 - if I stick a breakpoint on the constructor of MyDesignCanvas it gets hit when the first tab is added but not when other tabs are added. Only when I start closing tabs does the breakpoint get hit again - I would expect a hit on each tab addition.
I guess I don't really understand data templating enough, so can anyone explain what might be going on or point me at some resources that may help me diagnose this?
Thanks
I've realised what the issue is - the WPF TabControl does internal virtualization of tab content, so has been re-using the tab contents and just changing the data context despite me using Shared=False. See this SO question and this one too for more details.

DropShadowEffect applying to child elements - Silverlight XAML

I have an ItemsControl which contains some nested containers. I want to add a dropshadow around each element of the main ItemsControl. But instead it is adding it to certain containers that are within the main ItemsControl (creating rows of shadows). I have placed the effect at a number of different levels but it results in no change. I started out with the outermost container of the item within the main ItemsControl and went upward from there.
Here is where I currently have the effect for the drop shadow placed:
<ItemsControl >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<!-- I have tried adding the dropshadow effect within this stackpanel -->
<StackPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<!-- Where I define the dropshadow -->
<ItemsControl.Effect>
<DropShadowEffect BlurRadius="0" ShadowDepth="1" Color="LightGray"/>
</ItemsControl.Effect>
<!-- End of dropshadow definition -->
<ItemsControl.ItemTemplate>
<DataTemplate>
<media:Step5Item />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
And here is the definition for Step5Item, I added documentation for where the shadows are appearing: (edit) I removed the content for the elements since that was just styling and so forth.
<!-- This is inserted by the above code's DataTemplate -->
<!-- I have tried adding a border here and giving it a dropshadow effect -->
<Grid >
<!-- I have tried inserting a dropshadow effect here -->
<TextBlock Grid.Row="0"/>
<Border BorderBrush="LightGray" BorderThickness="1" >
<!-- I have tried inserting a dropshadow effect here -->
<Grid>
<Border >
<!-- There is a shadow around this border/grid -->
<Grid Grid.Row="0" >
<TextBlock Grid.Column="0" />
<Button Grid.Column="2"/>
</Grid>
</Border>
<!--There is a shadow around each element in this ItemsControl-->
<ItemsControl Grid.Row="2" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Margin="0,0,0,4" >
<Path Grid.Row="0">
<Path.Data>
<LineGeometry StartPoint="0,0" EndPoint="1500,0"/>
</Path.Data>
</Path>
<Grid Grid.Row="1">
<Image Grid.Column="0" />
</Grid>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Border>
</Grid>
There is also a shadow at the bottom but I don't know if its from the last element in the ItemsControl or if it is from the outer most border.
If you'd like I can clean up the second code piece more. I took out some stuff but left in the elements, thinking that might be best for readability.
EDIT
I tried applying the effect after I add the child elements hoping that since they would be created before the effect went into play that the problem would not occur. I tried placing the effect at the bottom of both the main ItemsControl as well as at the bottom of the outermost grid in Step5Item. I have also removed some content from Step5Item to make it hopefully more readable.
EDIT2
Here are two images with and without the effect. I left the DropShadow code exactly where I placed it above, though like I said, I can place it in many places to get the same effect.
With Dropshadow
Without Dropshadow
Without Error http://img402.imageshack.us/img402/1456/nodropshadowexample.png
Edit 3
This is the border and drop shadow effect that I am using from Erno's solution. I am hoping to be able to increase the shadowdepth some more because the right side is not getting any shadow, only the bottom. Currently if I change ShadowDepth it changes to location of the shadow to be at a distance away equal to the new size but it is only a thickness of 1.
<Border Margin="0,1,0,0" Height="auto" Width="auto" CornerRadius="5,5,5,5" BorderThickness="1" BorderBrush="LightGray">
<Border.Effect>
<DropShadowEffect BlurRadius="0" ShadowDepth="1" Direction="315" Color="LightGray"/>
</Border.Effect>
</Border>
Have you tried to do the following?
I added another Grid and added a SIBLING Border with the effect. The grid containing the rows is displayed on top of it but is NOT a child control of the Border.
<ItemsControl Grid.Row="2" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Margin="0,0,0,4" >
<Grid>
<Border>
<Border.Effect>
<DropShadow />
</Border.Effect>
</Border>
<Path Grid.Row="0">
<Path.Data>
<LineGeometry StartPoint="0,0" EndPoint="1500,0"/>
</Path.Data>
</Path>
<Grid Grid.Row="1">
<Image Grid.Column="0" />
</Grid>
</Grid>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

WPF Textblock in Listbox not clipping properly

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">

Resources