I was surprised to find that assigning a foreground color to a LinearAxis3D does not automatically make the axis line, title or labels take on that color. That was the behavior I would expect. But it does not.
Ok, no problem, I figured I would get around it by defining a style resource for LinearAxis3D that individually binds the color to its logical children to the parent LinearAxis3D. So I did that and it works just fine for the text title and labels. But it does not work for the axis line
Are a LinearAxis3D object's axis lines not logical children of that axis? And if not, is there any way to generically find the LinearAxis3D object for (binding purposes) from within the style?
To illustrate, here's a the Z axis on my chart. (I've nested the style here for simplicity but in my app it needs to be a separate static resource for re-use.)
<
tk:RadCartesianChart3D.ZAxis
>
<
tk:LinearAxis3D
x:Name
=
"ZAxis"
LabelFormat
=
"0.00"
SmartLabelsMode
=
"SmartStep"
Minimum
=
"{Binding ZAxisMin}"
Maximum
=
"{Binding ZAxisMax}"
Foreground
=
"Yellow"
>
<
tk:LinearAxis3D.Style
>
<
Style
TargetType
=
"{x:Type tk:LinearAxis3D}"
>
<
Setter
Property
=
"LabelStyle"
>
<
Setter.Value
>
<
Style
TargetType
=
"{x:Type TextBlock}"
>
<
Setter
Property
=
"Foreground"
Value
=
"{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type tk:LinearAxis3D}}, Path=Foreground}"
/>
</
Style
>
</
Setter.Value
>
</
Setter
>
<
Setter
Property
=
"LineStyle"
>
<
Setter.Value
>
<
Style
TargetType
=
"{x:Type Path}"
>
<
Setter
Property
=
"Stroke"
Value
=
"{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type tk:LinearAxis3D}}, Path=Foreground}"
/>
<
Setter
Property
=
"StrokeThickness"
Value
=
"2"
/>
</
Style
>
</
Setter.Value
>
</
Setter
>
</
Style
>
</
tk:LinearAxis3D.Style
>
</
tk:LinearAxis3D
>
</
tk:RadCartesianChart3D.ZAxis
>
Since I assigned "Yellow" to the LinearAxis3D.Foreground, my label text blocks manage to find the parent LinearAxis3D and bind to this property to show the correct label color. But the axis lines never show up. That binding never finds the LinearAxis3D and so never gets any color.
If I change this setter to just use a hard coded color then it setter works fine. But obviously, that's not going to work for me.
Surprisingly, when I tried -- just as a test -- to bind by ElementName to "ZAxis", that didn't work either. It's not an option for me if I want this Style to be a StaticResource but still I was surprised it did not work.
So do I have any other option here? Is the Path object used to draw the axis line a logical child of the LinearAxis3D? And if not, is there any way to achieve what I want? Or do I need to just individually nest the line style for each axis?
8 Answers, 1 is accepted
Hello Joe,
Thank you for the detailed description. I've used your code to assemble a test project, but I wasn't able to reproduce the issue. Can you please check the attachment and let me know if I am missing anything?
Regards,
Martin Ivanov
Progress Telerik
Hi Martin,
I've spent the past 2 days trying to see why your app works and mine doesn't.
When I run your example, it works perfectly.
When I take my Axis styles and put them into your application, they work perfectly. Both line styles and labels
But when I run my application with the exact same styles, the LineStyle binding for stroke does not work. All of the other bindings work just fine. But the axis line is NOT colorized as I would want. The debugger output window gives me a runtime binding error.
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='Telerik.Windows.Controls.ChartView.LinearAxis3D', AncestorLevel='1''. BindingExpression:Path=Foreground; DataItem=null; target element is 'Path' (Name=''); target property is 'Stroke' (type 'Brush')
Here is the exact XAML I am using:
<
tk:RadCartesianChart3D.ZAxis
>
<
tk:LinearAxis3D
x:Name
=
"ZAxis"
LabelFormat
=
"0.00"
SmartLabelsMode
=
"SmartStep"
Foreground
=
"GreenYellow"
>
<
tk:LinearAxis3D.LineStyle
>
<
Style
TargetType
=
"{x:Type Path}"
>
<
Setter
Property
=
"Stroke"
Value
=
"{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type tk:LinearAxis3D}}, Path=Foreground}"
/>
<
Setter
Property
=
"StrokeThickness"
Value
=
"4"
/>
</
Style
>
</
tk:LinearAxis3D.LineStyle
>
<
tk:LinearAxis3D.LabelStyle
>
<
Style
TargetType
=
"{x:Type Label}"
>
<
Setter
Property
=
"Foreground"
Value
=
"{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type tk:LinearAxis3D}}, Path=Foreground}"
/>
</
Style
>
</
tk:LinearAxis3D.LabelStyle
>
</
tk:LinearAxis3D
>
</
tk:RadCartesianChart3D.ZAxis
>
I have attached a photo to show the result ("BadVersion.png")
But if I hard-code the line color value to "GreenYellow" (same as axis foreground), then it works (see "GoodVersion.png")
<
Setter
Property
=
"Stroke"
Value
=
"GreenYellow"
As an experiment, I tried several other bindings, none of which worked
I tried binding (by ancestor type) to the RadCartesianChart3D itself. It couldn't find that either
<Setter Property="Stroke" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type tk:RadCartesianChart3D}}, Path=Foreground}"/>
I tried explicitly specifying (and increasing) the AncestorLevel value (1, 2, 3, and 4). None of those helped.
I tried using "ElementName" syntax
<
Setter
Property
=
"Stroke"
Value
=
"{Binding ElementName=ZAxis, Path=Foreground}"
/>
It changed nothing. It can't even find the "ZAxis" by name. I got this runtime binding error
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=ZAxis'. BindingExpression:Path=Foreground; DataItem=null; target element is 'Path' (Name=''); target property is 'Stroke' (type 'Brush')
I have verified that both of our applications are using the exact same version of the 3 Telerik Assemblies you reference
I do not apply any implicit styles to LinearAxis3D or RadCartesianChart anywhere in my code
I've removed all my chart behaviors and even plugged in your same exact "canned" data. Same problem.
I've verified that my "tk" namespace is the proper one (although I don't call it "telerik"
The fact that even an ElementName binding doesn't work completely baffles me.
Any ideas where I could look next?
Hello Joe,
Can you test with the latest version of Telerik UI for WPF, if you are not using it already? Also, you can open a new support ticket from your Telerik account and attach your project there. This way the support team can test it and investigate what happens.
Regards,
Martin Ivanov
Progress Telerik
Since, I can work around this problem - whatever it is -- by explicitly specifying each axis' color, I think at this point, you should just mark this as "closed" so you will not waste any more of your time on what has got to be my bug
Thank you, Martin
Appreciate the reply.
I am using the very latest version of UI for WPF (2019.3).
Unfortunately, there is no way I could possibly post this project. It is just one Module (a Prism Module) in a much larger application consisting of many modules which also relies on our custom C++/CLI wrapper on a C++ DLL and numerous third party libraries.
Of all the difficulties I've encountered in WPF (and they have been legion), nothing baffles me like this one.
OK, Martin I finally found it what causes it.
If the RadCartesianChart is not visible when it is loaded (I had its visibility set to "Collapsed"), then the binding fails as I described. If the chart is visible when it is loaded, the binding works just fine
I even made this happen in your test application. I bound the chart to a Visibility property (in code-behind) that defaulted to "Collapsed" Then I added a button that - when you push it - sets that visibility to "Visible".
I push the button and the chart appears and the labels are colored properly but the lines are not. Also the binding errors show up in the debug output window
So now, I still define the LineStyle in XAML but unlike all the rest of the other properties I do not apply it to the axes then, in XAML
<
Style
x:Key
=
"AxisLineStyle"
TargetType
=
"{x:Type Path}"
>
<
Setter
Property
=
"StrokeThickness"
Value
=
"2"
/>
<
Setter
Property
=
"Stroke"
Value
=
"{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type tk:LinearAxis3D}}, Path=Foreground}"
/>
</
Style
>
Instead, I set a handler for the RadCartesianChart's IsVisibleChanged event. When the chart becomes visible the very first time, I set them then, in code-behind to the named RadLinearAxis3D elements (named "XAxis", "YAxis" and "ZAxis"). As long as I do it like this, the bindings work just fine.
private bool _hasBeenVisibleBefore = false;
private void MyCartesianChart3D_OnIsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
// First, sanity checking:
if (!(e.NewValue is bool visible))
return;
if (!(sender is SurfaceView sv))
return;
// If we did not just become visible, nothing to do.
if (!visible)
return;
// If we became visible for the first time., apply line styles now
if (_hasBeenVisibleBefore)
return;
_hasBeenVisibleBefore = true;
if (!(TryFindResource("AxisLineStyle") is Style ls))
return;
XAxis.LineStyle = ls;
YAxis.LineStyle = ls;
ZAxis.LineStyle = ls;
}
Hello Joe,
I am glad to hear that you managed to resolve the issue on your side. And also thank you for sharing your solution here. I am sure that this will be useful for other people too.
Regards,
Martin Ivanov
Progress Telerik