尝试为 LiveCharts WPF 创建自定义工具提示 - 工具提示不显示数据

Trying to Create Custom Tooltip for LiveCharts WPF - Tooltip Doesn't Display Data

总的来说,我在为我的 LiveCharts 控件显示自定义工具提示时遇到了很多问题这是我的代码和我尝试过的方法。

图表 C# 代码:

public partial class Chart : UserControl
{

    public ChartData chartData { get; set; }
   
    public Chart()
    {
        chartData = new ChartData();
        InitializeComponent();
        Loaded += Control_Loaded;
    }

    private void Control_Loaded(object sender, RoutedEventArgs e)
    {
        // build here
        DataContext = this;
        chartData.Formatter = value => value.ToString("C", CultureInfo.CurrentCulture);
        
    }
}

图表XAML:

<UserControl x:Class="LandlordTenantDatabaseWPF.Chart"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         xmlns:local="clr-namespace:LandlordTenantDatabaseWPF"
         xmlns:lvc="clr-namespace:LiveCharts.Wpf;assembly=LiveCharts.Wpf"
         mc:Ignorable="d" 
         d:DesignHeight="450" d:DesignWidth="800" d:DataContext="{d:DesignInstance local:Chart}">
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    
        <TextBlock x:Name="txtTitle" Grid.Row="0" Text="{Binding chartData.Title}" Foreground="#FF4B52E4" FontSize="36" TextAlignment="Center"/>
    
        <lvc:CartesianChart x:Name="chart" Grid.Row="1" Series="{Binding chartData.SeriesCollection}" LegendLocation="Right" >            
            
            <lvc:CartesianChart.AxisX>
                <lvc:Axis Title="Month" Labels="{Binding chartData.Labels}"></lvc:Axis>
            </lvc:CartesianChart.AxisX>
        <lvc:CartesianChart.AxisY>
            <lvc:Axis Title="Payments" LabelFormatter="{Binding chartData.Formatter}"></lvc:Axis>
        </lvc:CartesianChart.AxisY>
        <lvc:CartesianChart.DataTooltip>
            <local:CustomersTooltip/>
        </lvc:CartesianChart.DataTooltip>
    </lvc:CartesianChart>            
</Grid>

工具提示 C# 代码:

public partial class CustomersTooltip : IChartTooltip
{
    private TooltipData _data;
    public CustomersTooltip()
    {
        InitializeComponent();
        //LiveCharts will inject the tooltip data in the Data property
        //your job is only to display this data as required
        DataContext = this;
    }
    public event PropertyChangedEventHandler PropertyChanged;
    public TooltipData Data
    {
        get { return _data; }
        set
        {
            _data = value;
            OnPropertyChanged("Data");
        }
    }
    public TooltipSelectionMode? SelectionMode { get; set; }
    protected virtual void OnPropertyChanged(string propertyName = null)
    {
        if (PropertyChanged != null)
            PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

工具提示XAML:

<UserControl x:Class="LandlordTenantDatabaseWPF.CustomersTooltip"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
         xmlns:wpf="clr-namespace:LiveCharts.Wpf;assembly=LiveCharts.Wpf"
         xmlns:local="clr-namespace:LandlordTenantDatabaseWPF"
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300" 
         d:DataContext="{d:DesignInstance local:CustomersTooltip}"
         Background="#E4555555" Padding="20 10" BorderThickness="2" BorderBrush="#555555">
<ItemsControl ItemsSource="{Binding Data.Points}" Grid.IsSharedSizeScope="True">
    <ItemsControl.ItemTemplate>
        <DataTemplate DataType="{x:Type wpf:DataPointViewModel}">
            <Grid Margin="2">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto"/>
                    <ColumnDefinition Width="Auto" SharedSizeGroup="Title"/>
                    <ColumnDefinition Width="Auto" SharedSizeGroup="LastName"/>
                    <ColumnDefinition Width="Auto" SharedSizeGroup="Phone"/>
                    <ColumnDefinition Width="Auto" SharedSizeGroup="PurchasedItems"/>
                </Grid.ColumnDefinitions>
                <Rectangle Grid.Column="0" Stroke="{Binding Series.Stroke}" Fill="{Binding Series.Fill}"
                           Height="15" Width="15"></Rectangle>
                <TextBlock Grid.Column="1" Text="{Binding ChartPoint.Instance.(local:CustomerVm.Name)}" 
                           Margin="5 0 0 0" VerticalAlignment="Center" Foreground="White"/>
                <TextBlock Grid.Column="2" Text="{Binding ChartPoint.Instance.(local:CustomerVm.LastName)}" 
                           Margin="5 0 0 0" VerticalAlignment="Center" Foreground="White"/>
                <TextBlock Grid.Column="3" Text="{Binding ChartPoint.Instance.(local:CustomerVm.Phone), 
                                                    StringFormat=Phone: {0}}" 
                           Margin="5 0 0 0" VerticalAlignment="Center" Foreground="White"/>
                <TextBlock Grid.Column="4" Text="{Binding ChartPoint.Instance.(local:CustomerVm.PurchasedItems), 
                                                            StringFormat=Purchased Items: {0:N}}" 
                           Margin="5 0 0 0" VerticalAlignment="Center" Foreground="White"/>
            </Grid>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

图表数据Class:

public class ChartData : INotifyPropertyChanged
{
    private SeriesCollection seriesCollection;

    public SeriesCollection SeriesCollection
    {
        get { return seriesCollection; }

        set
        {
            if (seriesCollection != value)
            {
                seriesCollection = value;
                OnPropertyChanged("seriesCollection");
            }
        }
    }

    private string[] labels;
    public string[] Labels
    {
        get { return labels; }
        set
        {
            if(labels != value)
            {
                labels = value;
                OnPropertyChanged("labels");
            }
        }
    }

    private string title;
    public string Title 
    { 
        get { return title; } 
        set
        {
            if(title != value)
            {
                title = value;
                OnPropertyChanged("title");
            }
        }
    }

    private Func<double, string> formatter;
    public Func<double, string> Formatter 
    { 
        get { return formatter; }
        set
        {
            if(formatter != value)
            {
                formatter = value;
                OnPropertyChanged("formatter");
            }
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

并且 Window 包含图表:

private void AddNewSeries(List<double> lstPayments, string sSeriesName, SolidColorBrush colorBrush)
    {
        if (m_SeriesCollection == null)
        {
            m_SeriesCollection = new SeriesCollection
            { new ColumnSeries
                {
                    Values = new ChartValues<double>(lstPayments),
                    Title = sSeriesName,
                    Fill = colorBrush                        
                }
            };
        }
        else
        {
            m_SeriesCollection.Add(
                new ColumnSeries
                {
                    Values = new ChartValues<double>(lstPayments),
                    Title = sSeriesName,
                    Fill = colorBrush
                }
            );
        }
    }
private void UpdateChartsBasedOnDates(Chart chart, string sChartTitle)
    {    //Pass data to the chart       
        chart.chartData.SeriesCollection = m_SeriesCollection;
        chart.chartData.Labels = GetFormattedDates(m_lstCombinedDates);
        chart.chartData.Title = sChartTitle;            
    }

当我只允许在图表上显示默认工具提示时,这一切都完美无缺。我对图表数据由 SeriesCollection 提供这一事实感到困惑,但在 LiveCharts 网站 https://lvcharts.net/App/examples/v1/wpf/Tooltips%20and%20Legends 的示例中,自定义工具提示不使用 SeriesCollection。我的自定义图表控件显示的一些图表有超过 1 个系列。我尝试将 Tooltip 控件绑定到 SeriesCollection 以获取数据值,但这没有用。当然,我可能做错了。显然,我在这里显示的工具提示 XAML 来自 LiveCharts 网站上的示例代码,但我不知道从这里去哪里。

是否可以使工具提示使用系列集合?对我来说使用 Series Collection 或 ChartData class 的最佳方式是什么,或者我可以使用

public TooltipData Data
{
    get { return _data; }
    set
    {
        _data = value;
        OnPropertyChanged("Data");
    }
}

从 ToolTip C# 代码在 ToolTip 中显示数据?

我很困惑。

编辑:我忘了说我的目标是什么。我想显示一个工具提示

string.Format("{0} Payment: ", SeriesName, PaymentAmount);

这将显示支付租金:$1000。或按揭付款:$1000.00

虽然我不是一个深入的 LiveCharts Tooltip Wiz,但我确实设法获得了一个适合我的版本。我的图表也使用了 SeriesCollection,所以也许这对你有帮助。

这就是我将它集成到我的图表中的方式:

CartesianChart cartesianChart = ...;
_cartesianChart.DataTooltip = new CustomTooltip
{
    SelectionMode = TooltipSelectionMode.OnlySender
};

这是我的 CustomToolTip class(我相信这几乎是直接从某处的文档中提取的):

public partial class CustomTooltip : UserControl, IChartTooltip
{
    private TooltipData _data;

    public CustomTooltip()
    {
        InitializeComponent();

        //LiveCharts will inject the tooltip data in the Data property
        //your job is only to display this data as required

        DataContext = this;
    }

    public event PropertyChangedEventHandler PropertyChanged;

    public TooltipData Data
    {
        get => _data;
        set
        {
            _data = value;
            OnPropertyChanged("Data");
        }
    }

    public TooltipSelectionMode? SelectionMode { get; set; }

    protected virtual void OnPropertyChanged(string propertyName = null)
    {
        if (PropertyChanged != null)
            PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

public partial class CustomTooltip : UserControl, IChartTooltip, INotifyPropertyChanged
{
    private bool _contentLoaded;

    [System.Diagnostics.DebuggerNonUserCodeAttribute()]
    [System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "4.0.0.0")]
    public void InitializeComponent()
    {
        if (_contentLoaded)
            return;

        System.Uri resourceLocater = new System.Uri(
            "/Plotting/CustomTooltipDesign.xaml",
            System.UriKind.Relative
        );
        System.Windows.Application.LoadComponent(this, resourceLocater);

        _contentLoaded = true;
    }
}

然后我构建了一个 XAML 组件,就像你一样。第一个超级简单:

public partial class CustomTooltipDesign : UserControl
{
    public CustomTooltipDesign()
    {
        DataContext = this;
    }
}

然后我做了更多努力来构建实际的工具提示:

<UserControl x:Class="expomrf4utility.CustomTooltipDesign"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
         xmlns:expomrf4utility="clr-namespace:ExpoMRF4Utility" 
         xmlns:wpf="clr-namespace:LiveCharts.Wpf;assembly=LiveCharts.Wpf"
         mc:Ignorable="d" 
         d:DataContext="{d:DesignInstance expomrf4utility:CustomTooltip}"
         x:Name="Control" d:DesignWidth="1" d:DesignHeight="1">
<UserControl.Resources>
    <Style TargetType="TextBlock">
        <Setter Property="Foreground" Value="{Binding Foreground}"></Setter>
    </Style>
    <wpf:SharedConverter x:Key="SharedConverter"/>
    <wpf:SharedVisibilityConverter x:Key="SharedVisibilityConverter"/>
    <wpf:ChartPointLabelConverter x:Key="ChartPointLabelConverter"/>
    <wpf:ParticipationVisibilityConverter x:Key="ParticipationVisibilityConverter"/>
    <BooleanToVisibilityConverter x:Key="Bvc"></BooleanToVisibilityConverter>
    <CollectionViewSource x:Key="GroupedPoints" Source="{Binding Data.Points}"/>
</UserControl.Resources>
<UserControl.Template>
    <ControlTemplate>
        <Grid VerticalAlignment="Center" HorizontalAlignment="Center" >
                <StackPanel Orientation="Vertical">
                    <ItemsControl ItemsSource="{Binding Source={StaticResource GroupedPoints}}" Grid.IsSharedSizeScope="True">
                        <ItemsControl.GroupStyle>
                            <GroupStyle>
                                <GroupStyle.HeaderTemplate>
                                    <DataTemplate>
                                        <ContentPresenter Content="{Binding Name}" />
                                    </DataTemplate>
                                </GroupStyle.HeaderTemplate>
                            </GroupStyle>
                        </ItemsControl.GroupStyle>
                        <ItemsControl.ItemTemplate>
                            <DataTemplate DataType="{x:Type wpf:DataPointViewModel}">
                                <Grid Margin="2" VerticalAlignment="Center" HorizontalAlignment="Center" Background="White">
                                    <Grid.RowDefinitions>
                                        <RowDefinition Height="Auto"/>
                                        <RowDefinition Height="Auto" SharedSizeGroup="Title"/>
                                        <RowDefinition Height="Auto" SharedSizeGroup="Label"/>
                                        <RowDefinition Height="Auto" SharedSizeGroup="Label"/>
                                    </Grid.RowDefinitions>
                                    <TextBlock Grid.Row="1" Text="{Binding Series.Title}" HorizontalAlignment="Left" FontWeight="Bold"/>
                                    <TextBlock Grid.Row="2" Text="{Binding ChartPoint.Y, StringFormat='Value: {0} V/m'}" HorizontalAlignment="Left"/>
                                    <TextBlock Grid.Row="3" Text="{Binding ChartPoint, Converter={StaticResource ChartPointLabelConverter}, StringFormat='Datetime: {0}'}" HorizontalAlignment="Left"/>
                                </Grid>
                            </DataTemplate>
                        </ItemsControl.ItemTemplate>
                    </ItemsControl>
                </StackPanel>
        </Grid>
    </ControlTemplate>
</UserControl.Template>

请注意我必须如何为要显示的不同属性创建绑定。为此,我在自己创建数据集时略有作弊。我用过 GLineSeries 但我觉得它也应该适用于其他系列:

int bandIndex = ...;
GearedValues<double> gearedValues = (some double[] data).AsGearedValues();
series.Add(new GLineSeries
{
    Values = gearedValues,
    LabelPoint = chartPoint => loggerData.Labels[(int)chartPoint.X].ToString(),
    Tag = bandIndex,
    Title = bandIndex.ToString()
});

请注意,我 chartPoint.X 是特定点(在 X 中)的 (zero-based) 索引 - 而 chartPoint.Y 是点的 Y-value (也用于 XAML)。使用此索引,我可以在我的姓名列表 loggerData.Labels 和 return 中查找与此索引关联的时间戳。