Click here to Skip to main content
15,886,873 members
Articles / Programming Languages / Visual Basic

Declarative Codesnippet Automation with T4 Templates

Rate me:
Please Sign up or sign in to vote.
4.77/5 (15 votes)
20 Apr 2011CPOL15 min read 56.5K   1.4K   36  
This article describes a technique for automating codesnippets which are associated with a class via attributes. This results in a declarative approach to the generation of boiler-plate code.
using System;
using System.Windows;
using System.Windows.Input;
using SilverTrack.View;
using TelemetryData.Telemetry;
using Visiblox.Charts;
using System.ComponentModel;
using Snippets;

namespace SilverTrack.ViewModel
{
    /// <summary>
    /// Provides the data context for a TelemetryChannelView and contains two data series to display
    /// on the chart from that view. Also contains behaviours and operations to modify, remove and add
    /// data series to the chart.
    /// </summary>
    [SnippetINotifyPropertyChanged]
    [SnippetPropertyINPC(property="SelectedSecondaryIndex", type="int", field="_selectedSecondaryIndex", defaultValue="1", 
      summary="The Index of the selected series in the secondary combo box.")]
    [SnippetPropertyINPC(property="SelectedPrimaryIndex", type="int", field="_selectedPrimaryIndex", defaultValue="1", 
      summary="The Index of the selected series in the primary combo box.")]
    [SnippetPropertyINPC(property="Behaviour", type="BehaviourManager", field="_behaviour", 
      summary="The Behaviour Manager which contains the trackball and the XAxisZoomBehaviour.")]
    [SnippetPropertyINPC(property="XAxisVisible", type="bool", field="_xAxisVisibile", defaultValue="false",
      summary="Whether this chart's X-Axis is visible.")]
    [SnippetPropertyINPC(property="LivePrimaryChartDataSeries", type="DataSeries<DateTime, double>", field="_livePrimaryData", 
      summary="The Live Updating DataSeries that is always displayed on the chart's primary y-axis.")]
    [SnippetPropertyINPC(property="LiveSecondaryChartDataSeries", type="DataSeries<DateTime, double>", field="_liveSecondaryData", 
      summary="The Live Updating DataSeries that is always displayed on the chart's secondary y-axis.")]
    public partial class TelemetryChannelViewModel : INotifyPropertyChanged
    {  

        #region partial methods

        partial void OnSelectedSecondaryIndexChanged(int value)
        {
          ModifySecondaryChannel(ParentTelemetryViewModel.Channels[SelectedSecondaryIndex]);
        }

        partial void OnSelectedPrimaryIndexChanged(int value)
        {
          ModifyPrimaryChannel(ParentTelemetryViewModel.Channels[SelectedPrimaryIndex]);
        }

        #endregion

        #region Fields

        #region Private Fields

        private DataSeries<DateTime, double> _primaryData = null;
        private DataSeries<DateTime, double> _secondaryData = null;

        private DateTimeRange _chartRange;

        private XAxisZoomBehaviour _xZoom;
        private TrackballBehaviour _trackball;

        private bool _running;

        #endregion

        #endregion

        #region Properties

        /// <summary>
        /// The TelemetryViewModel which created this TelemetryChannelViewModel.
        /// </summary>
        public TelemetryViewModel ParentTelemetryViewModel { get; set; }

        /// <summary>
        /// The X-Axis range for all the chart.
        /// </summary>
        public DateTimeRange ChartRange
        {
            get { return _chartRange; }
        }

        /// <summary>
        /// The Y-Axis range for the primary data series.
        /// </summary>
        public DoubleRange PrimaryYRange
        {
            get
            {
                return CalculateRange(ParentTelemetryViewModel.DataProvider.FindSeriesFromName(_primaryData.Title));
            }
        }

        /// <summary>
        /// The Y-Axis range for the secondary data series.
        /// </summary>
        public DoubleRange SecondaryYRange
        {
            get
            {
                return CalculateRange(ParentTelemetryViewModel.DataProvider.FindSeriesFromName(_secondaryData.Title));
            }
        }
       
        /// <summary>
        /// The Current selected point index on the series by the trackball.
        /// </summary>
        public int CurrentSelectedPoint
        {
            get
            {
                if (_trackball != null && _trackball.CurrentPoints.Count != 0)
                {
                    if (_trackball.CurrentPoints[0] != null)
                    {
                       return GetTrackballDataPoint(0);
                    }
                    else if (_trackball.CurrentPoints[1] != null)
                    {
                       return GetTrackballDataPoint(1);
                    }
                }
                return -1;
            }
        }

        /// <summary>
        /// The length of this data series.
        /// </summary>
        public int Length
        {
            get
            {
                if (_primaryData != null)
                    return _primaryData.Count;
                else if (_secondaryData != null)
                    return _secondaryData.Count;
                else
                    return -1;
            }
        }

        /// <summary>
        /// The command which will remove this Chart from the application.
        /// </summary>
        public ICommand RemoveCurrentChartViewCommand
        {
            get { return new RelayCommand<string>(p => RemoveCurrentChartView(p)); }
        }

        #endregion

        #region Methods

        #region Constructors

        public TelemetryChannelViewModel() { }

        /// <summary>
        /// Creates a TelemetryChannelViewModel with two initial data series and calculates the X-Axis range.
        /// Also creates a XAxisZoomBehaviour and a Trackball.
        /// </summary>
        /// <param name="PrimaryData">The initial primary data series.</param>
        /// <param name="SecondaryData">The initial secondary data series.</param>
        /// <param name="telemetryViewModel">The TelemetryViewModel which created this TelemetryChannelViewModel object.</param>
        public TelemetryChannelViewModel(TelemetryViewModel telemetryViewModel)
        {
            ParentTelemetryViewModel = telemetryViewModel;

            _primaryData = new DataSeries<DateTime, double>("NONE", new DataSeries<DateTime, double>());
            _livePrimaryData = new DataSeries<DateTime, double>("NONE", new DataSeries<DateTime, double>());

            _secondaryData = new DataSeries<DateTime, double>("NONE", new DataSeries<DateTime, double>());
            _liveSecondaryData = new DataSeries<DateTime, double>("NONE", new DataSeries<DateTime, double>());

            _behaviour = new BehaviourManager();
            _behaviour.AllowMultipleEnabled = true;

            _xZoom = new XAxisZoomBehaviour();
            _xZoom.IsEnabled = true;

            _trackball = new TrackballBehaviour();
            _trackball.IsEnabled = true;

            _behaviour.Behaviours.Add(_xZoom);

            _behaviour.Behaviours.Add(_trackball);

            _xZoom.ZoomStarted += new EventHandler(xZoom_ZoomStarted);
            
            Random RandomGenerator = new Random();
            SelectedPrimaryIndex = RandomGenerator.Next(1, ParentTelemetryViewModel.DataProvider.SeriesList.Count-1);
            SelectedSecondaryIndex = RandomGenerator.Next(1, ParentTelemetryViewModel.DataProvider.SeriesList.Count - 1);

            ModifyPrimaryChannel("NONE");
            ModifySecondaryChannel("NONE");
        }

        #endregion

        #region Public

        /// <summary>
        /// Updates the chart to a given time from a start point given by counter.
        /// </summary>
        /// <param name="counter">The start point in the data to update from.</param>
        /// <param name="currentTime">The representation of the current time of the lap.</param>
        /// <returns>Returns the counter value which represents how far through the data series the update reached.</returns>
        public int UpdateChart(int counter, DateTime currentTime)
        {
            //Set running to true so that the render loop knows it has data to add.
            _running = true;

            //Render loop. Adds data points to the chart based on the current time.
            while (_running)
            {
                //Check that the counter is not at the end of the list. Else restart.
                if (counter < _primaryData.Count - 1)
                {
                    if (_primaryData[counter + 1].X < currentTime)
                    {
                        //Increment the counter then Add the data point
                        counter++;
                        if (_primaryData != null && _primaryData.Count > 0)
                            _livePrimaryData.Add(_primaryData[counter]);
                        if (_secondaryData != null && _secondaryData.Count > 0)
                            _liveSecondaryData.Add(_secondaryData[counter]);
                    }
                    else
                    {
                        //Next point is in the future, set running to false.
                        _running = false;
                    }
                }
                else if (counter < _secondaryData.Count - 1)
                {
                    if (_secondaryData[counter + 1].X < currentTime)
                    {
                        //Increment the counter then Add the data point
                        counter++;
                        if (_primaryData != null && _primaryData.Count > 0)
                            _livePrimaryData.Add(_primaryData[counter]);
                        if (_secondaryData != null && _secondaryData.Count > 0)
                            _liveSecondaryData.Add(_secondaryData[counter]);
                    }
                    else
                    {
                        //Next point is in the future, set running to false.
                        _running = false;
                    }
                }
                else
                {
                    //Reached the end of the data for the lap. Pause the chart and stop it from running.
                    _running = false;
                    ParentTelemetryViewModel.Paused = true;
                }
            }
            return counter;
        }

        /// <summary>
        /// Fills the live primary and secondary data from the primary and secondary data, and returns the
        /// counter of the endpoint.
        /// </summary>
        /// <returns>Returns the counter value which represents how far through the data series the update reached.</returns>
        public int UpdateFullChart()
        {
            if (_primaryData.Title != "NONE")
            {
                _livePrimaryData.AddRange(_primaryData);
            }
            else
            {
                _livePrimaryData.Clear();
            }

            if (_secondaryData.Title != "NONE")
            {
                _liveSecondaryData.AddRange(_secondaryData);
            }
            else
            {
                _liveSecondaryData.Clear();
            }

            _running = false;
            ParentTelemetryViewModel.Paused = true;
            return _primaryData.Count - 1;
        }

        /// <summary>
        /// Removes the data points from both series on the chart.
        /// </summary>
        public void Clear()
        {
            _livePrimaryData.Clear();
            _liveSecondaryData.Clear();
        }

        /// <summary>
        /// Causes the chart to zoom to the given offset and the given scale.
        /// </summary>
        /// <param name="zoomOffset">The offset to zoom to.</param>
        /// <param name="zoomScale">The scale to zoom to.</param>
        public void ZoomTo(double zoomOffset, double zoomScale)
        {
            _xZoom.ZoomToScaleWithOffset(zoomScale, zoomOffset);
        }

        /// <summary>
        /// Causes the mouse move event to be called on the trackball at the given X position, and 0.0 Y position.
        /// The given Y value is ignored as charts can be different heights, causing the mouse move to be ignored when
        /// the the Y value is greater than the height of the chart.
        /// </summary>
        /// <param name="mousePosition">The position to move to (NOTE: the Y value is ignored).</param>
        public void MouseMove(Point mousePosition)
        {
            _trackball.MouseMove(new Point(mousePosition.X, 0.0));
        }

        #endregion

        #region Private/Internal

        private int GetTrackballDataPoint(int channelIndex)
        {
            DataPoint<DateTime, double> t = (DataPoint<DateTime, double>)_trackball.CurrentPoints[channelIndex];
            return (int)t.Tag;
        }

        private DoubleRange CalculateRange(TelemetrySeries telemetrySeries)
        {
            if (telemetrySeries.Name.Equals("NONE"))
            {
                return new DoubleRange(0.0, 1.0);
            }
            return new DoubleRange(telemetrySeries.Minimum, telemetrySeries.Maximum);
        }

        private void xZoom_ZoomStarted(object sender, EventArgs e)
        {
            ParentTelemetryViewModel.ZoomAll(_xZoom.Offset, _xZoom.Scale);
        }

        private void ModifyPrimaryChannel(string channel)
        {
            if (channel != null)
            {
                DataSeries<DateTime, double> tmpPrimary = ParentTelemetryViewModel.GetChannelData(channel);
                _primaryData = tmpPrimary;
                _primaryData.Title = tmpPrimary.Title;
                Clear();

                _chartRange = new DateTimeRange(_primaryData[0].X, _primaryData[_primaryData.Count - 1].X);

                ParentTelemetryViewModel.FullDataCommand.Execute("");
                OnPropertyChanged(LivePrimaryChartDataSeriesProperty);
                OnPropertyChanged("PrimaryYRange");
            }
        }

        private void ModifySecondaryChannel(string channel)
        {
            if (channel != null)
            {
                DataSeries<DateTime, double> tmpSecondary = ParentTelemetryViewModel.GetChannelData(channel);
                _secondaryData = tmpSecondary;
                _secondaryData.Title = tmpSecondary.Title;
                Clear();

                ParentTelemetryViewModel.FullDataCommand.Execute("");
                OnPropertyChanged(LiveSecondaryChartDataSeriesProperty);
                OnPropertyChanged("SecondaryYRange");
            }
        }

        private void RemoveCurrentChartView(string channel)
        {
            if (ParentTelemetryViewModel.ChannelViewModelCollection.ChannelViewModels.Count > 1)
            {
                ParentTelemetryViewModel.RemoveChannelView(this);
            }
        }

        #endregion

        #endregion
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect Scott Logic
United Kingdom United Kingdom
I am CTO at ShinobiControls, a team of iOS developers who are carefully crafting iOS charts, grids and controls for making your applications awesome.

I am a Technical Architect for Visiblox which have developed the world's fastest WPF / Silverlight and WP7 charts.

I am also a Technical Evangelist at Scott Logic, a provider of bespoke financial software and consultancy for the retail and investment banking, stockbroking, asset management and hedge fund communities.

Visit my blog - Colin Eberhardt's Adventures in .NET.

Follow me on Twitter - @ColinEberhardt

-

Comments and Discussions