Pseudo-Infinite Grid - wpf

I've got a bit of a design issue here.
I've got a view:
<ItemsControl x:Name="CellVMs">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Grid.Row"
Value="{Binding Position.Y}" />
<Setter Property="Grid.Column"
Value="{Binding Position.X}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
which is bound to a collection of viewmodels, that have a position property which the style there uses to position it on the ItemPanelTemplate. (Only one viewmodel per cell, and the grid cells are fixed size)
1) I would like that Grid to be pseudo-infinite, ie as EditorVMs get added and subtracted, the Grid should dynamically add and delete Row\Col Definitions, and there should always be enough Grid, and there should always be enough to fill the parent view.
2) In my containing viewmodel, I import an IGridEditor implementation instance which has a Grid property. How can I bind the ItemsPanelTemplateGrid to the IEditor.Grid?
Right now, I add the CellVM's to a collection in the IGridEditor's methods, then when the containing vm imports the instance, sets the containing vm's CellVM collection to the instances collection, and the item control binds to that using Caliburn.Micro's conventions.
I'm using Caliburn.Micro\MEF btw.
Can anyone help me figure this out?
Edit:
Been trying to understand this, but I'm coming up empty.
Only thing I can find is
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid x:Name="EditorGrid"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
and in my viemodel:
[Import("EditorGrid", typeof(Grid))]
public Grid EditorGrid { get; set; }
and a corresponding Export in class that has the methods to add things to the grid/

1) I would like that Grid to be pseudo-infinite, ie as EditorVMs get
added and subtracted, the Grid should dynamically add and delete
Row\Col Definitions, and there should always be enough Grid, and there
should always be enough to fill the parent view.
You could create a custom Grid that automatically adds rows or columns as needed:
public class AutoExpandGrid : Grid
{
protected override System.Windows.Media.Visual GetVisualChild(int index)
{
var visualChild = base.GetVisualChild(index);
var uiElement = visualChild as UIElement;
if (uiElement != null)
EnsureEnoughRowsAndColumns(uiElement);
return visualChild;
}
private void EnsureEnoughRowsAndColumns(UIElement child)
{
int minRows = GetRow(child) + GetRowSpan(child);
int minColumns = GetColumn(child) + GetColumnSpan(child);
while (minRows > RowDefinitions.Count)
{
RowDefinitions.Add(new RowDefinition());
}
while (minColumns > ColumnDefinitions.Count)
{
ColumnDefinitions.Add(new ColumnDefinition());
}
}
}
(not sure GetVisualChild is the best place to do this, but it's the best I could find)

Related

How to apply a style to a DependencyObject in a custom control library

I am creating a reusable custom control, based on the TreeView. I have on the custom control created a dependency property for the columns in the control, like this:
public GridViewColumnCollection Columns
{
get { return (GridViewColumnCollection)GetValue(ColumnsProperty); }
set { SetValue(ColumnsProperty, value); }
}
public static readonly DependencyProperty ColumnsProperty =
DependencyProperty.Register("Columns", typeof(GridViewColumnCollection), typeof(TreeListView), new PropertyMetadata(new GridViewColumnCollection()));
This lets me specify a bunch of columns in XAML. The catch is that I need the first column to have a custom cell template. I was going to approach this by deriving a class from GridViewColumn, something like this:
public class TreeGridViewColumn : GridViewColumn
{
}
and then give it the desired style in the Generic.xaml for the custom control:
<Style TargetType="{x:Type local:TreeGridViewColumn}">
<Setter Property="CellTemplate">
<Setter.Value>
<DataTemplate>
<Border Background="Black" /> <!-- Just for example -->
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
However the style is never applied to instances of TreeGridViewColumn. I know that I probably need to add:
DefaultStyleKeyProperty.OverrideMetadata(typeof(TreeGridViewColumn), new FrameworkPropertyMetadata(typeof(TreeGridViewColumn)));
However I cannot do this, as the GridColumn base class is not a FrameworkObject, it is a DependencyObject. How can I apply a style to a descendant of a GridViewColumn defined in a Custom Control library?
Think like this: The TreeGridViewColumn should be a dummy object holding important information for the column itself such as width and height and also for each cell under that columns header for example the cell template itself. Therefore do not try to create an FrameworkElement out of TreeGridViewColumn. Here is an example how you might end up using the TreeGridViewColumn.
<TreeGridViewColumn Header="First Col" Width="50">
<TreeGridViewColumn.CellTemplate>
<DataTemplate>
<Button>
click me
</Button>
</DataTemplate>
</TreeGridViewColumn.CellTemplate>
</TreeGridViewColumn>
Once you ready to display the columns and cells I suggest to you to write your own custom panel which deals with the FrameworkElements by calling their Measure and Arrange methods allowing you to position columns and cells the way you want. You will end up doing alot of math inside your custom panel class. That futhermore means you will end up spending a month on programming that TreeGridView. I suggest you to take a shortcut and download the code of such a thing. There are already few TreeListViews online. Just take their dlls and see if it will work out for you
EDIT:
Ok here is a suggestion how you could solve your issue. Its just a suggestion
The DefaultTextColumnData class is a dummy object holding all the necessary infos like columns width, etc.
DataGridCellControl will be that FrameworkElement that draws the cell. Its a FrameworkElement so it will have a defined style in your generic.xaml resource dictionary.
To sum up DefaultTextColumnData will hold all infos for the column itself. DataGridCellControl will be a control which might end up having 20 instances of itself in case you have 20 cells in that column.
DataGridCellControl must know about its column.
This is how the code of DataGridCellControl will look alike:
class DefaultTextColumnData : DataGridColumn
{
}
class ComplexColumnData : DataGridColumn
{
}
class DataGridCellControl : Control
{
public DataGridColumn Column
{
get; set;
}
public DataTemplate DefaultTextCellTemplate
{
get; set;
}
public override Size MeasureOverride(Size size)
{
...
if(this.Column is DefaultTextColumnData)
{
this.Template = this.DefaultTextCellTemplate
}
if(this.Column is ComplexColumnData)
{
this.Template = ...
}
...
return new Size(30, 30);
}
}
DefaultTextCellTemplate will be set in your generic.xaml like this:
<Style TargetType={x:Type DataGridCellControl}>
<Setter Property="DefaultTextCellTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Background="Black" Margin="5"/>
....
Thats how you set default cell template in your resource dictionary.

TreeView with multiple types

I would like to build a treeview like this:
People
Person 1
Relatives
Relative 1
Relative 2
Mom
Dad
Pets
Pet 1
Pet 2
The problem is that a person has 2 lists (Relatives and Pets) and two Single Items (Mom and Dad). I'm pretty familiar with HierarchicalDataTemplates but I haven't figured out a way to do this- there are lots of examples out there but none seem to mix types like this.
To make things even more interesting, there may be People without a Mom or a Dad (sad but true). The list would need to reflect this.
The data Im using originates from the data base using entity framework, so my list of people already has the correct structure- and I would prefer NOT to have all my objects derive from some common composite object (also in many of the examples) where everyone has a 'Name' and 'Children'. I would like to use the natural properties of each object, like MomsFirstName, PersonsName in the bindings of my datatemplates as well.
Is this possible?
Not sure if this helps you, but you can supply your own template selector, to pick a different template per type; They will all be encapsulated by the ItemsPanelTemplate object you choose, but it will allow you to have very different controls for any type you specify. This is my use case, but I imagine you could apply the same idea to the TreeView
<ItemsControl>
<ItemsControl.Resources>
<DataTemplate x:Key="templateFoo">
</DataTemplate>
<DataTemplate x:Key="templateBar">
</DataTemplate>
<DataTemplate x:Key="templateJoe">
</DataTemplate>
</ItemsControl.Resources>
<ItemsControl.ItemTemplateSelector>
<local:MyTemplateSelector></local:MyTemplateSelector>
</ItemsControl.ItemTemplateSelector>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Grid.Row"
Value="{Binding Row}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
And this somewhere
public class MyTemplateSelector: DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
FrameworkElement element = container as FrameworkElement;
if (element == null || item == null)
return null;
if(item is Foo)
{
return element.FindResource("templateFoo") as DataTemplate;
}
if (item is Bar)
{
return element.FindResource("templateBar") as DataTemplate;
}
if (item is Joe)
{
return element.FindResource("templateJeo") as DataTemplate;
}
return null;
}
}
Have you tried to use ItemTemplate and ItemTemplateSelector?

Find Control Inside ListBox?

<Style TargetType="ListBoxItem" x:Key="ListBoxItemTemplate">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Button Content="{TemplateBinding Content}"></Button>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<ListBox ItemsSource="{Binding S}"
x:Name="listBox"
ItemContainerStyle="{StaticResource ListBoxItemTemplate}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid x:Name="grid" Columns="5"></UniformGrid>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
I want to Find "grid" from ListBox Control.Please Help Me,Thank you.
A couple of things to add to Meleak's answer (and this was a bit too long to put in a comment.)
Normally, the way you obtain a named element from a template in WPF is to call the template's FindName method. However, because templates are basically factories, you also needs to say which particular instance of the template you require - a single ItemsPanelTemplate may have been instantiated several times over. So you'd need something like this:
var grid = (UniformGrid) listBox.ItemsPanel.FindName("grid", ???);
But what goes in that ??? placeholder? It's not the ListBox itself - the ListBox doesn't actually use that ItemsPanel directly. Ultimately, the it's used by the ItemsPresenter in the ListBox's template. So you'd need to do this:
var grid = (UniformGrid) listBox.ItemsPanel.FindName("grid", myItemsPresenter);
...except, there's no reliable way to get hold of the ItemsPresenter either. In fact, there might not even be one - it's legal to create a template for a ListBox that just provides the hosting panel directly - there's even a special property, Panel.IsItemsHost, for this very purpose.
And that leads onto the second point I wanted to add. In scenarios where the ListBox's template doesn't use the ItemsPresenter, the ItemsPanel will go unused. So it's actually possible that the UniformGrid you're trying to get hold of doesn't even exist.
One way to do it is to store it in code behind once it is Loaded.
<ListBox ItemsSource="{Binding S}"
x:Name="listBox"
ItemContainerStyle="{StaticResource ListBoxItemTemplate}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid x:Name="grid" Columns="5" Loaded="grid_Loaded"></UniformGrid>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
And in code behind
private UniformGrid m_uniformGrid = null;
private void grid_Loaded(object sender, RoutedEventArgs e)
{
m_uniformGrid = sender as UniformGrid;
}
If you want to find it from the ListBox then you can use the Visual Tree.
UniformGrid uniformGrid = GetVisualChild<UniformGrid>(listBox);
public static T GetVisualChild<T>(object parent) where T : Visual
{
DependencyObject dependencyObject = parent as DependencyObject;
return InternalGetVisualChild<T>(dependencyObject);
}
private static T InternalGetVisualChild<T>(DependencyObject parent) where T : Visual
{
T child = default(T);
int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < numVisuals; i++)
{
Visual v = (Visual)VisualTreeHelper.GetChild(parent, i);
child = v as T;
if (child == null)
{
child = GetVisualChild<T>(v);
}
if (child != null)
{
break;
}
}
return child;
}

Silverlight 4: how to display list of custom controls (not in list order)

There are following object:
'FieldItem' custom control;
'Field' - ... XAML-object, which will contains a dozen of field items;
FieldItemViewModel - data class that hosts data to be displayed with 'FieldItem' custom control;
position of 'FieldItem' control depend from data entity parameters that is bounded to the control (X and Y);
items - ObservableCollection - collection that contains a data.
Question: what kind of object should I put inside of the in order to have each item of the my FieldItems to be displayed inside of Canvas?
I've planned to use ListView... but... can't imagine how is it possible to change position of the list view item...
Any thoughts are welcome!
Thanks.
You can have a simple ItemsControl.
ItemsControl is just a container of items.
The ItemsPanel should be set to your canvas. And the data template of each item should be the 'FieldItem' control.
In your viewmodel expose a property that is called Items which will be a collection of the items data.
Something similar to this:
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<FieldItem />
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
Silverlight doesn't have ItemContainerStyle but you can set it in code:
public class MyItemsControl : ItemsControl
{
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
FrameworkElement contentitem = element as FrameworkElement;
Binding leftBinding = new Binding("Position.X");
Binding topBinding = new Binding("Position.Y");
contentitem.SetBinding(Canvas.LeftProperty, leftBinding);
contentitem.SetBinding(Canvas.TopProperty, topBinding);
base.PrepareContainerForItemOverride(element, item);
}
}
Taken from here: http://forums.silverlight.net/forums/p/29753/96429.aspx

Why does ItemContainerGenerator return null?

I have a ListBox, and I need to set its ControlTemplate to a Virtualizing WrapPanel which is a class that extends VirtualizingPanel, using a style that looks like this:
<Style TargetType="{x:Type ListBox}" x:Key="PhotoListBoxStyle">
<Setter Property="Foreground" Value="White" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBox}" >
<s:VirtualizingVerticalWrapPanel>
</s:VirtualizingVerticalWrapPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Now, in the private method of Virtualizing WrapPanel below I try to access this.ItemContainerGenerator, but I get null value, any idea what's the problem ??
private void RealizeFirstItem()
{
IItemContainerGenerator generator = this.ItemContainerGenerator;
GeneratorPosition pos = generator.GeneratorPositionFromIndex(0);
using (generator.StartAt(pos, GeneratorDirection.Forward))
{
UIElement element = generator.GenerateNext() as UIElement;
generator.PrepareItemContainer(element);
this.AddInternalChild(element);
}
}
I think I had a similar problem and this helped:
var necessaryChidrenTouch = this.Children;
IItemContainerGenerator generator = this.ItemContainerGenerator;
... for some reason you have to "touch" the children collection in order for the ItemContainerGenerator to initialize properly.
For Windows 8.1 Metro apps, the ItemContainerGenerator was depricated and will return null. New Apis:
ItemsControl.ItemContainerGenerator.ItemFromContainer = ItemsControl.ItemFromContainer
ItemsControl.ItemContainerGenerator.ContainerFromItem = ItemsControl.ContainerFromItem
ItemsControl.ItemContainerGenerator.IndexFromContainer = ItemsControl.IndexFromContainer
ItemsControl.ItemContainerGenerator.ContainerFromIndex = ItemsControl.ContainerFromIndex
http://msdn.microsoft.com/en-us/library/windows/apps/dn376326.aspx
Falck is mostly correct. Actually, you need to reference the 'InternalChildren' of the virtualized stack panel. The decompiled code for this property is:
protected internal UIElementCollection InternalChildren
{
get
{
this.VerifyBoundState();
if (this.IsItemsHost)
{
this.EnsureGenerator();
}
else if (this._uiElementCollection == null)
{
this.EnsureEmptyChildren(this);
}
return this._uiElementCollection;
}
}
The 'EnsureGenerator' does the work of making sure that a generator is available. Very poor 'just in time' design, IMO.
Most probably this is a virtualization-related issue so ListBoxItem containers get generated only for currently visible items (e.g. https://msdn.microsoft.com/en-us/library/system.windows.controls.virtualizingstackpanel(v=vs.110).aspx#Anchor_9)
I'd suggest switching to ListView instead of ListBox - it inherits from ListBoxand it supports ScrollIntoView() method which you can utilize to control virtualization;
targetListView.ScrollIntoView(itemVM);
DoEvents();
ListViewItem itemContainer = targetListView.ItemContainerGenerator.ContainerFromItem(itemVM) as ListViewItem;
(the example above also utilizes the DoEvents() static method explained in more detail here; WPF how to wait for binding update to occur before processing more code?)
There are a few other minor differences between the ListBox and ListView controls (What is The difference between ListBox and ListView) - which should not essentially affect your use case.
This is because you changed the Template of the Listbox, while u should have just changed the ItemsPanel:
<ListBox>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<s:VirtualizingVerticalWrapPanel />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>

Resources