简体   繁体   中英

Adding ChartSeries programmatically (MVVM) in Modern UI (Metro) Charts

I've been searching a while now for a good looking chart visualization solution for my project and I finally found it ( https://modernuicharts.codeplex.com ). I also posted this question on the discussion board there but I did not have a reply...

I've been playing around a little with the charts and I really love them. Very smooth and beautiful. There is 1 thing I need a little more information about and that is how I can add ChartSeries at runtime.

EDIT: I managed to add bars on runtime but there is some bug I can't seem to figure out. Let's start with an image:

图片

As you can see in the image, the titles on the axis are correct and scale when I scale the window. The problem here are the bars. The bars seem to have a fixed width and don't want to fit on the screen. The way I did this is as follow:

In XAML I placed the control like this:

<chart:StackedColumnChart ChartTitle="Total"
                          ChartSubTitle="(800)"
                          Series="{Binding Bars}">
</chart:StackedColumnChart>

In my code behind I added the Bars like this:

using System;
using System.Collections.Generic;
using De.TorstenMandelkow.MetroChart;
using System.Collections.ObjectModel;

namespace AutoShop
{
class OccupationChartGroupViewModel
{
    public OccupationChartGroupViewModel()
    {
        List<Tuple<string, int, double>> reqProdHoursPerWeek = DbServiceSegmentRequirement.GetProdSegReq();
        List<Tuple<string, int, double>> reqPlanHoursPerWeek = DbServiceSegmentRequirement.GetPlanSegReq();
        List<Tuple<string, int, double>> reqRepairHoursPerWeek = DbServiceSegmentRequirement.GetRepairSegReq();

        List<Tuple<string, int, double>> reqHoursPerWeek = new List<Tuple<string, int, double>>();
        reqHoursPerWeek.AddRange(reqProdHoursPerWeek);
        reqHoursPerWeek.AddRange(reqPlanHoursPerWeek);
        reqHoursPerWeek.AddRange(reqRepairHoursPerWeek);

        reqHoursPerWeek.Sort();

        switch (reqHoursPerWeek[0].Item2.ToString())
        {
            case "0":
                if (reqHoursPerWeek[1].Item2.ToString() != "1")
                    reqHoursPerWeek.Add(new Tuple<string, int, double>(reqHoursPerWeek[0].Item1, 1, 0.0));
                else
                    if (reqHoursPerWeek[1].Item1 != reqHoursPerWeek[0].Item1)
                        reqHoursPerWeek.Add(new Tuple<string, int, double>(reqHoursPerWeek[0].Item1, 1, 0.0));
                break;

            case "1":
                reqHoursPerWeek.Add(new Tuple<string, int, double>(reqHoursPerWeek[0].Item1, 0, 0.0));
                if (reqHoursPerWeek[1].Item2.ToString() != "2")
                    reqHoursPerWeek.Add(new Tuple<string, int, double>(reqHoursPerWeek[0].Item1, 2, 0.0));
                else
                    if (reqHoursPerWeek[1].Item1 != reqHoursPerWeek[0].Item1)
                        reqHoursPerWeek.Add(new Tuple<string, int, double>(reqHoursPerWeek[0].Item1, 2, 0.0));
                break;

            case "2":
                reqHoursPerWeek.Add(new Tuple<string, int, double>(reqHoursPerWeek[0].Item1, 0, 0.0));
                reqHoursPerWeek.Add(new Tuple<string, int, double>(reqHoursPerWeek[0].Item1, 1, 0.0));
                break;

            default: break;
        }

        reqHoursPerWeek.Sort();

        Bars = new ObservableCollection<De.TorstenMandelkow.MetroChart.ChartSeries>();

        ObservableCollection<TestClass> blocks = new ObservableCollection<TestClass>();

        foreach (Tuple<string, int, double> reqHours in reqHoursPerWeek)
        {
            string group = reqHours.Item2.ToString() == "2" ? "Order1" : reqHours.Item2.ToString() == "1" ? "Order2" : reqHours.Item2.ToString() == "0" ? "Order3" : "Unknown";

            blocks.Add(new TestClass() { Category = group, Number = reqHours.Item3 });

            if (reqHoursPerWeek.IndexOf(reqHours) + 1 < reqHoursPerWeek.Count)
            {
                if (reqHours.Item1 != reqHoursPerWeek[reqHoursPerWeek.IndexOf(reqHours) + 1].Item1)
                {
                    ChartSeries chartSerie = new ChartSeries();
                    chartSerie.SeriesTitle = reqHours.Item1;
                    chartSerie.DisplayMember = "Category";
                    chartSerie.ValueMember = "Number";
                    chartSerie.ItemsSource = blocks;
                    Bars.Add(chartSerie);

                    blocks = new ObservableCollection<TestClass>();
                }
            }
            else
            {
                ChartSeries chartSerie = new ChartSeries();
                chartSerie.SeriesTitle = reqHours.Item1;
                chartSerie.DisplayMember = "Category";
                chartSerie.ValueMember = "Number";
                chartSerie.ItemsSource = blocks;
                Bars.Add(chartSerie);
            }
        }
    }

    public ObservableCollection<ChartSeries> Bars { get; private set; }
}

// class which represent a data point in the chart
public class TestClass
{
    public string Category { get; set; }

    public double Number { get; set; }
}
}

Lastly, the error I get on design time in the XAML viewer is this one:

**NullReferenceException: Object reference not set to an instance of an object.**

**Stacktrace**
at De.TorstenMandelkow.MetroChart.ChartBase.UpdateDataContextOfSeries()
at De.TorstenMandelkow.MetroChart.ChartBase.InternalDataContextChanged()
at De.TorstenMandelkow.MetroChart.ChartBase.DataContextWatcher_Changed(DependencyObject sender, DependencyPropertyChangedEventArgs args)
at System.Windows.DependencyObject.OnPropertyChanged(DependencyPropertyChangedEventArgs e)
at System.Windows.FrameworkElement.OnPropertyChanged(DependencyPropertyChangedEventArgs e)
at System.Windows.DependencyObject.NotifyPropertyChange(DependencyPropertyChangedEventArgs args)
at System.Windows.DependencyObject.UpdateEffectiveValue(EntryIndex entryIndex, DependencyProperty dp, PropertyMetadata metadata, EffectiveValueEntry oldEntry, EffectiveValueEntry& newEntry, Boolean coerceWithDeferredReference, Boolean coerceWithCurrentValue, OperationType operationType)
at System.Windows.DependencyObject.InvalidateProperty(DependencyProperty dp, Boolean preserveCurrentValue)
at System.Windows.Data.BindingExpressionBase.Invalidate(Boolean isASubPropertyChange)
at System.Windows.Data.BindingExpression.TransferValue(Object newValue, Boolean isASubPropertyChange)
at System.Windows.Data.BindingExpression.Activate(Object item)
at System.Windows.Data.BindingExpression.AttachToContext(AttachAttempt attempt)
at System.Windows.Data.BindingExpression.MS.Internal.Data.IDataBindEngineClient.AttachToContext(Boolean lastChance)
at MS.Internal.Data.DataBindEngine.Task.Run(Boolean lastChance)
at MS.Internal.Data.DataBindEngine.Run(Object arg)
at MS.Internal.Data.DataBindEngine.OnLayoutUpdated(Object sender, EventArgs e)
at System.Windows.ContextLayoutManager.fireLayoutUpdateEvent()
at System.Windows.ContextLayoutManager.UpdateLayout()
at System.Windows.UIElement.UpdateLayout()

**InnerException: None**

You are using Series instead of SeriesSource property (I tried using SeriesSource but for some reason the chart is coming out empty). Anyways, the error is actually occurring because the above method is called before MVVM calls the get method of the Series binding, hence getting Series as null in the below definition in the ChartBase.cs . I hope that you have the project inside your solution, else open the code provided by them, update the code, compile and reference the generated dll.

    private void UpdateDataContextOfSeries()
    {
        onApplyTemplateFinished = false;

        //ADDED CODE STARTS
        if(this.Series != null)
        {
        //ADDED CODE ENDS
        foreach (var newItem in this.Series)
        {
            if (newItem is FrameworkElement)
            {
                (newItem as FrameworkElement).DataContext = this.DataContext;
            }
        }
        onApplyTemplateFinished = true;
        UpdateSeries();            
        //ADDED CODE STARTS
        }
        //ADDED CODE ENDS
    }    

Just need to check if the series is null before running the loop and applying updates. This method is also called once the series is Get'ed.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM