How would you design a Google images -like component?
I'm not sure how to handle the detail panel which shows under each image when selected.
The detail panel:
- shows between two rows of images.
- keep left and right items in place.
SOLUTION:
Thanks to LittleBit for providing a very nice solution to the problem. Based on his hints, i created the following general-purpose component taking care of a few details you will eventually face when making LittleBit solution production-ready.
file: DetailedList.cs
public class DetailedList : ListBox
{
#region DetailsTemplate
public DataTemplate DetailsTemplate
{
get { return (DataTemplate)GetValue( DetailsTemplateProperty ); }
set { SetValue( DetailsTemplateProperty, value ); }
}
public static readonly DependencyProperty DetailsTemplateProperty =
DependencyProperty.Register( nameof( DetailsTemplate ), typeof( DataTemplate ), typeof( DetailedList ) );
#endregion
static DetailedList()
{
Type ownerType = typeof( DetailedList );
DefaultStyleKeyProperty.OverrideMetadata( ownerType,
new FrameworkPropertyMetadata( ownerType ) );
StyleProperty.OverrideMetadata( ownerType,
new FrameworkPropertyMetadata( null, ( depObj, baseValue ) =>
{
var element = depObj as FrameworkElement;
if( element != null && baseValue == null )
baseValue = element.TryFindResource( ownerType );
return baseValue;
} ) );
}
}
file: StretchGrid.cs
internal class StretchGrid : Grid
{
private Expander _expander;
#region ParentPanel
public Panel ParentPanel
{
get { return (Panel)this.GetValue( ParentPanelProperty ); }
set { this.SetValue( ParentPanelProperty, value ); }
}
public static readonly DependencyProperty ParentPanelProperty = DependencyProperty.Register(
nameof( ParentPanel ), typeof( Panel ), typeof( StretchGrid ), new PropertyMetadata( null, ParentPanelChanged ) );
private static void ParentPanelChanged( DependencyObject d, DependencyPropertyChangedEventArgs e )
{
var stretchGrid = d as StretchGrid;
if( stretchGrid == null ) return;
if( e.NewValue is Panel panel )
panel.SizeChanged += stretchGrid.UpdateMargins;
}
#endregion
public StretchGrid()
{
this.Loaded += StretchGrid_Loaded;
}
private void StretchGrid_Loaded( object sender, RoutedEventArgs e )
{
_expander = this.FindLogicalParent<Expander>();
_expander.Expanded += UpdateMargins;
_expander.SizeChanged += UpdateMargins;
this.UpdateMargins( null, null );
}
private void UpdateMargins( object sender, RoutedEventArgs e )
{
if( ParentPanel == null ) return;
if( _expander == null ) return;
Point delta = _expander.TranslatePoint( new Point( 0d, 0d ), ParentPanel );
//Create negative Margin to allow the Grid to be rendered outside of the Boundaries (full row under the item)
this.Margin = new Thickness( -delta.X, 0, delta.X + _expander.ActualWidth - ParentPanel.ActualWidth, 0 );
}
}
file: DetailedList.xaml
<Style x:Key="{x:Type cc:DetailedList}" TargetType="{x:Type cc:DetailedList}"
BasedOn="{StaticResource {x:Type ListBox}}">
<Style.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="NoButtonExpander.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Style.Resources>
<Setter Property="Grid.IsSharedSizeScope" Value="True"/>
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled"/>
<Setter Property="SelectionMode" Value="Single"/>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<WrapPanel Width="{Binding ActualWidth, RelativeSource={RelativeSource AncestorType={x:Type cc:DetailedList}}}"
Tag="{Binding RelativeSource={RelativeSource Self}}" />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Border Name="Border" SnapsToDevicePixels="True">
<Expander Style="{StaticResource NoButtonExpander}" VerticalAlignment="Top"
IsExpanded="{Binding IsSelected, RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem}}}"
Width="{Binding ActualWidth, RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem}}}">
<Expander.Header>
<Grid>
<Grid.RowDefinitions>
<RowDefinition SharedSizeGroup="A"/>
</Grid.RowDefinitions>
<ContentPresenter Grid.Row="0"/>
</Grid>
</Expander.Header>
<cc:StretchGrid ParentWrapPanel="{Binding Path=Tag,
RelativeSource={RelativeSource AncestorType={x:Type WrapPanel}}}">
<ContentPresenter ContentTemplate="{Binding DetailsTemplate,
RelativeSource={RelativeSource AncestorType={x:Type cc:DetailedList}}}"/>
</cc:StretchGrid>
</Expander>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="Border" Property="Background"
Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
</Style>
file: NoButtonExpander.xaml
<Style x:Key="ExpanderRightHeaderStyle" TargetType="{x:Type ToggleButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Border Padding="{TemplateBinding Padding}">
<Grid Background="Transparent" SnapsToDevicePixels="False">
<ContentPresenter HorizontalAlignment="Center" Margin="0,4,0,0" Grid.Row="1" RecognizesAccessKey="True" SnapsToDevicePixels="True" VerticalAlignment="Top"/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="ExpanderUpHeaderStyle" TargetType="{x:Type ToggleButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Border Padding="{TemplateBinding Padding}">
<Grid Background="Transparent" SnapsToDevicePixels="False">
<ContentPresenter Grid.Column="1" HorizontalAlignment="Left" Margin="4,0,0,0" RecognizesAccessKey="True" SnapsToDevicePixels="True" VerticalAlignment="Center"/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="ExpanderLeftHeaderStyle" TargetType="{x:Type ToggleButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Border Padding="{TemplateBinding Padding}">
<Grid Background="Transparent" SnapsToDevicePixels="False">
<ContentPresenter HorizontalAlignment="Center" Margin="0,4,0,0" Grid.Row="1" RecognizesAccessKey="True" SnapsToDevicePixels="True" VerticalAlignment="Top"/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="ExpanderHeaderFocusVisual">
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate>
<Border>
<Rectangle Margin="0" SnapsToDevicePixels="true" Stroke="Black" StrokeThickness="1" StrokeDashArray="1 2"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="ExpanderDownHeaderStyle" TargetType="{x:Type ToggleButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Border Padding="{TemplateBinding Padding}">
<Grid Background="Transparent" SnapsToDevicePixels="False">
<ContentPresenter Grid.Column="1" HorizontalAlignment="Left" Margin="4,0,0,0" RecognizesAccessKey="True" SnapsToDevicePixels="True" VerticalAlignment="Center"/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="NoButtonExpander" TargetType="{x:Type Expander}">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Expander}">
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" CornerRadius="3" SnapsToDevicePixels="true">
<DockPanel>
<ToggleButton x:Name="HeaderSite" ContentTemplate="{TemplateBinding HeaderTemplate}" ContentTemplateSelector="{TemplateBinding HeaderTemplateSelector}" Content="{TemplateBinding Header}" DockPanel.Dock="Top" Foreground="{TemplateBinding Foreground}" FontWeight="{TemplateBinding FontWeight}" FocusVisualStyle="{StaticResource ExpanderHeaderFocusVisual}" FontStyle="{TemplateBinding FontStyle}" FontStretch="{TemplateBinding FontStretch}" FontSize="{TemplateBinding FontSize}" FontFamily="{TemplateBinding FontFamily}" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" IsChecked="{Binding IsExpanded, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" Margin="1" MinWidth="0" MinHeight="0" Padding="{TemplateBinding Padding}" Style="{StaticResource ExpanderDownHeaderStyle}" VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"/>
<ContentPresenter x:Name="ExpandSite" DockPanel.Dock="Bottom" Focusable="false" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" Visibility="Collapsed" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</DockPanel>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsExpanded" Value="true">
<Setter Property="Visibility" TargetName="ExpandSite" Value="Visible"/>
</Trigger>
<Trigger Property="ExpandDirection" Value="Right">
<Setter Property="DockPanel.Dock" TargetName="ExpandSite" Value="Right"/>
<Setter Property="DockPanel.Dock" TargetName="HeaderSite" Value="Left"/>
<Setter Property="Style" TargetName="HeaderSite" Value="{StaticResource ExpanderRightHeaderStyle}"/>
</Trigger>
<Trigger Property="ExpandDirection" Value="Up">
<Setter Property="DockPanel.Dock" TargetName="ExpandSite" Value="Top"/>
<Setter Property="DockPanel.Dock" TargetName="HeaderSite" Value="Bottom"/>
<Setter Property="Style" TargetName="HeaderSite" Value="{StaticResource ExpanderUpHeaderStyle}"/>
</Trigger>
<Trigger Property="ExpandDirection" Value="Left">
<Setter Property="DockPanel.Dock" TargetName="ExpandSite" Value="Left"/>
<Setter Property="DockPanel.Dock" TargetName="HeaderSite" Value="Right"/>
<Setter Property="Style" TargetName="HeaderSite" Value="{StaticResource ExpanderLeftHeaderStyle}"/>
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
My approach is to use a ListView with some modifications
The ItemsPanel of the ListView must have a 'wrap' function, so i used a Wrappanel to display the Image-Previews in a field.
The ItemContainter of the ListViewItem must have two states. An Image-Preview state and a Detail-View therfore an Expander should do the trick.
The Details of an Image must be displayed across the whole Column under the Image. We need something which renders outside the Boundaries of a ListViewItem (more later)
It now looks like this
The Image Details are now messed up due to the Boundaries. The Height is no problem because it just moves the next Column down until it fits in. The Problem is the Width and its Position (its not left aligned).
I created a small CustomControl, the StretchGrid which renders its Content across the whole Column and left aligned. This StretchGrid get the relative distance to the left Boundary and sets it as negative Margin.Left to render it properly. Now it looks like this (and i hope that is exactly what you are looking for).
Now the Style of the ListView
<!-- Image List with Detail -->
<Style x:Key="PicList" TargetType="{x:Type ListView}">
<!-- Only one Picture can be selected -->
<Setter Property="SelectionMode" Value="Single"/>
<!-- Enable Multi-Line with a WrapPanel Around the Items -->
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<WrapPanel Width="{Binding (ListView.ActualWidth),RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListView}}}" Tag="{Binding RelativeSource={RelativeSource Self}}" />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<!-- Override Display area of the Item -->
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="{x:Type ListViewItem}">
<!-- Define Image Item -->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<!-- Use Expander Header as Preview/Thumbnail and display Details below when expanded -->
<Expander x:Name="PicThumbnail" Style="{StaticResource NoButtonExpander}" IsExpanded="{Binding (ListViewItem.IsSelected), RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListViewItem}}}" Width="{Binding (ListViewItem.ActualWidth), RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListViewItem}}}">
<Expander.Header>
<!-- Thumbnail/Preview Section -->
<StackPanel>
<Image Source="/XAML;component/Assets/Images/Thumb.png" Height="16" Width="16" />
<Label Content="{Binding Name}"/>
</StackPanel>
</Expander.Header>
<!-- Self stretching Grid (Custom Control) -->
<cc:StretchGrid x:Name="PicDetails" Background="LightGray" ParentWrappanel="{Binding Path=Tag, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type WrapPanel}}}">
<!-- Picture Detail Section (quick & dirty designed, replace later) -->
<Image ClipToBounds="False" Source="/XAML;component/Assets/Images/Highres.png" Width="128" Height="128" HorizontalAlignment="Left" Margin="10,0" />
<Rectangle Fill="Black" Height="128" Width="5" HorizontalAlignment="Left"/>
<Rectangle Fill="Black" Height="128" Width="5" HorizontalAlignment="Right"/>
<StackPanel Margin="150,0">
<Label Content="{Binding Name}"/>
<Label Content="Description: Lorem"/>
<Label Content="Category: Ipsum"/>
<Label Content="Owner: Dolor"/>
<Label Content="Size: 5kB"/>
</StackPanel>
</cc:StretchGrid>
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
<!-- Brings selected element to front, details see edit -->
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Panel.ZIndex" Value="1" />
</Trigger>
</Style.Triggers>
</Style>
</Setter.Value>
</Setter>
</Style>
Note: Its necessary to add a Reference to the StretchGrid.
And the StretchGrid Custom Control
class StretchGrid : Grid
{
//Reference for parent expander (used to calculate Grid Margin)
private Expander m_expander;
//Property for relativesource Binding to Wrappanel in Style
public WrapPanel ParentWrappanel
{
get { return (WrapPanel)this.GetValue(Wrappanel); }
set { this.SetValue(Wrappanel, value); }
}
//DependencyProperty for RelativeSource Binding to Wrappanel in Style (Note: the Binding is set inside the Style, not here programmatically in PropertyMetaData)
public static readonly DependencyProperty Wrappanel = DependencyProperty.Register("ParentWrappanel", typeof(WrapPanel), typeof(StretchGrid), new PropertyMetadata(null));
//Constructor
public StretchGrid() : base()
{
Application.Current.MainWindow.Loaded += Init;
Application.Current.MainWindow.SizeChanged += UpdateMargins;
}
private void Init(object sender, RoutedEventArgs e)
{
m_expander = (Expander)this.Parent; //Change when xaml markup hirarchy changes
if(m_expander != null) //(or make it similar to the Wrappanel with
{ //RelativeSource Binding)
m_expander.Expanded += UpdateMargins; //Update when expander is expanded
m_expander.SizeChanged += UpdateMargins; //Update when the expander changes the Size
//Update all StretchGrids on Initialization
UpdateMargins(null, null);
}
}
//Calculate Grid Margin when an according Event is triggered
private void UpdateMargins(object sender, RoutedEventArgs e)
{
if(ParentWrappanel != null)
{
Point delta = m_expander.TranslatePoint(new Point(0d, 0d), ParentWrappanel);
//Create negative Margin to allow the Grid to be rendered outside of the Boundaries (full column under the Image)
this.Margin = new Thickness(-delta.X, 0, delta.X + m_expander.ActualWidth - ParentWrappanel.ActualWidth, 0);
//Theese Values arent calculated exavtly, just broad for example purpose
}
}
}
This is a little bit tricky to incorporate in existing Code. A working Example may help and is found HERE.
SideNote: If i got more time i would pack this stuff into a bigger Custom Control to use it like a normal UIElement (eg. Border). This would improve the re-usability and usability quite a lot.
EDIT
The 'newest' element in the ListView is also atop of the Z-Index, therefore it is 'above' the expanded StretchGrid and some Controls can not be clicked because they are 'behind' the next ListviewItem.
To fix this, add
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Panel.ZIndex" Value="1" />
</Trigger>
</Style.Triggers>
to the ListViewItem Style. Now when its visible it will place itself atop the other Controls.
This is what I have done so far:-
Images listed:
An Image Clicked - will show the Details view below it:
At the moment I am not closing other Detail Views - so below can happen for me.
However, all this needs is to propagate an event that some other item has been clicked. and close all other Detail Views (its mvvm and driven by a bool property)
======================================================
I have a Base UserControl which will load Items in a Top Down manner:
<Grid>
<Grid>
<ScrollViewer>
<ItemsControl ItemsSource="{Binding GridData, UpdateSourceTrigger=PropertyChanged}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</ScrollViewer>
</Grid>
</Grid>
Each Row (Item) of this UserControl itself uses another ItemsControl. So that Images are lined up horizontally. Also, Each Item has a Details View Section (Border with Visibility controlled by Image clicks)
<UserControl.Resources>
<ResourceDictionary>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
</ResourceDictionary>
</UserControl.Resources>
<Grid>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ItemsControl Grid.Row="0" ItemsSource="{Binding ImagesList, UpdateSourceTrigger=PropertyChanged}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Margin="5" Command="{Binding DataContext.ItemClickedCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}"
CommandParameter="{Binding}">
<Button.Style>
<Style TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Image Grid.Row="0" Source="{Binding Image, UpdateSourceTrigger=PropertyChanged}"/>
<TextBlock Name="DisplayText" Grid.Row="1" HorizontalAlignment="Center" Text="{Binding DisplayText, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Button.Style>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Border Margin="2" Grid.Row="1" Background="Black" Padding="10"
Visibility="{Binding ShowDetailsPanel, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource BooleanToVisibilityConverter}}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border Grid.Column="0" Background="White" CornerRadius="10">
<Image Margin="5" Source="{Binding SelectedImage.Image, UpdateSourceTrigger=PropertyChanged}"/>
</Border>
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Margin="20 20 0 0" FontSize="15"
Text="{Binding SelectedImage.ImageName, StringFormat={}Filename: {0}}" Foreground="White"/>
<TextBlock Grid.Row="1" Margin="20 20 0 0" FontSize="15"
Text="{Binding SelectedImage.Size, StringFormat={}Size: {0} bytes}" Foreground="White"/>
<TextBlock Grid.Row="2" Margin="20 20 0 0" FontSize="15"
Text="{Binding SelectedImage.Location, StringFormat={}Path: {0}}" Foreground="White"/>
<TextBlock Grid.Row="3" Margin="20 20 0 0" FontSize="15"
Text="{Binding SelectedImage.CreatedTime, StringFormat={}Last Modified: {0}}" Foreground="White"/>
</Grid>
</Grid>
</Border>
</Grid>
</Grid>
I have an odd issue with a WPF listview:
Background
Using WPF ListView
Using Grid View to add columns
Virtualizing
Using 4 layers of groups to group the data into expanders in a hierarchical structure
Binding to CollectionViewSource defined in window.resources
Due to indentation of the data (the data is contained in expanders) the headers are offset from the column data so I use TranslateTransform to offset the headers by a small amount
Problem
Everything displays correctly and in the perfect hierarchical structure, however when I scroll to the right, there comes a point when the data keeps scrolling but the headers stop, which results in an offset between the headers and the data columns.
I am sure this has something to do with the way I am offsetting the headers, and the length of the headers compared to the data but I cant seem to find the reason for this problem.
List View Scrolled to just before the offset point
List View Scrolled to the end
Code
nb. I have omitted some style stuff as there is a limit on characters for answers here
<!-- The main grid -->
<ListView x:Name="DataGrid"
Margin="0"
Background="{StaticResource MainBackgroundBrush}"
BorderThickness="0"
Foreground="{StaticResource LightBackgroundBrush}"
ItemsSource="{Binding Source={StaticResource DataCollectionView}}"
ScrollViewer.IsDeferredScrollingEnabled="False"
SelectionMode="Single"
VirtualizingPanel.CacheLength="1,2"
VirtualizingStackPanel.IsContainerVirtualizable="True"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.IsVirtualizingWhenGrouping="True"
VirtualizingStackPanel.ScrollUnit="Pixel"
VirtualizingStackPanel.VirtualizationMode="Recycling">
<!-- Some styles and resources exclusive to this list view -->
<ListView.Resources>
<Setter Property="IsExpanded" Value="False" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Expander}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
SnapsToDevicePixels="true">
<DockPanel>
<ToggleButton x:Name="HeaderSite"
Width="600"
MinWidth="0"
MinHeight="0"
Margin="1"
Padding="{TemplateBinding Padding}"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Background="{TemplateBinding Background}"
Content="{TemplateBinding Header}"
ContentTemplateSelector="{TemplateBinding HeaderTemplateSelector}"
DockPanel.Dock="Top"
FontFamily="{TemplateBinding FontFamily}"
FontSize="{TemplateBinding FontSize}"
FontStretch="{TemplateBinding FontStretch}"
FontStyle="{TemplateBinding FontStyle}"
FontWeight="{TemplateBinding FontWeight}"
Foreground="{TemplateBinding Foreground}"
IsChecked="{Binding IsExpanded, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
Template="{StaticResource AnimatedExpanderButton}" />
<ContentPresenter x:Name="ExpandSite"
Margin="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
DockPanel.Dock="Bottom"
Focusable="false"
Visibility="Collapsed" />
</DockPanel>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsExpanded" Value="true">
<Setter TargetName="ExpandSite" Property="Visibility" Value="Visible" />
</Trigger>
<Trigger Property="ExpandDirection" Value="Up">
<Setter TargetName="HeaderSite" Property="Background" Value="Red" />
<Setter TargetName="ExpandSite" Property="DockPanel.Dock" Value="Top" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding Name}" Value="Title">
<Setter Property="IsEnabled" Value="False" />
<Setter Property="IsExpanded" Value="True" />
</DataTrigger>
</Style.Triggers>
</Style>
<!-- Styles the column headers -->
<Style TargetType="{x:Type GridViewColumnHeader}">
<!-- Add the event handler for a right click on the header -->
<EventSetter Event="MouseRightButtonUp" Handler="GridViewColumnHeader_MouseRightButtonUp" />
<Setter Property="Background" Value="{StaticResource GradientBrush}" />
<Setter Property="ClickMode" Value="Press" />
<Setter Property="FontSize" Value="12" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="Foreground" Value="{StaticResource LightBackgroundBrush}" />
<Setter Property="Height" Value="30" />
<Setter Property="MinWidth" Value="20" />
<!-- Offset the column headers to match the column data as the expanders will have offset the data -->
<Setter Property="RenderTransform">
<Setter.Value>
<TranslateTransform X="13" />
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GridViewColumnHeader}">
<Border BorderBrush="Black" BorderThickness="1,0,0,1">
<Grid Background="{TemplateBinding Background}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<!-- The two column re sizers -->
<Thumb x:Name="PART_HeaderGripper"
Width="18"
Margin="0,0,-10,0"
HorizontalAlignment="Right"
Background="Black"
Cursor="SizeWE">
<Thumb.Template>
<ControlTemplate TargetType="{x:Type Thumb}">
<Border Padding="{TemplateBinding Padding}" Background="Transparent">
<Rectangle Width="1"
HorizontalAlignment="Center"
Fill="{TemplateBinding Background}" />
</Border>
</ControlTemplate>
</Thumb.Template>
</Thumb>
<!-- The main content of the header -->
<ContentPresenter Name="HeaderContent"
Grid.Column="0"
Margin="5"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListView.Resources>
<!-- Templates each level -->
<ListView.GroupStyle>
<!-- Style for groups at the top level. this level is equivalent to a single database -->
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Margin" Value="0" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Border Margin="1"
Background="{StaticResource FandFBrush}"
BorderBrush="Black"
BorderThickness="2"
CornerRadius="3">
<Expander Margin="0"
FontSize="12"
FontWeight="Bold"
Foreground="{StaticResource LightBackgroundBrush}"
Header="{Binding Name}"
IsExpanded="True">
<ItemsPresenter x:Name="IP"
Margin="0"
VirtualizingPanel.IsContainerVirtualizable="True"
VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.IsVirtualizingWhenGrouping="True"
VirtualizingPanel.ScrollUnit="Item"
VirtualizingPanel.VirtualizationMode="Recycling" />
</Expander>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
<!-- Style for groups at the second level. this level is equivalent to a single case -->
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Margin" Value="0" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Border Margin="0"
Background="{StaticResource MainBackgroundBrush}"
BorderBrush="Black"
BorderThickness="2"
CornerRadius="3">
<Expander Margin="0"
FontSize="12"
FontWeight="Bold"
Foreground="{StaticResource LightBackgroundBrush}"
Header="{Binding Name}"
IsExpanded="True">
<ItemsPresenter x:Name="IP"
Margin="0"
VirtualizingPanel.IsContainerVirtualizable="True"
VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.IsVirtualizingWhenGrouping="True"
VirtualizingPanel.ScrollUnit="Item"
VirtualizingPanel.VirtualizationMode="Recycling" />
</Expander>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
<!-- Style for groups at the third level. this level is equivalent to a single sample type -->
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Margin" Value="0" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Border Margin="1"
BorderBrush="Black"
BorderThickness="0"
CornerRadius="3">
<ItemsPresenter x:Name="IP"
Margin="0"
VirtualizingPanel.IsContainerVirtualizable="True"
VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.IsVirtualizingWhenGrouping="True"
VirtualizingPanel.ScrollUnit="Item"
VirtualizingPanel.VirtualizationMode="Recycling" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
<!-- Style for groups at the fourth level. this level is equivalent to a single sample -->
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Margin" Value="0" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Border Margin="0,0,0,1"
Background="Transparent"
BorderBrush="Black"
BorderThickness="2,2,2,2"
CornerRadius="3,3,0,0"
Visibility="{Binding Items[0].Fragment.Sample.IsVisible, Converter={StaticResource BooleanToVisibilityConverter}}">
<Expander Margin="0"
FontSize="12"
FontWeight="Bold"
Foreground="Black"
IsExpanded="True">
<Border Grid.Row="0"
Margin="1,0,1,1"
Background="{StaticResource MainBackgroundBrush}"
BorderBrush="Black"
BorderThickness="2,1,2,2"
CornerRadius="2">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!-- The measurements themselves -->
<Border Grid.Row="0"
Margin="0"
Background="{StaticResource MainBackgroundBrush}"
BorderBrush="Black"
BorderThickness="0,0,0,1"
CornerRadius="0">
<ItemsPresenter x:Name="IP"
Margin="0"
VirtualizingPanel.IsContainerVirtualizable="True"
VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.IsVirtualizingWhenGrouping="True"
VirtualizingPanel.ScrollUnit="Item"
VirtualizingPanel.VirtualizationMode="Recycling" />
</Border> </Grid>
</Border>
</Expander>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ListView.GroupStyle>
</ListView>
Is there a way to draw a separator line below the headers in a DataGrid? I have set GridLinesVisibility to None as I don't want any gridlines except the one below the headers. I'm struggling to find a way to do this and any help would be greatly appreciated.
This is what I want to achieve.
You can modify the DataGridColumnHeader's ControlTemplate.
I used the original DataGrid's template and replaced the default border and fill with a Rectangle with a height of 1.
<DataGrid>
<DataGrid.ColumnHeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Style.Resources>
<!-- This style is required for the column resize thumbs -->
<Style x:Key="ColumnHeaderGripperStyle" TargetType="{x:Type Thumb}">
<Setter Property="Width" Value="8" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="Cursor" Value="SizeWE" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Thumb}">
<Border Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Style.Resources>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridColumnHeader}">
<Grid Background="White">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="1"/>
</Grid.RowDefinitions>
<ContentPresenter Grid.Row="0" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
<Thumb Grid.Row="0" x:Name="PART_LeftHeaderGripper" HorizontalAlignment="Left" Style="{StaticResource ColumnHeaderGripperStyle}" />
<Thumb Grid.Row="0" x:Name="PART_RightHeaderGripper" HorizontalAlignment="Right" Style="{StaticResource ColumnHeaderGripperStyle}" />
<Rectangle Grid.Row="1" Height="1" HorizontalAlignment="Stretch" Stroke="Black"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGrid.ColumnHeaderStyle>
</DataGrid>
So I have a listview in which I set up drag and drop, but for some reason it only lets me drop inside the actual items in the listview, rather than on any portion of the controltemplate that I overrode. How do I make it so that I can also do drag and drop over the textblock containing the title for the column?
<ListView
Margin="0,4,0,0"
Grid.Column="0"
x:Name="NameListView"
HorizontalContentAlignment="Stretch"
VerticalAlignment="Stretch"
ItemsSource="{Binding Path = AddedItems}"
SelectionChanged="NameListView_SelectionChanged"
AllowDrop="True"
SelectionMode="Extended"
VirtualizingStackPanel.VirtualizationMode="Standard"
>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}" />
<Setter Property="SnapsToDevicePixels" Value="true"/>
<Setter Property="OverridesDefaultStyle" Value="true"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListViewItem">
<Border
Name="Border"
Padding="{TemplateBinding Padding}"
SnapsToDevicePixels="true"
Background="Transparent"
>
<ContentPresenter
Content="{TemplateBinding Content}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="true">
<Setter TargetName="Border"
Property="Background" Value="{x:Static SystemColors.HighlightBrush}"
/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
<EventSetter Event="PreviewMouseDoubleClick" Handler="NameListView_PreviewMouseDoubleClick"/>
</Style>
</ListView.ItemContainerStyle>
<!-- Until NET 4.0 Keygesture's cannot bind to a command, so the inputbindings must be set using static commands or with code behind-->
<ListView.Template>
<!--Template Defining the layout of this treeview-->
<ControlTemplate>
<Grid
Background="{TemplateBinding Background}"
>
<Grid.RowDefinitions>
<RowDefinition
Height="{Binding GraphHeight, Source={x:Static DaedalusGraphViewer:SettingsManager.AppSettings},
Converter={StaticResource GridLengthConverter}}"
/>
<RowDefinition Height="*"/>
<RowDefinition Height="18" />
</Grid.RowDefinitions>
<Border
Grid.ZIndex="1"
Grid.Row="0"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
>
<Grid>
<TextBlock
Foreground="{TemplateBinding Foreground}"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Text="Signal Names"
/>
</Grid>
</Border>
<Canvas>
<Line
Grid.ZIndex="2"
x:Name="SelectedItemUnderline"
Stroke="Black"
StrokeThickness="3"
Visibility="Collapsed"
/>
</Canvas>
<ScrollViewer
Grid.ZIndex="1"
x:Name="SignalNameScrollViewer"
Grid.Row="1" Grid.RowSpan="2"
CanContentScroll="False"
VerticalScrollBarVisibility="Hidden" HorizontalScrollBarVisibility="Visible"
>
<ItemsPresenter />
</ScrollViewer>
</Grid>
</ControlTemplate>
</ListView.Template>
</ListView>
I had a first chance exception being handled inside the drag operation, so when I fixed that code, it allowed me to drop anywhere in the listview. Basically, if an exception is thrown in the drag handlers, the mouse cursor will display not available to drop and fail drop
How can I create tabs (in XAML) at the bottom of a LayoutDocument in AvalonDock 2.0, like the Code and Design tabs in Visual Studio? (without using a TabControl, of course)
I'd like to end up with tabs at the top, one per document, but then within each document, be able to have multiple "views," with one tab each, at the bottom of the window.
So, you might have "page.htm" as one document, appearing on a tab at the top. Then "source" and "design" as two tabs at the bottom of the open/activated document.
You sould override the default style of the LayoutDocumentPaneControl and for this I'd suggest to take look at one of the provided theme.
Sample code (not tested):
<!--MyCustomDocumentPaneControlStyle-->
<Style x:Key="MyCustomDocumentPaneControlStyle" TargetType="{x:Type avalonDockControls:LayoutDocumentPaneControl}">
<Setter Property="TabStripPlacement" Value="Bottom"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type avalonDockControls:LayoutDocumentPaneControl}">
<Grid ClipToBounds="true" SnapsToDevicePixels="true" KeyboardNavigation.TabNavigation="Local">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!--Following border is required to catch mouse events-->
<Border Background="Transparent" Grid.RowSpan="2"/>
<Grid Panel.ZIndex="1" Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<avalonDockControls:DocumentPaneTabPanel x:Name="HeaderPanel" Grid.Column="0" IsItemsHost="true" Margin="2,2,2,0" Grid.Row="0" KeyboardNavigation.TabIndex="1"/>
<avalonDockControls:DropDownButton
x:Name="MenuDropDownButton"
Style="{StaticResource {x:Static ToolBar.ToggleButtonStyleKey}}"
Focusable="False"
Grid.Column="1">
<avalonDockControls:DropDownButton.DropDownContextMenu>
<avalonDockControls:ContextMenuEx
ItemsSource="{Binding Model.ChildrenSorted, RelativeSource={RelativeSource TemplatedParent}}">
<avalonDockControls:ContextMenuEx.ItemContainerStyle>
<Style TargetType="{x:Type avalonDockControls:MenuItemEx}" BasedOn="{StaticResource {x:Type MenuItem}}">
<Setter Property="HeaderTemplate" Value="{Binding Path=Root.Manager.DocumentPaneMenuItemHeaderTemplate}"/>
<Setter Property="HeaderTemplateSelector" Value="{Binding Path=Root.Manager.DocumentPaneMenuItemHeaderTemplateSelector}"/>
<Setter Property="IconTemplate" Value="{Binding Path=Root.Manager.IconContentTemplate}"/>
<Setter Property="IconTemplateSelector" Value="{Binding Path=Root.Manager.IconContentTemplateSelector}"/>
<Setter Property="Command" Value="{Binding Path=., Converter={StaticResource ActivateCommandLayoutItemFromLayoutModelConverter}}"/>
</Style>
</avalonDockControls:ContextMenuEx.ItemContainerStyle>
</avalonDockControls:ContextMenuEx>
</avalonDockControls:DropDownButton.DropDownContextMenu>
<Image Source="/AvalonDock;component/Images/PinDocMenu.png"/>
</avalonDockControls:DropDownButton>
</Grid>
<Border x:Name="ContentPanel"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
Grid.Column="0"
KeyboardNavigation.DirectionalNavigation="Contained"
Grid.Row="0"
KeyboardNavigation.TabIndex="2"
KeyboardNavigation.TabNavigation="Cycle">
<ContentPresenter x:Name="PART_SelectedContentHost"
ContentSource="SelectedContent"
Margin="{TemplateBinding Padding}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</Border>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=Self}, Path=Model.ChildrenCount}" Value="0">
<Setter Property="Visibility" Value="Collapsed" TargetName="MenuDropDownButton" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Visibility" Value="{Binding IsVisible, Converter={StaticResource BoolToVisibilityConverter}}"/>
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
<Setter Property="ToolTip" Value="{Binding ToolTip}"/>
</Style>
</Setter.Value>
</Setter>
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<avalonDockControls:LayoutDocumentTabItem Model="{Binding}"/>
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<avalonDockControls:LayoutDocumentControl Model="{Binding}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
As you have the new style just plug it using the DocumentPaneControlStyle property of the DockingManager:
<ad:DockingManager DocumentPaneControlStyle="{StaticResource MyCustomDocumentPaneControlStyle}">
...
</ad:DockingManager>
Ado