views:

1637

answers:

3

I need to be able to specify a command to run when the SelectionChanged event fires. I already know how to implement the ICommandSource interface; what I need to know is how I can just add a command to the column series to handle the SelectionChanged event.

When I inherit from the ColumnBarBaseSeries<...> base class I have to override GetAxes() and UpdateDatePoint() which I am not sure how to implement.

+1  A: 
Chris Nicol
This is a much better solution than the one I posted below. Wish I knew about it before I spent 6 hours copying the ColumnSeries source code over to a new class.
Jacob
Yup attached behaviours are great. One thing I've noticed with WPF development ... you should be able to do everything UI specific from XAML with Triggers, DP's and attached behaviours, et la. If you find yourself hacking with code on the codebehind then there's almost certainly a better way. The only code-behind I have is UI specific calucations that need to be done at runtime.
Chris Nicol
A: 

Here is some code that appears to work for me to implement your own CommandColumnSeries, I stole a lot of it from the source for the ColumnSeries Sealed Class:

public class CommandColumnSeries : ColumnBarBaseSeries<ColumnDataPoint>
{
    #region "ICommandSource"

    [Localizability(LocalizationCategory.NeverLocalize), Category("Action"), Bindable(true)]
    public ICommand Command
    {
        get
        {
            return (ICommand)base.GetValue(CommandProperty);
        }
        set
        {
            base.SetValue(CommandProperty, value);
        }
    }

    [Bindable(true), Category("Action"), Localizability(LocalizationCategory.NeverLocalize)]
    public object CommandParameter
    {
        get
        {
            return base.GetValue(CommandParameterProperty);
        }
        set
        {
            base.SetValue(CommandParameterProperty, value);
        }
    }

    [Category("Action"), Bindable(true)]
    public IInputElement CommandTarget
    {
        get
        {
            return (IInputElement)base.GetValue(CommandTargetProperty);
        }
        set
        {
            base.SetValue(CommandTargetProperty, value);
        }
    }

    public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.Register("CommandParameter", typeof(object), typeof(CommandColumnSeries), new FrameworkPropertyMetadata(null));
    public static readonly DependencyProperty CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(CommandColumnSeries), new FrameworkPropertyMetadata(null));
    public static readonly DependencyProperty CommandTargetProperty = DependencyProperty.Register("CommandTarget", typeof(IInputElement), typeof(CommandColumnSeries), new FrameworkPropertyMetadata(null));

    #endregion

    #region public IRangeAxis DependentRangeAxis

    /// <summary>
    /// Gets or sets the dependent range axis.
    /// </summary>
    public IRangeAxis DependentRangeAxis
    {
        get { return GetValue(DependentRangeAxisProperty) as IRangeAxis; }
        set { SetValue(DependentRangeAxisProperty, value); }
    }

    /// <summary>
    /// Identifies the DependentRangeAxis dependency property.
    /// </summary>
    [SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes", Justification = "This member is necessary because the base classes need to share this dependency property.")]
    public static readonly DependencyProperty DependentRangeAxisProperty =
        DependencyProperty.Register(
            "DependentRangeAxis",
            typeof(IRangeAxis),
            typeof(ColumnSeries),
            new PropertyMetadata(null, OnDependentRangeAxisPropertyChanged));

    /// <summary>
    /// DependentRangeAxisProperty property changed handler.
    /// </summary>
    /// <param name="d">ColumnBarBaseSeries that changed its DependentRangeAxis.</param>
    /// <param name="e">Event arguments.</param>
    private static void OnDependentRangeAxisPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        CommandColumnSeries source = (CommandColumnSeries)d;
        IRangeAxis newValue = (IRangeAxis)e.NewValue;
        source.OnDependentRangeAxisPropertyChanged(newValue);
    }

    /// <summary>
    /// DependentRangeAxisProperty property changed handler.
    /// </summary>
    /// <param name="newValue">New value.</param>
    private void OnDependentRangeAxisPropertyChanged(IRangeAxis newValue)
    {
        this.InternalDependentAxis = (IAxis)newValue;
    }
    #endregion public IRangeAxis DependentRangeAxis

    #region public IAxis IndependentAxis
    /// <summary>
    /// Gets or sets the independent category axis.
    /// </summary>
    public IAxis IndependentAxis
    {
        get { return GetValue(IndependentAxisProperty) as IAxis; }
        set { SetValue(IndependentAxisProperty, value); }
    }

    /// <summary>
    /// Identifies the IndependentAxis dependency property.
    /// </summary>
    [SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes", Justification = "This member is necessary because the base classes need to share this dependency property.")]
    public static readonly DependencyProperty IndependentAxisProperty =
        DependencyProperty.Register(
            "IndependentAxis",
            typeof(IAxis),
            typeof(ColumnSeries),
            new PropertyMetadata(null, OnIndependentAxisPropertyChanged));

    /// <summary>
    /// IndependentAxisProperty property changed handler.
    /// </summary>
    /// <param name="d">ColumnBarBaseSeries that changed its IndependentAxis.</param>
    /// <param name="e">Event arguments.</param>
    private static void OnIndependentAxisPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        CommandColumnSeries source = (CommandColumnSeries)d;
        IAxis newValue = (IAxis)e.NewValue;
        source.OnIndependentAxisPropertyChanged(newValue);
    }

    /// <summary>
    /// IndependentAxisProperty property changed handler.
    /// </summary>
    /// <param name="newValue">New value.</param>
    private void OnIndependentAxisPropertyChanged(IAxis newValue)
    {
        this.InternalIndependentAxis = (IAxis)newValue;
    }
    #endregion public IAxis IndependentAxis



    public CommandColumnSeries()
    {
        this.SelectionChanged += new SelectionChangedEventHandler(CommandColumnSeries_SelectionChanged);
    }

    private void CommandColumnSeries_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
    {   
        if (Command != null)
        {
            RoutedCommand routedCommand = Command as RoutedCommand;
            CommandParameter = e.Source;

            if (routedCommand != null)
            {
                routedCommand.Execute(CommandParameter, CommandTarget);
            }
            else
            {
                Command.Execute(CommandParameter);
            }
        }
    }

    protected override void GetAxes(DataPoint firstDataPoint)
    {
        // Taken from the source of the ColumnSeries sealed class.
        GetAxes(
            firstDataPoint,
            (axis) => axis.Orientation == AxisOrientation.X,
            () => new CategoryAxis { Orientation = AxisOrientation.X },
            (axis) =>
            {
                IRangeAxis rangeAxis = axis as IRangeAxis;
                return rangeAxis != null && rangeAxis.Origin != null && axis.Orientation == AxisOrientation.Y;
            },
            () =>
            {
                IRangeAxis rangeAxis = CreateRangeAxisFromData(firstDataPoint.DependentValue);
                rangeAxis.Orientation = AxisOrientation.Y;
                if (rangeAxis == null || rangeAxis.Origin == null)
                {
                    throw new InvalidOperationException("No Suitable Axes found for plotting range axis.");
                }
                DisplayAxis axis = rangeAxis as DisplayAxis;
                if (axis != null)
                {
                    axis.ShowGridLines = true;
                }
                return rangeAxis;
            });

    }

    protected override void UpdateDataPoint(DataPoint dataPoint)
    {
        // This code taken from the ColumnSeries sealed class.
        if (SeriesHost == null )//|| PlotArea == null)
        {
            return;
        }

        object category = dataPoint.ActualIndependentValue ?? (IndexOf<DataPoint>(this.ActiveDataPoints, dataPoint) + 1);
        Range<UnitValue> coordinateRange = GetCategoryRange(category);
        if (!coordinateRange.HasData)
        {
            return;
        }
        else if (coordinateRange.Maximum.Unit != Unit.Pixels || coordinateRange.Minimum.Unit != Unit.Pixels)
        {
            throw new InvalidOperationException("This Series Does Not Support Radial Axes");
        }

        double minimum = (double)coordinateRange.Minimum.Value;
        double maximum = (double)coordinateRange.Maximum.Value;

        double plotAreaHeight = ActualDependentRangeAxis.GetPlotAreaCoordinate(ActualDependentRangeAxis.Range.Maximum).Value.Value;
        IEnumerable<CommandColumnSeries> columnSeries = SeriesHost.Series.OfType<CommandColumnSeries>().Where(series => series.ActualIndependentAxis == ActualIndependentAxis);
        int numberOfSeries = columnSeries.Count();
        double coordinateRangeWidth = (maximum - minimum);
        double segmentWidth = coordinateRangeWidth * 0.8;
        double columnWidth = segmentWidth / numberOfSeries;
        int seriesIndex = IndexOf<CommandColumnSeries>(columnSeries, this);

        double dataPointY = ActualDependentRangeAxis.GetPlotAreaCoordinate(ToDouble(dataPoint.ActualDependentValue)).Value.Value;
        double zeroPointY = ActualDependentRangeAxis.GetPlotAreaCoordinate(ActualDependentRangeAxis.Origin).Value.Value;

        double offset = seriesIndex * Math.Round(columnWidth) + coordinateRangeWidth * 0.1;
        double dataPointX = minimum + offset;

        if (GetIsDataPointGrouped(category))
        {
            // Multiple DataPoints share this category; offset and overlap them appropriately
            IGrouping<object, DataPoint> categoryGrouping = GetDataPointGroup(category);
            int index = GroupIndexOf(categoryGrouping, dataPoint);
            dataPointX += (index * (columnWidth * 0.2)) / (categoryGrouping.Count() - 1);
            columnWidth *= 0.8;
            Canvas.SetZIndex(dataPoint, -index);
        }

        if (CanGraph(dataPointY) && CanGraph(dataPointX) && CanGraph(zeroPointY))
        {
            double left = Math.Round(dataPointX);
            double width = Math.Round(columnWidth);

            double top = Math.Round(plotAreaHeight - Math.Max(dataPointY, zeroPointY) + 0.5);
            double bottom = Math.Round(plotAreaHeight - Math.Min(dataPointY, zeroPointY) + 0.5);
            double height = bottom - top + 1;

            Canvas.SetLeft(dataPoint, left);
            Canvas.SetTop(dataPoint, top);
            dataPoint.Width = width;
            dataPoint.Height = height;
        }
    }

    private static int IndexOf<T>(IEnumerable<T> collection, T target)
    {
        int i = 0;
        foreach (var obj in collection)
        {
            if (obj.Equals(target))
                return i;
            i++;
        }
        return -1;
    }

    private static int GroupIndexOf(IGrouping<object, DataPoint> group, DataPoint point)
    {
        int i = 0;
        foreach (var pt in group)
        {
            if (pt == point)
                return i;
            i++;
        }

        return -1;
    }

    /// <summary>
    /// Returns a value indicating whether this value can be graphed on a 
    /// linear axis.
    /// </summary>
    /// <param name="value">The value to evaluate.</param>
    /// <returns>A value indicating whether this value can be graphed on a 
    /// linear axis.</returns>
    private static bool CanGraph(double value)
    {
        return !double.IsNaN(value) && !double.IsNegativeInfinity(value) && !double.IsPositiveInfinity(value) && !double.IsInfinity(value);
    }

    /// <summary>
    /// Converts an object into a double.
    /// </summary>
    /// <param name="value">The value to convert to a double.</param>
    /// <returns>The converted double value.</returns>
    private static double ToDouble(object value)
    {
        return Convert.ToDouble(value, CultureInfo.InvariantCulture);
    }


}
Jacob
A: 

Here is some code to add an attached behavior to a ColumnSeries for a SelectionChanged Command.

public static class ColumnSeriesBehavior
{
    private static DelegateCommand<object> SelectionChangedCommand;       

    public static DelegateCommand<object> GetSelectionChangedCommand(ColumnSeries cs)
    {
        return cs.GetValue(SelectionChangedCommandProperty) as DelegateCommand<object>;
    }

    public static void SetSelectionChangedCommand(ColumnSeries cs, DelegateCommand<object> value)
    {
        cs.SetValue(SelectionChangedCommandProperty, value);
    }

    // Using a DependencyProperty as the backing store for SelectionChangedCommand.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty SelectionChangedCommandProperty =
        DependencyProperty.RegisterAttached("SelectionChangedCommand", typeof(DelegateCommand<object>), typeof(ColumnSeriesBehavior), new UIPropertyMetadata(null, OnSelectionChangedCommandChanged));

    private static void OnSelectionChangedCommandChanged(
        DependencyObject depObj, DependencyPropertyChangedEventArgs e)
    {
        ColumnSeries item = depObj as ColumnSeries;
        if (item == null)
        {                
            return;
        }
        if (e.NewValue is DelegateCommand<object> == false)
        {

            return;
        }

        SelectionChangedCommand = e.NewValue as DelegateCommand<object>;
        item.SelectionChanged += new System.Windows.Controls.SelectionChangedEventHandler(Column_SelectionChanged);
    }

    private static void Column_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
    {
        if (SelectionChangedCommand != null)
            SelectionChangedCommand.Execute(sender);
    }

}

And in the XAML to attach the property:

<chartingToolkit:Chart.Series>
            <chartingToolkit:ColumnSeries
                IsSelectionEnabled="True"                                        
                ItemsSource="{Binding YourItemSource}"
                IndependentValueBinding="{Binding YourIndValue, Path=YourIndValuePath}"
                DependentValueBinding="{Binding YourDepValue, Path=YourDepValuePath}"                                        
                >
                <chartingToolkit:ColumnSeries.Style>
                    <Style>
                        <!-- Attaching the SelectionChangedCommand behavior -->
                        <Setter Property="local:ColumnSeriesBehavior.SelectionChangedCommand"
                                Value="{Binding YourDelegateCommand}"/>
                    </Style>
                </chartingToolkit:ColumnSeries.Style>
            </chartingToolkit:ColumnSeries>
        </chartingToolkit:Chart.Series>
Jacob