using System; using System.Collections.Generic; using System.Linq; using System.Text; using Aitex.UI.Charting.Model; using System.Threading; using Abt.Controls.SciChart; using System.Windows.Media; using Aitex.UI.Charting.Command; using System.Windows.Input; using System.Collections.ObjectModel; using System.Windows.Threading; using System.Windows; using System.Windows.Controls; using Abt.Controls.SciChart.Model.DataSeries; using Abt.Controls.SciChart.Visuals.RenderableSeries; using System.ComponentModel; using Aitex.UI.Charting.View; using DataAnalysisControl.Core; namespace Aitex.UI.Charting.ViewModel { public sealed class CommonViewModel : ChartingBaseViewModel { /// /// 获取所有数据曲线对象 /// public List GetAllDataLineSeries() { List lines = new List(); for (int i = 0; i < RenderableSeries.Count; i++) { var line = RenderableSeries[i] as MyLineSeries; if (line != null) { lines.Add(line); } } return lines; } /// /// new object is not allowed /// private CommonViewModel() { //主窗口的显示 DataConfigViewVisibility = Visibility.Collapsed; DataDisplayViewVisibility = Visibility.Visible; Switch2DataCfgViewCommand = new ChartingCommand((o) => true, (o) => { DataDisplayViewVisibility = Visibility.Collapsed; DataConfigViewVisibility = Visibility.Visible; InvokePropertyChanged("DataDisplayViewVisibility"); InvokePropertyChanged("DataConfigViewVisibility"); }); Switch2DataDisplayViewCommand = new ChartingCommand((o) => true, (o) => { DataConfigViewVisibility = Visibility.Collapsed; DataDisplayViewVisibility = Visibility.Visible; InvokePropertyChanged("DataDisplayViewVisibility"); InvokePropertyChanged("DataConfigViewVisibility"); }); RenderableSeries = new ObservableCollection(); DataSources = new Dictionary(); //显示或隐藏数据配置窗口 ToggleDataDisplayPanelCommand = new ChartingCommand((o) => true, (o) => { if (DataConfigViewRow != null) { DataConfigViewRow.Height = new GridLength(DataConfigViewRow.Height.Value > 0 ? 0 : 150); InvokePropertyChanged("IsDataConfigVisiable"); } }); //Charting控件的某些元素必须STA,UI主线程完成,因此此处需定义个DispatcherTimer来实现某些GUI相关的操作 _dispatcherTimer = new DispatcherTimer(); _dispatcherTimer.Interval = new TimeSpan(0, 0, 0, 0, 100); _dispatcherTimer.Tick += new EventHandler(_dispatcherTimer_Tick); } public void Start() { if (_isAlive) return; _isAlive = true; System.Threading.Tasks.Task.Factory.StartNew(TaskRun); _dispatcherTimer.Start(); } public void Stop() { _dispatcherTimer.Stop(); _isAlive = false; } bool _isAlive = false; /// /// DispatcherTimer中定期处理一些和GUI相关的处理,例如新建、删除LineSeries对象等 /// /// /// void _dispatcherTimer_Tick(object sender, EventArgs e) { //执行命令队列中的指令 if (_commandQueue.Count > 0) { if (!Monitor.TryEnter(_dataLocker)) return; try { IEnumerable commandList = null; lock (_cmdQLocker) { commandList = _commandQueue.ToList(); _commandQueue.Clear(); } foreach (var command in commandList) { command.Execute(this); } } catch (Exception ex) { CONTEXT.WriteLog(ex); } finally { Monitor.Exit(_dataLocker); } } } DispatcherTimer _dispatcherTimer; //单实例定义 private readonly static Lazy _instance = new Lazy(() => new CommonViewModel(), true); /// /// 获取当前对象的单实例 /// public static CommonViewModel Instance { get { return _instance.Value; } } #region view switch public ICommand ToggleDataDisplayPanelCommand { get; set; } public Visibility DataConfigViewVisibility { get; private set; } public Visibility DataDisplayViewVisibility { get; private set; } public ICommand Switch2DataCfgViewCommand { get; set; } public ICommand Switch2DataDisplayViewCommand { get; set; } #endregion #region data source operations /// /// 生长率曲线相关的定义 /// public MyLineSeries GrowthRateRenderableSeries { get; set; } public RowDefinition DataConfigViewRow { get; set; } public bool IsDataConfigVisiable { get { return DataConfigViewRow != null && DataConfigViewRow.Height.Value > 0; } } Queue _commandQueue = new Queue(); object _cmdQLocker = new object(); object _dataLocker = new object(); public Dictionary DataSources { get; set; } public ObservableCollection RenderableSeries { get; set; } public DateTime Time1 { get; set; } //时间标尺1 public DateTime Time2 { get; set; } //时间标尺2 IDataSource _currentSelectedDataSource; public IDataSource CurrentSelectedDataSource { get { return _currentSelectedDataSource; } set { if ((value != null) &&(_currentSelectedDataSource == null || _currentSelectedDataSource != value)) { if (!string.IsNullOrEmpty(value.WaferDisplayIndex) && (_currentSelectedDataSource == null || !value.WaferDisplayIndex.Equals(_currentSelectedDataSource.WaferDisplayIndex))) { WaferDisplayIndex = value.WaferDisplayIndex; } else { WaferDisplayIndex = GetWaferDisplayIndex(value.BeginTime, value.ChamberName); } value.WaferDisplayIndex = WaferDisplayIndex; } _currentSelectedDataSource = value; if (_currentSelectedDataSource != null) CurrentSelectedDataSourceName = _currentSelectedDataSource.Title; InvokePropertyChanged("CurrentSelectedDataSource"); InvokePropertyChanged("CurrentSelectedDataSourceName"); } } /// /// 获取数据曲线对象 /// /// /// public MyLineSeries GetDataLineSeries(string uniqueDataId) { for (int i = 0; i < RenderableSeries.Count; i++) { var line = RenderableSeries[i] as MyLineSeries; if (line != null && line.UniqueId == uniqueDataId) { return RenderableSeries[i] as MyLineSeries; } } return null; } public string CurrentSelectedDataSourceName { get; private set; } public ObservableCollection DataSourceList { get { var ret = new ObservableCollection(); foreach (var key in DataSources.Keys) ret.Add(DataSources[key]); return ret; } } public List DataSourceNameList { get { return DataSources.Keys.ToList(); } } /// /// 移除先前绘制的生长率计算曲线 /// public void ClearGrowthRateCurve() { lock (_cmdQLocker) { _commandQueue.Enqueue(new RemoveGrowthCurveCommand()); } } /// /// 新增DataSource对象 /// /// /// public void AddDataSource(IDataSource dataSource) { lock (_cmdQLocker) { CONTEXT.WriteLog("添加数据源"); _commandQueue.Enqueue(new AddDataSourceCommand(dataSource)); } } /// /// 移除DataSource对象 /// /// 移除指定的数据源,如果sourceName为null,则移除所有的数据源 /// public void RemoveDataSource(string sourceName) { lock (_cmdQLocker) { _commandQueue.Enqueue(new RemoveDataSourceCommand(sourceName)); } } #endregion #region data item operations /// /// 添加Data Item对象 /// /// /// /// /// /// /// /// public void AddDataSeries(string dataSourceName, string seriesName, string displayName, Color lineColor, float factor, float offset, int lineWidth) { lock (_cmdQLocker) { _commandQueue.Enqueue(new AddDataSeriesCommand(dataSourceName, seriesName, displayName, lineColor, factor, offset, lineWidth)); } } /// /// 显示所有当前数据 /// public void ShowAllSeries() { lock (_cmdQLocker) { _commandQueue.Enqueue(new ShowAllSeriesCommand()); } } /// /// 隐藏当前所有数据 /// public void HidAllSeries() { lock (_cmdQLocker) { _commandQueue.Enqueue(new HidAllSeriesCommand()); } } /// /// 显示预设的数据管理对话框 /// public void LoadPresetSetting() { lock (_cmdQLocker) { _commandQueue.Enqueue(new LoadPresetSettingCommand()); } } /// /// 数据源时间更新 /// /// /// /// public void SyncSourceTime(string dataSourceName, DateTime syncTimePoint, string syncStepName) { lock (_cmdQLocker) { _commandQueue.Enqueue(new SourceTimeSyncCommand(dataSourceName, syncTimePoint, syncStepName)); } } /// /// 数据缩放因子+Offset改变命令 /// /// /// public void ChangeDataDisplayFactor(string uniqueDataName, double newFactor, double newOffset) { lock (_cmdQLocker) { _commandQueue.Enqueue(new ChangeFactorOffsetCommand(uniqueDataName, newFactor, newOffset)); } } /// /// 去除DataItem对象 /// /// public void RemoveDataSeries(string uniqueDataId) { lock (_cmdQLocker) { _commandQueue.Enqueue(new RemoveDataSeriesCommand(uniqueDataId)); } } /// /// 保存当前所有数据 /// public void ExportAllSeries() { lock (_cmdQLocker) { _commandQueue.Enqueue(new ExportAllSeriesCommand()); } } #endregion private int GetIndex(List Total, int index) { int _result = -1; //string[] _temp = Total.Split(','); //for (int i = 0; i < _temp.Length; i++) //{ // if (_temp[i].PadLeft(2,'0').Equals(index.ToString().Trim().PadLeft(2,'0'))) // { // _result = i+1; // break; // } //} _result = Total.FindIndex((x) => x == (index.ToString().Trim().PadLeft(2, '0'))); if (_result >= 0) { _result++; } return _result; } #region thread - timely retrieve data /// /// data getting task /// void TaskRun() { while (_isAlive) { Thread.Sleep(100); try { //为提升效率,将线程从IDataSource对象获取数据的缓慢过程尽量和Chart对象通过UI线程的更新分开进行 //通过Dictionary将数据进行当前线程内的暂时缓存 List> newDataDic = new List>(); Dictionary>> rqDataDic = new Dictionary>>(); lock (_dataLocker) { foreach(MyLineSeries line in RenderableSeries) { if (DateTime.Now < line.NextQueryTime || line == null || line.DataSource == null) continue; IDataSource dataSource = line.DataSource; //数据源对象 string chamName = dataSource.ChamberName; //Chamber名称 DateTime lastUpdateTime = line.LastUpdateTime; //最后一次获取数据的时间 DateTime dataEndTime = dataSource.EndTime; //数据源的结束时间 if (lastUpdateTime >= dataEndTime) continue; DateTime nextDataTime = lastUpdateTime.Date + new TimeSpan(0, 23, 59, 59, 999); //ChartDataDic[dataId].Item4.LastUpdateTime + new TimeSpan(2, 0, 0); if (nextDataTime > dataEndTime) nextDataTime = dataEndTime; string dataName = line.SeriesName; //if(int.TryParse(WaferDisplayIndex.Split(',')[index-1],out index)) if (!rqDataDic.ContainsKey(dataSource)) rqDataDic.Add(dataSource, new List>()); rqDataDic[dataSource].Add(new Tuple(dataSource, dataName, line.UniqueId, lastUpdateTime, nextDataTime)); } } //优化1:在此作判断,如果多个DataItem的如果在同一个DataSource中而且beginTime,EndTime完全一致,那么合并后进行Select Query可以进行加速 foreach (var ds in rqDataDic.Keys) { var tempDic = new Dictionary>>(); foreach (var daq in rqDataDic[ds]) { string id = string.Format("{0}-{1}", daq.Item4.Ticks, daq.Item5.Ticks); if (!tempDic.ContainsKey(id)) tempDic.Add(id, new List>()); tempDic[id].Add(daq); } //优化2:如果存在2组不同时间区间的数据请求,那么优化执行时间较早的数据请求,目的为了将多组不同时间段的请求合并为一个SQL语句,提升SQL查询效率 DateTime earliestEndT = DateTime.MaxValue; foreach (var tickKey in tempDic.Keys) { var endT = tempDic[tickKey][0].Item5; if (endT < earliestEndT) earliestEndT = endT; } ListremoveQueryList = new List(); foreach (var tickKey in tempDic.Keys) { var beginT = tempDic[tickKey][0].Item4; if (beginT > earliestEndT) removeQueryList.Add(tickKey); } foreach (var tickKey in removeQueryList) tempDic.Remove(tickKey); //database query foreach (var tickKey in tempDic.Keys) { List dataNameList = new List(); List dataIdList = new List(); foreach (var da in tempDic[tickKey]) { dataNameList.Add(da.Item2); dataIdList.Add(da.Item3); } if (tempDic[tickKey].Count > 0) { List rtdata; var singleData = tempDic[tickKey][0]; var dataSource = singleData.Item1; var beginT = singleData.Item4; var endT = singleData.Item5; var isSucc = dataSource.GetData(dataNameList, beginT, endT, out rtdata); if (isSucc) { for (int ss = 0; ss < dataIdList.Count; ss++) { newDataDic.Add(new Tuple(dataIdList[ss], rtdata[ss], endT)); } } } } } //对Chart对象的数据集进行集中更新 Application.Current.Dispatcher.Invoke(new Action>>((o) => { if (o.Count > 0) { lock (_dataLocker) { var nextRetryQueryTime = DateTime.Now + new TimeSpan(0, 0, 0, 2); foreach (var item in o) { var dataId = item.Item1; var dataName = item.Item2.DataName; var line = GetDataLineSeries(dataId); if (line == null) continue; if (item.Item2.TimeStamp.Count == 0) //get nothing for this querying { if (item.Item3.Date >= DateTime.Now.Date)//possible data is still in generating today, try again 2 seconds later { line.NextQueryTime = nextRetryQueryTime; } else //data not available for this day, jump to query next day { line.NextQueryTime = DateTime.Now; line.LastUpdateTime = item.Item3.Date + new TimeSpan(1, 0, 0, 0); } } else //get something for this querying { //update next query time point line.NextQueryTime = DateTime.Now; if (item.Item3.Date >= DateTime.Now.Date) //update the last update time by exactly last data timestamp, for some more data is in generating state line.LastUpdateTime = item.Item2.TimeStamp[item.Item2.TimeStamp.Count - 1] + new TimeSpan(0, 0, 0, 0, 500); else line.LastUpdateTime = item.Item3.Date + new TimeSpan(1, 0, 0, 0); //write to data buf line.DataSource.Datas[dataName].RawData.AddRange(item.Item2.RawData); line.DataSource.Datas[dataName].TimeStamp.AddRange(item.Item2.TimeStamp); var timeMove = line.DataSource.TimeMove; var factorScale = (float)line.Factor; var offset = (float)line.Offset; var dataSet = line.DataSeries as DataSeries; if (timeMove.Ticks != 0 && (factorScale == 1 && offset == 0)) { List movedTimeList = new List(); foreach (var time1 in item.Item2.TimeStamp) { movedTimeList.Add(time1 + timeMove); } dataSet.Append(movedTimeList, item.Item2.RawData); } else if (timeMove.Ticks == 0 && (factorScale != 1 || offset != 0)) { List scaledValue = new List(); foreach (var value1 in item.Item2.RawData) { scaledValue.Add(value1 * factorScale + offset); } dataSet.Append(item.Item2.TimeStamp, scaledValue); } else if (timeMove.Ticks != 0 && (factorScale != 1|| offset != 0)) { List movedTimeList = new List(); List scaledValue = new List(); foreach (var time1 in item.Item2.TimeStamp) { movedTimeList.Add(time1 + timeMove); } foreach (var value1 in item.Item2.RawData) { scaledValue.Add(value1 * factorScale + offset); } dataSet.Append(movedTimeList, scaledValue); } else { dataSet.Append(item.Item2.TimeStamp, item.Item2.RawData); } } } } InvokePropertyChanged("RenderableSeries"); } }), newDataDic); } catch (Exception ex) { CONTEXT.WriteLog(ex, "Charting绘图Task运行发生异常"); } } } #endregion } }