Create multiple visuals per generated item - wpf

Here's the plan. I have an ItemsControl that will have a two columns Grid as its main panel:
<ItemsControl>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="70" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
</Grid>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
For each item in the underlying VM, I need to generate a Label that will go into first column, and a Rectangle that will go into the second one. How can I achieve this?
<ItemsControl.ItemTemplate>
<DataTemplate DataType="local:TimeSlotVM">
???
</DataTemplate>
</ItemsControl.ItemTemplate>

As you want to use Grid.Row attached property, then assign this attached property to the parent ContentPresenter by the GridHelper.
And the visual tree will be like that:
Grid
-ContentPresenter Grid.Row="1"
-Gird
-ContentPresenter Grid.Row="0"
-Gird
-ContentPresenter Grid.Row="2"
-Gird
...
GridHelper.cs
public class GridHelper
{
public static readonly DependencyProperty GridRowProperty =
DependencyProperty.RegisterAttached("GridRow", typeof(bool), typeof(GridHelper),
new PropertyMetadata(false, new PropertyChangedCallback(OnGridRowPropertyChanged)));
public static bool GetGridRow(DependencyObject d)
{
return (bool)d.GetValue(GridRowProperty);
}
public static void SetGridRow(DependencyObject d, bool value)
{
d.SetValue(GridRowProperty, value);
}
private static void OnGridRowPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Grid grid = d as Grid;
bool gridRow = (bool)e.NewValue;
if (gridRow)
{
// construct the required row definitions
grid.LayoutUpdated += (s, e2) =>
{
// iterate over any new content presenters (i.e. instances of our DataTemplate)
// that have been added to the grid
var presenters = grid.Children.OfType<ContentPresenter>().ToList();
foreach (var presenter in presenters)
{
// the child of each DataTemplate
var itemGrid = VisualTreeHelper.GetChild(presenter, 0) as Grid;
if (itemGrid != null)
{
Grid.SetRow(presenter, Grid.GetRow(itemGrid));
if (Grid.GetRow(itemGrid) > grid.RowDefinitions.Count - 1)
{
presenter.Visibility = Visibility.Collapsed;
}
}
}
};
}
}
}
XAML
<ItemsControl ItemsSource="{Binding ItemList}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid Name="outer" local:GridHelper.GridRow="True">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
...
</Grid.RowDefinitions>
</Grid>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Grid.Row="{Binding Row}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="70" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Content="{Binding Field}" />
<Rectangle Grid.Row="{Binding Row}" Grid.Column="1" Fill="Blue" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

What about using a UniformGrid and a Collection of 24 Empty TimeSlotVM's and then insert your TimeSlotVM at the desired postion in the Collection like this :
in your VM :
TimeSlotCollection=new ObservableCollection<TimeSlotVM>(new TimeSlotVM[24]);
TimeSlotCollection.Insert(10,new TimeSlotVM());
TimeSlotCollection.Insert(5, new TimeSlotVM());
and in your View :
<Grid>
<Grid.Resources>
<DataTemplate DataType="{x:Type local:TimeSlotVM}">
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="70" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Content="{Binding BindingField}" />
<Rectangle Grid.Column="1" Fill="Blue" />
</Grid>
</DataTemplate>
</Grid.Resources>
<ItemsControl ItemsSource="{Binding TimeSlotCollection}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows="24"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>

Related

WPF MVVM ListBox does not display last record

I find the problem I have very strange.
I have a WPF application using MVVM pattern.
I retrieve data from my database using Linq-to-SQL and display the Description field data in a ListBox using binding.
It works, but not completely. For some reason the last record's does not display. I have verified it is fetched from the database. It cannot be selected; its not there at all.
The only way to get the record to appear is by setting focus to the ListBox and either scrolling the mouse scroller or actually moving down using the keyboard to past the last record (which is supposed to be the second last record). Once you do that, the record appears.
Another way to get the record to show up is update any of the records in the ListBox. I have a TextBox bound in two-way mode to the SelectedItem of the ListBox, So when I select an item, change the text in the TextBox and click the Update button, it calls the Command to do the database update, which works fine, but then also the missing record of the last record shows up.
Is there a rendering problem with ListBox and ListView, because I have tried both? Why does it seem like the ListBox needs to be "redrawn" before the last items appears?
CategoryModel.cs
public class CategoryModel
{
public int CategoryID { get; set; }
public string Description { get; set; }
public List<CategoryModel> categories = new List<CategoryModel>();
readonly SalesLinkerDataContext _dbContext = new SalesLinkerDataContext();
public void GetCategories()
{
categories.Clear();
var result = _dbContext.tblSalesCategories.ToList();
foreach (var item in result)
{
categories.Add(new CategoryModel
{
CategoryID = item.CategoryID,
Description = item.Description.Trim()
});
}
}
internal void Update(CategoryModel cm)
{
try
{
var category = (from a in _dbContext.tblSalesCategories
where a.CategoryID == cm.CategoryID
select a).FirstOrDefault();
if (category != null)
{
category.Description = cm.Description;
_dbContext.SubmitChanges();
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}
CategoriesViewModel.cs
public class CategoriesViewModel : ViewModelBase, IPageViewModel
{
public CategoryModel Categories = new CategoryModel();
private DelegateCommand _getCategoriesCommand;
private DelegateCommand _updateCategoryCommand;
/// <summary>
/// Describes the name that will be used for the menu option
/// </summary>
public string Name
{
get { return "Manage Categories"; }
}
public string Description
{
get { return Categories.Description; }
set
{
Categories.Description = value;
OnPropertyChanged("Description");
}
}
public List<CategoryModel> ReceivedCategories
{
get { return Categories.categories; }
set
{
Categories.categories = value;
OnPropertyChanged("ReceivedCategories");
}
}
public ICommand GetCategoriesCommand
{
get
{
if (_getCategoriesCommand == null)
{
_getCategoriesCommand = new DelegateCommand(GetCategories, CanGetCategories);
}
return _getCategoriesCommand;
}
}
private bool CanGetCategories()
{
return true;
}
private void GetCategories()
{
Categories.GetCategories();
ReceivedCategories = Categories.categories;
}
public ICommand UpdateCategoryCommand
{
get
{
if (_updateCategoryCommand == null)
{
_updateCategoryCommand = new DelegateCommand(UpdateCategory, CanUpdateCategory);
}
return _updateCategoryCommand;
}
}
private bool CanUpdateCategory()
{
return true;
}
private void UpdateCategory()
{
Categories.Update(Categories);
}
}
CategoriesView.xaml
<UserControl x:Class="SalesLinker.CategoriesView"
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"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="600" Background="White" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding GetCategoriesCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<Grid >
<Grid.RowDefinitions>
<RowDefinition Height="45"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="250"/>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0" Margin="20,0,0,0" FontSize="20" HorizontalAlignment="Center" Content="Categories"/>
<ListView x:Name="LstCategories" ItemsSource="{Binding ReceivedCategories, Mode=TwoWay}" Grid.Column="0" Grid.Row="1"
VerticalAlignment="Stretch"
Background="LightGray"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
SelectionChanged="LstCategories_OnSelectionChanged">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" VerticalAlignment="Stretch" >
<TextBlock Text="{Binding Description }"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button Command="{Binding AddCategoryCommand}" Grid.Column="1" Grid.Row="1" VerticalAlignment="Top" Height="50" Width="50" Margin="0,20,0,0" Background="Transparent" BorderThickness="0" BorderBrush="Transparent" >
<Image Source="/Images/Plus.png"/>
</Button>
<Button Command="{Binding RemoveCategoryCommand}" Grid.Column="1" Grid.Row="1" VerticalAlignment="Top" Height="50" Width="50" Margin="0,75,0,0" Background="Transparent" BorderThickness="0">
<Image Source="/Images/Minus.png"/>
</Button>
<Grid Grid.Row="1" Grid.Column="2">
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="75"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label VerticalAlignment="Center" HorizontalAlignment="Center" Grid.Row="0" Grid.Column="0" Content="Description:"/>
<TextBox DataContext="CategoryModel" Grid.Row="0" Grid.Column="1" Width="250" Height="Auto" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="10,0,0,0"
Text="{Binding SelectedItem.Description, ElementName=LstCategories}"/>
<Button Command="{Binding UpdateCategoryCommand}"
Grid.Row="1" Grid.Column="1" HorizontalAlignment="Left" Margin="10,0,0,0" Height="20" Width="120" Content="Update Description"/>
</Grid>
</Grid>
I am very new to MVVM so hopefully I am just not seeing something simple.
For some reason the last record's does not display. I have verified it is fetched from the database.
If your fetch all data from database, then it can be concluded that why you cannot see your last item is as you use using not correct layout. I mean static layout.
I suggest you to use dynamic layout, not static. I mean it is bad:
<Grid.RowDefinitions>
<RowDefinition Height="45"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="250"/>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
The following code is better and it affords you to see all items to be seen and to be resized accoriding the display and Width and Height of your Window or UserControl:
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="2*"/>
</Grid.ColumnDefinitions>
Let me show the full example:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="2*"/>
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0" Margin="20,0,0,0" FontSize="20" HorizontalAlignment="Center" Content="Categories"/>
<ListView x:Name="LstCategories" ItemsSource="{Binding ReceivedCategories, Mode=TwoWay}" Grid.Column="0" Grid.Row="1"
VerticalAlignment="Stretch"
Background="LightGray"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" VerticalAlignment="Stretch" >
<TextBlock Text="{Binding Description }"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button Command="{Binding AddCategoryCommand}" Grid.Column="1" Grid.Row="1" VerticalAlignment="Top" Height="50" Width="50" Margin="0,20,0,0" Background="Transparent" BorderThickness="0" BorderBrush="Transparent" >
<Image Source="/Images/Plus.png"/>
</Button>
<Button Command="{Binding RemoveCategoryCommand}" Grid.Column="1" Grid.Row="1" VerticalAlignment="Top" Height="50" Width="50" Margin="0,75,0,0" Background="Transparent" BorderThickness="0">
<Image Source="/Images/Minus.png"/>
</Button>
<Grid Grid.Row="1" Grid.Column="2">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1.5*"/>
<ColumnDefinition Width="2*"/>
</Grid.ColumnDefinitions>
<Label VerticalAlignment="Center" HorizontalAlignment="Center" Grid.Row="0" Grid.Column="0" Content="Description:"/>
<TextBox DataContext="CategoryModel" Grid.Row="0" Grid.Column="1" Height="Auto" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="10,0,0,0"
Text="{Binding SelectedItem.Description, ElementName=LstCategories}"/>
<Button Command="{Binding UpdateCategoryCommand}"
Grid.Row="1" Grid.Column="1" HorizontalAlignment="Left" Content="Update Description"/>
</Grid>
Update:
In order to dispel any doubts, I've made a test to show that all items are shown:
I've made some test model class:
public class FooClass
{
public string Description { get; set; }
}
And I've populated your ListView in a loop:
public MainWindow()
{
InitializeComponent();
PopulateCollection();
}
private void PopulateCollection()
{
List<FooClass> fooColl = new List<FooClass>();
for (int i = 0; i <= 1000; i++)
{
fooColl.Add(new FooClass() { Description=i.ToString()});
}
LstCategories.ItemsSource = fooColl;
}
XAML:
<ListView x:Name="LstCategories" Grid.Column="0" Grid.Row="1"
VerticalAlignment="Stretch"
Background="LightGray"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" VerticalAlignment="Stretch" >
<TextBlock Text="{Binding Description }"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The result is:

How to crop the elements not fit to a WrapPanel

The example of the WrapPanel.
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ItemsControl Grid.Row="1" Style="{StaticResource DestinationItemsControlStyle}"
DataContext="{StaticResource ViewModelKey}"
ItemsSource="{Binding Stations}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Margin="5" Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Style="{DynamicResource DestinationButtonStyle}">
<TextBlock Text="{Binding FullName}" Style="{StaticResource DestinationTextBlockStyle}"
TextTrimming="CharacterEllipsis" />
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
If there are to many elements then all these "excessive" elements become rendered in half of their's real size. Is there a way not to render such the elements?
Am I forced to use something like VirtualizedWrapPanel?
I also want to notice that I can't use a scroll bar. All the "excessive" elements should be rendered on the next page which can be visited by the user's click on the button "Next".
This XAML:
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="1024" Width="1280">
<Grid >
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ItemsControl Name="ItemsControl1" Grid.Row="1"
ItemsSource="{Binding Stations}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Margin="5" Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Margin="20" MinHeight="50" MinWidth="400">
<TextBlock Text="{Binding FullName}"
TextTrimming="CharacterEllipsis" />
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
and the addition of 70 items in the Stations collection at startup, produces this result:
What does your DestinationItemsControlStyle look like?
For reference, this is the code-behind that runs on Startup:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
ViewModelKey vmk = new ViewModelKey();
ItemsControl1.DataContext = vmk;
}
}
and in the ViewModelKey class:
public class ViewModelKey : INotifyPropertyChanged
{
public ObservableCollection<station> Stations { get; set; }
public ViewModelKey()
{
Stations = new ObservableCollection<station>();
for (int i = 1; i < 70; i++)
{
Stations.Add(new station("This is station " + i.ToString()));
}
}
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}

How to automatically scale font size for a group of controls?

I have a few TextBlocks in WPF in a Grid that I would like to scale depending on their available width / height. When I searched for automatically scaling Font size the typical suggestion is to put the TextBlock into a ViewBox.
So I did this:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Viewbox MaxHeight="18" Grid.Column="0" Stretch="Uniform" Margin="5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<TextBlock Text="{Binding Text1}" />
</Viewbox>
<Viewbox MaxHeight="18" Grid.Column="1" Stretch="Uniform" Margin="5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<TextBlock Text="{Binding Text2}" />
</Viewbox>
<Viewbox MaxHeight="18" Grid.Column="2" Stretch="Uniform" Margin="5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<TextBlock Text="{Binding Text3}" />
</Viewbox>
</Grid>
And it scales the font for each TextBlock automatically. However, this looks funny because if one of the TextBlocks has longer text then it will be in a smaller font while it's neighboring grid elements will be in a larger font. I want the Font size to scale by group, perhaps it would be nice if I could specify a "SharedSizeGroup" for a set of controls to auto size their font.
e.g.
The first text blocks text might be "3/26/2013 10:45:30 AM", and the second TextBlocks text might say "FileName.ext". If these are across the width of a window, and the user begins resizing the window smaller and smaller. The date will start making its font smaller than the file name, depending on the length of the file name.
Ideally, once one of the text fields starts to resize the font point size, they would all match. Has anyone came up with a solution for this or can give me a shot at how you would make it work? If it requires custom code then hopefully we / I could repackage it into a custom Blend or Attached Behavior so that is re-usable for the future. I think it is a pretty general problem, but I wasn't able to find anything on it by searching.
Update
I tried Mathieu's suggestion and it sort of works, but it has some side-effects:
<Window x:Class="WpfApplication6.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="270" Width="522">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Rectangle Grid.Row="0" Fill="SkyBlue" />
<Viewbox Grid.Row="1" MaxHeight="30" Stretch="Uniform" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" >
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="col"/>
<ColumnDefinition Width="Auto" SharedSizeGroup="col"/>
<ColumnDefinition Width="Auto" SharedSizeGroup="col"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="SomeLongText" Margin="5" />
<TextBlock Grid.Column="1" Text="TextA" Margin="5" />
<TextBlock Grid.Column="2" Text="TextB" Margin="5" />
</Grid>
</Viewbox>
</Grid>
</Window>
Honestly, missing hte proportional columns is probably fine with me. I wouldn't mind it AutoSizing the columns to make smart use of the space, but it has to span the entire width of the window.
Notice without maxsize, in this extended example the text is too large:
<Window x:Class="WpfApplication6.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="270" Width="522">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Rectangle Grid.Row="0" Fill="SkyBlue" />
<Viewbox Grid.Row="1" Stretch="Uniform" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" >
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="col"/>
<ColumnDefinition Width="Auto" SharedSizeGroup="col"/>
<ColumnDefinition Width="Auto" SharedSizeGroup="col"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="SomeLongText" Margin="5" />
<TextBlock Grid.Column="1" Text="TextA" Margin="5" />
<TextBlock Grid.Column="2" Text="TextB" Margin="5" />
</Grid>
</Viewbox>
</Grid>
Here, I would want to limit how big the font can get, so it doesn't waste vertical window real estate. I'm expecting the output to be aligned left, center, and right with the Font being as big as possible up to the desired maximum size.
#adabyron
The solution you propose is not bad (And is the best yet) but it does have some limitations. For example, initially I wanted my columns to be proportional (2nd one should be centered). For example, my TextBlocks might be labeling the start, center, and stop of a graph where alignment matters.
<Window x:Class="WpfApplication6.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:b="clr-namespace:WpfApplication6.Behavior"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Rectangle Grid.Row="0" Fill="SkyBlue" />
<Line X1="0.5" X2="0.5" Y1="0" Y2="1" Stretch="Fill" StrokeThickness="3" Stroke="Red" />
<Grid Grid.Row="1">
<i:Interaction.Behaviors>
<b:MoveToViewboxBehavior />
</i:Interaction.Behaviors>
<Viewbox Stretch="Uniform" />
<ContentPresenter >
<ContentPresenter.Content>
<Grid x:Name="TextBlockContainer">
<Grid.Resources>
<Style TargetType="TextBlock" >
<Setter Property="FontSize" Value="16" />
<Setter Property="Margin" Value="5" />
</Style>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="SomeLongText" VerticalAlignment="Center" HorizontalAlignment="Center" />
<TextBlock Grid.Column="2" Text="TextA" HorizontalAlignment="Center" VerticalAlignment="Center" />
<TextBlock Grid.Column="4" Text="TextB" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
</ContentPresenter.Content>
</ContentPresenter>
</Grid>
</Grid>
</Window>
And here is the result. Notice it does not know that it is getting clipped early on, and then when it substitutes ViewBox it looks as if the Grid defaults to column size "Auto" and no longer aligns center.
I wanted to edit the answer I had already offered, but then decided it makes more sense to post a new one, because it really depends on the requirements which one I'd prefer. This here probably fits Alan's idea better, because
The middle textblock stays in the middle of the window
Fontsize adjustment due to height clipping is accomodated
Quite a bit more generic
No viewbox involved
The other one has the advantage that
Space for the textblocks is allocated more efficiently (no unnecessary margins)
Textblocks may have different fontsizes
I tested this solution also in a top container of type StackPanel/DockPanel, behaved decently.
Note that by playing around with the column/row widths/heights (auto/starsized), you can get different behaviors. So it would also be possible to have all three textblock columns starsized, but that means width clipping does occur earlier and there is more margin. Or if the row the grid resides in is auto sized, height clipping will never occur.
Xaml:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:beh="clr-namespace:WpfApplication1.Behavior"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="0.9*"/>
<RowDefinition Height="0.1*" />
</Grid.RowDefinitions>
<Rectangle Fill="DarkOrange" />
<Grid x:Name="TextBlockContainer" Grid.Row="1" >
<i:Interaction.Behaviors>
<beh:ScaleFontBehavior MaxFontSize="32" />
</i:Interaction.Behaviors>
<Grid.Resources>
<Style TargetType="TextBlock" >
<Setter Property="Margin" Value="5" />
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="SomeLongText" />
<TextBlock Grid.Column="1" Text="TextA" HorizontalAlignment="Center" />
<TextBlock Grid.Column="2" Text="TextB" HorizontalAlignment="Right" />
</Grid>
</Grid>
</Window>
ScaleFontBehavior:
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;
using System.Windows.Media;
using WpfApplication1.Helpers;
namespace WpfApplication1.Behavior
{
public class ScaleFontBehavior : Behavior<Grid>
{
// MaxFontSize
public double MaxFontSize { get { return (double)GetValue(MaxFontSizeProperty); } set { SetValue(MaxFontSizeProperty, value); } }
public static readonly DependencyProperty MaxFontSizeProperty = DependencyProperty.Register("MaxFontSize", typeof(double), typeof(ScaleFontBehavior), new PropertyMetadata(20d));
protected override void OnAttached()
{
this.AssociatedObject.SizeChanged += (s, e) => { CalculateFontSize(); };
}
private void CalculateFontSize()
{
double fontSize = this.MaxFontSize;
List<TextBlock> tbs = VisualHelper.FindVisualChildren<TextBlock>(this.AssociatedObject);
// get grid height (if limited)
double gridHeight = double.MaxValue;
Grid parentGrid = VisualHelper.FindUpVisualTree<Grid>(this.AssociatedObject.Parent);
if (parentGrid != null)
{
RowDefinition row = parentGrid.RowDefinitions[Grid.GetRow(this.AssociatedObject)];
gridHeight = row.Height == GridLength.Auto ? double.MaxValue : this.AssociatedObject.ActualHeight;
}
foreach (var tb in tbs)
{
// get desired size with fontsize = MaxFontSize
Size desiredSize = MeasureText(tb);
double widthMargins = tb.Margin.Left + tb.Margin.Right;
double heightMargins = tb.Margin.Top + tb.Margin.Bottom;
double desiredHeight = desiredSize.Height + heightMargins;
double desiredWidth = desiredSize.Width + widthMargins;
// adjust fontsize if text would be clipped vertically
if (gridHeight < desiredHeight)
{
double factor = (desiredHeight - heightMargins) / (this.AssociatedObject.ActualHeight - heightMargins);
fontSize = Math.Min(fontSize, MaxFontSize / factor);
}
// get column width (if limited)
ColumnDefinition col = this.AssociatedObject.ColumnDefinitions[Grid.GetColumn(tb)];
double colWidth = col.Width == GridLength.Auto ? double.MaxValue : col.ActualWidth;
// adjust fontsize if text would be clipped horizontally
if (colWidth < desiredWidth)
{
double factor = (desiredWidth - widthMargins) / (col.ActualWidth - widthMargins);
fontSize = Math.Min(fontSize, MaxFontSize / factor);
}
}
// apply fontsize (always equal fontsizes)
foreach (var tb in tbs)
{
tb.FontSize = fontSize;
}
}
// Measures text size of textblock
private Size MeasureText(TextBlock tb)
{
var formattedText = new FormattedText(tb.Text, CultureInfo.CurrentUICulture,
FlowDirection.LeftToRight,
new Typeface(tb.FontFamily, tb.FontStyle, tb.FontWeight, tb.FontStretch),
this.MaxFontSize, Brushes.Black); // always uses MaxFontSize for desiredSize
return new Size(formattedText.Width, formattedText.Height);
}
}
}
VisualHelper:
public static List<T> FindVisualChildren<T>(DependencyObject obj) where T : DependencyObject
{
List<T> children = new List<T>();
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
var o = VisualTreeHelper.GetChild(obj, i);
if (o != null)
{
if (o is T)
children.Add((T)o);
children.AddRange(FindVisualChildren<T>(o)); // recursive
}
}
return children;
}
public static T FindUpVisualTree<T>(DependencyObject initial) where T : DependencyObject
{
DependencyObject current = initial;
while (current != null && current.GetType() != typeof(T))
{
current = VisualTreeHelper.GetParent(current);
}
return current as T;
}
Put your grid in the ViewBox, which will scale the whole Grid :
<Viewbox Stretch="Uniform" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Text1}" Margin="5" />
<TextBlock Grid.Column="1" Text="{Binding Text2}" Margin="5" />
<TextBlock Grid.Column="2" Text="{Binding Text3}" Margin="5" />
</Grid>
</Viewbox>
I think I know the way to go and will leave the rest to you. In this example, I bound the FontSize to the ActualHeight of the TextBlock, using a converter (the converter is below):
<Window x:Class="MyNamespace.Test"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:Converters="clr-namespace:UpdateYeti.Converters"
Title="Test" Height="570" Width="522">
<Grid Height="370" Width="522">
<Grid.Resources>
<Converters:HeightToFontSizeConverter x:Key="conv" />
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Rectangle Grid.Row="0" Fill="SkyBlue" />
<Grid Grid.Row="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" MinHeight="60" Background="Beige">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="SomeLongText" Margin="5"
FontSize="{Binding RelativeSource={RelativeSource Self}, Path=ActualHeight, Converter={StaticResource conv}}" />
<TextBlock Grid.Column="1" Text="TextA" Margin="5" HorizontalAlignment="Center"
FontSize="{Binding RelativeSource={RelativeSource Self}, Path=ActualHeight, Converter={StaticResource conv}}" />
<TextBlock Grid.Column="2" Text="TextB" Margin="5" FontSize="{Binding RelativeSource={RelativeSource Self}, Path=ActualHeight, Converter={StaticResource conv}}" />
</Grid>
</Grid>
</Window>
[ValueConversion(typeof(double), typeof(double))]
class HeightToFontSizeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
// here you can use the parameter that you can give in here via setting , ConverterParameter='something'} or use any nice login with the VisualTreeHelper to make a better return value, or maybe even just hardcode some max values if you like
var height = (double)value;
return .65 * height;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
General remark: A possible alternative to the whole text scaling could be to just use TextTrimming on the TextBlocks.
I've struggled to find a solution to this one. Using a viewbox is really hard to mix with any layout adjustments. Worst of all, ActualWidth etc. do not change inside a viewbox. So I finally decided to use the viewbox only if absolutely necessary, which is when clipping would occur. I'm therefore moving the content between a ContentPresenter and a Viewbox, depending upon the available space.
This solution is not as generic as I would like, mainly the MoveToViewboxBehavior does assume it is attached to a grid with the following structure. If that cannot be accomodated, the behavior will most likely have to be adjusted. Creating a usercontrol and denoting the necessary parts (PART_...) might be a valid alternative.
Note that I have extended the grid's columns from three to five, because that makes the solution a lot easier. It means that the middle textblock will not be exactly in the middle, in the sense of absolute coordinates, instead it is centered between the textblocks to the left and right.
<Grid > <!-- MoveToViewboxBehavior attached to this grid -->
<Viewbox />
<ContentPresenter>
<ContentPresenter.Content>
<Grid x:Name="TextBlockContainer">
<TextBlocks ... />
</Grid>
</ContentPresenter.Content>
</ContentPresenter>
</Grid>
Xaml:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:beh="clr-namespace:WpfApplication1.Behavior"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Rectangle Grid.Row="0" Fill="SkyBlue" />
<Grid Grid.Row="1">
<i:Interaction.Behaviors>
<beh:MoveToViewboxBehavior />
</i:Interaction.Behaviors>
<Viewbox Stretch="Uniform" />
<ContentPresenter >
<ContentPresenter.Content>
<Grid x:Name="TextBlockContainer">
<Grid.Resources>
<Style TargetType="TextBlock" >
<Setter Property="FontSize" Value="16" />
<Setter Property="Margin" Value="5" />
</Style>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="SomeLongText" />
<TextBlock Grid.Column="2" Text="TextA" />
<TextBlock Grid.Column="4" Text="TextB" />
</Grid>
</ContentPresenter.Content>
</ContentPresenter>
</Grid>
</Grid>
</Window>
MoveToViewBoxBehavior:
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;
using System.Windows.Media;
using WpfApplication1.Helpers;
namespace WpfApplication1.Behavior
{
public class MoveToViewboxBehavior : Behavior<Grid>
{
// IsClipped
public bool IsClipped { get { return (bool)GetValue(IsClippedProperty); } set { SetValue(IsClippedProperty, value); } }
public static readonly DependencyProperty IsClippedProperty = DependencyProperty.Register("IsClipped", typeof(bool), typeof(MoveToViewboxBehavior), new PropertyMetadata(false, OnIsClippedChanged));
private static void OnIsClippedChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var beh = (MoveToViewboxBehavior)sender;
Grid grid = beh.AssociatedObject;
Viewbox vb = VisualHelper.FindVisualChild<Viewbox>(grid);
ContentPresenter cp = VisualHelper.FindVisualChild<ContentPresenter>(grid);
if ((bool)e.NewValue)
{
// is clipped, so move content to Viewbox
UIElement element = cp.Content as UIElement;
cp.Content = null;
vb.Child = element;
}
else
{
// can be shown without clipping, so move content to ContentPresenter
cp.Content = vb.Child;
vb.Child = null;
}
}
protected override void OnAttached()
{
this.AssociatedObject.SizeChanged += (s, e) => { IsClipped = CalculateIsClipped(); };
}
// Determines if the width of all textblocks within TextBlockContainer (using MaxFontSize) are wider than the AssociatedObject grid
private bool CalculateIsClipped()
{
double totalDesiredWidth = 0d;
Grid grid = VisualHelper.FindVisualChildByName<Grid>(this.AssociatedObject, "TextBlockContainer");
List<TextBlock> tbs = VisualHelper.FindVisualChildren<TextBlock>(grid);
foreach (var tb in tbs)
{
if (tb.TextWrapping != TextWrapping.NoWrap)
return false;
totalDesiredWidth += MeasureText(tb).Width + tb.Margin.Left + tb.Margin.Right + tb.Padding.Left + tb.Padding.Right;
}
return Math.Round(this.AssociatedObject.ActualWidth, 5) < Math.Round(totalDesiredWidth, 5);
}
// Measures text size of textblock
private Size MeasureText(TextBlock tb)
{
var formattedText = new FormattedText(tb.Text, CultureInfo.CurrentUICulture,
FlowDirection.LeftToRight,
new Typeface(tb.FontFamily, tb.FontStyle, tb.FontWeight, tb.FontStretch),
tb.FontSize, Brushes.Black);
return new Size(formattedText.Width, formattedText.Height);
}
}
}
VisualHelper:
public static class VisualHelper
{
public static T FindVisualChild<T>(DependencyObject obj) where T : DependencyObject
{
T child = null;
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
var o = VisualTreeHelper.GetChild(obj, i);
if (o != null)
{
child = o as T;
if (child != null) break;
else
{
child = FindVisualChild<T>(o); // recursive
if (child != null) break;
}
}
}
return child;
}
public static List<T> FindVisualChildren<T>(DependencyObject obj) where T : DependencyObject
{
List<T> children = new List<T>();
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
var o = VisualTreeHelper.GetChild(obj, i);
if (o != null)
{
if (o is T)
children.Add((T)o);
children.AddRange(FindVisualChildren<T>(o)); // recursive
}
}
return children;
}
public static T FindVisualChildByName<T>(DependencyObject parent, string name) where T : FrameworkElement
{
T child = default(T);
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
{
var o = VisualTreeHelper.GetChild(parent, i);
if (o != null)
{
child = o as T;
if (child != null && child.Name == name)
break;
else
child = FindVisualChildByName<T>(o, name);
if (child != null) break;
}
}
return child;
}
}
You can use a hidden ItemsControl in a ViewBox.
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Viewbox VerticalAlignment="Bottom">
<Grid>
<TextBlock Text="SomeLongText"/>
<ItemsControl Visibility="Hidden">
<ItemsPanelTemplate>
<Grid/>
</ItemsPanelTemplate>
<TextBlock Text="SomeLongText"/>
<TextBlock Text="TextA"/>
<TextBlock Text="TextB"/>
</ItemsControl>
</Grid>
</Viewbox>
<Viewbox Grid.Column="1" VerticalAlignment="Bottom">
<Grid>
<TextBlock Text="TextA"/>
<ItemsControl Visibility="Hidden">
<ItemsPanelTemplate>
<Grid/>
</ItemsPanelTemplate>
<TextBlock Text="SomeLongText"/>
<TextBlock Text="TextA"/>
<TextBlock Text="TextB"/>
</ItemsControl>
</Grid>
</Viewbox>
<Viewbox Grid.Column="2" VerticalAlignment="Bottom">
<Grid>
<TextBlock Text="TextB"/>
<ItemsControl Visibility="Hidden">
<ItemsPanelTemplate>
<Grid/>
</ItemsPanelTemplate>
<TextBlock Text="SomeLongText"/>
<TextBlock Text="TextA"/>
<TextBlock Text="TextB"/>
</ItemsControl>
</Grid>
</Viewbox>
</Grid>
or
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Viewbox VerticalAlignment="Bottom">
<Grid>
<TextBlock Text="{Binding Text1}"/>
<ItemsControl Visibility="Hidden" ItemsSource="{Binding AllText}">
<ItemsPanelTemplate>
<Grid/>
</ItemsPanelTemplate>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Viewbox>
<Viewbox Grid.Column="1" VerticalAlignment="Bottom">
<Grid>
<TextBlock Text="{Binding Text2}"/>
<ItemsControl Visibility="Hidden" ItemsSource="{Binding AllText}">
<ItemsPanelTemplate>
<Grid/>
</ItemsPanelTemplate>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Viewbox>
<Viewbox Grid.Column="2" VerticalAlignment="Bottom">
<Grid>
<TextBlock Text="{Binding Text3}"/>
<ItemsControl Visibility="Hidden" ItemsSource="{Binding AllText}">
<ItemsPanelTemplate>
<Grid/>
</ItemsPanelTemplate>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Viewbox>
</Grid>
A solution could be something like that :
Choose a maxFontSize, then define the appropriate FontSize to be displayed considering the current Window by using a linear equation. Window's height or width would limit the final FontSize choice.
Let's take the case of a "single kind TextBlock" for the whole grid :
Window.Current.SizeChanged += (sender, args) =>
{
int minFontSize = a;
int maxFontSize = b;
int maxMinFontSizeDiff = maxFontSize - minFontSize;
int gridMinHeight = c;
int gridMaxHeight = d;
int gridMaxMinHeightDiff = gridMaxHeight - gridMinHeight;
int gridMinWidth = e;
int gridMaxWidth = f;
int gridMaxMinHeightDiff = gridMaxWidth - gridMaxWidth;
//Linear equation considering "max/min FontSize" and "max/min GridHeight/GridWidth"
double heightFontSizeDouble = (maxMinFontSizeDiff / gridMaxMinHeightDiff ) * Grid.ActualHeight + (maxFontSize - (gridMaxHeight * (maxMinFontSizeDiff / gridMaxMinHeightDiff)))
double widthFontSizeDouble = (maxMinFontSizeDiff / gridMaxMinWidthDiff ) * Grid.ActualWidth + (maxFontSize - (gridMaxWidth * (maxMinFontSizeDiff / gridMaxMinWidthDiff)))
int heightFontSize = (int)Math.Round(heightFontSizeDouble)
int widthFontSize = (int)Math.Round(widthFontSizeDouble)
foreach (var children in Grid.Children)
{
(children as TextBlock).FontSize = Math.Min(heightFontSize, widthFontSize);
}
}
I use ScaleTransform. This scales the current font by the desired amount:
<ScaleTransform x:Key="FontDoubled" ScaleX="2" ScaleY="2" />
<ScaleTransform x:Key="FontHalved" ScaleX="0.5" ScaleY="0.5" />
The larger the ScaleX and ScaleY, the larger the font is scaled by. No need to change individual font sizes.
It can also be used with just ScaleX or ScaleY to change the font size in one direction only, or with different values.
To use it:
<Label Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="4"
FontWeight="Bold"
Content="This is normal the size" />
...gives:
And:
<Label Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="4"
FontWeight="Bold"
LayoutTransform="{StaticResource FontDoubled}"
Content="This is Double size" />
...gives:

ItemsControl Grid.Row Grid.Column Binding in Silverlight

I'm trying to fill a Grid using different X and Y values for Grid.Row and Grid.Column using an ItemsControl, I copied it from a WPF project of mine and can't get it to work in Silverlight (Windows Phone).
Here is a simplified version of it:
ViewModel which the DataContext is set to:
public class ViewModel
{
public ObservableCollection<GridItem> Data { get; set; }
public ViewModel()
{
Data = new ObservableCollection<GridItem>();
FillData();
}
// fill Data property with some random color GridItems
private void FillData()
{
string[] colors = { "Red", "Green", "Yellow", "Blue" };
Random r = new Random();
for (int i = 0; i < 10; i++)
{
Data.Add(new GridItem(i, i, colors[r.Next(colors.Length)]));
}
}
}
public class GridItem
{
public int X { get; set; }
public int Y { get; set; }
public string Color { get; set; }
public GridItem(int x, int y, string color)
{
X = x;
Y = y;
Color = color;
}
}
The XAML:
<Grid x:Name="LayoutRoot" Background="Transparent">
<ItemsControl ItemsSource="{Binding Data}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid Background="Orange">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
</Grid>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Ellipse
Grid.Row="{Binding Y}"
Grid.Column="{Binding X}"
Fill="{Binding Color}"
Stroke="White" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
This is the result (it placed all ellipses on top of eachother):
While it should have done this:
Does anyone know why this doesn't work?
Try setting the binding for the Grid.Row and Grid.Column within the ContentPresenter rather than as part of the DataTemplate that defines the Ellipse:
<ItemsControl.Resources>
<Style TargetType="ContentPresenter">
<Setter Property="Grid.Row" Value="{Binding Y}"/>
<Setter Property="Grid.Column" Value="{Binding X}"/>
</Style>
</ItemsControl.Resources>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Ellipse
Fill="{Binding Color}"
Stroke="White" />
</DataTemplate>
</ItemsControl.ItemTemplate>
It's because the DataTemplate wraps its content in a ContentPresenter. The Grid.Row and Grid.Column properties only apply to direct descendants so in this case they are not relevant because they are two levels deep in the control hierarchy.
The ContentPresenter is added dynamically at run time when the DataTemplate renders. To make this work you'd need to hook into the dynamic generation of the items and set the Row and Column attached properties on the containing item.
There is a blog post showing one way of getting this working at: http://www.scottlogic.co.uk/blog/colin/2010/11/using-a-grid-as-the-panel-for-an-itemscontrol/

WP7 List Box alignment and styling issues

I want to create a windows phone screen which will display the some users info in a listbox like following
Right now i am doing this using following code its working fine.
<ListBox>
<ListBoxItem Height="100">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="60"/>
<RowDefinition Height="40"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Image Source="Assets/Users/dummyUser2.png" Grid.RowSpan="2" Grid.Column="0" Height="90" Width="90" />
<TextBlock Text="John Smith" Style="{StaticResource ProfileNameStyleForTextBlock}" />
<TextBlock Text="Birthday" Style="{StaticResource EventNameStyleForTextBlock}" />
<TextBlock Text="Today" Style="{StaticResource EventDateStyleForTextBlock}" Foreground="#09aba9" />
</Grid>
</ListBoxItem>
...........
......
</ListBox>
What i want to do is, i want to load the items into list box dynamically from code behind and the view should be look like above image.
Is it possible to create a style/control like this and add it to all the lists i'll create in future.
Edit
My ListBox
<ListBox Name="lstlist" ItemTemplate="{StaticResource GuyDataTemplate}" />
DataTemplate
<DataTemplate x:Key="GuyDataTemplate">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="60"/>
<RowDefinition Height="40"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Image Source="Assets/Users/dummyUser2.png" Grid.RowSpan="2" Grid.Column="0" Height="90" Width="90" />
<TextBlock Text="{Binding Name}" Style="{StaticResource ProfileNameStyleForTextBlock}" />
<TextBlock Text="{Binding Event}" Style="{StaticResource EventNameStyleForTextBlock}" />
<TextBlock Text="{Binding Date}" Style="{StaticResource EventDateStyleForTextBlock}" Foreground="#09aba9" />
</Grid>
</DataTemplate>
I created a class as
public class Guy
{
public string Name { get; set; }
public string Event { get; set; }
public string Date { get; set; }
}
and wrote the logic in a buttonclick
ObservableCollection<Guy> Guys = new ObservableCollection<Guy>();
Guys.Add(new Guy() { Name = "xyzabc", Event = "event details", Date = "19/25/0258" });
Guys.Add(new Guy() { Name = "xyzabc", Event = "event details", Date = "19/25/0258" });
Guys.Add(new Guy() { Name = "xyzabc", Event = "event details", Date = "19/25/0258" });
Guys.Add(new Guy() { Name = "xyzabc", Event = "event details", Date = "19/25/0258" });
lstlist.ItemsSource = Guys;
After adding this app got stucked
define DataTemplate for ListBox item in resources :
<DataTemplate x:Key="GuyDataTemplate">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="60"/>
<RowDefinition Height="40"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Image Source="Assets/Users/dummyUser2.png" Grid.RowSpan="2" Grid.Column="0" Height="90" Width="90" />
<TextBlock Text="John Smith" Style="{StaticResource ProfileNameStyleForTextBlock}" FontWeight="ExtraBold" />
<TextBlock Text="Birthday" Style="{StaticResource EventNameStyleForTextBlock}" />
<TextBlock Text="Today" Style="{StaticResource EventDateStyleForTextBlock}" Foreground="#09aba9" />
</Grid>
</DataTemplate>
and use it from every ListBox you want:
<ListBox ItemsSource={Binding Guys} ItemTemplate={StaticResource GuyDataTemplate} />
, where Guys is ObservableCollection<>
add new item:
Guys.Add(new Guy() { Name = "Will Snith" });
P.S.
UserControl is not recommended to use as ListBox item

Resources