Click here to Skip to main content
13,667,276 members
Click here to Skip to main content

Tagged as

Stats

39.8K views
1.4K downloads
34 bookmarked
Posted 20 Apr 2011
Licenced CPOL

Declarative Codesnippet Automation with T4 Templates

, 20 Apr 2011
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.
CodeSnippetAutomation
CodeSnippetAutomation.suo
CodeSnippetAutomation
CodeGen
Snippets
inpc.snippet
prop_inpc.snippet
CodeSnippetAutomation.csproj.user
Properties
SilverTrackProject
SilverTrack
CodeGen
Snippets
dp.snippet
inpc.snippet
prop_inpc.snippet
controls
gauge
converters
images
addchart.png
bg.png
end.png
flag.png
info.png
playpauseicon.png
restart.png
yellowbg.png
lib
Microsoft.Expression.Controls.dll
Microsoft.Expression.Drawing.dll
Visiblox.Charts.dll
Properties
SilverTrack.csproj.user
SilverTrack.csproj.vs10x
Themes
Util
View
ViewModel
SilverTrackData
Importer
Properties
SilverTrackData.csproj.vs10x
using System;
using System.Collections.Generic;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Threading;
using SilverTrack.Telemetry;
using TelemetryData.Telemetry;
using Visiblox.Charts;

namespace SilverTrack.ViewModel
{
    /// <summary>
    /// Provides the top level data context and contains a TelemetryChannelCollectionViewModel 
    /// which stores and displays the ChannelViewModels currently added to the application. Holds
    /// properties for the custom controls in the bottom panel, which are updated in ViewModelBase
    /// as well as Commands for playback speed, restart, full data, play/pause, import data and 
    /// add chart.
    /// </summary>
    public class TelemetryViewModel : ViewModelBase
    {
        #region Fields

        private int _counter = 0;
        private long _ticksSinceLastUpdate;
        private DispatcherTimer _timer;
        private DateTime _currentTime;
        private DateTime _lastUpdated;
        private double _startDistance;
        private double _endDistance;
        private double _lapDistance;

        //Stores Telemetry series for widgets so repeated findSeriesByName calls are not necessary.
        private TelemetrySeries _gLongSeries;
        private TelemetrySeries _gLatSeries;
        private TelemetrySeries _throttleSeries;
        private TelemetrySeries _brakeSeries;
        private TelemetrySeries _gearSeries;
        private TelemetrySeries _speedSeries;

        #endregion

        #region Properties

        /// <summary>
        /// Text displayed on the View to illustrate the current point in the data the charts 
        /// are at.
        /// </summary>
        public string TimeText
        {
            get { return _timeText; }
            set { SetField(ref _timeText, value, "TimeText"); }
        }
        private string _timeText = "00:00:000";

        /// <summary>
        /// Returns the data channels from the data provider so they can be displayed in the 
        /// channel selection combo boxes in the view.
        /// </summary>
        public List<string> Channels
        {
            get { return DataProvider.SeriesList; }
        }

        /// <summary>
        /// Bound to the selected item in the TelemetryView playback speed combo box. When get is 
        /// called on the property, the _playBackSpeedSelectedValue field is set to the Double 
        /// value of the PlaybackSpeedSelectedItem's Content.
        /// </summary>
        public ComboBoxItem PlaybackSpeedSelectedItem
        {
            get
            {
                if (_playbackSpeedSelectedItem != null)
                {
                    _playBackSpeedSelectedValue = Double.Parse(
                        _playbackSpeedSelectedItem.Content.ToString());
                }

                return _playbackSpeedSelectedItem;
            }
            set
            {
                SetField(ref _playbackSpeedSelectedItem, value, "PlaybackSpeedSelectedItem");
            }
        }
        private ComboBoxItem _playbackSpeedSelectedItem;
        private double _playBackSpeedSelectedValue = 1.0;

        /// <summary>
        /// Whether playback is paused.
        /// </summary>
        public bool Paused
        {
            get
            {
                return _paused;
            }
            set
            {
                SetField(ref _paused, value, "Paused");
            }
        }
        private bool _paused = true;

        /// <summary>
        /// The TelemetryChannelCollectionViewModel which is placed in the centre of the 
        /// Telemetry view. It is responsible for creating a layout for each of the 
        /// ChannelViewModels and displaying them in the ViewModel's layout.
        /// </summary>
        public TelemetryChannelCollectionViewModel ChannelViewModelCollection
        {
            get;
            set;
        }

        /// <summary>
        /// Provides access to the DataProvider for the ChannelViewModels 
        /// </summary>
        public ITelemetryDataProvider DataProvider
        {
            get;
            set;
        }

        #region Commands

        public ICommand AddChartCommand
        {
            get { return new RelayCommand<string>(p => AddChart()); }
        }

        public ICommand PlayButtonCommand
        {
            get { return new RelayCommand<string>(p => PlayButtonPressed()); }
        }

        public ICommand RestartButtonCommand
        {
            get { return new RelayCommand<string>(p => RestartCharts()); }
        }

        public ICommand OpenFileCommand
        {
            get { return new RelayCommand<string>(p => OpenFile()); }
        }

        public ICommand FullDataCommand
        {
            get { return new RelayCommand<string>(p => ShowFullData()); }
        }

        #endregion

        #region Custom Control Properties

        /// <summary>
        /// The percentage value of how far around the circuit the vehicle currently is.
        /// </summary>
        public double Position
        {
            get { return _position; }
            set { SetField(ref _position, value, "Position"); }
        }
        private double _position = 0.0;

        /// <summary>
        /// The current longitudinal g-force being exerted on the vehicle.
        /// </summary>
        public double Long
        {
            get { return _long; }
            set { SetField(ref _long, value, "Long"); }
        }
        private double _long = 0.0;

        /// <summary>
        /// The current Lateral g-force being exerted on the vehicle.
        /// </summary>
        public double Lateral
        {
            get { return _lateral; }
            set { SetField(ref _lateral, value, "Lateral"); }
        }
        private double _lateral = 0.0;

        /// <summary>
        /// The current brake pressure being applied to the vehicle.
        /// </summary>
        public double Brake
        {
            get { return _brake; }
            set { SetField(ref _brake, value, "Brake"); }
        }
        private double _brake = 0.0;

        /// <summary>
        /// The current throttle depression percentage.
        /// </summary>
        public double Throttle
        {
            get { return _throttle; }
            set { SetField(ref _throttle, value, "Throttle"); }
        }
        private double _throttle = 0.0;

        /// <summary>
        /// The vehicle's current speed for the speedometer.
        /// </summary>
        public double Speed
        {
            get { return _speed; }
            set { SetField(ref _speed, value, "Speed"); }
        }
        private double _speed = 0.0;

        #endregion

        #endregion

        #region Methods

        #region Constructors

        public TelemetryViewModel() { }

        /// <summary>
        /// Creates a Telemetry View Model. Also calculates the total lap distance for the current file, as well as a range for each
        /// of the series in the seriesList. The rendering loop for animation is also started in this constructor.
        /// </summary>
        /// <param name="provider">The Data Provider for the View Model</param>
        public TelemetryViewModel(ITelemetryDataProvider provider)
        {
            ChannelViewModelCollection = new TelemetryChannelCollectionViewModel();
            DataProvider = provider;

            var distanceSeries = provider.FindSeriesFromName("DISTANCE").DataList;
            _startDistance = distanceSeries[0].Value;
            _endDistance = distanceSeries[distanceSeries.Count - 1].Value;
            _lapDistance = _endDistance - _startDistance;

            //Initialises the series used for the widgets.
            _gLongSeries = DataProvider.FindSeriesFromName("GLONG");
            _gLatSeries = DataProvider.FindSeriesFromName("GLAT");
            _throttleSeries = DataProvider.FindSeriesFromName("THROTTLE");
            _brakeSeries = DataProvider.FindSeriesFromName("BRAKE");
            _gearSeries = DataProvider.FindSeriesFromName("GEAR");
            _speedSeries = DataProvider.FindSeriesFromName("SPEED");

            //Create a dispatcher timer.
            _timer = new DispatcherTimer();
            _timer.Interval = TimeSpan.FromMilliseconds(50);
            _timer.Tick += new EventHandler(UpdateChannelViewModels);
            _timer.Start();
        }

        #endregion

        #region Public

        /// <summary>
        /// Displays and Open File Dialog which accepts CSV files and creates a stream reader based
        /// on this file which is then passed to the Model's DataProvider object.
        /// </summary>
        public void OpenFile()
        {
            //Display the Open File Dialog with a CSV File filter and multiselect disabled.
            OpenFileDialog fileDialog = new OpenFileDialog();
            fileDialog.Multiselect = false;
            fileDialog.Filter = "CSV Files (*.csv)|*.csv";

            if (fileDialog.ShowDialog().GetValueOrDefault())
            {
                StreamReader r = new StreamReader(fileDialog.File.OpenRead());
                DataProvider.ReadFile(r);
                ChannelViewModelCollection.ChannelViewModels.Clear();
                AddChartCommand.Execute("");
            }
        }

        /// <summary>
        /// Removes the specified TelemetryChannelViewModel from the ChannelViewModelCollection, and
        /// also sets the XAxisVisible property of the last TelemetryChannelViewModel to true.
        /// </summary>
        /// <param name="telemetryChannelViewModel"></param>
        public void RemoveChannelView(TelemetryChannelViewModel telemetryChannelViewModel)
        {
            ChannelViewModelCollection.ChannelViewModels.Remove(telemetryChannelViewModel);
            ChannelViewModelCollection.ChannelViewModels[ChannelViewModelCollection.ChannelViewModels.Count - 1].XAxisVisible = true;
        }

        /// <summary>
        /// Zoom every chart in the ChannelViewModelCollection to the same offset and scale.
        /// </summary>
        /// <param name="zoomOffset">The Zoom Offset</param>
        /// <param name="zoomScale">The Zoom Scale</param>
        public void ZoomAll(double zoomOffset, double zoomScale)
        {
            foreach (TelemetryChannelViewModel channelViewModel in ChannelViewModelCollection.ChannelViewModels)
            {
                channelViewModel.ZoomTo(zoomOffset, zoomScale);
            }
        }

        /// <summary>
        /// Move the mouse position of every TelemetryChannelViewModel to the same position when it
        /// is moved on one of the TelemetryChannelViewModels.
        /// </summary>
        /// <param name="mousePosition">Point representing the current position of the mouse 
        /// relative to the chart </param>
        public void MouseMove(Point mousePosition)
        {
            foreach (TelemetryChannelViewModel channelViewModel in ChannelViewModelCollection.ChannelViewModels)
            {
                channelViewModel.MouseMove(mousePosition);
            }
        }

        /// <summary>
        /// Returns channel data when given a name passed as a string.
        /// </summary>
        /// <param name="channel">The name of the channel to be returned.</param>
        /// <returns>The requested DataSeries.</returns>
        public DataSeries<DateTime, double> GetChannelData(string channel)
        {
            return TelemetryDataToVisibloxDataSeries(DataProvider.FindSeriesFromName(channel));
        }

        #endregion

        #region Private/Internal

        /// <summary>
        /// Called by the _timer DispatcherTimer every 50ms. Calculates the time elapsed since the last
        /// update, and then calls updateChart on the ChannelViewModels in the ChannelViewModel collection.
        /// The widgets are also updated based on the counter. If the chart is paused, the widgets are 
        /// updated based on the selected index of the top chart (which should be synchronised with the other
        /// charts in the application).
        /// </summary>
        private void UpdateChannelViewModels(object sender, EventArgs e)
        {
            //Calculate how much time has passed since the last update in ticks.
            _ticksSinceLastUpdate = DateTime.Now.Ticks - _lastUpdated.Ticks;

            //If the charts are paused, do not calculate the updated _currentTime, 
            //update anything on the display or check to see if any data points need to be added.
            if (!Paused)
            {
                //Calculate the number of ticks to add based on the playback speed and add the 
                //number to the current time.
                TimeSpan timeToAdd = TimeSpan.FromTicks(
                    (long)(_ticksSinceLastUpdate * _playBackSpeedSelectedValue));
                _currentTime = _currentTime.Add(timeToAdd);
                TimeText = _currentTime.ToString("mm:ss.fff");

                int tmpCounter = 0;
                foreach (TelemetryChannelViewModel channelViewModel in
                    ChannelViewModelCollection.ChannelViewModels)
                {
                    tmpCounter = channelViewModel.UpdateChart(_counter, _currentTime);
                }
                _counter = tmpCounter;

                UpdateControlsFromIndex(_counter);
            }
            else
            {
                int selectedIndex = 0;
                selectedIndex = ChannelViewModelCollection.ChannelViewModels[0].CurrentSelectedPoint;

                if (selectedIndex > 0)
                {
                    UpdateControlsFromIndex(selectedIndex);
                    TimeText = _speedSeries.DataList[selectedIndex].Time.ToString("mm:ss.fff");
                }
            }

            //Set the last update to be the now, so that the difference between the time when the 
            //charts update next and this time can be calculated.
            _lastUpdated = DateTime.Now;
        }

        /// <summary>
        /// Updates the controls on the chart given a selected index to read the value from the series.
        /// </summary>
        /// <param name="selectedIndex">The index to display.</param>
        private void UpdateControlsFromIndex(int selectedIndex)
        {
            Position = GetDistancePercentage(selectedIndex);

            Long = _gLongSeries.DataList[selectedIndex].Value;
            Lateral = _gLatSeries.DataList[selectedIndex].Value;

            Throttle = _throttleSeries.DataList[selectedIndex].Value;

            var maxBrake = _brakeSeries.Maximum;
            Brake = _brakeSeries.DataList[selectedIndex].Value / maxBrake * 100;

            Speed = _speedSeries.DataList[selectedIndex].Value;
        }

        private double GetDistancePercentage(int counter)
        {
            double currentDistance = DataProvider.FindSeriesFromName("DISTANCE").DataList[counter].Value;
            return (double)currentDistance / _lapDistance;
        }

        /// <summary>
        /// Clears the current data in the charts before adding all of the data points to each.
        /// </summary>
        private void ShowFullData()
        {
            ClearCharts();
            foreach (TelemetryChannelViewModel channelViewModel in
                ChannelViewModelCollection.ChannelViewModels)
            {
                _counter = channelViewModel.UpdateFullChart();
            }
            Paused = true;
        }

        /// <summary>
        /// Clears all of the channel view models in the collection.
        /// </summary>
        private void ClearCharts()
        {
            foreach (TelemetryChannelViewModel channelViewModel in
                ChannelViewModelCollection.ChannelViewModels)
            {
                channelViewModel.Clear();
            }
        }

        /// <summary>
        /// Adds a chart to the application with the default RPM primary series and NONE 
        /// secondary series. The new chart (added at the bottom) has XAxisVisible set to true,
        /// and all other charts have XAxisVisible set to false.
        /// 
        /// Shows full data once it has been added. 
        /// </summary>
        private void AddChart()
        {
            if (ChannelViewModelCollection.ChannelViewModels.Count < 3)
            {
                TelemetryChannelViewModel newModel = new TelemetryChannelViewModel(this);
                newModel.XAxisVisible = true;

                ChannelViewModelCollection.ChannelViewModels.Add(newModel);
            }

            for (int i = 0; i < ChannelViewModelCollection.ChannelViewModels.Count - 1; i++)
            {
                ChannelViewModelCollection.ChannelViewModels[i].XAxisVisible = false;
            }
        }

        /// <summary>
        /// Converts a TelemetryDataSeries into a DataSeries&lt;DateTime, double&gt; which can be bound to the DataSeries
        /// of a LineSeries on a Visiblox Chart.
        /// </summary>
        /// <param name="telemetrySeries">The TelemetrySeries to convert.</param>
        /// <returns>The DataSeries&lt;DateTime, double&gt; representation of the TelemetrySeries. </returns>
        private static DataSeries<DateTime, double> TelemetryDataToVisibloxDataSeries(TelemetrySeries telemetrySeries)
        {
            DataSeries<DateTime, double> tmpDataSeries = new DataSeries<DateTime, double>();
            int i = 0;
            foreach (TimestampedDataPoint point in telemetrySeries.DataList)
            {
                tmpDataSeries.Add(new DataPoint<DateTime, double>(point.Time, point.Value) { Tag = i });
                i++;
            }
            tmpDataSeries.Title = telemetrySeries.Name;
            return tmpDataSeries;
        }

        /// <summary>
        /// When the Play Button is pressed, invert the paused Boolean value and check to see if the current _counter value
        /// is at the end of the data series. If it is, restart the charts.
        /// </summary>
        private void PlayButtonPressed()
        {
            Paused = !Paused;
            int length = -1;
            while (length == -1)
            {
                foreach (TelemetryChannelViewModel channelViewModel in
                    ChannelViewModelCollection.ChannelViewModels)
                {
                    length = channelViewModel.Length;
                }
            }
            if (_counter > length - 2)
            {
                RestartCharts();
                Paused = false;
            }
        }

        /// <summary>
        /// Resets the charts and sets the counter to 0 so data can be added to the chart again.
        /// </summary>
        private void RestartCharts()
        {
            ClearCharts();
            _counter = 0;
            _currentTime = DateTime.MinValue;
            _lastUpdated = DateTime.Now;
            Paused = false;
        }

        #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)

Share

About the Author

Colin Eberhardt
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

-

You may also be interested in...

Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web01-2016 | 2.8.180820.1 | Last Updated 20 Apr 2011
Article Copyright 2011 by Colin Eberhardt
Everything else Copyright © CodeProject, 1999-2018
Layout: fixed | fluid