Skip to content

Commit

Permalink
Merge pull request #100 from ChrisPulman/UpdateListView
Browse files Browse the repository at this point in the history
Update ListView
  • Loading branch information
ChrisPulman authored Mar 21, 2024
2 parents 5deaa0d + fd54b28 commit a9ab2ce
Show file tree
Hide file tree
Showing 6 changed files with 374 additions and 97 deletions.
2 changes: 1 addition & 1 deletion Version.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json",
"version": "1.0.18",
"version": "1.0.19",
"publicReleaseRefSpec": [
"^refs/heads/master$",
"^refs/heads/main$"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@
BorderThickness="1"
CornerRadius="8"
SnapsToDevicePixels="True">
<ListView
<controls:ListView
x:Name="PART_SuggestionsList"
MaxHeight="{TemplateBinding MaxSuggestionListHeight}"
DisplayMemberPath="{TemplateBinding DisplayMemberPath}"
Expand All @@ -154,15 +154,15 @@
ItemsSource="{TemplateBinding ItemsSource}"
KeyboardNavigation.DirectionalNavigation="Cycle"
SelectionMode="Single">
<ListView.ItemsPanel>
<controls:ListView.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel
IsItemsHost="True"
IsVirtualizing="True"
VirtualizationMode="Recycling" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
</controls:ListView.ItemsPanel>
</controls:ListView>
</Border>
</Popup>
</Grid>
Expand Down
89 changes: 89 additions & 0 deletions src/CrissCross.WPF.UI/Controls/ListView/ListView.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Copyright (c) Chris Pulman. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace CrissCross.WPF.UI.Controls;

/// <summary>
/// Extends <see cref="System.Windows.Controls.ListView"/>, and adds customized support <see cref="ListViewViewState.GridView"/> or <see cref="ListViewViewState.Default"/>.
/// </summary>
/// <example>
/// <code lang="xml">
/// &lt;ui:ListView ItemsSource="{Binding ...}" &gt;
/// &lt;ui:ListView.View&gt;
/// &lt;GridView&gt;
/// &lt;GridViewColumn
/// DisplayMemberBinding="{Binding FirstName}"
/// Header="First Name" /&gt;
/// &lt;GridViewColumn
/// DisplayMemberBinding="{Binding LastName}"
/// Header="Last Name" /&gt;
/// &lt;/GridView&gt;
/// &lt;/ui:ListView.View&gt;
/// &lt;/ui:ListView&gt;
/// </code>
/// </example>
public class ListView : System.Windows.Controls.ListView
{
/// <summary>Identifies the <see cref="ViewState"/> dependency property.</summary>
public static readonly DependencyProperty ViewStateProperty = DependencyProperty.Register(nameof(ViewState), typeof(ListViewViewState), typeof(ListView), new FrameworkPropertyMetadata(ListViewViewState.Default, OnViewStateChanged));

static ListView() => DefaultStyleKeyProperty.OverrideMetadata(typeof(ListView), new FrameworkPropertyMetadata(typeof(ListView)));

/// <summary>
/// Initializes a new instance of the <see cref="ListView"/> class.
/// </summary>
public ListView() => Loaded += OnLoaded;

/// <summary>
/// Gets or sets the view state of the <see cref="ListView"/>, enabling custom logic based on the current view.
/// </summary>
/// <value>The current view state of the <see cref="ListView"/>.</value>
public ListViewViewState ViewState
{
get => (ListViewViewState)GetValue(ViewStateProperty);
set => SetValue(ViewStateProperty, value);
}

/// <summary>
/// Raises the <see cref="E:ViewStateChanged" /> event.
/// </summary>
/// <param name="e">The <see cref="DependencyPropertyChangedEventArgs"/> instance containing the event data.</param>
protected virtual void OnViewStateChanged(DependencyPropertyChangedEventArgs e)
{
// Hook for derived classes to react to ViewState property changes
}

private static void OnViewStateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is not ListView self)
{
return;
}

self.OnViewStateChanged(e);
}

private void OnLoaded(object sender, RoutedEventArgs e)
{
Loaded -= OnLoaded; // prevent memory leaks

// Setup initial ViewState and hook into View property changes
var descriptor = DependencyPropertyDescriptor.FromProperty(System.Windows.Controls.ListView.ViewProperty, typeof(System.Windows.Controls.ListView));
descriptor?.AddValueChanged(this, OnViewPropertyChanged);
UpdateViewState(); // set the initial state
}

private void OnViewPropertyChanged(object? sender, EventArgs e) => UpdateViewState();

private void UpdateViewState()
{
var viewState = View switch
{
System.Windows.Controls.GridView => ListViewViewState.GridView,
null => ListViewViewState.Default,
_ => ListViewViewState.Default
};

SetCurrentValue(ViewStateProperty, viewState);
}
}
209 changes: 162 additions & 47 deletions src/CrissCross.WPF.UI/Controls/ListView/ListView.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,63 +10,178 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:CrissCross.WPF.UI.Controls">

<Style x:Key="DefaultListViewStyle" TargetType="{x:Type ListView}">
<ControlTemplate x:Key="NullViewTemplate" TargetType="{x:Type controls:ListView}">
<Grid>
<controls:PassiveScrollViewer
x:Name="PART_ContentHost"
CanContentScroll="{TemplateBinding ScrollViewer.CanContentScroll}"
HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}"
VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}">
<ItemsPresenter />
</controls:PassiveScrollViewer>
<Rectangle
x:Name="PART_DisabledVisual"
Opacity="0"
RadiusX="2"
RadiusY="2"
Stretch="Fill"
Stroke="Transparent"
StrokeThickness="0"
Visibility="Collapsed">
<Rectangle.Fill>
<SolidColorBrush Color="{DynamicResource ControlFillColorDefault}" />
</Rectangle.Fill>
</Rectangle>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsGrouping" Value="True">
<Setter Property="ScrollViewer.CanContentScroll" Value="False" />
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="PART_DisabledVisual" Property="Visibility" Value="Visible" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>

<ControlTemplate x:Key="GridViewScrollViewerTemplate" TargetType="ScrollViewer">
<Grid Background="Transparent">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<DockPanel Margin="{TemplateBinding Control.Padding}">
<ScrollViewer
DockPanel.Dock="Top"
Focusable="False"
HorizontalScrollBarVisibility="Hidden"
VerticalScrollBarVisibility="Hidden">
<GridViewHeaderRowPresenter
Margin="0"
AllowsColumnReorder="{Binding Path=View.AllowsColumnReorder, RelativeSource={RelativeSource AncestorType=ListView}}"
ColumnHeaderContainerStyle="{Binding Path=View.ColumnHeaderContainerStyle, RelativeSource={RelativeSource AncestorType=ListView}}"
ColumnHeaderContextMenu="{Binding Path=View.ColumnHeaderContextMenu, RelativeSource={RelativeSource AncestorType=ListView}}"
ColumnHeaderTemplate="{Binding Path=View.ColumnHeaderTemplate, RelativeSource={RelativeSource AncestorType=ListView}}"
ColumnHeaderToolTip="{Binding Path=View.ColumnHeaderToolTip, RelativeSource={RelativeSource AncestorType=ListView}}"
Columns="{Binding Path=View.Columns, RelativeSource={RelativeSource AncestorType=ListView}}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
</ScrollViewer>
<ScrollContentPresenter
Name="PART_ScrollContentPresenter"
CanContentScroll="{TemplateBinding ScrollViewer.CanContentScroll}"
CanHorizontallyScroll="False"
CanVerticallyScroll="False"
Content="{TemplateBinding ContentControl.Content}"
ContentTemplate="{TemplateBinding ContentControl.ContentTemplate}"
KeyboardNavigation.DirectionalNavigation="Local"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
</DockPanel>
<ScrollBar
Name="PART_HorizontalScrollBar"
Grid.Row="1"
Cursor="Arrow"
Maximum="{TemplateBinding ScrollViewer.ScrollableWidth}"
Minimum="0"
Orientation="Horizontal"
Visibility="{TemplateBinding ScrollViewer.ComputedHorizontalScrollBarVisibility}"
Value="{Binding Path=HorizontalOffset, RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay}" />
<ScrollBar
Name="PART_VerticalScrollBar"
Grid.Column="1"
Cursor="Arrow"
Maximum="{TemplateBinding ScrollViewer.ScrollableHeight}"
Minimum="0"
Orientation="Vertical"
Visibility="{TemplateBinding ScrollViewer.ComputedVerticalScrollBarVisibility}"
Value="{Binding Path=VerticalOffset, RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay}" />
<DockPanel
Grid.Row="1"
Grid.Column="1"
LastChildFill="False">
<Rectangle
Width="1"
DockPanel.Dock="Left"
Fill="#FFFFFFFF"
Visibility="{TemplateBinding ScrollViewer.ComputedVerticalScrollBarVisibility}" />
<Rectangle
Height="1"
DockPanel.Dock="Top"
Fill="#FFFFFFFF"
Visibility="{TemplateBinding ScrollViewer.ComputedHorizontalScrollBarVisibility}" />
</DockPanel>
</Grid>
</ControlTemplate>

<!-- replaces framework styling for GridView column headers -->
<Style TargetType="GridViewColumnHeader">
<Setter Property="Background" Value="Transparent" />
<Setter Property="Padding" Value="4" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="GridViewColumnHeader">
<!-- margins and padding need some help -->
<controls:TextBlock
Margin="6,2,0,6"
Padding="4"
FontWeight="Bold"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
Text="{TemplateBinding Content}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

<ControlTemplate x:Key="GridViewTemplate" TargetType="{x:Type controls:ListView}">
<Border
Name="Bd"
Background="Transparent"
BorderBrush="{TemplateBinding Border.BorderBrush}"
BorderThickness="{TemplateBinding Border.BorderThickness}">
<controls:PassiveScrollViewer
Padding="{TemplateBinding Control.Padding}"
CanContentScroll="{TemplateBinding ScrollViewer.CanContentScroll}"
Focusable="False"
Template="{DynamicResource GridViewScrollViewerTemplate}">
<ItemsPresenter />
</controls:PassiveScrollViewer>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="Bd" Property="Background" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>

<Style x:Key="ListViewStyle" TargetType="{x:Type controls:ListView}">
<Setter Property="Margin" Value="0" />
<Setter Property="Padding" Value="0" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto" />
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto" />
<Setter Property="ScrollViewer.CanContentScroll" Value="True" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="VirtualizingPanel.IsVirtualizing" Value="True" />
<Setter Property="VirtualizingPanel.VirtualizationMode" Value="Standard" />
<Setter Property="SnapsToDevicePixels" Value="True" />
<Setter Property="OverridesDefaultStyle" Value="True" />
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<VirtualizingStackPanel IsVirtualizing="{TemplateBinding VirtualizingPanel.IsVirtualizing}" VirtualizationMode="{TemplateBinding VirtualizingPanel.VirtualizationMode}" />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListView}">
<Grid>
<controls:PassiveScrollViewer
x:Name="PART_ContentHost"
CanContentScroll="{TemplateBinding ScrollViewer.CanContentScroll}"
HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}"
VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}">
<ItemsPresenter />
</controls:PassiveScrollViewer>
<Rectangle
x:Name="PART_DisabledVisual"
Opacity="0"
RadiusX="2"
RadiusY="2"
Stretch="Fill"
Stroke="Transparent"
StrokeThickness="0"
Visibility="Collapsed">
<Rectangle.Fill>
<SolidColorBrush Color="{DynamicResource ControlFillColorDefault}" />
</Rectangle.Fill>
</Rectangle>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsGrouping" Value="True">
<Setter Property="ScrollViewer.CanContentScroll" Value="False" />
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="PART_DisabledVisual" Property="Visibility" Value="Visible" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=ViewState, RelativeSource={RelativeSource Mode=Self}}" Value="{x:Static controls:ListViewViewState.Default}">
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<VirtualizingStackPanel IsVirtualizing="{TemplateBinding VirtualizingPanel.IsVirtualizing}" VirtualizationMode="{TemplateBinding VirtualizingPanel.VirtualizationMode}" />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="Template" Value="{DynamicResource NullViewTemplate}" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=ViewState, RelativeSource={RelativeSource Mode=Self}}" Value="{x:Static controls:ListViewViewState.GridView}">
<Setter Property="Template" Value="{DynamicResource GridViewTemplate}" />
</DataTrigger>
</Style.Triggers>
</Style>

<Style BasedOn="{StaticResource DefaultListViewStyle}" TargetType="{x:Type ListView}" />
<Style BasedOn="{StaticResource ListViewStyle}" TargetType="{x:Type controls:ListView}" />

</ResourceDictionary>
Loading

0 comments on commit a9ab2ce

Please sign in to comment.