using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Forms;
using Aitex.Core.RT.Log;
using Aitex.Sorter.Common;
using MECF.Framework.Common.DataCenter;
using MECF.Framework.Common.Equipment;
using MECF.Framework.UI.Client.CenterViews.DataLogs.Event;
using MECF.Framework.UI.Client.CenterViews.DataLogs.ProcessHistory;
#if !EXPORT_TO_CSV
using MECF.Framework.Common.Utilities;
#endif
using MECF.Framework.UI.Client.CenterViews.Operations.RealTime;
using MECF.Framework.UI.Client.ClientBase;
using MECF.Framework.UI.Client.ClientBase.Charting;
using MECF.Framework.UI.Client.ClientBase.EventArgs;
using MECF.Framework.UI.Client.ClientBase.Pipelines;
using MECF.Framework.UI.Client.ClientBase.Tree;
using SciChart.Charting.Model.DataSeries;
using SciChart.Charting.Visuals.Axes;
using SciChart.Charting.Visuals.RenderableSeries;
using SciChart.Core.Extensions;
using SciChart.Core.Framework;
using SciChart.Data.Model;
using Cali = Caliburn.Micro;
using DataGridCell = System.Windows.Controls.DataGridCell;
using DateRange = SciChart.Data.Model.DateRange;
using MessageBox = System.Windows.MessageBox;
using TreeNode = MECF.Framework.UI.Client.ClientBase.Tree.TreeNode;
namespace MECF.Framework.UI.Client.CenterViews.DataLogs.DataHistory
{
public class DataViewModel : BusyIndicateableUiViewModelBase
{
#region Variables
///
/// 一次最多查询的项目数。
///
private const int MAX_ITEMS_PER_QUERY = 50;
private IRange _timeRange;
private IRange _visibleRangeValue;
private AutoRange _autoRange;
private CancellationTokenSource _cancellationTokenSource;
///
/// 更新报告导出信息。
///
private readonly IProgress _progCsvExport;
///
/// 查询进度信息更新。
///
private IProgress _progQueryUpdate;
///
/// 挂起或恢复Chart更新
///
private readonly IProgress _progChartSuspendUpdating;
private IUpdateSuspender _suspender;
///
/// 打开错误消息对话框。
///
private readonly IProgress _progShowErrorMessageBox;
#endregion
#region Constructors
public DataViewModel()
{
DisplayName = "Data History";
SelectedData = new ChartingLineSeriesCollection(DisplayName);
var now = DateTime.Today;
SearchBeginTime = DateTime.Now.AddHours(-1); // -new TimeSpan(1, 0, 0, 0);
SearchEndTime = DateTime.Now;
//SearchBeginTime = new DateTime(now.Year, now.Month, now.Day, 00, 00, 00); ;// -new TimeSpan(1, 0, 0, 0);
//SearchEndTime = new DateTime(now.Year, now.Month, now.Day, 23, 59, 59);
var provider = new RealtimeProvider();
ParameterNodes = new TreeNode(DisplayName)
{
MaxTerminalSelectionAllowed = MAX_ITEMS_PER_QUERY
};
ParameterNodes.ChildNodes.AddRange(provider.GetTreeNodeParameters());
VisibleRangeTime = new DateRange(DateTime.Now.AddMinutes(-60), DateTime.Now.AddMinutes(60));
VisibleRangeValue = new DoubleRange(0, 10);
_progQueryUpdate = new Progress(e =>
{
if (e.CurrentProgress == e.TotalProgress)
{
IsBusy = false;
if (_cancellationTokenSource?.Token.IsCancellationRequested == false)
{
foreach (var renderableSeries in SelectedData)
{
var series = renderableSeries as FastLineSeries;
var dataSeries = series?.GetDataSeries();
try
{
if (series != null && dataSeries != null)
{
var node = series.BackendParameterNode;
if (double.IsInfinity((double)dataSeries.YRange.Diff))
{
node.ClearStatistic();
}
else
{
var min = ((double)dataSeries.YMin);
var max = ((double)dataSeries.YMax);
var average =
dataSeries.Metadata.Cast().Average(x => x.Value);
node.SetStatistic(min, max, average);
}
}
}
catch (Exception ex)
{
var err = $"It's failed to load data of {series?.DataName ?? "Unknown"}, {ex.Message}";
LOG.Error(err, ex);
}
}
}
ChartAutoRange = AutoRange.Never;
((DataView)View).chart.ZoomExtents();
}
BusyIndicatorContent = e.Message;
});
_progCsvExport = new Progress(e =>
{
BusyIndicatorContent = e.Message;
});
_progChartSuspendUpdating = new Progress(isSuspend =>
{
if (isSuspend)
_suspender = ((DataView)View).chart.SuspendSuspendUpdates();
else
{
try
{
if (_suspender?.IsSuspended == true)
_suspender?.SafeDispose();
}
catch (Exception)
{
// 查询过程最后会强制恢复挂起一次,可能引发异常,忽略此异常。
}
}
});
_progShowErrorMessageBox = new Progress((error =>
{
System.Windows.Forms.MessageBox.Show(error, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}));
}
#endregion
#region Property
public bool IsPermission => Permission == 3;
private DataView view;
public TreeNode ParameterNodes { get; }
public ChartingLineSeriesCollection SelectedData { get; set; }
public AutoRange ChartAutoRange
{
get => _autoRange;
set
{
_autoRange = value;
NotifyOfPropertyChange(nameof(ChartAutoRange));
}
}
public IRange VisibleRangeTime
{
get => _timeRange;
set
{
_timeRange = value;
NotifyOfPropertyChange(nameof(VisibleRangeTime));
}
}
public IRange VisibleRangeValue
{
get => _visibleRangeValue;
set
{
_visibleRangeValue = value;
NotifyOfPropertyChange(nameof(VisibleRangeValue));
}
}
//public DateTime StartDateTime
//{
// get => ((DataView)View).wfTimeFrom.Value;
// set
// {
// ((DataView)View).wfTimeFrom.Value = value;
// NotifyOfPropertyChange(nameof(StartDateTime));
// }
//}
//public DateTime EndDateTime
//{
// get => ((DataView)View).wfTimeTo.Value;
// set
// {
// ((DataView)View).wfTimeTo.Value = value;
// NotifyOfPropertyChange(nameof(EndDateTime));
// }
//}
public DateTime SearchBeginTime { get; set; }
public DateTime SearchEndTime { get; set; }
#endregion
#region Methods
protected override void OnViewLoaded(object view)
{
base.OnViewLoaded(view);
this.view = (DataView)view;
//StartDateTime = DateTime.Now.Date;
//EndDateTime = DateTime.Now.Date.AddDays(1).AddTicks(-1);
this.view.wfTimeFrom.Content = SearchBeginTime.ToString("yyyy-MM-dd HH:mm:ss");
this.view.wfTimeTo.Content = SearchEndTime.ToString("yyyy-MM-dd HH:mm:ss");
}
public void SelectDate(string SelectType)
{
var windowManager = Caliburn.Micro.Core.IoC.Get();
if (SelectType == "Start")
{
this.SearchBeginTime = Convert.ToDateTime(this.view.wfTimeFrom.Content);
SelectDateViewModel selectdateViewModel = new SelectDateViewModel(SearchBeginTime);
var result = (windowManager as Caliburn.Micro.WindowManager)?.ShowDialogWithTitle(selectdateViewModel, null, "Start Time");
if (result == true)
{
SearchBeginTime = selectdateViewModel.SearchDate;
this.view.wfTimeFrom.Content = selectdateViewModel.SearchDate;
}
}
else
{
this.SearchEndTime = Convert.ToDateTime(this.view.wfTimeTo.Content);
SelectDateViewModel selectdateViewModel = new SelectDateViewModel(SearchEndTime);
var result = (windowManager as Caliburn.Micro.WindowManager)?.ShowDialogWithTitle(selectdateViewModel, null, "End Time");
if (result == true)
{
this.view.wfTimeTo.Content = selectdateViewModel.SearchDate;
}
}
}
public void ZoomInClick(int index)
{
var view = GetView() as DataView;
index += 1;
if ((index & 0x01) == 1)
view.chart.sciChart.ChartModifier.XAxis.ZoomBy(-0.1, -0.1);
if ((index & 0x02) == 2)
view.chart.sciChart.ChartModifier.YAxis.ZoomBy(-0.1, -0.1);
}
public void ZoomOutClick(int index)
{
var view = GetView() as DataView;
index += 1;
if ((index & 0x01) == 1)
view.chart.sciChart.ChartModifier.XAxis.ZoomBy(0.1, 0.1);
if ((index & 0x02) == 2)
view.chart.sciChart.ChartModifier.YAxis.ZoomBy(0.1, 0.1);
}
public void ArrowClick(string direction)
{
var view = GetView() as DataView;
switch (direction)
{
case "Up":
view.chart.sciChart.ChartModifier.YAxis.Scroll(50, SciChart.Charting.ClipMode.ClipAtMax);
break;
case "Down":
view.chart.sciChart.ChartModifier.YAxis.Scroll(-50, SciChart.Charting.ClipMode.ClipAtMin);
break;
case "Left":
view.chart.sciChart.ChartModifier.XAxis.Scroll(-50, SciChart.Charting.ClipMode.ClipAtMin);
break;
case "Right":
view.chart.sciChart.ChartModifier.XAxis.Scroll(50, SciChart.Charting.ClipMode.ClipAtMax);
break;
}
}
public void Query(object parameter)
{
WaferHistoryRecipe waferHistoryRecipe = parameter as WaferHistoryRecipe;
if (waferHistoryRecipe != null)
{
System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
if (waferHistoryRecipe.StartTime != DateTime.MinValue)
SearchBeginTime = waferHistoryRecipe.StartTime;
if (waferHistoryRecipe.EndTime != DateTime.MinValue)
SearchEndTime = waferHistoryRecipe.EndTime;
else
SearchEndTime = waferHistoryRecipe.StartTime.AddHours(1);
//ParameterNodes.ForEachDo((x) =>
//{
// x.Selected = true;
// ParameterCheck(x);
//});
QueryData();
}));
}
}
///
/// 查询数据。
///
///
public void QueryData(bool isAppend = false)
{
this.SearchBeginTime = Convert.ToDateTime(this.view.wfTimeFrom.Content);
this.SearchEndTime = Convert.ToDateTime(this.view.wfTimeTo.Content);
if (SearchBeginTime > SearchEndTime)
{
MessageBox.Show("time range invalid, start time should be early than end time.", "Error",
MessageBoxButton.OK,
MessageBoxImage.Error);
return;
}
// 哪些模组(TreeView的顶层Nodes)有选中项。
var selectedModules = ParameterNodes.ChildNodes.Where(x => x.HasTerminalSelected).ToList();
if (!selectedModules.Any())
{
MessageBox.Show($"No item(s) are selected to query.", "Warning", MessageBoxButton.OK,
MessageBoxImage.Warning);
return;
}
VisibleRangeTime = new DateRange(SearchBeginTime.AddHours(-1), SearchEndTime.AddHours(1));
ChartAutoRange = AutoRange.Always;
BusyIndicatorContent = "Preparing list ...";
IsBusy = true;
_cancellationTokenSource = new CancellationTokenSource();
#region 生成曲线列表
var selectedTerminal =
selectedModules.SelectMany(x => x.Flatten(true).Where(p => p.IsSelected == true)).ToList();
List appendedSeries = null;
if (isAppend)
appendedSeries = SelectedData.Append(selectedTerminal);
else
{
SelectedData.ReArrange(selectedTerminal);
appendedSeries = SelectedData.ToList();
}
SelectedData.ResetColors();
#endregion
var dataSeriesList = appendedSeries.Select(x => x.DataSeries).ToList();
Task.Run(async () =>
{
// 延时启动,等待UI准备好
await Task.Delay(500);
var pipeline = new TwoStagePipelineBasedTaskExecutor();
pipeline.Stage2ActionStarted += (s, e) =>
{
// 挂起Chart
_progChartSuspendUpdating.Report(true);
};
pipeline.Stage2ActionFinished += (s, e) =>
{
// 挂起刷新
_progChartSuspendUpdating.Report(false);
};
pipeline.Stage1Finished += (s, e) =>
{
// 查询完毕,Chart可能仍在渲染。
_progQueryUpdate?.Report(new ProgressUpdatingEventArgs(50, 100, "Still rendering chart ..."));
};
using (pipeline)
{
var plTasks = pipeline.Start(null);
try
{
//! 按时间分段查询,解决查询速度慢导致卡后台业务的问题。
//var ts = SearchEndTime - SearchBeginTime;
//if (ts.Days <= 1)
//{
// Query(pipeline, selectedModules, dataSeriesList, view.dataGrid1, SearchBeginTime, SearchEndTime,
// _cancellationTokenSource, _progQueryUpdate);
// // 结束流水线
// pipeline.AppendFunc1(null);
// pipeline.AppendFunc2(null);
//}
//else
{
var daySlices =
DateRangeHelper.SplitInToHours(new DateRangeHelper(SearchBeginTime, SearchEndTime), 12);
foreach (var range in daySlices)
{
Query(pipeline, selectedModules, dataSeriesList, view.dataGrid1, range.Start, range.End,
_cancellationTokenSource, _progQueryUpdate);
if (_cancellationTokenSource.Token.IsCancellationRequested)
break;
await Task.Delay(1);
}
// 结束流水线
pipeline.AppendFunc1(null);
pipeline.AppendFunc2(null);
}
await Task.WhenAll(plTasks.ToArray());
}
catch (ThreadAbortException ex)
{
Thread.Sleep(500);
// 查询操作被取消。
Debug.WriteLine(ex);
}
catch (AggregateException ae)
{
var errs = new StringBuilder();
foreach (var ex in ae.Flatten().InnerExceptions)
{
LOG.Error(ex.Message, ex);
errs.AppendLine(ex.Message);
}
var errMsg = $"It's failed to query data, {errs}";
_progShowErrorMessageBox.Report(errMsg);
LOG.Error(errMsg, ae);
}
catch (Exception ex)
{
var errMsg = $"It's failed to query data, {ex.Message}";
_progShowErrorMessageBox.Report(errMsg);
LOG.Error(errMsg, ex);
}
finally
{
// 等待一下UI
Thread.Sleep(100);
_progQueryUpdate.Report(new ProgressUpdatingEventArgs(100, 100, ""));
// 强制恢复Chart刷新,避免异常情况导致的Chart挂起。
_progChartSuspendUpdating.Report(false);
}
}
});
}
///
/// 查询数据
///
private static void Query(TwoStagePipelineBasedTaskExecutor pipeline, IEnumerable selectedModules, List dataSeriesList, System.Windows.Controls.DataGrid dataGrid, DateTime startTime, DateTime endTime,
CancellationTokenSource cancellation, IProgress progressReporter = null)
{
pipeline.AppendFunc1(() =>
SearchDataBaseAsync(
selectedModules,
new DateRangeHelper(startTime, endTime),
cancellation,
progressReporter));
pipeline.AppendFunc2(ds =>
{
RenderChartAndTable(ds, dataSeriesList, dataGrid, cancellation, progressReporter);
return null;
});
//pipeline.AppendFunc2(ds =>
//{
// RenderDataGridTable(ds, , cancellation, progressReporter);
// return null;
//});
}
///
/// 取消查询。
///
public void CancelQuery()
{
Task.Run(() =>
{
if (_cancellationTokenSource?.Token.CanBeCanceled == true)
{
_cancellationTokenSource.Cancel();
}
Thread.Sleep(100);
_progQueryUpdate.Report(new ProgressUpdatingEventArgs(100, 100, ""));
_progChartSuspendUpdating.Report(false);
});
if (View is DataView view)
view.dataGrid.CancelOperation();
}
///
/// 检查指定的表名是否存在。
///
///
///
private static bool CheckTableExists(string tableName)
{
var sql =
$"SELECT EXISTS ( SELECT FROM pg_tables WHERE schemaname = 'public' AND tablename = '{tableName}' );";
var table = QueryDataClient.Instance.Service.QueryData(sql);
if (table == null)
return false;
if (table.Rows.Count <= 0)
return false;
var value = table.Rows[0][0].ToString();
if (value.ToLower() == "true")
return true;
return false;
}
///
/// 根据左侧选项查询数据
///
///
private static DataSet SearchDataBaseAsync(
IEnumerable modules, DateRangeHelper dateRange,
CancellationTokenSource cancellation = null,
IProgress progressReporter = null)
{
var ds = new DataSet();
using (cancellation?.Token.Register(Thread.CurrentThread.Abort, true))
{
// 遍历模组
foreach (var module in modules)
{
var sql = new StringBuilder();
//! 因为数据库中按天拆表,无法一次性查询数据,需使用UNION合并多表查询,因此此处按天拼接SQL表达式
// 最终SQL表达式结构为:
// (select xx from date1.xx) union (select xx from date2.xx) union (select xx from date3.xx)
// where time between xxx and xxx
// order by time asc
var ts = dateRange.Diff;
for (var day = 0; day <= ts.Days; day++)
{
// 检查表名是否存在,否则SQL执行出错。
var tblName = $"{dateRange.Start.AddDays(day):yyyyMMdd}.{module}";
if (module.ToString() == "IO")
{
//var node = module.ChildNodes.FirstOrDefault(x => (bool)x.HasTerminalSelected);
tblName = $"{dateRange.Start.AddDays(day):yyyyMMdd}.System";
}
else
if (!ModuleHelper.IsPm(module.ToString()))
{
tblName = $"{dateRange.Start.AddDays(day):yyyyMMdd}.System";
}
if (CheckTableExists(tblName))
{
sql.Append("select \"time\" AS InternalTimeStamp");
var selectedParams = module.Flatten(true)
.Where(x => x.IsSelected == true);
// 添加待查询的列
foreach (var item in selectedParams)
{
sql.Append("," + $"\"{item}\"");
}
sql.Append($" from \"{tblName}\" ");
if (day < ts.Days)
sql.Append(" UNION ");
}
}
// 所有表名不可用,可能是日期范围错误
if (sql.Length <= 0)
{
continue;
}
sql.Append(
$" where \"time\" between {dateRange.Start.Ticks} and {dateRange.End.Ticks} order by InternalTimeStamp asc");
progressReporter?.Report(new ProgressUpdatingEventArgs(20, 100,
$"Querying {dateRange}..."));
if (cancellation?.Token.IsCancellationRequested == true)
return null;
// 查询数据并将返回的结果存储在DataSet中
var dataTable = QueryDataClient.Instance.Service.QueryData(sql.ToString());
if (cancellation?.Token.IsCancellationRequested == true)
return null;
//! 返回的 DataTable 可能不存在,原因是上述代码自动生成的表明可能不存在。
if (dataTable == null)
continue;
dataTable.TableName = module.Name;
ds.Tables.Add(dataTable);
}
}
return ds;
}
///
/// 渲染图表。
///
///
///
///
///
///
private static void RenderChartAndTable(
DataSet ds, List dataSeriesList, System.Windows.Controls.DataGrid dataGrid,
CancellationTokenSource cancellation = null,
IProgress progressReporter = null)
{
if (ds == null || ds.Tables.Count <= 0)
return;
Dictionary> bigTable = new Dictionary>();
System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
dataGrid.Columns.Clear();
DataGridTextColumn dataGridTextColumn = new DataGridTextColumn();
dataGridTextColumn.Header = "Time";
dataGridTextColumn.Binding = new System.Windows.Data.Binding("internaltimestamp");
((System.Windows.Data.Binding)dataGridTextColumn.Binding).Converter = new StringToDateTimeStringConvert();
dataGrid.Columns.Add(dataGridTextColumn);
}));
// 一个Table一个模组
foreach (var table in ds.Tables.Cast())
{
if (table.Rows.Count <= 0)
continue;
// 一列对应模组中的某个数据记录点
foreach (var col in table.Columns.Cast())
{
// 忽略时间列
if (col.Ordinal == 0)
continue;
Dictionary dictColumn = new Dictionary();
var fullName = col.ColumnName;
col.ColumnName = col.ColumnName.Replace(".", "");
var dataSeries =
dataSeriesList.FirstOrDefault(x => x.SeriesName == fullName) as XyDataSeries;
if (dataSeries == null)
continue;
var rows = table.Rows;
var dateList = new List();
var valueList = new List();
var metaList = new List();
for (var i = 0; i < rows.Count; i++)
{
var date = new DateTime(long.Parse(rows[i][0].ToString()));
var cellValue = rows[i][col];
var value = double.NaN;
if (cellValue is bool b)
value = b ? 1 : 0;
else if (double.TryParse(cellValue.ToString(), out var num))
value = num;
else
value = 0;
dateList.Add(date);
valueList.Add(value);
metaList.Add(new ParameterNodePoint(date, value));
dictColumn.Add(long.Parse(rows[i][0].ToString()), value.ToString());
if (cancellation?.Token.IsCancellationRequested == true)
return;
}
bigTable.Add(fullName.Replace(".", ""), dictColumn);
System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
DataGridTextColumn dataGridTextColumn2 = new DataGridTextColumn();
dataGridTextColumn2.Header = fullName;
dataGridTextColumn2.Binding = new System.Windows.Data.Binding(fullName.Replace(".", ""));
var MyStyle = new Style(typeof(DataGridCell))
{
Setters = {
new Setter(TextBlock.TextAlignmentProperty, TextAlignment.Center)
}
};
dataGridTextColumn2.CellStyle = MyStyle;
dataGrid.Columns.Add(dataGridTextColumn2);
}));
if (cancellation?.Token.IsCancellationRequested == true)
return;
dataSeries.Append(dateList, valueList, metaList);
}
if (cancellation?.Token.IsCancellationRequested == true)
return;
// 每一轮更新完毕后会恢复Chart刷新,
// 数据量太小时会频繁挂起和恢复Chart,导致Chart不刷新,需要稍等一下UI。
Thread.Sleep(50);
}
System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
System.Data.DataTable dt = new System.Data.DataTable();
dt = new System.Data.DataTable($"Temp");
dt.Columns.Add("internaltimestamp", typeof(long));
foreach (var item in bigTable.Keys)
{
dt.Columns.Add(item, typeof(string));
}
if (bigTable.Count > 0)
{
foreach (var subitem in bigTable[bigTable.FirstOrDefault().Key].Keys)
{
var row = dt.NewRow();
row[0] = subitem;
foreach (var col in bigTable.Keys)
{
row[col] = bigTable[col][subitem];
}
dt.Rows.Add(row);
}
}
dataGrid.ItemsSource = dt.DefaultView;
}));
}
public void Exporting(object sender, EventArgs e)
{
BusyIndicatorContent = "Exporting Start ...";
IsBusy = true;
}
public void Exported(object sender, EventArgs e)
{
IsBusy = false;
}
public void Deleted(object sender, EventArgs e)
{
((DataView)View).tvParameterNodes.ClearPresetGroupSelectionOnly();
}
public void ProgressUpdating(object sender, ProgressUpdatingEventArgs e)
{
_progCsvExport.Report(e);
}
#endregion
}
public class StringToDateTimeStringConvert : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return new DateTime((Int64)value).ToString("yyyy/MM/dd HH:mm:ss");
}
public object ConvertBack(object value, Type targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
}
}