How to make itemtemplate aware of its containing template? - wpf

I want this Ellipse to get its coordinates from its corresponding BallViewModel, and to use them to determine its location inside a canvas.
The list of balls is bound to List<BallVM> in the mainviewmodel and thus I chose an itemsControl which has a canvas panel.
Is this approach correct?
If I try to bind to X and Y inside an itemcontainerstyle, then it's not specific to a certain ball.
No matter what I set in the Canvas.bottom or canvas.left properties the ellipse is always at the top left.
<Grid>
<ItemsControl ItemsSource="{Binding Balls}" Background="red">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas MouseMove="Canvas_MouseMove" Background="Blue"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type VM:BallVM}">
<Ellipse Canvas.Bottom="{Binding Y}" Canvas.Left="{Binding X}" Width="100" Height="100" Fill="Red"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>

When you use an ItemTemplate with an ItemControls it does not directly put your Elippses on the Canvas but wraps them into an ContentPresenter. So you have to apply your canvas.Bottom/Left properties on the ItemsPresenter. You can can do this with the ItemContainerStyle:
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Canvas.Bottom" Value="{Binding Y}" />
<Setter Property="Canvas.Left" Value="{Binding X}" />
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type VM:BallVM}">
<Ellipse Width="100" Height="100" Fill="Red"/>
</DataTemplate>
</ItemsControl.ItemTemplate>

Related

How can you add Children to an ItemsPanelTemplate of an ItemsControl in WPF?

I have the following ItemsControl:
<ItemsControl ItemsSource="{Binding ControllableLedList}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Width="600" Height="600" Grid.Row="0" Grid.Column="0" Background="FloralWhite" >
<Ellipse Width="600" Height="600" Fill="#FF252525" /> <!-- big Ellipse causing my error -->
</Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding PositionX}" />
<Setter Property="Canvas.Top" Value="{Binding PositionY}" />
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Ellipse Width="5" Height="5" Fill="Yellow"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Basically the idea here is to have a canvas with a collection of ellipses (let's call them LEDs, since that what they represent in the real world), where the position of the LED ellipses is controlled from the ViewModel
The above code works well except for the big Ellipse in the Canvas -> as soon as I add it, I get the following Error:
InvalidOperationException: Cannot explicitly modify Children collection of Panel used as ItemsPanel for ItemsControl. ItemsControl generates child elements for Panel
This makes sense, since the "actual children - the LED Ellipses" should be generated dynamically during runtime. Anyway, how can I get my big ellipse into my canvas, so that the order of visibility would be Canvas.Background->Big Ellipse-> LED Ellipses?
You may add the Ellipse to the Template of the ItemsControl:
<ItemsControl.Template>
<ControlTemplate TargetType="ItemsControl">
<Canvas>
<Ellipse Width="600" Height="600" Fill="#FF252525" />
<ItemsPresenter/>
</Canvas>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>

How to resize ListBoxItems in a horizontal ListBox as the number of items grows [duplicate]

I have a problem with my ListBox in WPF. First of all, I have a horizontal ListBox with custom ItemTemplate. Now, I want to stretch the items, so that the items fits over the complete width of the ListBox. I tried things like setting the HorizontalContentAlignment to Stretch, but this still not working.
Here is my ItemTemplate:
<DataTemplate x:Key="ListViewStepTemplate">
<Grid VerticalAlignment="Stretch">
<TextBlock Text="{Binding Path=Title}"
FontSize="15"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
<Image Height="16"
Width="16"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Source="/images/Content/check_16x16.png"
Visibility="{Binding Path=IsDone, Converter={StaticResource BooleantoVisibilityConverter}, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</DataTemplate>
And this is my ListBox:
<ListBox DockPanel.Dock="Top"
ItemsSource="{Binding AllItemsList}"
SelectedItem="{Binding CurrentItem}"
ItemTemplate="{StaticResource ListViewStepTemplate}"
Height="60"
HorizontalContentAlignment="Stretch">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsEnabled" Value="{Binding IsEnabled, UpdateSourceTrigger=PropertyChanged}" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Padding" Value="30 0" />
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
If there are 4 items, each item should have a width of 25%. If there are 5 items, each item should have a width of 20% and so on.
Is it possible to do what I want to do? I tried a lot of things now, but it does never work.
Instead of using StackPanel use UniformGrid
Provides a way to arrange content in a grid where all the cells in the grid have the same size.
and bind number of columns to number of items in the list and disable horizontal scrolling functionality.
<ListBox
...
ItemsSource="{Binding AllItemsList}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollBarVisibility="Disabled" >
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows="1" Columns="{Binding AllItemsList.Count}"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<!-- style -->
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
Don't use a StackPanel, use an UniformGrid instead.
<ItemsPanelTemplate>
<UniformGrid Rows="1" Columns="{Binding DataContext.Count, RelativeSource={RelativeSource Self}}"/>
</ItemsPanelTemplate>

WPF Grid as ItemsPanel for a list dynamically bound to an ItemsControl

I am using a Grid as ItemsPanel for a list dynamically bound to an ItemsControl. The code below is working - with a remaining problem: I can’t find a way to dynamically initialize the ColumnDefinitions and RowDefinitions of the grid. As consequence all values are placed on top of each other.
<ItemsControl ItemsSource="{Binding Cells}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Grid.Row" Value="{Binding RowIndex}"/>
<Setter Property="Grid.Column" Value="{Binding ColumnIndex}"/>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Value}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Please be aware, that I am searching an answer according the MVVM pattern. Therefore sub classing and code behind are just workarounds, but no solutions.
You'll need some way to tell the Grid how many Rows/Columns it has. Perhaps as each Item loads you could check the value of RowIndex and ColumnIndex and add Rows/Columns to the Grid if needed.
As another alternative, perhaps you can expose RowCount and ColumnCount properties in your ViewModel that return the max RowIndex and ColumnIndex, and in the Grid's Loaded event add however many Columns/Rows you need.
I find it perfectly acceptable to use code-behind in MVVM IF the code is related to the UI only.
Another idea would be to arrange your items in your code-behind into a 2D grid before returning it to the View, and then bind that Grid to a DataGrid with AutoGenerateColumns=True and the headers removed
Update
My current solution to this problem is to use a set of AttachedProperties for a Grid that allow you to bind RowCount and ColumnCount properties to a property on the ViewModel
You can find the code for my version of the attached properties on my blog here, and they can be used like this:
<ItemsPanelTemplate>
<Grid local:GridHelpers.RowCount="{Binding RowCount}"
local:GridHelpers.ColumnCount="{Binding ColumnCount}" />
</ItemsPanelTemplate>
Your grid has zero rows and columns so everything will be displayed on top of each other. Do something like below and it will work.
<ItemsControl ItemsSource="{Binding Cells}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
</Grid>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Grid.Row" Value="{Binding RowIndex}" />
<Setter Property="Grid.Column" Value="{Binding ColumnIndex}" />
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Value}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
i found this from here The ItemsControl
<ItemsControl ItemsSource="{Binding VehicleMakes}" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="3" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Width="300" Height="100" Click="ButtonOption_Click" Tag="{Binding Name}">
<StackPanel Orientation="Vertical">
<Image
Initialized="Image_Initialized"
Tag="{Binding ResourseImageName}"
Width="116"
Height="30"
Margin="0,0,0,10" >
</Image>
<TextBlock Text="{Binding Name}" VerticalAlignment="Bottom" HorizontalAlignment="Center"/>
</StackPanel>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

Using MouseDragElementBehavior with an ItemsControl and Canvas

I am currently having a problem using the MouseDragElementsBehavior from the Blend SDK when using a ItemsControl and a Custom Canvas. My custom canvas simply adds or removes the MouseDragElement from its children depending on a DependencyProperty. This worked just fine when I was manually adding Items to the Canvas' children but appears to have broken when moving to an ItemsControl.
I am currently using the following ItemsControl code:
<ItemsControl ItemsSource="{Binding Path=CanvasItems}">
<ItemsControl.DataContext>
<ViewModels:ViewModel/>
</ItemsControl.DataContext>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<my:CustomCanvas Background="Black" IsEditable="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.CanEdit}" AllowDrop="{Binding RelativeSource={RelativeSource Self}, Path=IsEditable}" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
Adding the Drag Behavior in the Canvas.VisualChildrenChanged method does not allow the newly created object to be moved like before.
Do I need to add the Drag behavior to something other then the ContentPresenter that is passed to VisualChildrenChanged or provide a special style?
I don't really know the blend sdk behaviours, but I've worked with behaviours in general, so I hope the same mechanisms apply.
If you want to add a behaviour to the controls created by an ItemsControl the best way is adding it via a setter in the ItemsControl.ItemContainerStyle, though in this case I found it easier to add it in the ItemsControl.ItemTemplate
Something like
<ItemsControl ItemsSource="{Binding CanvasItems}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Background="Transparent" AllowDrop="True" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Green" BorderThickness="1" Background="AntiqueWhite">
<i:Interaction.Behaviors>
<ei:MouseDragElementBehavior ConstrainToParentBounds="True" DragBegun="MouseDragElementBehavior_DragBegun"/>
</i:Interaction.Behaviors>
<ContentControl Content="{Binding}" />
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<ItemsControl ItemsSource="{Binding CanvasItems}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="YourControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="YourControl">
<Border>
<Grid>
<SystemWindowsInteractivity:Interaction.Behaviors>
<MicrosoftExpressionInteractivityLayout:MouseDragElementBehavior />
</SystemWindowsInteractivity:Interaction.Behaviors>
<ContentPresenter />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>

Silverlight: Set Items Widths in ItemsControl to Stretch

I've got an ItemsControl which fills from top to bottom, but I can't get it's child items to occupy the whole width of the ItemsControl:
I basically need to stretch the green bits to fill the width of the control (as shown by the blue bits).
I've tried things like setting the HorizontalAlignment property of the template item to Stretch and I've even tried binding it's Width property to the ItemsControl Width, but neither worked.
Should be a straight forward one, but it's something I just can't quite figure out...
Edit: here's the ItemTemplate (the whole thing is the ItemTemplate which itself contains an ItemsControl which is bound to a list of child objects):
<DataTemplate>
<Border CornerRadius="5" Background="#ddd">
<StackPanel>
<TextBlock Text="{Binding Name}" FontSize="18" Foreground="#bbb"/>
<ItemsControl ItemsSource="{Binding PlugIns}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<toolkit:WrapPanel HorizontalAlignment="Stretch" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Margin="10,0,10,10" Tag="{Binding}"
MouseEnter="StackPanel_MouseEnter">
<Border Child="{Binding Icon}" />
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Border>
</DataTemplate>
You need to configure the ListBoxItem HorizontalContentAlignment, you do this by using a Style object in the ListBox ItemContainerStyle as follows:-
<ListBox ....>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListBox.ItemContainerStyle>
OK, so as I continued to build the app up, I figured out how to resolve this. Essentially, when I changed the template of the ItemsControl to support scrolling, the items spanned to fill horizontally :)
<ItemsControl>
<ItemsControl.Template>
<ControlTemplate>
<ScrollViewer Padding="{TemplateBinding Padding}">
<ItemsPresenter />
</ScrollViewer>
</ControlTemplate>
</ItemsControl.Template>
...
</ItemsControl>

Resources