Fairly new to WPF...
I have a collection of data I would like to bind to a grid panel. Each object contains its grid row and column, as well as stuff to fill in at the grid location. I really like how I can create data templates in the listbox XAML to create a UI with almost nothing in the code behind for it. Is there a way to create a data template for grid panel elements, and bind the panel to a data collection?
You can use an ItemsControl with a Grid as its panel. Here is an example. XAML:
<ItemsControl x:Name="myItems">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
</Grid>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding MyText}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style>
<Style.Setters>
<Setter Property="Grid.Row" Value="{Binding MyRow}" />
<Setter Property="Grid.Column" Value="{Binding MyColumn}" />
</Style.Setters>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
Codebehind (for testing purposes):
public Window1()
{
InitializeComponent();
myItems.ItemsSource = new[] {
new {MyRow = 0, MyColumn = 0, MyText="top left"},
new {MyRow = 1, MyColumn = 1, MyText="middle"},
new {MyRow = 2, MyColumn = 2, MyText="bottom right"}
};
}
Not sure if this will help you, but why don't you try setting the ItemsPanel of an ItemsControl (ListBox, ListView) to a UniformGrid. Something like this:
<ItemsControl>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
It's similar to the previous solution, only a little more dynamic.
Related
Following on from this question (DataTemplateSelector for Uniform Grid binding?), I still am not getting my Buttons/Textblocks to adhere to the row and the column that they are bound to in the view model. I will only post the XAML as I know the row and column bindings are correct (Live Visual Tree tells me Row is 1 and Column is 1 for example, but on the grid it shows otherwise...).
If you need anymore code let me know. This is the small bit of XAML.
<Grid DockPanel.Dock="Left" Background="Transparent" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" MinWidth="800" Height="400">
<ItemsControl ItemsSource="{Binding ObjCompositeCollection}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid DockPanel.Dock="Top" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" Name="objGrid" Grid.Row="1"
Rows="{Binding RowCount}"
Columns="{Binding ColumnCount}"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Grid.Row" Value="{Binding Row}"/>
<Setter Property="Grid.Column" Value="{Binding Column}"/>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.Resources>
<DataTemplate DataType="{x:Type engine:ObjA}">
<Button Content="{Binding Id}" />
</DataTemplate>
<DataTemplate DataType="{x:Type engine:GridLabeller}">
<TextBlock Text="{Binding HeaderName}"/>
</DataTemplate>
</ItemsControl.Resources>
</ItemsControl>
</Grid>
A UniformGrid in WPF doesn't care about the Grid.Row and Grid.Column attached properties. You need to create a Grid with RowDefinitions and ColumnsDefinitions for setting these properties to have any effect.
You may create a Grid programmatically in the view based on the values of the RowCount and ColumnCount source properties.
I cannot get this ItemsControl to virtualize properly. Debugging shows that the collection is initialized quickly, but all the items are being added to the control instead of a subset (I simply put a TracePoint in the TextBox_Initializeevent in theUserControl` that makes up the item).
Note: I have looked at other, similar questions, but have been unable to solve this issue with those answers.
The ViewModel:
public class ImportInformationViewModel : CommandViewModel
{
public ImportInformationViewModel()
{
this.PropertyChanged += ImportInformationViewModel_PropertyChanged;
}
private ObservableCollection<SingleTransactionViewModel> mTransactions;
public ReadOnlyObservableCollection<SingleTransactionViewModel> Transactions
{
get
{
if (mTransactions == null)
mTransactions = new ObservableCollection<SingleTransactionViewModel>();
var filtered = mTransactions.Where(trans => !trans.IgnoreTransaction)
.OrderBy(trans => trans.DateStamp)
.ThenBy(trans => trans.TransactionName)
.ThenBy(trans => trans.TransactionDetail);
return new ReadOnlyObservableCollection<SingleTransactionViewModel>(new ObservableCollection<SingleTransactionViewModel>(filtered));
}
}
private void ImportInformationViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == "TransactionFileName")
{
if (File.Exists(mTransactionFileName))
{
mTransactions.Clear();
// Process File (Not Shown)
mTransactions.Add(new SingleTransactionViewModel()
{
DateStamp = date,
TransactionDetail = someText;
});
}
}
if (e.PropertyName != "Transactions")
NotifyPropertyChanged("Transactions");
}
}
SingleTransactionViewModel is just another class that implements INotifyPropertyChanged. Nothing special.
Here is the control containing the ItemsControl
<UserControl x:Class="ImportInformationView">
<UserControl.Resources>
<CollectionViewSource x:Key="TransactionsData" Source="{Binding Transactions}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="YearAndMonth" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
<BooleanToVisibilityConverter x:Key="booleanToVisibility" />
</UserControl.Resources>
<ItemsControl ItemsSource="{Binding Source={StaticResource TransactionsData}}"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
ScrollViewer.CanContentScroll="True"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Recycling"
MinHeight="20">
<ItemsControl.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<GroupBox Padding="5" Margin="2,5">
<GroupBox.Header>
<Border Background="Black"
CornerRadius="4">
<TextBlock Text="{Binding Name}" />
</Border>
</GroupBox.Header>
<ItemsPresenter />
</GroupBox>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ItemsControl.GroupStyle>
<ItemsControl.Template>
<ControlTemplate>
<ScrollViewer CanContentScroll="True">
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" />
</ScrollViewer>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid IsItemsHost="True" Columns="2" Grid.IsSharedSizeScope="True" VirtualizingStackPanel.IsVirtualizing="True" />
<!--<VirtualizingStackPanel IsItemsHost="True" />-->
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<views:SingleTransactionView Margin="4,6" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</UserControl>
And I've boiled the SingleTransactionView down to something very simple:
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="ExpanderColumn" />
<ColumnDefinition Width="*" SharedSizeGroup="TransactionNameColumn" />
<ColumnDefinition Width="Auto" SharedSizeGroup="DateColumn" />
<ColumnDefinition Width="Auto" SharedSizeGroup="OptionsColumn" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Column="1"
Grid.Row="0"
Initialized="TextBlock_Initialized"
Text="{Binding TransactionName}"
HorizontalAlignment="Left"
VerticalAlignment="Center"
FontSize="14"
FontWeight="Bold"
Margin="4"/>
</Grid>
This is probably due to your use of grouping. By default, enabling grouping effectively switches off virtualization. However, as of .NET 4.5, you should be able to regain this functionality via the VirtualizingPanel.IsVirtualizingWhenGrouping property.
Excerpt from this blog post on WPF enhancements in .NET 4.5:
In WPF 4.0, you lost virtualization when grouping is done on the collection you display. I repeat : Grouping = no virtualization in WPF 4.0. This is still the default behavior of WPF 4.5, but you can turn on the virtualization by using the IsVirtualizingWhenGrouping attached property of the VirtualizingPanel class. When this is done, you benefit of all the already described advantages of virtualization.
Here is how you can enable it via XAML:
<ListBox ItemsSource="{Binding Persons}"
ItemTemplate="{StaticResource PersonDataTemplate}"
VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.IsVirtualizingWhenGrouping="True">
<ListBox.GroupStyle>
<GroupStyle HeaderTemplate="{StaticResource GroupHeaderTemplate}" />
</ListBox.GroupStyle>
</ListBox>
Sounds like setting VirtualizingPanel.IsVirtualizingWhenGrouping="True" alongside your other virtualization-related properties should give you the behavior you want.+
I'm new to XAML. I searched about ItemsControl and I found a tutorial, which is easy to understand, but the problem is that it is not work in WinRT.
Tutorial: https://rachel53461.wordpress.com/2011/09/17/wpf-itemscontrol-example/
I tried to use TargetType in Style tag, however, in runtime I got an exception.
<ItemsControl ItemsSource="{Binding MyCollection}">
<!-- ItemsPanelTemplate -->
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
</Grid>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<!-- ItemContainerStyle -->
<ItemsControl.ItemContainerStyle>
<Style TargetType="TextBox">
<Setter Property="Grid.Column"
Value="{Binding xIndex}" />
<Setter Property="Grid.Row"
Value="{Binding yIndex}" />
</Style>
</ItemsControl.ItemContainerStyle>
<!-- ItemTemplate -->
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Background="{Binding color}" Text="{Binding xIndex,Mode=OneWay}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Your problem is here:
<Style TargetType="TextBox">
this is what is should be:
<Style TargetType="ContentPresenter">
The ItemContainer for an ItemsControl is a ContentPresenter (unless a specific item is added to the ItemsControl).
So your view hierarchy looks something like this (assuming you didn't change the ItemsPanel to something other than a StackPanel):
<StackPanel>
<ContentPresenter>
<TextBox/>
</ContentPresenter>
</StackPanel>
Edit:
As Scott pointed out in the comments, this solution doesn't actually work for WinRT. I did something similar and you can probably modify it to do the appropriate binding:
public class CustomItemsControl : ItemsControl
{
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
base.PrepareContainerForItemOverride(element, item);
FrameworkElement source = element as FrameworkElement;
if (source != null)
{
source.SetBinding(Canvas.LeftProperty, new Binding { Path = new PropertyPath("X"), Mode = BindingMode.TwoWay });
source.SetBinding(Canvas.TopProperty, new Binding { Path = new PropertyPath("Y"), Mode = BindingMode.TwoWay });
}
}
}
This binds the Canvas.LeftProperty to the X property on each item in the collection and similarly the Canvas.TopProperty and the Y property.
I want to display data with level of detail, so i use a TreeView, but each detail is quite short, so i would like to use a WrapPanel (horizontal) to have many details per line.
Something like :
This is an unexpanded item
This is The Header of an expanded item
Info 1 Info 2 Info 3 Info 4
Info 5 Info 6 Info 7
So i tried defining TreeViewItem's Template but i could not get the wrappanel to
wrap. I have only one info per line, when info's datatemplate width is 100 and TreeView
is 500. i tried setting Width of WrapPanel, ItemsWidth, are other things with no success.
Any idea ?
EDIT : i finally got this to work with a 'simpler' solution. Still it seems that we
have to define the WrapPanel's Width, which make the solution less generic.
Here's the solution i came to : just defining, in a style, the ItemsPanel used in a
TreeViewItem :
<Style TargetType="TreeViewItem">
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal"
Width="520"
HorizontalAlignment="Stretch"
Margin="0"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
IsItemsHost="True"
/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style>
And i still let the not working solution here, for completeness sake.
(Why wouldn't it work ???)
<Style TargetType="TreeViewItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TreeViewItem">
<Grid Margin="2" Width="500">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ContentPresenter Name="PART_Header"
ContentSource="Header"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
!!!! this is the wrapanel not wrapping
<ListBox Name="AllItems" Grid.Row="1" >
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ItemsPresenter />
</ListBox>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsExpanded" Value="False">
<Setter
TargetName="AllItems"
Property="Visibility"
Value="Collapsed" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
EDIT : i finally got this to work with a 'simpler' solution. Still it seems that we
have to define the WrapPanel's Width, which make the solution less generic.
(Maybe a binding of the width (but which ?) would solve this)
Here's the solution i came to : just defining, in a style, the ItemsPanel used in a
TreeViewItem :
<Style TargetType="TreeViewItem">
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal"
Width="520"
HorizontalAlignment="Stretch"
Margin="0"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
IsItemsHost="True"
/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style>
You must set
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
to your
<TreeView/>
not for
<WrapPanel/>
example:
<TreeView x:Name="fieldTreeView" Grid.Row="1" Margin="5,0,5,0" Background="Beige"
ItemsSource="{Binding Source={StaticResource bla}}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<TreeView.Resources>
<DataTemplate DataType="{x:Type Model:bla}">
<StackPanel Orientation="Vertical">
<Label Content="{Binding Name}"/>
<TextBox Text=""/>
</StackPanel>
</DataTemplate>
</TreeView.Resources>
<TreeView.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</TreeView.ItemsPanel>
</TreeView>
This solution works for me.
Try
Disable the scroll bars
Widen the WrapPanel and see the visual impact, is it effected?
Make a color border/background to track the actual size of the WrapPanel
These will help you trace the problem
How can I create horizontally oriented ItemsControl, where items are aligned the same way how these two labels are aligned in this Grid?
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label Grid.Column="1">Text 1</Label>
<Label Grid.Column="3">Text 2</Label>
</Grid>
I already have a View for the items, so using ItemsTemplate is probably not an option.
Resources
Here are some tutorials and articles about creating your own custom panel. Hope this will help someone with similar problem...
UniformGrid for Silverlight
Animate WPF Datatemplate when item added to Listbox?
Creating Custom Panels In WPF
FishEyePanel/FanPanel - Examples of custom layout panels in WPF
WPF - A Constraining Stack Panel
About the closest you could get without writing your own panel would be:
<ItemsControl ItemsSource="{Binding TheItems}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows="1"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Control.HorizontalAlignment" Value="Center"/>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
Note that I'm .NET-less at the moment so cannot test the above.