Добавление курсора расположения (Location Crosshair) к диаграммам Silverlight

Silverlight развивается очень быстро. Прошлая конференция MIX09 увидела выпуск Silverlight 3 (Beta) а также выпуск инструментария Silverlight Toolkit. Все это развитие усложняет следование за новыми технологиями!

Относительно диаграмм, были введены некоторые незначительные изменения, которые могут привести также к изменению способа их реализации, что включает в себя изменения пространства имен или типа ориентации по осям диаграммы. Тем не менее, наибольшим изменением является шаблон элемента управления диаграммой. В прошлом шаблон диаграммы был структурирован посредством элемента табличной сетки (Grid) как это показано ниже:

 <ControlTemplate TargetType="charting:Chart">
    <Grid Name="ChartArea" Style="{TemplateBinding ChartAreaStyle}">
        <!-- ##### элемент управления диаграммой добавляет определение колонок/строк и осей в данном месте#### -->
        <Grid Height="250" x:Name="PlotArea" Style="{TemplateBinding PlotAreaStyle}">
            <!-- стандартные компоненты шаблона диаграммы -->
            <Grid x:Name="GridLinesContainer" />
            <Grid x:Name="SeriesContainer"/>
            <Border BorderBrush="#FF919191" BorderThickness="1" />
            <!-- ##### здесь добавляется курсор расположения и обозначения #### -->
        </Grid>
    </Grid>
</ControlTemplate>

После загрузки шаблона диаграмма добавляет оси и соответствующие определения колонок / строк. Это позволяет нам спокойно добавить новое визуаальное содержимое в пределах сетки ‘PlotArea’. Визуальное дерево при помощи Silverlight Spy выглядит следующим образом:

 

Содержимое, которое было добавлено нами для построения указателя/курсора и обозначений, обведено красным.

Мартовский выпуск инструментария Silverlight модифицирует способ того, как оси будут добавлены к диаграмме - сделано это путем введения новой панели разметки - EdgePanel (кажется, DockPanel!). Эта панель позволяет вам располагать элементы на одном из четырех краев панели либо в ее центре. Модифицированный шаблон элемента показан ниже:

 <ControlTemplate TargetType="charting:Chart" x:Key="ChartTemplate">
    <Grid x:Name="ChartRoot" Style="{TemplateBinding PlotAreaStyle}">
        <chartingprimitives:EdgePanel x:Name="ChartArea" Style="{TemplateBinding ChartAreaStyle}">                          
            <Grid Canvas.ZIndex="1" Style="{TemplateBinding PlotAreaStyle}" />
            <Border Canvas.ZIndex="1" BorderBrush="#FF919191" BorderThickness="1" />
            <!-- ##### Здесь добавляются курсор и обозначения#### -->
 
            <!-- ##### Здесь элемент управления диаграммой добавляет определения колонок/строк и осей#### -->
 
        </chartingprimitives:EdgePanel>
    </Grid>
</ControlTemplate>

Мы указали место расположения, где было добавлено наше визуальное содержимое, а также место, где элемент управления диаграммой добавляет оси. Далее продемонстрировано результирующее визуальное дерево:

 

Значительным отличием в данном месте является то, что все компоненты нашей диаграммы содержатся в пределах все той же панели EdgePanel, где их расположение диктуется свойством EdgePanel.Edge. Единственная проблема заключается в том, что если раньше из-за Z-образного порядка наше содержимое было в верху, то теперь оно где-то посередине. К счастью, EdgePanel поддерживает прикрепленное свойство Canvas.ZIndex , тем самым позволяя продвигать содержимое к верху. Модифицированный шаблон будет выглядеть следующим образом:

<ControlTemplate TargetType="charting:Chart" x:Key="ChartTemplate">
    <Grid x:Name="ChartRoot" Style="{TemplateBinding PlotAreaStyle}">
 
        <chartingprimitives:EdgePanel x:Name="ChartArea" Style="{TemplateBinding ChartAreaStyle}">                                  
 
            <Grid Canvas.ZIndex="-1" Style="{TemplateBinding PlotAreaStyle}" />
            <Border Canvas.ZIndex="3" BorderBrush="#FF919191" BorderThickness="1" />
 
            <!-- курсор расположения (location crosshair) -->
            <Grid Name="CrosshairContainer" Canvas.ZIndex="1" Background="Transparent"
                 MouseMove="CrosshairContainer_MouseMove" MouseEnter="CrosshairContainer_MouseEnter"
                 MouseLeave="CrosshairContainer_MouseLeave" >
                <Grid Name="Crosshair">
                    <Line Name="Vertical" X1="{Binding Path=X}" Y1="0" X2="{Binding Path=X}" Y2="400" Stroke="Black"/>
                    <Line Name="Horizontal" X1="0" Y1="{Binding Path=Y}" X2="400" Y2="{Binding Path=Y}" Stroke="Black"/>
                </Grid>
            </Grid>
 
            <!-- расположение обозначений 'legend' -->
            <Border Canvas.ZIndex="2" Name="LocationIndicator" Visibility="Collapsed" Style="{StaticResource LocationLegendStyle}">
                <StackPanel Orientation="Horizontal" Margin="5">
                    <TextBlock Text="Location: "/>
                    <TextBlock Text="{Binding Path=Key,
                                Converter={StaticResource FormattingConverter}, ConverterParameter=hh:mm:ss}"/>
                    <TextBlock Text=", "/>
                    <TextBlock Text="{Binding Path=Value,
                                Converter={StaticResource FormattingConverter}, ConverterParameter=0.00}"/>
                </StackPanel>
            </Border>
 
        </chartingprimitives:EdgePanel>
    </Grid>
</ControlTemplate>

Еще одной важной особенностью является то, что обработчики событий для MouseMove, MouseLeave и т.д. при ассоциации с табличной сеткой (Grid) будут работать, только если их свойство background не равно null, то есть не прозрачно. Неизвестно почему, но оно работает.

Последним важным изменением является метод, используемый для преобразования точек из координат экрана в позиции в пределах системы координат диаграммы. Ранее можно было использовать метод GetPlotAreaCoordinateValueRange по отношению к скрытому интерфейсу IRangeAxis. Теперь оно переименовано в GetValueAtPosition и возвращает и получает тип UnitValue в качестве входного параметра (скорее всего, для сохранения постоянства прикладного интерфейса по отношению к секторным диаграммам). А вот как выглядит код:

/// <summary>
/// Преобразует полученную позицию из табличной сетки в точки в
/// пределах координатной системы элемента
/// </summary>
private KeyValuePair<DateTime, double> GetPlotAreaCoordinates(Point position)
{
    IComparable yAxisHit = ((IRangeAxis)YAxis).GetValueAtPosition(
        new UnitValue(PlotArea.ActualHeight - position.Y, Unit.Pixels));
 
    IComparable xAxisHit = ((IRangeAxis)XAxis).GetValueAtPosition(
        new UnitValue(position.X, Unit.Pixels));
 
    return new KeyValuePair<DateTime, double>((DateTime)xAxisHit, (double)yAxisHit);

Имея данные изменения, наш курсор расположения (crosshair) будет теперь полнофункциональным:

Get  Microsoft Silverlight

… до следующего выпуска инструментария.

Исходный код можно закачать по данной ссылке : sllinechartcrosshairupdated.zip.

Автор: Colin Eberhardt