ListView: define ItemsPanelTemplate in resource dictionary - wpf

I have a ListView which layout looks like a Windows Explorer view (icon + some details), bound to a list somewhere in the ViewModel.
My aim here is to be able to switch between explorer view or classic view whenever we want.
I could define an ItemsPanelTemplate doing exactly the work to display correctly the layout, directly in the ListView.ItemsPanel field. Now, I'd like to define it in the resources so that I'll be able to use it in different views, and especially in one control, the user should have the choice between Explorer view or classic list view (the default rendering for a list)
How'd you do that? I cannot define any ItemsPanelTemplate in my ResourceDictionary, and if I define a DataTemplate it is not compatible (while I thought that following pure logic, ItemsPanelTemplate should inherit from DataTemplate, but it actually doesn't look like so).
Code snippet for the actual list:
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel
Width="{Binding (FrameworkElement.ActualWidth),
RelativeSource={RelativeSource
AncestorType=ScrollContentPresenter}}"
ItemWidth="{Binding (ListView.View).ItemWidth,
RelativeSource={RelativeSource AncestorType=ListView}}"
ItemHeight="{Binding (ListView.View).ItemHeight,
RelativeSource={RelativeSource AncestorType=ListView}}"
/>
<!--
MinWidth="{Binding ItemWidth, RelativeSource={RelativeSource Self}}"
-->
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel
Orientation="Horizontal"
Height="Auto"
Width="150" >
<Image
Source="{Binding Path=Appli.AppType, Converter={StaticResource TypeToIconConverter}}"
Margin="5"
Height="50"
Width="50" />
<StackPanel
VerticalAlignment="Center"
Width="90" >
<TextBlock
Text="{Binding Path=Appli.AppName}"
FontSize="13"
HorizontalAlignment="Left"
TextWrapping="WrapWithOverflow"
Margin="0,0,0,1" />
<TextBlock
Text="{Binding Path=Appli.AppType}"
FontSize="9"
HorizontalAlignment="Left"
Margin="0,0,0,1" />
</StackPanel>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
Keeping the ItemTemplate in a static resource was easy to do, but now I can't do anything with the ItemsPanelTemplate...
Any ideas? I'm using MVVM so I'm trying ideally not to use code-behind if possible

You would use a style for the whole ListView for that. So you would do:
<Grid.Resources>
<Style x:Key="ListViewStyle" TargetType="ListView">
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<WrapPanel
Width="{Binding (FrameworkElement.ActualWidth), RelativeSource={RelativeSource AncestorType=ScrollContentPresenter}}"
ItemWidth="{Binding (ListView.View).ItemWidth, RelativeSource={RelativeSource AncestorType=ListView}}"
ItemHeight="{Binding (ListView.View).ItemHeight, RelativeSource={RelativeSource AncestorType=ListView}}" />
<!--
MinWidth="{Binding ItemWidth, RelativeSource={RelativeSource Self}}"
-->
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style>
</Grid.Resources>
<ListView
SelectionMode="Single"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Bottom"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ItemsSource="{Binding ListUserApps, UpdateSourceTrigger=PropertyChanged}"
SelectedIndex="{Binding SelectedUserApp, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Background="White"
Style="{StaticResource ListViewStyle}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel
Orientation="Horizontal"
Height="Auto"
Width="150">
<Image
Source="{Binding Path=Appli.AppType, Converter={StaticResource TypeToIconConverter}}"
Margin="5"
Height="50"
Width="50"/>
<StackPanel
VerticalAlignment="Center"
Width="90">
<TextBlock
Text="{Binding Path=Appli.AppName}"
FontSize="13"
HorizontalAlignment="Left"
TextWrapping="WrapWithOverflow"
Margin="0,0,0,1" />
<TextBlock
Text="{Binding Path=Appli.AppType}"
FontSize="9"
HorizontalAlignment="Left"
Margin="0,0,0,1" />
</StackPanel>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
If you want the user then be able to switch between explorer and classic view, just define a second Style and switch the style of the listview. This can be done for example with some VisualStates and a 'DataStateBehavior'.
Alternatively you could create a style with some DataTriggers and Setters for the individual ItemsPanels.

Related

Nested Scrollbar only part visible

I am unable to work out how to provide space for a nested ScrollViewer control.
I have a Border with nested ScrollViewer which contains ListView templated to render multiple DevExpress GridControl controls.
I've tried adding padding, margins etc nothing seems to work.
This Grid is there to allow me to add multiple StackPanel as they are made visible at different points. I've trimmed out the code for conciseness.
<Border BorderThickness="4" Grid.Column="0" Grid.Row="2">
<ScrollViewer VerticalScrollBarVisibility="Auto" Width="450" MinHeight="200" MaxHeight="600">
<Grid>
<StackPanel HorizontalAlignment="Center">
<ListView ItemsSource="{Binding FilterItemLists}" ScrollViewer.HorizontalScrollBarVisibility="Disabled" view:MarginSetter.Margin="5">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal"></WrapPanel>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical" Width="200">
<dxlc:LayoutItem VerticalAlignment="Stretch" Padding="4">
<Label Content="{Binding Title}"/>
</dxlc:LayoutItem>
<dxlc:LayoutItem VerticalAlignment="Stretch" Padding="4" Height="200" >
<dxg:GridControl ItemsSource="{Binding Path=Items, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
AutoGenerateColumns="None" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<dxg:GridControl.Columns>
<dxg:GridColumn FieldName="IsSelected" Width="20" FixedWidth="True" Fixed="Left">
<dxg:GridColumn.CellTemplate>
<DataTemplate>
<dxe:CheckEdit MaxWidth="20" IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent},
Path=DataContext.RowData.Row.IsSelected, Mode=TwoWay}"
Command="{Binding RelativeSource={RelativeSource AncestorType={x:Type dxg:GridControl}}, Path=DataContext.FilterItemSelectedCommand}"
CommandParameter="{Binding Path=RowData.Row}"
HorizontalAlignment="Center" VerticalAlignment="Center">
</dxe:CheckEdit>
</DataTemplate>
</dxg:GridColumn.CellTemplate>
</dxg:GridColumn>
<dxg:GridColumn FieldName="Name" BestFitMode="AllRows"/>
</dxg:GridControl.Columns>
<dxg:GridControl.View>
<dxg:TableView AllowEditing="False" AutoWidth="True" ShowBandsInCustomizationForm="False" ShowBandsPanel="False"
ShowColumnHeaders="False" ShowGroupPanel="False" ShowIndicator="False" ShowSearchPanelCloseButton="False"
ShowVerticalLines="False" ShowHorizontalLines="False"/>
</dxg:GridControl.View>
</dxg:GridControl>
</dxlc:LayoutItem>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</Grid>
</ScrollViewer>
</Border>

ScrollViewer content is scrolling over other parts of window

I have the following as part of my XAML:
<DockPanel>
<ToolBar Name="toolbar1" DockPanel.Dock="Top" Height="41" Background="#FFA5D95A">
//other controls
</ToolBar>
<ScrollViewer>
<ListBox Name="listBox1" ItemsSource="{Binding ElementName=This, Path=Items}"
ItemTemplateSelector="{StaticResource entryItemTemplateSelector}">
<ListBox.Template>
<ControlTemplate>
<WrapPanel IsItemsHost="True" />
</ControlTemplate>
</ListBox.Template>
</ListBox>
</ScrollViewer>
</DockPanel>
I want the scrool bar to move the items in the ListBox just to the top of the WrapPanel, and then to stop showing them when they get to the top; in stead, these items scroll into the ToolBar at the top.
What am I doing wrong?
Note: Haing been asked, here are the DataTemplates I currently have; EntryItemHost is a type derived from WindowsFormsHost.
<DataTemplate x:Key="folderTemplate">
<my:EntryItemHost Item="{Binding}"
Background="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=my:MainWindow, AncestorLevel=1}, Path=Background}"
DoubleClick="FolderDoubleClick" />
</DataTemplate>
<DataTemplate x:Key="imageTemplate">
<my:EntryItemHost Item="{Binding}"
Background="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=my:MainWindow, AncestorLevel=1}, Path=Background}" />
</DataTemplate>
Instead of setting ListBox Template set the ItemsPanel and remove the ScrollViewer
sample
<DockPanel>
<ToolBar Name="toolbar1"
DockPanel.Dock="Top"
Height="41"
Background="#FFA5D95A">
//other controls
</ToolBar>
<ListBox Name="listBox1" ItemsSource="{Binding ElementName=This, Path=Items}"
ItemTemplateSelector="{StaticResource entryItemTemplateSelector}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Width="{Binding ActualWidth, RelativeSource={RelativeSource FindAncestor, AncestorType=ContentPresenter}}" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
</DockPanel>
notice that I have replaced the template with ItemsPanel and restricted the width to the listbox
I made this based on the assumptions, let me know is this not what you are looking for

Button in ListBox item's ItemsControl doesn't fire in WPF XAML

I've got the following setup for my ListBox in wpf:
ListBox with as ItemsPanel a StackPanel (orientation horizontal)
ItemTemplate of ListBox is a Grid with a TextBox and an ItemsControl
the Items control contains buttons with as ItemPanel a WrapPanel.
Somehow when I click on one of the buttons in the ItemsPanel, the ICommand wont fire in the ViewModel class which is bound to the View.xaml.
It looks like I'm selecting the ListBox item and not the item within the ItemsControl.
Here's the xaml code
<ListBox
Background="Transparent"
ItemTemplate="{StaticResource ProductGroupTemplate}"
FocusVisualStyle="{x:Null}"
VerticalContentAlignment="Top"
ItemsSource="{Binding NodeHeaders}"
Grid.Row="1"
Grid.ColumnSpan="3"
Grid.Column="0"
Grid.RowSpan="2"
SelectedItem="{Binding CurrentItem}"
BorderBrush="Transparent"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.VerticalScrollBarVisibility="Disabled"
Margin="30,0,55,0"
VerticalAlignment="Top">
<ListBox.Style>
<Style TargetType="ListBox">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding HasParent}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.Style>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" IsItemsHost="True"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
And the DataTemplate
<DataTemplate x:Key="ProductGroupTemplate">
<Grid Margin="0,0,20,0">
<Grid.RowDefinitions>
<RowDefinition Height="40"/>
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Description}" FontSize="20" Grid.Row="0" Grid.Column="0" Foreground="{StaticResource DefaultLabelColor}" />
<ItemsControl ItemsSource="{Binding Nodes}" Grid.Row="1" Grid.Column="0">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Vertical" IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Text="{Binding Description}" Height="75" Width="150" Padding="5" Margin="5" Background="{StaticResource SalesItemsBackground}"
Foreground="{StaticResource SalesItemsForeground}" HorizontalAlignment="Center" VerticalAlignment="Center" TextAlignment="Left" Command="{Binding RelativeSource=
{RelativeSource FindAncestor,
AncestorType={x:Type ContentControl}},
Path=DataContext.Select}" CommandParameter="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</DataTemplate>
If you have a view model that declares an ICommand instance and is set as the DataContext of a view (UserControl, or Window), then you can use that property to access your ICommand instance. From the Button in your DataTemplate, you can use this RelativeSource Binding:
<Button Text="{Binding Description}" Height="75" Width="150" Padding="5" Margin="5"
Background="{StaticResource SalesItemsBackground}" TextAlignment="Left"
Foreground="{StaticResource SalesItemsForeground}" HorizontalAlignment="Center"
VerticalAlignment="Center" Command="{Binding DataContext.Select, RelativeSource={
RelativeSource AncestorType={x:Type YourViewsPrefix:YourView}}}"
CommandParameter="{Binding}"/>
That's because your data template's DataContext is set to an item from the listbox. If you have List<Person> bound to your list box every list box item's DataContext would be a Person which is why you can write something like "{Binding Description}". You have two ways to execute a command from DataTemplate one of them is the one that "Sheridan" told you. As an alternative you could set your element's DataContext to your parent View or ViewModel.
<DataTemplate>
<Grid DataContext="{Binding RelativeSource={RelativeSource AncestorType={x:Type View/ViewModel}}">
</Grid>
</DataTemplate>

Wpf Button Width in GroupBox Header

I have the following code:
<Window.Resources>
<DataTemplate x:Key="ParameterItemTemplate">
<my:ParameterItem ParamValue="{Binding Value}" Description="{Binding Name}"/>
</DataTemplate>
</Window.Resources>
<Grid Width="Auto">
<GroupBox BorderBrush="Black"
BorderThickness="2"
Width="Auto"
HorizontalAlignment="Left"
VerticalAlignment="Top">
<GroupBox.HeaderTemplate>
<DataTemplate>
<Button Content="Header"
Width="{Binding RelativeSource={RelativeSource Self}, Path=Width}"
Height="30">
</Button>
</DataTemplate>
</GroupBox.HeaderTemplate>
<ScrollViewer HorizontalScrollBarVisibility="Auto">
<ItemsControl x:Name="Test"
ItemsSource="{Binding Items}"
ItemTemplate="{StaticResource ParameterItemTemplate}" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Vertical" Height="228"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</ScrollViewer>
</GroupBox>
</Grid>
When the binded items fill my ItemsControl, the Button placed in the Header of the GroupBox does not change it's width. Do I have a binding problem?
The width of the button fits only it's content.
What do you expect if you bind the width with itself? Try
<Button Content="Header"
Width="{Binding RelativeSource={RelativeSource AncestorType={x:Type GroupBox}}, Path=ActualWidth}"
Height="30">

Styling ListView.GroupStyle with a WrapPanel

Okay, here is my pretty simple problem.
I have a ListView that I styled so as to make it look like the Windows Explorer.
Now, I'd like to group the items inside. Therefore, I defined a GroupStyle with an Expander to group it. The grouping is now fine.
What I don't like is that now, my ListView displays each group on a separate line, while I'd like to have some expander-wrapping in order to display many groups on the same line.
An image is better than some text I guess.
Here is what I have:
Here is what I want:
I cannot find which property should I have to style in order to make the GroupItems fit in a WrapPanel, just like I did for the items.
Here is my ListView style:
<ResourceDictionary>
<!-- Explorer-style layout-->
<DataTemplate x:Key="ExplorerView">
<StackPanel Orientation="Horizontal" Height="Auto" Width="150">
<Image Source="{Binding Path=Value.AppConfig.Appli.AppType, Converter={StaticResource TypeToIconConverter}}" Margin="5"
Height="50" Width="50"/>
<StackPanel VerticalAlignment="Center" Width="90">
<TextBlock Text="{Binding Path=Value.AppConfig.Appli.AppName}"
FontSize="13" HorizontalAlignment="Left" TextWrapping="WrapWithOverflow"
Margin="0,0,0,1" />
<TextBlock Text="{Binding Path=Value.AppConfig.Appli.AppType}" FontSize="9"
HorizontalAlignment="Left" Margin="0,0,0,1" />
</StackPanel>
</StackPanel>
</DataTemplate>
<!-- Group header style-->
<Style x:Key="GroupHeaderStyle" TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander x:Name="exp" IsExpanded="True" Width="310"
BorderBrush="CornflowerBlue">
<Expander.Header>
<DockPanel HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
Background="CornflowerBlue" x:Name="expContent"
Width="{Binding RelativeSource={RelativeSource
Mode=FindAncestor, AncestorType={x:Type Expander}},
Path=Width}"
Height="{Binding RelativeSource={RelativeSource
Mode=FindAncestor, AncestorType={x:Type ToggleButton}},
Path=ActualHeight}">
<CheckBox IsChecked="False" DockPanel.Dock="Right"/>
<TextBlock Text="{Binding Path=Name}" Foreground="White"
FontWeight="Bold" HorizontalAlignment="Center" />
</DockPanel>
</Expander.Header>
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
<!-- (...) -->
<ListView ItemsSource="{Binding GroupedConfig, Mode=TwoWay}"
ItemTemplate="{StaticResource ExplorerView}">
<ListView.ItemsPanel>
<ItemsPanelTemplate >
<WrapPanel Width="{Binding (FrameworkElement.ActualWidth),
RelativeSource={RelativeSource
AncestorType=Expander}}"
ItemWidth="{Binding (ListView.View).ItemWidth,
RelativeSource={RelativeSource AncestorType=ListView}}"
ItemHeight="{Binding (ListView.View).ItemHeight,
RelativeSource={RelativeSource AncestorType=ListView}}" />
<!--MinWidth="{Binding ItemWidth,
RelativeSource={RelativeSource Self}}"-->
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.GroupStyle>
<GroupStyle ContainerStyle="{StaticResource GroupHeaderStyle}" />
</ListView.GroupStyle>
</ListView>
Any ideas?
I'm trying to insert some appropriate Setter in the style defined for GroupItem, but I'm starting to think that this is not the right way to do.
Thanks!
I finally found the right property to edit after many tries.
I guess it could be useful to post it here if anybody would need to do something with the same behavior:
So we actually have a property Panel in the GroupStyle in which we can add this so needed WrapPanel :
<ListView.GroupStyle>
<GroupStyle ContainerStyle="{StaticResource GroupHeaderStyle}">
<GroupStyle.Panel>
<ItemsPanelTemplate>
<WrapPanel Width="800" />
</ItemsPanelTemplate>
</GroupStyle.Panel>
</GroupStyle>
</ListView.GroupStyle>
In case anyone is here like I am trying to make a the ListBox Items Wrap but based on an unknown amount of items so you cannot set a Width like the above answer, this is how I did it.
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Vertical" MaxHeight="{Binding Converter={StaticResource ListBoxHeightToItemsPanelHeightConverter}, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBox}}, Path=ActualHeight}"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
In my converter I simply subtract 30 to account for the height of the header.
Here is the complete code:
<ListBox.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock Margin="8" FontSize="18" TextAlignment="Center" FontWeight="Bold" Foreground="White" >
<TextBlock.Text>
<Binding Path="Name"/>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</GroupStyle.HeaderTemplate>
<GroupStyle.Panel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</GroupStyle.Panel>
</GroupStyle>
</ListBox.GroupStyle>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Vertical" MaxHeight="{Binding Converter={StaticResource ListBoxHeightToGroupStyleHeightConverter}, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBox}}, Path=ActualHeight}"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.Template>
<ControlTemplate>
<!-- Your template here. -->
</ControlTemplate>
</ListBox.Template>
<ListBox.ItemTemplate>
<DataTemplate >
<!-- Your template here. -->
</DataTemplate>
</ListBox.ItemTemplate>
Hope this helps save someone some time!

Resources