Using a ScrollViewer with an ItemsControl with a Canvas as the ItemsPanel - wpf

I have an ItemsControl and I set the ItemsPanel to a Canvas. The Canvas needs to be able to dynamically size with the content I put in it, and I need to be able to scroll when content runs off the bounds of the control. The trouble is I can't get the content to scroll. I have the scroll bar visibilities set to auto, so I don't end up seeing the scroll bars pop up when content runs off the edge.
I tried both putting the ItemsControl inside a ScrollViewer, and I tried using a ScrollViewer in the ItemsControl's Template.
Here's the ItemsControl inside the ScrollViewer:
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding Tiles}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Canvas.Left" Value="{Binding Left}" />
<Setter Property="Canvas.Top" Value="{Binding Top}" />
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplateSelector>
...
</ItemsControl.ItemTemplateSelector>
</ItemsControl>
</ScrollViewer>
And here it is with the ScrollViewer in the Template:
<ItemsControl ItemsSource="{Binding Tiles}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Canvas.Left" Value="{Binding Left}" />
<Setter Property="Canvas.Top" Value="{Binding Top}" />
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.Template>
<ControlTemplate>
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
<ItemsPresenter />
</ScrollViewer>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemTemplateSelector>
...
</ItemsControl.ItemTemplateSelector>
</ItemsControl>
This post on MSDN seemed promising, but implementing it on my code, or even explicitly with a Canvas in lieu of a WrapPanel wasn't working (or, should I say, I wasn't able to get it to work).
I've also taken a look at this post but the solution doesn't work for me as I need the canvas to be able to size with the content (otherwise the scroll bars are always visible).
Thank you kindly, in advance!

The canvas's width/height when not explicitly set, will inherit from the ItemsControl. The "dynamic sizing" behavior you're expecting isn't how panels sizing and layout works out of the box.
You options are to:
Explicitly set the ItemsControl Width/Height.
Explicitly set the Canvas Width/Height.

Related

How to make scrollbar visible around a Canvas in WPF

I have a list of items, which I display on a Canvas. I display those items vertically, the next below the previous, just like in ListBox or ListView.
Because I need to be able to drag those items around and for that purpose I have to use a Canvas, since Canvas allows me to set concrete positions for the items.
There are many items in the collection reaching outside the boundaries of the window when displayed and for that I need to use a ScrollViewer. The problem here is that if I don't set the Canvas's height, then the ScrollViewer isn't visible.
<ScrollViewer VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Width="500" /> // Can't set Height here
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding Left}" />
<Setter Property="Canvas.Top" Value="{Binding Top}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</ScrollViewer>
What could I do to make the vertical scroll bar around canvas visible?
So, based on #EdPlunkett's answer I came up with this solution:
I added bindings to MinHeight and MinWidth of the ItemsControl (altought the normal Height and Width would work as well I suppose).
<ScrollViewer VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding AlbumItems}"
MinHeight="{Binding EditorHeight}"
MinWidth="{Binding EditorWidth}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding Left}" />
<Setter Property="Canvas.Top" Value="{Binding Top}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</ScrollViewer>
and in my data context I call this method to calculate the size and set the EditorHeight and EditorWidth properties to their calculated values.
private void calculateEditorSize()
{
int maxEditorHeight = AlbumItems.Max((component) => component.Top + component.Height);
int maxEditorWidth = AlbumItems.Max((component) => component.Left + component.Width);
EditorHeight = maxEditorHeight;
EditorWidth = maxEditorWidth;
}

How to set proportional Canvas.Left?

Is there the way to set the distance between the left side of a control and the left side of its parent Canvas proportionally, not as a fixed value?
I use Canvas (actually DragCanvas, because I allow for the user to drag controls in the canvas). I'm binding values in xaml:
<ItemsControl ItemsSource="{Binding Tables}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Style="{DynamicResource TableButton}" Content="{Binding}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Canvas.Top" Value="{Binding Path=YTransl}" />
<Setter Property="Canvas.Left" Value="{Binding Path=XTransl}" />
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
<j:DragCanvas Margin="10"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
The problem that the controls which are inside of canvas for bigger screen size, are outside of view for smaller screen size.
I need to set the value "YTransl" and "XTransl" as percentage of the whole canvas, proportionally to the screen size.
Something like <RowDefinition Height="1*" /> in Grid container.
Or multiplying by screen width and height... I don't know if it is good idea..
Is there the way to do it in xaml? Or I should calculate this in cs and bind the prepared value in xaml?

Dragging behavior is not working for items in ItemsControl: MouseDragElementBehavior

I have a class
public class DrawingCanvas : FrameworkElement
which uses MouseDragElementBehavior to implement dragging.
I want to drag the DrawingCanva like a layer in Photoshop relative to another layer
Working alone with (XAML on pic #1)
<Grid x:Name="_layoutRootControl">
<Canvas
Grid.Column="0">
<canvas:DrawingCanvas />
<canvas:DrawingCanvas />
</Canvas>
</Grid>
do allows dragging, but when I place DrawingCanvas inside ItemsControl
and bind ItemsSource to collection of items, draggins doesn't work.
(XAML on pic #2)
<ItemsControl ItemsSource="{Binding Source={StaticResource Locator}, Path=LayersViewModel.Layers}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="0" />
<Setter Property="Canvas.Top" Value="0" />
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<canvas:DrawingCanvas />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I don't understand why.
First case is the following XAML structure:
And the second case is the following XAML structure:
Update:
I've found out that in second case with ItemsControl a DrawingCanvas.Parent = null
In connection with that item.Parent inside ItemsControls is always null
I've found the solution in the following topics with explanation:
Why does the Parent property of a container of an ItemsControl return null and not the Panel it sits on?
Combining ItemsControl with draggable items - Element.parent always null
Update:
Now the Parent is not null, still the problem remains.
Update 2
The last lacking thing
I had to define "extenders:DragHelper.CanDrag" inside ContentPresenter instead of
<ItemsControl.ItemTemplate>
The resulting template is as follows, so the issue is solved:
<ItemsControl
ItemsSource="{Binding Source={StaticResource Locator}, Path=LayersViewModel.Layers}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property=**"extenders:DragHelper.CanDrag"** Value="True"/>
<Setter Property="Canvas.Left" Value="{Binding Path=X}" />
<Setter Property="Canvas.Top" Value="{Binding Path=Y}" />
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<surfaces:DrawingCanvas />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

Issue when setting a Style for StackPanel elements in an ItemsControl

If I create a simple StackPanel and place some items in it and define a style to add margins, all works well, as demonstrated by this quick example:
<StackPanel Grid.Row="1" Orientation="Vertical">
<StackPanel.Resources>
<Style TargetType="module:ModuleElement">
<Setter Property="Margin" Value="20"/>
</Style>
</StackPanel.Resources>
<module:ModuleElement/>
<module:ModuleElement/>
</StackPanel>
Obviously module:ModuleElement is my UserControl, but the issue remains if I use a default control, like a typical Button.
However, I don't want just a StackPanel - I want an ItemsControl. So I defined my control as such:
<ItemsControl Grid.Row="1" Margin="20,0,20,0">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical">
<StackPanel.Resources>
<Style TargetType="module:ModuleElement">
<Setter Property="Margin" Value="0,20,0,0"/>
</Style>
</StackPanel.Resources>
</StackPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.Items>
<module:ModuleElement/>
<module:ModuleElement/>
</ItemsControl.Items>
</ItemsControl>
The few items added in the code above are for testing purposes - in this case the margins are default values (0).
What's going on? Why isn't this working? What do I need to do to fix this? I could of course set the margins manually for each element, but I feel I missing something and would much rather see it defined in a Style...
You might put the Style into the ItemsControl Resources instead of the StackPanel Resources:
<ItemsControl Grid.Row="1" Margin="20,0,20,0">
<ItemsControl.Resources>
<Style TargetType="module:ModuleElement">
<Setter Property="Margin" Value="0,20,0,0"/>
</Style>
</ItemsControl.Resources>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.Items>
<module:ModuleElement/>
<module:ModuleElement/>
</ItemsControl.Items>
</ItemsControl>
Resource lookup in Silverlight traverses the "object tree" (see here) which as far as I understand is not the visual tree, but the tree defined in XAML.

How to use Canvas as the ItemsPanel for an ItemsControl in Silverlight 3

I am trying to set the Canvas properties in an ItemsControl DataTemplate with Silverlight 3. According to this post, the only way of doing that is to set it using the ItemsContainerStyle for the ContentPresenter type, since the Canvas properties only take effect on direct children of the Canvas. This doesn't seem to work in SL3, since the ItemsControl doesn't have an ItemsContainerStyle property, so I tried a ListBox as advised by this article, but it still doesn't work. From the XAML below, I would expect to see a green square, with the numbers 10, 30, 50, 70 cascading from "NW" to "SE" direction. Can anyone tell me why they are all stacked on top of eachother in the NW corner?
<UserControl x:Class="TestControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:System="clr-namespace:System;assembly=mscorlib" >
<StackPanel>
<ListBox>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Background="Green" Width="100" Height="100" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding}" />
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding}" />
<Setter Property="Canvas.Top" Value="{Binding}" />
</Style>
</ListBox.ItemContainerStyle>
<ListBox.Items>
<System:Int32>10</System:Int32>
<System:Int32>30</System:Int32>
<System:Int32>50</System:Int32>
<System:Int32>70</System:Int32>
</ListBox.Items>
</ListBox>
</StackPanel>
</UserControl>
I'm not sure if it will work in your scenario, but I've accomplished this in the past using the RenderTransform.
<ItemsControl>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Background="Green" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding}">
<TextBox.RenderTransform>
<TranslateTransform X="100" Y="100" />
</TextBox.RenderTransform>
</TextBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.Items>
<System:Int32>10</System:Int32>
<System:Int32>30</System:Int32>
<System:Int32>50</System:Int32>
<System:Int32>70</System:Int32>
</ItemsControl.Items>
</ItemsControl>
Or in the case of binding you will need to use a converter
<ItemsControl>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Background="Green" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding}" RenderTransform="{Binding Converter={StaticResource NumberToTransformGroupConverter}}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.Items>
<System:Int32>10</System:Int32>
<System:Int32>30</System:Int32>
<System:Int32>50</System:Int32>
<System:Int32>70</System:Int32>
</ItemsControl.Items>
</ItemsControl>
Converter
public void ConvertTo(object value, ...)
{
int intValue = int.Parse(value.ToString());
return new TransformGroup()
{
Children = new TransformCollection()
{
new TranslateTransform { X = intValue, Y = intValue }
}
};
}
Silverlight4 does not bind to attached properties in style. I suggest using David Anson's approach described here.
<UserControl.Resources>
<Style x:Key="ScreenBindStyle" TargetType="ListBoxItem">
<Setter Property="Helpers:SetterValueBindingHelper.PropertyBinding">
<Setter.Value>
<Helpers:SetterValueBindingHelper>
<Helpers:SetterValueBindingHelper Type="Canvas" Property="Left" Binding="{Binding LocationField.Value.X}" />
<Helpers:SetterValueBindingHelper Type="Canvas" Property="Top" Binding="{Binding LocationField.Value.Y}" />
<Helpers:SetterValueBindingHelper Type="Canvas" Property="ZIndex" Binding="{Binding ZIndex.Value}" />
</Helpers:SetterValueBindingHelper>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<ContentPresenter/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
And in listbox:
ItemContainerStyle="{StaticResource ScreenBindStyle}"
Old post but I went into the same problem but now with SL5 that now allows Binding in style setters. I was trying to avoid to use the ListBox (because it handles selection and so on) and the ItemsControl still doesn't have an ItemContainerStyle. So I tried a few things.
I haven't found many subject discussing this problem so let me share my solution (sorry if it duplicates)
In fact, I found a very convenient way to solve the problem by adding an unnamed Style in the ItemsControl resources :
<ItemsControl ItemsSource="{Binding Path=MyData}">
<ItemsControl.Resources>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Top" Value="{Binding Path=Bounds.Top}"/>
<Setter Property="Canvas.Left" Value="{Binding Path=Bounds.Left}"/>
<Setter Property="Width" Value="{Binding Path=Bounds.Width}"/>
<Setter Property="Height" Value="{Binding Path=Bounds.Height}"/>
</Style>
</ItemsControl.Resources>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="my:DataType">
...
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Works like a charm in SL5 :)
I can't explain what you are seeing either. Your Xaml is broken in at least a couple of ways.
First the Xaml itself fails because this:-
<Style TargetType="ContentPresenter">
should be
<Style TargetType="ContentControl">
the Item Containers in the case of ListBox are of type ListBoxItem which derive from ContentControl.
Still without that the placing {Binding} in style setters still doesn't work. I guess you were imagining that the style would be applied to each item in turn and get its Value from the current item. However even if binding worked in the style, there would only be one style and it would get its data binding from the ListBox DataContext. This is a different DataContext that applies to each ListBox item (which in this case is each Item in the Items collection).
Still I think Ben has a reasonable solution in place which eliminates this approach.

Resources