Skip to content

Example Implementation of ExtraMetadata Images

Tally edited this page Jan 27, 2021 · 1 revision

Example Implementation of ExtraMetadata Images

Documentation of my own implementation of loading additional metadata images, e.g., headers and logos in this theme.

Accessible and configurable variables in Constants.xaml

From Constants.xaml

<sys:Boolean x:Key="UseAbsoluteExtraMetadataPath">false</sys:Boolean>
<sys:String x:Key="ExtraMetadataPath">..\..\..\..</sys:String>

Alternatively, from a configured Constants.xaml

<sys:Boolean x:Key="UseAbsoluteExtraMetadataPath">true</sys:Boolean>
<sys:String x:Key="ExtraMetadataPath">C:\Users\Dan\AppData\Roaming\Playnite</sys:String>

By default navigate from the installed theme folder to the ExtraMetadataFolder. This relative path cannot be used with Crow's image caching converter so loaded files are locked while in use. This can be circumvented if the user enables the boolean variable here and provides the absolute path to their Playnite directory (without an ending backslash) so that this path can be used in the theme to load, convert, and cache these additional images. ThemeModifier will make these options available in the settings. DarkLinkPower has written an extension to configure these variables automatically.

The boolean is a switch between methods of loading the image: if false, then directly which locks the file but can use a relative path; if true, then it passes the path to ImageStringToImageConverter which caches the image but requires an absolute path.

Responsive StringFormatted binding to produce a path to the desired image.

From DetailsViewGameOverview.xaml

<Style x:Key="ExtraMetadataLogoPath" TargetType="TextBlock">
    <Setter Property="Text">
        <Setter.Value>
            <MultiBinding StringFormat="{}{0}{1}{2}{3}">
                <Binding RelativeSource="{RelativeSource Self}" Path="Tag" />
                <Binding Source="\ExtraMetadata\games\"/>
                <Binding Path="Game.Id"/>
                <Binding Source="\logo.png" />
            </MultiBinding>
        </Setter.Value>
    </Setter>
</Style>

<TextBlock Name="LogoPath" Style="{DynamicResource ExtraMetadataLogoPath}"
           Tag="{DynamicResource ExtraMetadataPath}"/>

If the converter is to be used, it will not accept a multibinding so this must be done separately and passed to the image.

Loading the image.

From DetailsViewGameOverview.xaml

<Image Name="LogoImage" Tag="{DynamicResource UseAbsoluteExtraMetadataPath}">
    <Image.Style>
        <Style TargetType="Image">
            <Setter Property="Source" Value="{Binding ElementName=LogoPath, Path=Text}"/>
            <Style.Triggers>
                <DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=Tag}" Value="True">
                    <Setter Property="Source" Value="{Binding ElementName=LogoPath, Path=Text, Converter {StaticResource ImageStringToImageConverter}}"/>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </Image.Style>
</Image>

Tag it with how to interpret the path passed to it then attempt to load the image. If the path is absolute pass it through the converter before assigning it to the source to avoid file locking.

Further Example

From DetailsViewGameOverview.xaml

<Style x:Key="ExtraMetadataHeaderPath" TargetType="TextBlock">
    <Setter Property="Text">
        <Setter.Value>
            <MultiBinding StringFormat="{}{0}{1}{2}{3}">
                <Binding RelativeSource="{RelativeSource Self}" Path="Tag" />
                <Binding Source="\ExtraMetadata\games\"/>
                <Binding Path="Game.Id"/>
                <Binding Source="\header.jpg" />
            </MultiBinding>
        </Setter.Value>
    </Setter>
</Style>

<TextBlock Name="HeaderPath" Style="{DynamicResource ExtraMetaHeaderPath}"  Tag="{DynamicResource ExtraMetadataPath}"/>

From GridViewGameOverview.xaml

<Image Name="HeaderImage" Height="215" Width="460" RenderOptions.BitmapScalingMode="Fant"
        Stretch="UniformToFill" SnapsToDevicePixels="True"
        Tag="{DynamicResource UseAbsoluteExtraMetadataPath}">
    <Image.Style>
        <Style TargetType="Image">
            <Setter Property="Source" Value="{Binding ElementName=HeaderPath, Path=Text}"/>
            <Style.Triggers>
                <Trigger Property="Source" Value="{x:Null}">
                    <Setter Property="Visibility" Value="Collapsed" />
                </Trigger>
                <DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=Tag}" Value="True">
                    <Setter Property="Source" Value="{Binding ElementName=HeaderPath, Path=Text, Converter={StaticResource ImageStringToImageConverter}}"/>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </Image.Style>
</Image>

This allows for several types of supplementary images to be loaded. I also use header images in the grid view details pane utilizing the following chain. In theory doing something similar for platform banners loaded from \ExtraMetadata\platforms[PlatformID] should be easy.

Closing Notes and Thanks

When you figure out a better way to do this, I look forward to stealing that code from you.

Of course, this is the product of assistance and work of several people to all of whom I am grateful. Explicitly, to Crow for Playnite and it's robust support for customization as well as informing us of the existence of ImageStringToImageConverter despite not "supporting a hack"; to joyrider3774 whose personal hack for banner support he posted and inspired me to attempt something similar for logos, and whose code I appropriated and adapted; to DarkLinkPower who directed me to Joyrider3774's work, advocated for using a designated ExtraMetadata folder, encouraged me throuout, and likely will imminently release an extension to automatically configure my and others' themes to be able to use the converter for caching; and finally to Lacro59 whose tagging and binding method I used to resolve several issues.

Oh. Thanks to Microsoft Docs, DuckDuckGo, and StackExchange too.