WinRT ItemsControl with Grid columns and rows - c#

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.

Related

ItemsControl Not Virtualizing

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.+

Custom UserControl with nested UserControls in ListBox

I am trying to get a custom UserControl to render in a ListBox, but nothing is being rendered. I came across this question and solution which works for the simple example, but my situation is a little different. I have a PersonControl for a Person object and a CoupleControl that can reference two PersonControl controls.
I've tried a couple things in the CoupleControl which haven't worked. I commented out one of the ways:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<Control:PersonControl Grid.Column="0"
x:Name="LeftPerson" />
<Control:PersonControl Grid.Column="1"
x:Name="RightPerson" />
<!-- This is how I'd like to do it in case I create other controls
I wish to replace the PersonControls (e.g. AnimalControl) -->
<!--<UserControl Grid.Column="0"
x:Name="LeftPerson" />-->
<!--<UserControl Grid.Column="1"
x:Name="RightPerson" />-->
</Grid>
The relevant WPF snippet for the list box:
<ListBox Grid.Row="1"
ItemsSource="{Binding Persons}">
<ListBox.ItemTemplate>
<DataTemplate>
<Control:CoupleControl />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
In the code-behind:
public ObservableCollection<CoupleControl> Persons { get; private set; }
Person joe = new Person("Joe", "Smith", Person.SexType.Male);
Person jane = new Person("Jane", "Smith", Person.SexType.Female);
PersonControl joeControl = new PersonControl();
PersonControl janeControl = new PersonControl();
joeControl.DataContext = joe;
janeControl.DataContext = jane;
CoupleControl coupleControl = new CoupleControl();
coupleControl.LeftPerson.DataContext = joe;
coupleControl.RightPerson.DataContext = jane;
//coupleControl.LeftPerson.Content = joeControl; // Also doesn't work
//coupleControl.RightPerson.Content = janeControl; // Also doesn't work
Persons.Add(coupleControl);
Can someone help me get the CoupleControl to render in a ListBox?
Your approach is a bit too code-heavy for my taste, why not set DataContext in XAML?
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Control:PersonControl DataContext="{Binding LeftPerson}" />
<Control:PersonControl DataContext="{Binding RightPerson}" Grid.Column="1" />
</Grid>
Or maybe even drop UserControls altogether if they are not so complex? In this case using DataTemplates can be faster and simpler. Say we defined templates for Person and Couple in Window resources (Couple is just a class with LeftPerson and RightPerson properties):
<Window.Resources>
<DataTemplate x:Key="personTemplate" DataType="TestWPF:Person">
<Border BorderThickness="1" BorderBrush="Green" CornerRadius="5">
<StackPanel>
<TextBlock Text="{Binding FirstName}" />
<TextBlock Text="{Binding LastName}" Margin="3,0,0,0" />
</StackPanel>
</Border>
</DataTemplate>
<DataTemplate x:Key="coupleTemplate" DataType="TestWPF:Couple">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ContentControl Content="{Binding LeftPerson}"
ContentTemplate="{StaticResource personTemplate}" />
<ContentControl Content="{Binding RightPerson}"
ContentTemplate="{StaticResource personTemplate}" Grid.Column="1" />
</Grid>
</DataTemplate>
</Window.Resources>
Then you set ItemTemplate for your ListBox:
<ListBox Grid.Row="1" ItemsSource="{Binding Persons}" ItemTemplate="{StaticResource coupleTemplate}" />
This way you can make some more templates for the types you need and just set them in ListBox in one single line.

Set Image Source depending on ViewModels Type

I have hierarchical data which I want to display inside a TreeView.
All those objects have a common base class and the Bindings work with that baseclass.
Now I want to show a different Image depending on the Type of the ViewModel.
Right now I have a HierachicalDataTemplate for that.
<HierarchicalDataTemplate DataType="{x:Type viewModels:ItemBaseViewModel}" ItemsSource="{Binding Children}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="20*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100*" />
<ColumnDefinition Width="5" />
<ColumnDefinition Width="100*" />
<ColumnDefinition Width="100*" />
<ColumnDefinition Width="100*" />
</Grid.ColumnDefinitions>
<Image Grid.Column="0"
Height="12"
Source="/Client;component/Images/icon.png" />
<TextBlock Grid.Column="2" Text="{Binding DisplayString}" />
</Grid>
</HierarchicalDataTemplate>
What is the easiest way to display bar.png for BarViewModel and foo.png for FooViewModel? (Both of them are derived from ItemBaseViewModel)
The easiest way is when the difference can be distinguished by a property, which can be checked through a DataTrigger. For example, if there was a TypeId property which was a string or enum with values foo and bar, a style like this could be applied to the image:
<Style TargetType="Image">
<Setter Property="Source" Value="foo.png" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=TypeId}" Value="bar">
<Setter Property="Source" Value="bar.png" />
</DataTrigger>
</Style>
</Style>
Another way is to take advantage of the fact that DataTemplate resources defined with a DataType will automatically be applied to that type. But instead of having global resources that re-implement your entire template for each type, you could place a ContentControl within your main template, with its own local resources for just the image part:
<ContentControl Grid.Column="0" Content="{Binding}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type viewModels:foo}">
<Image Source="foo.png" />
</DataTemplate>
<DataTemplate DataType="{x:Type viewModels:bar}">
<Image Source="bar.png" />
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
Have a String ImagePath Property on your base view model and override the get accessor or set the Property in the constructor of your derived types

is there a datatemplate for grid panel elements in WPF?

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.

WPF - Change a style in code behind

I have a list box that displays the results of a TFS Query. I want to change the style of the ListBoxItem in the code behind to have the columns that are included in the query results.
The style for the ListBoxItem is defined in my Windows.Resoruces Section. I have tried this:
public T GetQueryResultsElement<T>(string name) where T : DependencyObject
{
ListBoxItem myListBoxItem =
(ListBoxItem)(lstQueryResults.ItemContainerGenerator.ContainerFromIndex(0));
// Getting the ContentPresenter of myListBoxItem
ContentPresenter myContentPresenter =
myListBoxItem.Template.LoadContent().FindVisualChild<ContentPresenter>();
// Finding textBlock from the DataTemplate that is set on that ContentPresenter
DataTemplate myDataTemplate = myContentPresenter.ContentTemplate; <------+
T myControl = (T)myDataTemplate.FindName(name, myContentPresenter); |
|
return (T)myControl; |
} |
|
ContentTemplate is null ----------------------------------------------+
But the ContentTemplate is null. I got that code from here, then modified it with the LoadContent call (the orginal code gave null for the ContentPresenter).
Anyway. If you know a way to change an existing style in the code behind I would love to see it.
Specifics if you want them:
I am going for WrapPanel in my ListBoxItem Style. This is what I want to add the extra TextBlock items to.
Here is part of my style:
<!--Checkbox ListBox-->
<Style x:Key="CheckBoxListStyle" TargetType="ListBox">
<Style.Resources>
<Style x:Key="ListBoxItemStyle" TargetType="ListBoxItem">
<Setter Property="Tag" Value="{Binding Id}"/>
<Setter Property="Background">
<Setter.Value>
<Binding Path="Type" Converter="{StaticResource WorkItemTypeToColorConverter}" />
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Border BorderThickness="1" BorderBrush="#D4D4FF">
<Grid Width="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type WrapPanel}}, Path=ActualWidth}" ScrollViewer.CanContentScroll="True" Margin="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="30" />
</Grid.ColumnDefinitions>
<Grid.Background>
<Binding Path="Type" Converter="{StaticResource WorkItemTypeToColorConverter}" />
</Grid.Background>
<CheckBox VerticalAlignment="Center" Grid.Column="0" IsChecked="{Binding IsSelected,
RelativeSource={RelativeSource TemplatedParent},
Mode=TwoWay}" Name="chkIsSelected" />
<WrapPanel Grid.Column="1" Margin="5,0,5,0" Name="QueryColumns">
<TextBlock VerticalAlignment="Center" Text="{Binding Id}" Name="txtID" />
<TextBlock VerticalAlignment="Center" Margin="5,0,5,0" Text="{Binding Title}" Name="txtTitle" />
</WrapPanel>
You're going against the grain here, trying to manipulate visual elements directly in code-behind. There's a much simple solution involving data binding.
I'll provide the general solution because I don't know the specifics of your solution.
Once you get your query results, create an enumeration that returns a column name, and a field value for each iteration.
Example:
class NameValuePair
{
public string Name { get; set; }
public object Value { get; set; }
}
public IEnumerable<IEnumerable<NameValuePair>> EnumerateResultSet(DataTable resultSet)
{
foreach (DataRow row in resultSet.Rows)
yield return EnumerateColumns(resultSet, row);
}
public IEnumerable<NameValuePair> EnumerateColumns(DataTable resultSet, DataRow row)
{
foreach (DataColumn column in resultSet.Columns)
yield return new NameValuePair
{ Name = column.ColumnName, Value = row[column] };
}
And in your code-behind, once you get your DataTable result set, do this:
myResultsList.ItemsSource = EnumerateResultSet(myDataTable);
The XAML might look like this:
<Window.Resources>
<DataTemplate x:Key="ColumnTemplate">
<Border BorderBrush="Black" BorderThickness="1" CornerRadius="2" Padding="2">
<WrapPanel>
<TextBlock Text="{Binding Name}" Margin="0,0,5,0"/>
<TextBlock Text="{Binding Value}" Margin="0,0,10,0"/>
</WrapPanel>
</Border>
</DataTemplate>
<DataTemplate x:Key="RowTemplate">
<Grid>
<ItemsControl
ItemsSource="{Binding}"
ItemTemplate="{StaticResource ColumnTemplate}"
Margin="0,5,0,5"/>
</Grid>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox Name="myResultsList" ItemTemplate="{StaticResource RowTemplate}"/>
</Grid>
Sample output:

Categories

Resources