LabelTemplate and RelativeSource Binding Strangeness

4 posts, 0 answers
  1. Louis
    Louis avatar
    83 posts
    Member since:
    Aug 2013

    Posted 07 May 2014 Link to this post

    I've come across a strange problem when trying to use RelativeSource binding in the DataTemplate used for styling the LabelTemplate of an axis. When RelativeSource binding is used, the labels are drawn over the left side of the chart instead of to the left.

    Below is  the code for a simple example exhibiting the problem. (It's obviously a convoluted example since the numbers are all being set to the same value. In my real application I experienced the problem using MultiBinding to pass in some settings as well as the value, but this simpler example shows the same symptoms with single Binding).

    If I bind to the Property using ElementName, it displays correctly. However, if I reference the exact same property using RelativeSource, the value is picked up successfully, but the labels are displayed incorrectly. See attached screen shot.

    I have a work-around by using the ElementName, but ideally I'd like to use the RelativeSource instead to reference the owning ChartView such that the formatter can be put in a dictionary and used across several charts without having to assign them all the same name.

    Thanks,
    Louis

    The sample app:

    <Window x:Class="LabelTemplate_MultiBinding.MainWindow"
                    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation"
                    xmlns:local="clr-namespace:LabelTemplate_MultiBinding"
                    Title="MainWindow" Height="768" Width="1024">
        <Grid>
            <telerik:RadCartesianChart x:Name="PropertyChart">
                <telerik:RadCartesianChart.Resources>
                    <local:ChartNumberFormatter x:Key="NumberFormatter"/>
                    <DataTemplate x:Key="FormattedNumericAxisTemplate">
     
                        <!-- This uses the converter sending the desired value, and it works fine -->
                        <!--<TextBlock Text="{Binding ., Converter={StaticResource NumberFormatter}}" />-->
                         
                        <!-- This uses the converter sending some value via binding using ElementName,
                             and it works fine -->
                        <!--<TextBlock Text="{Binding Path=DataContext.JustANumber,
                                                  Converter={StaticResource NumberFormatter},
                                                  ElementName=PropertyChart}" />-->
                         
                        <!-- This uses the converter sending the same value as above via binding,
                             but using RelativeSource instead, and it displays incorrectly! -->
                        <TextBlock Text="{Binding Path=DataContext.JustANumber,
                                                  Converter={StaticResource NumberFormatter},
                                                  RelativeSource={RelativeSource AncestorType={x:Type telerik:RadCartesianChart}}}" />
                    </DataTemplate>
                </telerik:RadCartesianChart.Resources>
     
                <telerik:RadCartesianChart.HorizontalAxis>
                    <telerik:DateTimeCategoricalAxis/>
                </telerik:RadCartesianChart.HorizontalAxis>
     
                <telerik:RadCartesianChart.VerticalAxis>
                    <telerik:LinearAxis LabelTemplate="{StaticResource FormattedNumericAxisTemplate}" />
                </telerik:RadCartesianChart.VerticalAxis>
     
                <telerik:RadCartesianChart.Series>
                    <telerik:LineSeries
                           CategoryBinding="Date"
                           ValueBinding="Value"
                           ItemsSource="{Binding Path=Series1}">
                    </telerik:LineSeries>
                </telerik:RadCartesianChart.Series>
            </telerik:RadCartesianChart>
        </Grid>
    </Window>

    The code-behind:
    using System;
    using System.Collections.Generic;
    using System.Windows;
    using System.Windows.Data;
     
    namespace LabelTemplate_MultiBinding
    {
        public class MyPoint
        {
            public DateTime Date { get; set; }
            public Double Value { get; set; }
        }
        public partial class MainWindow : Window
        {
            public List<MyPoint> Series1 { get; private set; }
            public string DisplaySuffix { get; private set; }
            public double JustANumber { get; private set; }
            public MainWindow()
            {
                Series1 = new List<MyPoint>();
                DisplaySuffix = "M";
                JustANumber = 50000;
                for (int i = 0; i < 5; i++)
                {
                    DateTime date = DateTime.Today.AddDays(i);
                    Series1.Add(new MyPoint() { Date = date, Value = i * 1000 });
                }
                InitializeComponent();
                DataContext = this;
            }
        }
        public class ChartNumberFormatter : IValueConverter
        {
            public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                double number = System.Convert.ToDouble(value);
                return number.ToString("N0");
            }
            public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                throw new NotImplementedException();
            }
        }
    }

  2. Petar Marchev
    Admin
    Petar Marchev avatar
    968 posts

    Posted 08 May 2014 Link to this post

    Hi Louis,

    Thank you for the code snippets, I was able to create a new project and reproduce the behavior.

    What you are observing is a result of the internal caching mechanism of the chart. The chart measures the ContentPresenter with the given DataTemplate and caches this size. However, this happens before the RelativeSource kicks in and the Text value has not yet been evaluated. So the axis cached size (0,0) and this is the size that is used later when arranging the label.

    I can suggest a couple of resolutions for this. One is to set a Width (say 50) of the TextBlock and set the text alignment to right. Another is for you to use the LabelTemplateSelector feature of the axis and create your template selector (and build DataTemplates in C# code). You can format the string you need to set to the Text of the TextBlock and not use any bindings. 

    Depending on your actual requirements you can probably work this out in a different way. If the only thing you need to change is the formatting, you can probably not use a DataTmplate at all and work with the LabelFormat property of the axis, bind it to your view model. 

    Let us know if we can be of further help.

    Regards,
    Petar Marchev
    Telerik
     
    Check out Telerik Analytics, the service which allows developers to discover app usage patterns, analyze user data, log exceptions, solve problems and profile application performance at run time. Watch the videos and start improving your app based on facts, not hunches.
     
  3. UI for WPF is Visual Studio 2017 Ready
  4. Louis
    Louis avatar
    83 posts
    Member since:
    Aug 2013

    Posted 08 May 2014 in reply to Petar Marchev Link to this post

    Thanks for the reply Petar. I don't think either of your suggestions would work. As I mentioned, this is a greatly simplified example. In my app, I'm using a multi-binding to pass the value to be displayed along with user-configurable formatting information held by the ViewModel to format the value. I don't know how wide it's going to be in advance (depends greatly on the formatting to be applied), and need both the value the chart wants to display as well as the formatting information to produce the result.

    For now, using the ElementName instead of RelativeSource seems to be the best work-around. Will this issue be fixed in a future version? (Certainly the ContentPresenter should evaluate the Text value before caching the size, as it does for the ElementName mechanic...)

    Thanks again,
    Louis
  5. Petar Marchev
    Admin
    Petar Marchev avatar
    968 posts

    Posted 09 May 2014 Link to this post

    Hi Louis,

    The relative source binding works in a different way than the element name binding. It requires a layout pass in order to get evaluated. We will consider improving this, but currently you can think of this as a limitation of the control.

    Perhaps you can change the binding to RelativeSource={RelativeSource Self}, and in the converter get the TextBlock, the axis label value (the text block's DataContext) and the view model (the parenting Canvas' DataContext):
    var tb = (TextBlock)value;
    double number = System.Convert.ToDouble(tb.DataContext);
    var vm = Telerik.Windows.Controls.ParentOfTypeExtensions.ParentOfType<Canvas>(tb).DataContext;

    Regards,
    Petar Marchev
    Telerik
     
    Check out Telerik Analytics, the service which allows developers to discover app usage patterns, analyze user data, log exceptions, solve problems and profile application performance at run time. Watch the videos and start improving your app based on facts, not hunches.
     
Back to Top