WPF chart component: Code structure and MVVM pattern - wpf
I'm coding a WPF component which displays a chart looking like a Pareto chart. It's working properly but I feel that it's pretty much crap, here is why:
It's using SO MANY containers, for a simple chart there's probably about fifty containers structuring it;
I put rectangle at the good place using margins (which are stored in the ViewModel), I feel that's really ugly but I didn't think of a better way yet;
I need to know the size of graphic components in the ViewModel to put components at the right location and scale them according to it;
I'm using two layers to render the chart, one for the chart and another one to display the scales of the chart, I think this is not good at all
How it looks
http://hpics.li/fd2b0bd
(can't display the image because I'm new)
ViewModel
The top object is ParetoChartVM, containing an ObservableCollection of SerieVM and another one of AxisVM, a Title and the current size of the chart.
A SerieVM is composed of an OservableCollection of ValuePointVM (representing a rectangle in the chart).
A ValuePointVM contains a Brush, a numeric value, a width & height and margins (Thickness object).
An AxisVM contains a MinimumValue, MaximumValue, a NumberOfScales and an ObservableCollection of ScaleVM.
A ScaleVM contains a Value, a ValuePercentage (at the top I display the value, at the bottom the percentage of the maximum value), a TopMargin and a BottomMargin (both Thickness objects).
View
The View layer only contains a ParetoChartV WPF component. This component only contains a ParetoChartVM, his DataContext is set to this ParetoChartVM.
How it works
Every time the chart container is resized then I notify the ParetoChartVM of it, which recalculates every location/width/height, the interface is updated using Bindings on those properties.
Now here is the XAML (it's pretty big):
<UserControl x:Class="ParetoChart.View.ParetoChartV"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:viewModel="clr-namespace:ParetoChart.ViewModel"
xmlns:converter="clr-namespace:ParetoChart.ViewModel.Converter"
DataContext="{Binding RelativeSource={RelativeSource self}, Path=ParetoChart}">
<Grid>
<Grid.Resources>
<Style x:Key="TitleTextStyle" TargetType="{x:Type TextBlock}">
<Setter Property="FontSize" Value="20"/>
<Setter Property="TextAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
</Grid.Resources>
<!--
Fours parts: Title, Scales, Chart, Caption
Scales & Chart have the same location, the Scales layer is an overlay on the chart layer
-->
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width=".5*"/>
<ColumnDefinition Width=".5*"/>
</Grid.ColumnDefinitions>
<Grid Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2">
<!--Title-->
<TextBlock Style="{StaticResource TitleTextStyle}" Text="{Binding Title, Mode=OneWay}"/>
</Grid>
<Grid Grid.Row="1" Grid.Column="0" Margin="0,20,0,0"> <!--Chart layer-->
<Grid.Background>
<ImageBrush ImageSource="../Resources/Images/GlassBlock.png"/>
</Grid.Background>
<Grid SizeChanged="FrameworkElement_OnSizeChanged" Margin="10, 0">
<ItemsControl ItemsSource="{Binding Series, Mode=OneWay}"> <!-- Container for SerieVM -> for each "line" on the chart -->
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type viewModel:SerieVM}">
<ItemsControl ItemsSource="{Binding Values, Mode=OneWay}"> <!--Container for ValuePoints -> each rectangle -->
<ItemsControl.Resources>
<converter:SolidColorToGradientColor x:Key="SolidColorToGradientColor"/>
</ItemsControl.Resources>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type viewModel:IValuePoint}">
<Rectangle Width="{Binding RectangleWidth, Mode=OneWay}"
Height="{Binding RectangleHeight, Mode=OneWay}"
Fill="{Binding BrushColor, Converter={StaticResource SolidColorToGradientColor}, Mode=OneWay}"
Margin="{Binding Margins, Mode=OneWay}">
<Rectangle.Effect>
<DropShadowEffect Color="Gray"/>
</Rectangle.Effect>
</Rectangle>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>
</Grid>
<Grid Grid.Row="1" Grid.Column="0" Margin="0,0,0,0"> <!--Scales layer-->
<ItemsControl ItemsSource="{Binding Axes, Mode=OneWay}"> <!-- Container containing axes -->
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type viewModel:AxisVM}">
<ItemsControl ItemsSource="{Binding Scales, Mode=OneWay}"> <!-- Container containing scales -->
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type viewModel:ScaleVM}">
<Canvas>
<Canvas.Resources>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="FontSize" Value="15"/>
<Setter Property="TextAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Foreground" Value="#0C077D"/>
</Style>
</Canvas.Resources>
<TextBlock x:Name="MyTB2" Text="{Binding Value, StringFormat={}{0:N0}}"
Margin="{Binding TopCaptionMargins, Mode=OneWay}"/> <!--Scale point value-->
<Line X2="{Binding TopCaptionMargins.Left, Mode=OneWay}"
Y1="20"
Y2="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Grid}}, Path=ActualHeight, Mode=OneWay}"
X1="{Binding TopCaptionMargins.Left, Mode=OneWay}"
StrokeDashArray="1 2" Stroke="Gray"/> <!-- vertical dashed line at the same X location of the scale -->
<TextBlock Text="{Binding ValuePercentage, StringFormat={}{0:N0}%, Mode=OneWay}"
Margin="{Binding BottomCaptionMargins, Mode=OneWay}"/><!--Scale point percentage of maximum-->
</Canvas>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>
<!-- This part is probably ok -->
<Grid Grid.Row="1" Grid.Column="1" Margin="10,20,0,0"> <!--Caption-->
<Grid.Background>
<ImageBrush ImageSource="../Resources/Images/GlassBlock.png"/>
</Grid.Background>
<ItemsControl ItemsSource="{Binding Series, Mode=OneWay}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type viewModel:SerieVM}">
<ItemsControl ItemsSource="{Binding Values, Mode=OneWay}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type viewModel:IValuePoint}">
<Grid Margin="20, 20, 10, 20">
<Grid.Resources>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="FontSize" Value="15"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Foreground" Value="#0C077D"/>
</Style>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="70"/>
<ColumnDefinition Width=".8*"/>
</Grid.ColumnDefinitions>
<Ellipse Grid.Column="0" Width="30" Height="30" Fill="{Binding BrushColor, Mode=OneWay}"/>
<TextBlock Margin="20,0,0,0" Grid.Column="1" Text="{Binding ValueOfXAxis, StringFormat={}{0:N0}, Mode=OneWay}"
HorizontalAlignment="Stretch" TextAlignment="Right"/>
<TextBlock Margin="20,0,0,0" Grid.Column="2" Text="{Binding ValueDescription, Mode=OneWay}" TextWrapping="WrapWithOverflow"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>
</Grid>
</UserControl>
So, for each ObservableCollection I create an ItemsControl to store and display those, I also need to put a Canvas in the ItemsControl.ItemsPanel to put every component where I want with Margins. Those items are also in an ObservableCollection, so I need to put them also in an ItemsControl with a Canvas as ItemsPanel.
Do you think there are problems with my code structure? Please tell me if you do see some, explain them as much as possible because I'm beggining WPF and MVVM pattern.
(I'm using the dotnet framework version 3.5 so I couldn't use Interactivity for the SizeChanged event of the container)
Thank you for your help and your time
Edit (A related problem)
A side problem I have is a converter I did to center Textblocks on a specific point (center the texts showing the scale value with the vertical dashed line in the chart).
Here is how I did it:
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
namespace ParetoChart.ViewModel.Converter {
public class CenterTextblockTextConverter : IMultiValueConverter {
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) {
if (values[0] == DependencyProperty.UnsetValue || values[1] == DependencyProperty.UnsetValue) {
return DependencyProperty.UnsetValue;
}
if (values[0] is Thickness) {
Thickness margins = (Thickness) values[0];
double width = (double) values[1];
margins.Left = margins.Left - (width / 2.0);
return margins;
}
return DependencyProperty.UnsetValue;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) {
throw new NotImplementedException();
}
}
}
In the XAML, I changed the Textblocks like this:
<TextBlock Text="{Binding ValuePercentage, StringFormat={}{0:N0}%, Mode=OneWay}"
x:Name="MyTB">
<TextBlock.Margin>
<MultiBinding Converter="{StaticResource CenterTextblockTextConverter}">
<Binding Path="BottomCaptionMargins"/>
<Binding ElementName="MyTB" Path="ActualWidth"/>
</MultiBinding>
</TextBlock.Margin>
</TextBlock>
So I name them and pass multiple values to the converter.
So it's working except that when stressed, this converter makes the component crazy, the converter is then called about 10 000 times a second, seemingly in an infinite loop. The chart won't then resize anymore (but other components in the window are still responding to resizing). Note that the screenshot I provided is one where I'm using this converter, I stopped using it because of this problem.
Do you have an idea about why this happens?
Edit n°2 (about the side problem)
I did some tests and the converter problem seems to happen with the ActualWidth parameter to the converter. The Textblock seems to have problem with floating point. Indeed, the width, which I don't change suddenly change from ~8.08 to ~28.449. The following screenshot shows this value:
http://hpics.li/63af790
(The left value is the number of calls to the converter, the right one is the actualwidth passed as parameter)
The ActualWidth value changes between 28.44999999... and 28.45, which triggers the converter every time and makes the chart crazy.
Any idea how to fix it? (I'm trying to understand why the width suddenly jumps as I don't ever touch it (I change the Textblock left & top margins, never its width))
Edit n°3 (About the side problem)
I checked if margins could change the Textblock's width but only Left and Top margins change, Bottom and Right don't. I changed in the xaml the binding from Margin to Canvas.Left and Canvas.Top like this:
<TextBlock Text="{Binding ValuePercentage, StringFormat={}{0:N0}%, Mode=OneWay}"
x:Name="MyTB" MaxWidth="40">
<Canvas.Left>
<MultiBinding Converter="{StaticResource CenterTextblockTextConverter}" Mode="OneWay">
<Binding Path="BottomCaptionMargins.Left"/>
<Binding ElementName="MyTB" Path="ActualWidth" Mode="OneWay"/>
</MultiBinding>
</Canvas.Left>
<Canvas.Top>
<Binding Path="BottomCaptionMargins.Top"/>
</Canvas.Top>
</TextBlock>
The bug then disappeared, the Textblock's width doesn't change anymore, which caused this bug. So problem solved but I still don't understand why.
Related
How to achieve the complex UI using ItemControl in silverlight
I am working on creating a complex report which almost looks like shown in here image For this I have create a collection where I will store all the descriptions and its corresponding ratings. This collection is then I am binding to a ItemControl. The collection will be fetched from database depending on criteria's. Now my problem is how to fragment or separate single ItemControl to look like shown in image. Should I use multiple collections which will be then bind to different ItemControl ? Can I use multiple datagrids? I am out of ideas... Any suggestions / examples much appreciated.
Definitely do-able. Treat each block (such as Mathmatics, Arts Education etc) as an item, and you're then just dealing with an ItemsCollection. Create a style for how to present each item in that collection, and another style for how to present each property in your block (which will also feature a collection of something. An example I have of something similar, was blocks which consisted of a heading, and a varied number of checkboxes each with a description. There could be a varying number of these blocks too. In my view, that displayed these blocks, my xaml looked something like this: <ScrollViewer VerticalScrollBarVisibility="Visible" MaxHeight="100"> <ItemsControl ItemsSource="{Binding FeatureFlags, Mode=TwoWay}" Style="{StaticResource FlagGroupsAndFlagsItemsControlStyle}" /> </ScrollViewer> I then had a style for that ItemsControl defined in a resource dictionary somewhere ... <Style x:Key="FlagGroupsAndFlagsItemsControlStyle" TargetType="ItemsControl"> <Setter Property="Width" Value="Auto" /> <Setter Property="ItemTemplate"> <Setter.Value> <DataTemplate> <Grid x:Name="FlagListGrid"> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <TextBox Grid.Row="0" Width="{Binding Width, ElementName=FlagListGrid, Mode=OneWay}" IsReadOnly="True" Text="{Binding Name}" /> <ListBox Grid.Row="1" Width="{Binding Width, ElementName=FlagListGrid, Mode=OneWay}" ItemsSource="{Binding Flags}" ScrollViewer.HorizontalScrollBarVisibility="Disabled" Style="{StaticResource FlagsListBoxItemsStyle}" Background="{Binding IsOptional, Converter={StaticResource YNToOptionalBackgroundColour}}"/> </Grid> </DataTemplate> </Setter.Value> </Setter> </Style> And then a style for the items within that templates ListBox ... <Style x:Key="FlagsListBoxItemsStyle" TargetType="ListBox"> <Setter Property="ItemsPanel"> <Setter.Value> <ItemsPanelTemplate> <toolkit:WrapPanel Width="{Binding Width, ElementName=FlagListGrid, Mode=OneWay}" Orientation="Horizontal" ScrollViewer.HorizontalScrollBarVisibility="Disabled" /> </ItemsPanelTemplate> </Setter.Value> </Setter> <Setter Property="ItemTemplate"> <Setter.Value> <DataTemplate> <StackPanel Orientation="Horizontal"> <CheckBox Width="20" Height="18" VerticalAlignment="Top" IsChecked="{Binding IsSelected, Mode=TwoWay}" /> <TextBlock Width="180" MinHeight="18" VerticalAlignment="Center" Text="{Binding Description}" TextWrapping="Wrap" /> </StackPanel> </DataTemplate> </Setter.Value> </Setter> </Style> Unfortunately I can't show you an image of what the finished result looks like, but I hope these pointers could set you on track for your very similar problem
ComboBox with two columns in popup
I need to create a custom control containing a combobox whose popup will have the Name propeprty of the bound objects aligned to the left, and the CreatedDate property of the bound objects aligned to the right in each pop up item. Also the Name and the CreatedDate must not overlap. The Name of the object is of variable length I tried solving this problem using a DataTemplate in the Combobox.ItemTemplate, inside the data template I have a grid with two columns aligned appropriately. The Grid's horizontal alignment is set to Stretch but for some reason the Grid doesn't fill out the available space in the popup. Does anyone know how to get around this and why it happens? I am using WPF 3.5. <UserControl.Resources> <CollectionViewSource x:Key="cvsProcessingSessionList" Source="{Binding ProcessingSessionList, ElementName=View}"/> <Style TargetType="{x:Type ComboBox}" BasedOn="{StaticResource {x:Static res:ObjectResources.LEComboBoxStyle}}"> <Setter Property="Margin" Value="5,3"/> <Setter Property="HorizontalAlignment" Value="Stretch"/> <Setter Property="VerticalAlignment" Value="Center"/> </Style> </UserControl.Resources> <GroupBox x:Name="grbProcessingSession"> <GroupBox.Header> <Bold>Processing Session:</Bold> </GroupBox.Header> <GroupBox.Content> <ComboBox x:Name="ccbProcessingSessionName" ItemsSource="{Binding Source={StaticResource cvsProcessingSessionList}}" Loaded="OnLoaded" TextSearch.TextPath="Name" IsEditable="True" MaxDropDownHeight="500" > <ComboBox.ItemTemplate> <DataTemplate> <Grid Background="Pink" HorizontalAlignment="Stretch" > <Grid.ColumnDefinitions> <ColumnDefinition Width="*" /> <ColumnDefinition Width="100" /> </Grid.ColumnDefinitions> <TextBlock Grid.Column="0" TextAlignment="Left" HorizontalAlignment="Left" Margin="0,0,5,0" Text="{Binding Name}"/> <TextBlock Grid.Column="1" TextAlignment="Right" HorizontalAlignment="Right" Text="{Binding CreatedDate, StringFormat=dd/MM/yyyy}"/> </Grid> </DataTemplate> </ComboBox.ItemTemplate> </ComboBox> </GroupBox.Content> </GroupBox>
Just add this binding to your grid in yiour DataTemplate ... Width="{Binding ActualWidth, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ComboBoxItem}}, Mode=OneTime}" Also for better effect, apply the background color to the ComboBoxItem and not to the grid... <ComboBox.ItemContainerStyle> <Style TargetType="{x:Type ComboBoxItem}"> <Setter Property="Background" Value="Pink"/> </Style> </ComboBox.ItemContainerStyle>
Whenever I want to do the same that you are asking I add: <ComboBox ...> <ComboBoxItem HorizontalContentAlignment="Stretch"> </ComboBoxItem> </ComboBox> The thing is that I place my custom DataTemplate inside the ComboBoxItem. Maybe you should try it...
Actually, the better way is to set HorizontalContentAlignment="Stretch" at the ComboBox level. This will work even if width of ComboBox changes (e.g. if it is in resizable container). However, be aware that this works in WPF and Silverlight 5 but don't work in some older versions of Silverlight.
How to make itemtemplate aware of its containing template?
I want this Ellipse to get its coordinates from its corresponding BallViewModel, and to use them to determine its location inside a canvas. The list of balls is bound to List<BallVM> in the mainviewmodel and thus I chose an itemsControl which has a canvas panel. Is this approach correct? If I try to bind to X and Y inside an itemcontainerstyle, then it's not specific to a certain ball. No matter what I set in the Canvas.bottom or canvas.left properties the ellipse is always at the top left. <Grid> <ItemsControl ItemsSource="{Binding Balls}" Background="red"> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <Canvas MouseMove="Canvas_MouseMove" Background="Blue"/> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> <ItemsControl.ItemTemplate> <DataTemplate DataType="{x:Type VM:BallVM}"> <Ellipse Canvas.Bottom="{Binding Y}" Canvas.Left="{Binding X}" Width="100" Height="100" Fill="Red"/> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> </Grid>
When you use an ItemTemplate with an ItemControls it does not directly put your Elippses on the Canvas but wraps them into an ContentPresenter. So you have to apply your canvas.Bottom/Left properties on the ItemsPresenter. You can can do this with the ItemContainerStyle: <ItemsControl.ItemContainerStyle> <Style> <Setter Property="Canvas.Bottom" Value="{Binding Y}" /> <Setter Property="Canvas.Left" Value="{Binding X}" /> </Style> </ItemsControl.ItemContainerStyle> <ItemsControl.ItemTemplate> <DataTemplate DataType="{x:Type VM:BallVM}"> <Ellipse Width="100" Height="100" Fill="Red"/> </DataTemplate> </ItemsControl.ItemTemplate>
WPF Grid as ItemsPanel for a list dynamically bound to an ItemsControl
I am using a Grid as ItemsPanel for a list dynamically bound to an ItemsControl. The code below is working - with a remaining problem: I can’t find a way to dynamically initialize the ColumnDefinitions and RowDefinitions of the grid. As consequence all values are placed on top of each other. <ItemsControl ItemsSource="{Binding Cells}"> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <Grid/> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> <ItemsControl.ItemContainerStyle> <Style> <Setter Property="Grid.Row" Value="{Binding RowIndex}"/> <Setter Property="Grid.Column" Value="{Binding ColumnIndex}"/> </Style> </ItemsControl.ItemContainerStyle> <ItemsControl.ItemTemplate> <DataTemplate> <TextBlock Text="{Binding Value}" /> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> Please be aware, that I am searching an answer according the MVVM pattern. Therefore sub classing and code behind are just workarounds, but no solutions.
You'll need some way to tell the Grid how many Rows/Columns it has. Perhaps as each Item loads you could check the value of RowIndex and ColumnIndex and add Rows/Columns to the Grid if needed. As another alternative, perhaps you can expose RowCount and ColumnCount properties in your ViewModel that return the max RowIndex and ColumnIndex, and in the Grid's Loaded event add however many Columns/Rows you need. I find it perfectly acceptable to use code-behind in MVVM IF the code is related to the UI only. Another idea would be to arrange your items in your code-behind into a 2D grid before returning it to the View, and then bind that Grid to a DataGrid with AutoGenerateColumns=True and the headers removed Update My current solution to this problem is to use a set of AttachedProperties for a Grid that allow you to bind RowCount and ColumnCount properties to a property on the ViewModel You can find the code for my version of the attached properties on my blog here, and they can be used like this: <ItemsPanelTemplate> <Grid local:GridHelpers.RowCount="{Binding RowCount}" local:GridHelpers.ColumnCount="{Binding ColumnCount}" /> </ItemsPanelTemplate>
Your grid has zero rows and columns so everything will be displayed on top of each other. Do something like below and it will work. <ItemsControl ItemsSource="{Binding Cells}"> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" /> </Grid.ColumnDefinitions> </Grid> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> <ItemsControl.ItemContainerStyle> <Style> <Setter Property="Grid.Row" Value="{Binding RowIndex}" /> <Setter Property="Grid.Column" Value="{Binding ColumnIndex}" /> </Style> </ItemsControl.ItemContainerStyle> <ItemsControl.ItemTemplate> <DataTemplate> <TextBlock Text="{Binding Value}" /> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl>
i found this from here The ItemsControl <ItemsControl ItemsSource="{Binding VehicleMakes}" > <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <UniformGrid Columns="3" /> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> <ItemsControl.ItemTemplate> <DataTemplate> <Button Width="300" Height="100" Click="ButtonOption_Click" Tag="{Binding Name}"> <StackPanel Orientation="Vertical"> <Image Initialized="Image_Initialized" Tag="{Binding ResourseImageName}" Width="116" Height="30" Margin="0,0,0,10" > </Image> <TextBlock Text="{Binding Name}" VerticalAlignment="Bottom" HorizontalAlignment="Center"/> </StackPanel> </Button> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl>
Silverlight: Set Items Widths in ItemsControl to Stretch
I've got an ItemsControl which fills from top to bottom, but I can't get it's child items to occupy the whole width of the ItemsControl: I basically need to stretch the green bits to fill the width of the control (as shown by the blue bits). I've tried things like setting the HorizontalAlignment property of the template item to Stretch and I've even tried binding it's Width property to the ItemsControl Width, but neither worked. Should be a straight forward one, but it's something I just can't quite figure out... Edit: here's the ItemTemplate (the whole thing is the ItemTemplate which itself contains an ItemsControl which is bound to a list of child objects): <DataTemplate> <Border CornerRadius="5" Background="#ddd"> <StackPanel> <TextBlock Text="{Binding Name}" FontSize="18" Foreground="#bbb"/> <ItemsControl ItemsSource="{Binding PlugIns}"> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <toolkit:WrapPanel HorizontalAlignment="Stretch" /> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> <ItemsControl.ItemTemplate> <DataTemplate> <StackPanel Margin="10,0,10,10" Tag="{Binding}" MouseEnter="StackPanel_MouseEnter"> <Border Child="{Binding Icon}" /> <TextBlock Text="{Binding Name}" /> </StackPanel> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> </StackPanel> </Border> </DataTemplate>
You need to configure the ListBoxItem HorizontalContentAlignment, you do this by using a Style object in the ListBox ItemContainerStyle as follows:- <ListBox ....> <ListBox.ItemContainerStyle> <Style TargetType="ListBoxItem"> <Setter Property="HorizontalContentAlignment" Value="Stretch" /> </Style> </ListBox.ItemContainerStyle>
OK, so as I continued to build the app up, I figured out how to resolve this. Essentially, when I changed the template of the ItemsControl to support scrolling, the items spanned to fill horizontally :) <ItemsControl> <ItemsControl.Template> <ControlTemplate> <ScrollViewer Padding="{TemplateBinding Padding}"> <ItemsPresenter /> </ScrollViewer> </ControlTemplate> </ItemsControl.Template> ... </ItemsControl>