添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

Originally, I had planned that the next post was going to talk about making use of the Shadow property and creating a custom shadow. However, following part 1 there was a bit of dialog online talking about the use of shadows in lists. I figured that it shouldn’t be too hard to extend my example to use the ThemeShadow for items in a list.

Let’s start with a simple list of 1000 items which we’ll be displaying using a ListView – I’m sure there’s a better way to generate random list items but I was just hacking around, so didn’t feel like engineering anything more fancy.

public IList<string> StaticItems { get; } = BuildStaticItems();
private static List<string> BuildStaticItems()
    var list = new List<string>();
    const int max = 1000;
    for (int i = 0; i < max; i++)
        list.Add($"{max} items - {i}");
    return list;
    <Grid x:Name="ParentBackgroundGrid" />
    <ListView ItemContainerStyle="{StaticResource ThousandItemContainerStyle}"
                ItemsSource="{x:Bind StaticItems}">
        <ListView.ItemTemplate>
            <DataTemplate x:DataType="x:String">
                <TextBlock Text="{x:Bind }"
                            Margin="6" />
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>
</Grid>

There are a couple of things to note here:

  • The ItemsSource is bound to the StaticItems property from the previous code block
  • The ItemTemplate is just a simple TextBlock with a Margin set to indent the text from the edge of the item template.
  • There is a ParentBackgroundGrid priors to the ListView which will render beneath the ListView and will be the surface where the shadows are rendered onto.
  • Lastly this snippet defines an ItemContainerStyle called ThousanItemContainerStyle which we’ll go into more detail in a sec.

Note that in this XAML we haven’t defined either an instance of the ThemeShadow and we haven’t set a background on the items in the list. These will both be defined as part of the ItemContainerStyle, so that we can take advantage of the visual states to adjust the elevation (i.e. the Z axis translate), and thus the shadow cast.

Ok, so here’s the ItemContainerStyle.

<Style x:Key="ThousandItemContainerStyle"
        TargetType="ListViewItem">
    <Setter Property="Padding"
            Value="6" />
    <Setter Property="HorizontalContentAlignment"
            Value="Stretch" />
    <Setter Property="VerticalContentAlignment"
            Value="Stretch" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="ListViewItem">
                <Grid x:Name="Root">
                    <VisualStateManager.VisualStateGroups>
                        <VisualStateGroup x:Name="CommonStates">
                            <VisualState x:Name="Pressed">
                                <VisualState.Setters>
                                    <Setter Target="ContentBackground.Elevation"
                                            Value="0" />
                                </VisualState.Setters>
                            </VisualState>
                            <VisualState x:Name="PressedSelected">
                                <VisualState.Setters>
                                    <Setter Target="ContentBackground.Elevation"
                                            Value="0" />
                                </VisualState.Setters>
                            </VisualState>
                        </VisualStateGroup>
                    </VisualStateManager.VisualStateGroups>
                    <local:ItemBackground Elevation="32"
                                            x:Name="ContentBackground"
                                            ContentPadding="{TemplateBinding Padding}" />
                    <ContentPresenter Margin="{TemplateBinding Padding}" />
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Things to note:

  • I’ve removed a significant portion of the default ItemsContainerStyle you would normally get when you clone the built-in style. What I’m left with is a basic ContentPresenter element and a new UserControl of type ItemBackground.
  • ItemBackground has an initial Elevation of 32 but this property is adjusted when the user presses the button to control the elevation (see the Pressed and PressedSelected visual states), and thus the amount of shadow.

Of course, now we need to look at the ItemsBackground class so we can understand how it’s generating the shadow for each item. The XAML for the ItemBackground UserControl is similar to what we had in my previous post – a root Grid which defines the ThemeShadow resource and includes a Rectangle which is the background of the item. It’s the Rectangle that has the Shadow property set and will be generating the shadow that is to be cast.

<UserControl
    x:Class="ShadowTest.ItemBackground"
    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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006">
    <Grid Loaded="RootLoaded">
        <Grid.Resources>
            <ThemeShadow x:Name="SharedShadow" />
        </Grid.Resources>
        <Rectangle x:Name="Rectangle2"
                   Fill="Turquoise"
                   Margin="{x:Bind ContentPadding, Mode=OneWay}"
                   Shadow="{StaticResource SharedShadow}" />
    </Grid>
</UserControl>

We’re intercepting the Loaded event on the Grid in order to connect the ThemeShadow with the corresponding Grid to be the receiver for the shadow (in this case the Grid is the ParentBackgroundGrid defined on the page). Note that this logic could definitely be improved. It steps up the visual tree from the current item (i.e. the Grid in the ItemTemplate) all the way up to the Grid defined on the page called RootGrid.

private void RootLoaded(object sender, RoutedEventArgs e)
    var parent = VisualTreeHelper.GetParent(this);
    while (parent != null)
        if(parent is Grid grid)
            if(grid.Name == "RootGrid")
                var bg = grid.Children.FirstOrDefault(x => x is Grid g && g.Name == "ParentBackgroundGrid");
                SharedShadow.Receivers.Add(bg);
                return;
        parent = VisualTreeHelper.GetParent(parent);

Again, remembering that we can’t specify a ancestor as a Receiver for the shadow. Instead we’re going to find the first child element with the name ParentBackgroundGrid. This Grid will then be set as a Receiver for the ThemeShadow.

As we want to be able to adjust the Z axis translate when the user presses on the item in the list, we need to expose a mechanism whereby the ItemContainerStyle can simply set a property in the visual state definition. We need to define two dependency properties: ContentPadding, which determines the inset of content, and Elevation, which will determine the z translate value.

public Thickness ContentPadding
    get { return (Thickness)GetValue(ContentPaddingProperty); }
    set { SetValue(ContentPaddingProperty, value); }
public static readonly DependencyProperty ContentPaddingProperty =
    DependencyProperty.Register("ContentPadding", typeof(Thickness), typeof(ItemBackground), new PropertyMetadata(new Thickness(0)));
public int Elevation
    get { return (int)GetValue(ElevationProperty); }
    set { SetValue(ElevationProperty, value); }
public static readonly DependencyProperty ElevationProperty =
    DependencyProperty.Register("Elevation", typeof(int), typeof(ItemBackground), new PropertyMetadata(0, ElevationChanged));
private static void ElevationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    (d as ItemBackground).ChangeElevation((int)e.NewValue);
private void ChangeElevation(int newValue)
    Rectangle2.Translation = new Vector3(0, 0, newValue);

Let’s give this a run – the following image shows the list of items with the shadow appearing for each item. The only difference between the left and right sides is that in the list on the left side, the item “1000 items – 3” has been pressed.

Using Web Authentication (OAuth2) to access Figma from Windows and Uno Platform Application