背景
我需要一个我正在处理的应用程序的图表系统,并且无法找到能够做我所需要的任何免费的,所以我决定自己编写。
到目前为止的解决方案
我将避免在他的观点中详细介绍设计,但这是XAML代码的示例和结果的屏幕截图。希望这能说明系统(一组自定义WPF控件)的结构。
<Window x:Class="ChartTestApp_1.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:charts="clr-namespace:BB.Play.Wpf.Charts;assembly=BB.Play.Wpf.Charts"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="560"
Background="Black">
<Window.Resources>
<ResourceDictionary Source="/BB.PLay.Wpf.Charts;component/Themes/Generic.xaml"/>
</Window.Resources>
<Grid>
<charts:Chart Margin="20" BorderThickness="0">
<charts:Chart.Axes>
<!-- The Y axis -->
<charts:NumericAxis Style="{StaticResource VerticalNumercAxis}"
Name="ValueAxis"
IsScrollable="True"
IsZoomable="True"
Minimum="{Binding MinValue}"
Maximum="{Binding MaxValue}"
Source="Value">
<charts:Axis.Hatching>
<charts:LinearHatching Style="{StaticResource MajorVerticalHatching}" IsAuto="True" MinimumSpacing="30"/>
<charts:LinearHatching Style="{StaticResource MinorVerticalHatching}" IsAuto="True" MinimumSpacing="30" IsGridLine="True"/>
</charts:Axis.Hatching>
</charts:NumericAxis>
<!-- The X axis -->
<charts:TimeAxis Style="{StaticResource HorizontalTimeAxis}"
Name="TimeAxis"
IsPrimaryAxis="True"
Minimum="{Binding StartTime}"
Maximum="{Binding EndTime}"
Source="Time">
<charts:Axis.Hatching >
<charts:LinearHatching Style="{StaticResource MinorHorizontalHatching}" IsAuto="True" MinimumSpacing="240" LabelFormat="|dd MMM yy"/>
<charts:LinearHatching Style="{StaticResource MinorHorizontalHatching}" IsAuto="True" MinimumSpacing="60" LabelFormat=""/>
<charts:LinearHatching Style="{StaticResource MinorHorizontalHatching}" IsAuto="True" MinimumSpacing="20" IsLabeled="False" IsGridLine="True"/>
</charts:Axis.Hatching>
</charts:TimeAxis>
</charts:Chart.Axes>
<!-- The main chart area -->
<charts:Chart.Presenters>
<!-- The background -->
<charts:UIPresenter>
<Grid>
<Rectangle HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Rectangle.Fill>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
<GradientStop Color="Black" Offset="0.0" />
<GradientStop Color="#000044" Offset="1.0" />
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
<TextBlock Foreground="SteelBlue" FontSize="14" Margin="5">Chart Test 1</TextBlock>
</Grid>
</charts:UIPresenter>
<!-- The grid lines -->
<charts:GridLinePresenter SnapsToDevicePixels="True"/>
<!-- The data -->
<charts:DataPresenter DataItemsProvider="{Binding DataProvider}"
charts:ChartPanel.HorizontalPosition="Center"
charts:ChartPanel.VerticalPosition="Center"
XAxisName="TimeAxis"
YAxisName ="ValueAxis"
ClipToBounds="True">
<charts:DataPresenter.ArtistTemplate>
<DataTemplate>
<charts:Artist X="{Binding Time}"
Y="{Binding Value}">
<charts:Artist.Template>
<DataTemplate>
<Ellipse Height="5" Width="5" Fill="DarkOrange"/>
</DataTemplate>
</charts:Artist.Template>
</charts:Artist>
</DataTemplate>
</charts:DataPresenter.ArtistTemplate>
</charts:DataPresenter>
</charts:Chart.Presenters>
</charts:Chart>
</Grid>
</Window>
现状
我目前正在为系统提供图表网格线的部分工作。此功能由名为 GridLinePresenter 的控件提供,并包含在XAML底部的 Presenters 中。 由于图表既可滚动又可缩放,我无法使用静态网格线,并且由于网格线与轴上的阴影线对齐,因此使用它们的位置来排列网格线似乎是合乎逻辑的。
部分代码
只要轴滚动或缩放,就会调用以下方法之一。
private void UpdateScrollPosition(Point currentScrollPosition)
{
var distance = Orientation == PartOrientation.Horizontal
? _lastScrollPosition.X - currentScrollPosition.X
: _lastScrollPosition.Y - currentScrollPosition.Y;
_lastScrollPosition = currentScrollPosition;
VisualRange.Move(distance);
InvalidateAxis();
RaiseAxisChanged();
}
private void Zoom(double delta)
{
VisualRange.Zoom(delta / 12.0);
ScaleRange(DesiredSize);
InvalidateAxis();
RaiseAxisChanged();
}
两种方法的最后一行是
RaiseAxisChanged();
这将触发演示者可以订阅的事件(未路由),以便他们可以根据新轴详细信息自行更新。在上面的示例中,这包括GridLinePresenter和DataPresenter,但不包括UIPresenter,因为它不需要更改。在使用它的两种情况下,事件处理程序都是
private void OnAxisMoved(object sender, EventArgs e)
{
InvalidateMeasure();
}
问题
由于轴的阴影线是在 AxisHatching 控件的 MeasureOverride 中计算的,因此必须处理Axis及其所有子控件在渲染的测量过程之前调用 GridLinePresenter 上的 MeasureOverride ,否则它将最终使用阴影位置的旧值来排列网格线。
即使 Axis 在 GridLinePresenter 之前失效并且在可视树中位于较高位置,但这并不能保证它将首先被处理。实际上目前的订单是
GridLinePresenter.MeasureOverride()
Axis.MeasureOverride()
Axis.ArrangeOverride()
GridLinePresenter.ArrangeOverride()
导致绘制错误数量的网格线(缩放时)或绘制在错误的位置。
问题
首先 - WPF如何确定渲染过程中处理元素的顺序?我认为这可能是他们在视觉树中的位置和/或他们的Z-Index,但事实并非如此。
第二 - 这会受到影响吗