Thursday, April 11, 2019

Custom formatted tooltips

Attribution: The book synopses used in this blog were taken from Wikipedia.

This blog shows how to create custom tooltips for a XamDataGrid, but the same technique works for all tooltips.

The tooltip property is not a string, even though the tooltip value converter can handle a string value. You can specify any custom container and use it as a tooltip. In this example we will create a custom read-only property on our DataSource that returns a StackPanel and populate it dynamically from the data.

I'm using Visual Studio 2019, Framework 4.7, and C#.

Start a new WPF App project called FormattedXamDataTooltip. Add the Infragistics references to it.



The XAML defines a XamDataGrid with one column containing the name of a book. That column will have a formatted tooltip containing a synopsis of the book with the first line in bold.

<Window x:Class="FormattedXamDataGridTooltip.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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:igDP="http://infragistics.com/DataPresenter"
        xmlns:local="clr-namespace:FormattedXamDataGridTooltip"
        mc:Ignorable="d"
        DataContext="{Binding RelativeSource={RelativeSource Self}}"
        Title="MainWindow" Height="450" Width="800">
    <igDP:XamDataGrid DataSource="{Binding Novels}" GroupByAreaLocation="None" >
        <igDP:XamDataGrid.FieldLayoutSettings>
            <igDP:FieldLayoutSettings SelectionTypeRecord="Single" AutoGenerateFields="False"/>
        </igDP:XamDataGrid.FieldLayoutSettings>
        <igDP:XamDataGrid.FieldSettings>
            <igDP:FieldSettings AllowSummaries="False" SummaryUIType="SingleSelect" LabelTextAlignment="Center" AllowEdit="False"/>
        </igDP:XamDataGrid.FieldSettings>
        <igDP:XamDataGrid.FieldLayouts>
            <igDP:FieldLayout>
                <igDP:FieldLayout.Fields>
                    <igDP:TextField Label="Novel" Name="Name" Width="auto">
                        <igDP:TextField.CellValuePresenterStyle>
                            <Style TargetType="igDP:CellValuePresenter">
                                <Setter Property="ToolTip" Value="{Binding DataItem.Tooltip}"/>
                                <Setter Property="ToolTipService.ShowDuration" Value="60000"/>
                            </Style>
                        </igDP:TextField.CellValuePresenterStyle>
                    </igDP:TextField>
                </igDP:FieldLayout.Fields>
            </igDP:FieldLayout>
        </igDP:XamDataGrid.FieldLayouts>
    </igDP:XamDataGrid>
</Window>


The code behind defines the cNovel class and the Novels collection which is populated with two books. The Synopsis property is populated with strings that contain newline separators. The read-only Tooltip property converts these into a stack panel containing a TextBlock for each line and makes the first TextBlock bold.

If you bind ToolTip to DataItem.Synopsis you will see the tool tip display a simple multi-line text block, but if you bind it to DataItem.Tooltip you will see the stack panel with the first line bold.

Here's the code that displays the stack panel.

using System;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;

namespace FormattedXamDataGridTooltip
{
    public class cNovel
    {
        public string Name { get; set; }
        public string Synopsis { get; set; }
        public StackPanel Tooltip
        {
            get
            {
                StackPanel tt = new StackPanel();
                foreach (string s in Synopsis.Split(new string[] { Environment.NewLine }, StringSplitOptions.None))
                {
                    tt.Children.Add(new TextBlock() { Text = s, FontWeight = (tt.Children.Count == 0) ? FontWeights.Bold : FontWeights.Normal });
                }
                return tt;
            }
        }
    }

    public partial class MainWindow : Window
    {
        private ObservableCollection<cNovel> _Novels = new ObservableCollection<cNovel>()
        {
            new cNovel() { Name = "The Old Man and the Sea", Synopsis = "The Old Man and the Sea is a short novel written by the American author Ernest Hemingway in 1951 in Cuba, and published in 1952." + Environment.NewLine + "It was the last major work of fiction by Hemingway that was published during his lifetime." + Environment.NewLine + "One of his most famous works, it tells the story of Santiago, an aging Cuban fisherman who struggles with a giant marlin far out in the Gulf Stream off the coast of Cuba" },
            new cNovel() { Name = "To Kill a Mockingbird", Synopsis = "To Kill a Mockingbird is a novel by Harper Lee published in 1960." + Environment.NewLine + "Instantly successful, widely read in high schools and middle schools in the United States, it has become a classic of modern American literature winning the Pulitzer Prize." + Environment.NewLine + "The plot and characters are loosely based on Lee's observations of her family, her neighbors and an event that occurred near her hometown of Monroeville, Alabama, in 1936, when she was 10 years old." }
        };

        public ObservableCollection<cNovel> Novels
        {
            get { return _Novels; }
            set { _Novels = value; }
        }
        public MainWindow()
        {
            InitializeComponent();
        }
    }
}


The resulting tool tip looks like this.


Obviously this is a very trivial example of formatting, but I hope you can see that we could have used any type of container and filled it with almost any content.

No comments:

Post a Comment