Search code examples
wpfxamlstylesmaterial-design-in-xaml

MaterialDesginInXamlToolkit - Vertically align ListViewItem content


Using the default styling for everything, I can't get the heart icon to be vertically centered in this listview. To reproduce the problem just create a super simple brand new WPF app and add MaterialDesginInXamlToolkit ResourceDictionarys to App.xaml, then add the following code:

MainWindow.xaml

<Window
    x:Class="WpfApp1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:local="clr-namespace:WpfApp1"
    xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Title="MainWindow"
    Width="800"
    Height="450"
    Background="{DynamicResource MaterialDesignPaper}"
    FontFamily="{DynamicResource MaterialDesignFont}"
    TextElement.FontSize="13"
    TextElement.FontWeight="Regular"
    TextElement.Foreground="{DynamicResource MaterialDesignBody}"
    TextOptions.TextFormattingMode="Ideal"
    TextOptions.TextRenderingMode="Auto"
    mc:Ignorable="d">
    <Window.Resources>
        <local:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
    </Window.Resources>
    <ListView
        x:Name="listview"
        VerticalContentAlignment="Center"
        VirtualizingPanel.IsVirtualizing="True"
        VirtualizingPanel.VirtualizationMode="Recycling">
        <ListView.View>
            <GridView>
                <GridViewColumn Width="Auto">
                    <GridViewColumn.CellTemplate>
                        <DataTemplate>
                            <Path
                                Width="{Binding Height, Mode=OneWay, RelativeSource={RelativeSource Self}}"
                                Height="12"
                                VerticalAlignment="Center"
                                Fill="Red"
                                Stretch="Uniform"
                                Stroke="White"
                                StrokeThickness="1"
                                Visibility="{Binding IsLoved, ConverterParameter=True, Converter={StaticResource BooleanToVisibilityConverter}, Mode=OneWay}">
                                <Path.Data>
                                    <PathGeometry Figures="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z" />
                                </Path.Data>
                            </Path>
                        </DataTemplate>
                    </GridViewColumn.CellTemplate>
                </GridViewColumn>
                <GridViewColumn Width="330" Header="Title">
                    <GridViewColumn.CellTemplate>
                        <DataTemplate>
                            <TextBlock Text="{Binding Title}" TextTrimming="CharacterEllipsis" />
                        </DataTemplate>
                    </GridViewColumn.CellTemplate>
                </GridViewColumn>
            </GridView>
        </ListView.View>
    </ListView>
</Window>

MainWindow.xaml.cs

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Windows;
using System.Windows.Data;

namespace WpfApp1
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            this.InitializeComponent();

            var items = new List<Item>();
            items.Add(new Item { IsLoved = true, Title = "This is a test" });
            items.Add(new Item { IsLoved = false, Title = "This is a test" });
            items.Add(new Item { IsLoved = true, Title = "This is a test" });
            items.Add(new Item { IsLoved = false, Title = "This is a test" });
            items.Add(new Item { IsLoved = true, Title = "This is a test" });

            this.listview.ItemsSource = items;
        }
    }
    public class Item
    {
        public bool IsLoved { get; set; }
        public string Title { get; set; }
    }
    public class BooleanToVisibilityConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            bool isVisible;

            try
            {
                isVisible = System.Convert.ToBoolean(value);
            }
            catch
            {
                return DependencyProperty.UnsetValue;
            }

            var falseVisibility = parameter == null
                ? Visibility.Collapsed
                : Visibility.Hidden;

            return isVisible
                ? Visibility.Visible
                : falseVisibility;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return DependencyProperty.UnsetValue;
        }
    }
}

Solution

  • Material Design's default ListViewItem style leaves ListViewItem.VerticalContentAlignment unchanged from its default value of Top, instead of using the value set by the parent ListView. We can fix that like so:

    <ListView.ItemContainerStyle>
        <Style TargetType="ListViewItem" BasedOn="{StaticResource MaterialDesignGridViewItem}">
            <Setter 
                Property="VerticalContentAlignment" 
                Value="{Binding VerticalContentAlignment, RelativeSource={RelativeSource AncestorType=ListView}}" 
                />
        </Style>
    </ListView.ItemContainerStyle>
    

    Make it Stretch instead of Center if you have items that need to fill the row vertically (e.g. borders, colored backgrounds). In that case, you'll have to explicitly set VerticalAlignment="Center" in all the templates, as you're now doing with the Path.

    MaterialDesignGridViewItem is a style defined here.

    On line 155 we find the VerticalContentAlignment of the GridViewRowPresenter which presents the row content. It has a TemplateBinding which uses the VerticalContentAlignment defined for the ListViewItem. That Style has no Setter for that property, so it will just use the default value of Control.VerticalContentAlignment, which is VerticalAlignment.Top. default(VerticalAlignment) happens to be Top.

        <GridViewRowPresenter VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />