Sic.Framework-Nanjing-Baishi/MECF.Framework.UI.Client/CenterViews/Core/UserControls/DataViewDataGrid.xaml.cs

710 lines
25 KiB
C#
Raw Normal View History

2023-04-13 11:51:03 +08:00
/************************************************************************
*@file FrameworkLocal\UIClient\CenterViews\Core\UserControls\DataViewDataGrid.cs
* @author Su Liang
* @Date 2022-08-01
*
* @copyright &copy Sicentury Inc.
*
* @brief Reconstructed to support rich functions.
*
* @details
* *****************************************************************************/
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Forms;
using System.Windows.Input;
using MECF.Framework.UI.Client.CenterViews.Core.Charting;
using MECF.Framework.UI.Client.CenterViews.Core.EventArgs;
using SciChart.Charting.Visuals.RenderableSeries;
using Sicentury.Core.EventArgs;
using MessageBox = System.Windows.MessageBox;
namespace MECF.Framework.UI.Client.CenterViews.Core.UserControls
{
/// <summary>
/// Interaction logic for DataViewDataGrid.xaml
/// </summary>
public partial class DataViewDataGrid
{
#region Variables
/// <summary>
/// 正在导出数据事件。
/// </summary>
public event EventHandler Exporting;
/// <summary>
/// 导出数据完毕事件。
/// <para>导出过程异常仍触发此事件。</para>
/// </summary>
public event EventHandler Exported;
/// <summary>
/// 正在删除曲线事件。
/// </summary>
public event EventHandler<RenderableSeriesDeletingEventArgs> Deleting;
/// <summary>
/// 删除曲线完成事件。
/// </summary>
public event EventHandler<RenderableSeriesDeletingEventArgs> Deleted;
/// <summary>
/// 常时操作的进度信息更新事件。
/// </summary>
public event EventHandler<ProgressUpdatingEventArgs> ProgressMessageUpdating;
/// <summary>
/// 取消操作。
/// </summary>
private CancellationTokenSource _cancellationTokenSource;
/// <summary>
/// 在UI线程上报告进度。
/// </summary>
private readonly IProgress<ProgressUpdatingEventArgs> _progress;
#endregion
#region Constructors
public DataViewDataGrid()
{
_progress = new Progress<ProgressUpdatingEventArgs>(e => { ProgressMessageUpdating?.Invoke(this, e); });
}
private void ItemsSourceOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
RegisterPropertiesChangedEvent(e.NewItems);
break;
case NotifyCollectionChangedAction.Replace:
RegisterPropertiesChangedEvent(e.NewItems);
UnRegisterPropertiesChangedEvent(e.OldItems);
break;
case NotifyCollectionChangedAction.Remove:
UnRegisterPropertiesChangedEvent(e.OldItems);
break;
case NotifyCollectionChangedAction.Reset:
UnRegisterPropertiesChangedEvent(e.OldItems);
RegisterPropertiesChangedEvent(e.NewItems);
break;
case NotifyCollectionChangedAction.Move:
default:
// Ignore.
break;
}
}
private void RegisterPropertiesChangedEvent(IList collection)
{
foreach (var item in collection)
{
if (item is SicFastLineSeries series)
{
series.PropertyChanged += SeriesOnPropertyChanged;
}
}
}
private void UnRegisterPropertiesChangedEvent(IList collection)
{
foreach (var item in collection)
{
if (item is SicFastLineSeries series)
{
series.PropertyChanged -= SeriesOnPropertyChanged;
}
}
}
private void SeriesOnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(IRenderableSeries.IsVisible))
{
// 设置标题栏中CheckBox的IsChecked属性
var group =
ItemsSource.ToList()
.Cast<SicFastLineSeries>()
.GroupBy(x => x.IsVisible)
.ToList();
if (group.Count() > 1)
{
IsVisibleInColumnHeader = null;
}
else if (group.Count() == 1)
{
var visible = group[0].Key;
IsVisibleInColumnHeader = visible;
}
}
}
#endregion
#region Properties
public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register(
"ItemsSource", typeof(ChartingLineSeriesCollection), typeof(DataViewDataGrid),
new PropertyMetadata(default(ChartingLineSeriesCollection), (sender, e) =>
{
//if (!(sender is DataViewDataGrid dg))
// return;
//if (e.NewValue is ChartingLineSeriesCollection newColl)
//{
// newColl.CollectionChanged += dg.ItemsSourceOnCollectionChanged;
// dg.RegisterPropertiesChangedEvent(newColl);
//}
//if (e.OldValue is ChartingLineSeriesCollection oldColl)
//{
// oldColl.CollectionChanged -= dg.ItemsSourceOnCollectionChanged;
// dg.UnRegisterPropertiesChangedEvent(oldColl);
//}
}));
/// <summary>
/// 设置或返回DataGrid数据源。
/// </summary>
public ChartingLineSeriesCollection ItemsSource
{
get => (ChartingLineSeriesCollection)GetValue(ItemsSourceProperty);
set => SetValue(ItemsSourceProperty, value);
}
public static readonly DependencyProperty IsShowStatisticColumnProperty = DependencyProperty.Register(
nameof(IsShowStatisticColumn),
typeof(bool),
typeof(DataViewDataGrid),
new PropertyMetadata(default(bool)));
/// <summary>
/// 设置或返回是否显示统计数据列。
/// </summary>
public bool IsShowStatisticColumn
{
get => (bool)GetValue(IsShowStatisticColumnProperty);
set => SetValue(IsShowStatisticColumnProperty, value);
}
public static readonly DependencyProperty IsVisibleInColumnHeaderProperty = DependencyProperty.Register(
"IsVisibleInColumnHeader",
typeof(bool?),
typeof(DataViewDataGrid),
new PropertyMetadata(default(bool?), (sender, e) =>
{
if (!(sender is DataViewDataGrid dg))
return;
if (e.Property.Name != nameof(IsVisibleInColumnHeader))
return;
if (!(e.NewValue is bool visible))
return;
// 如果CheckBox被选中状态则本次点击后隐藏所有序列。
foreach (var series in dg.ItemsSource)
{
series.IsVisible = visible;
}
}));
public bool? IsVisibleInColumnHeader
{
get => (bool?)GetValue(IsVisibleInColumnHeaderProperty);
set => SetValue(IsVisibleInColumnHeaderProperty, value);
}
#endregion
#region Methods
protected override void OnInitialized(System.EventArgs e)
{
InitializeComponent();
base.OnInitialized(e);
}
/// <summary>
/// 取消操作。
/// </summary>
public void CancelOperation()
{
if (_cancellationTokenSource?.Token.CanBeCanceled == true)
_cancellationTokenSource.Cancel();
}
private void OnProgressMessageUpdating(int currentProgress, int totalProgress, string message)
{
_progress.Report(new ProgressUpdatingEventArgs(currentProgress, totalProgress, message));
}
#endregion
#region Events
/// <summary>
/// 导出全部曲线数据。
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void BtnExportAll_OnClick(object sender, RoutedEventArgs e)
{
if (!(ItemsSource is ChartingLineSeriesCollection collection))
return;
try
{
if (collection.Count == 0)
{
MessageBox.Show($"Please select the data you want to export.", "Export", MessageBoxButton.OK,
MessageBoxImage.Warning);
return;
}
#if EXPORT_TO_CSV
var dlg = new SaveFileDialog
{
DefaultExt = ".xlsx", // Default file extension
Filter = "Excel数据表格文件(*.csv)|*.csv", // Filter files by extension
FileName = $"{collection.DisplayName}_{DateTime.Now:yyyyMMdd_HHmmss}"
};
#else
var dlg = new SaveFileDialog
{
DefaultExt = ".xlsx", // Default file extension
Filter = "Excel数据表格文件(*.xlsx)|*.xlsx", // Filter files by extension
FileName = $"{DisplayName}_{DateTime.Now:yyyyMMdd_HHmmss}"
};
#endif
var ret = dlg.ShowDialog(); // Show open file dialog box
if (ret == DialogResult.OK) // Process open file dialog box results
{
Exporting?.Invoke(this, System.EventArgs.Empty);
_cancellationTokenSource = new CancellationTokenSource();
var sw = new Stopwatch();
sw.Restart();
#if EXPORT_TO_CSV
var columns = new List<string>();
#else
var ds = new DataSet();
ds.Tables.Add(new DataTable(dlg.FileName));
ds.Tables[0].Columns.Add("Time");
ds.Tables[0].Columns[0].DataType = typeof(DateTime);
#endif
var timeValue = new Dictionary<DateTime, double[]>();
var dataSeriesCollection =
collection.Cast<SicFastLineSeries>().Select(x => x.GetDataSeries()).ToList();
await Task.Run(() =>
{
for (var i = 0; i < dataSeriesCollection.Count; i++)
{
OnProgressMessageUpdating(
50,
100,
$"Exporting {dataSeriesCollection[i].SeriesName} {i}/{dataSeriesCollection.Count} ...");
var points = dataSeriesCollection[i].Metadata
.Cast<ParameterNodePoint>().ToList();
for (var n = 0; n < points.Count; n++)
{
var p = points[n];
if (!timeValue.ContainsKey(p.Time))
timeValue[p.Time] = new double[collection.Count];
timeValue[p.Time][i] = p.Value;
if (_cancellationTokenSource.Token.IsCancellationRequested)
break;
}
#if EXPORT_TO_CSV
columns.Add((collection[i] as SicFastLineSeries)?.DataName ?? "Unknown Col");
#else
ds.Tables[0].Columns.Add((SelectedData[i] as SicFastLineSeries)?.DataName);
ds.Tables[0].Columns[i + 1].DataType = typeof(double);
#endif
if (_cancellationTokenSource.Token.IsCancellationRequested)
break;
}
}, _cancellationTokenSource.Token).ContinueWith(t =>
{
if (t.IsCanceled || t.IsFaulted)
return;
#if EXPORT_TO_CSV
var csvBuilder = new StringBuilder();
csvBuilder.Append("Time,");
csvBuilder.AppendLine(string.Join(",", columns));
var totalPoints = (double)timeValue.Count;
var processedPoints = 0d;
var lastPercent = 0d;
foreach (var tv in timeValue)
{
csvBuilder.Append($" {tv.Key:yyyy/MM/dd HH:mm:ss.fff}");
csvBuilder.Append(",");
csvBuilder.AppendLine(string.Join(",", tv.Value));
if (_cancellationTokenSource?.Token.IsCancellationRequested == true)
return;
processedPoints++;
var currentPercent = (int)(processedPoints / totalPoints * 100);
if (currentPercent > lastPercent)
{
/*
* UI卡顿1%
* currentPercent和lastPercent必须是整数
*/
OnProgressMessageUpdating(
60,
100,
$"Building report content ({currentPercent}%) ...");
lastPercent = currentPercent;
}
}
OnProgressMessageUpdating(
90,
100,
$"Writing to file ...");
using (_cancellationTokenSource.Token.Register(Thread.CurrentThread.Abort))
{
File.WriteAllText(dlg.FileName, csvBuilder.ToString());
}
#else
OnProgressMessageUpdating(
60,
100,
$"Building report content ...");
foreach (var item in timeValue)
{
var row = ds.Tables[0].NewRow();
row[0] = item.Key;
for (var j = 0; j < item.Value.Length; j++)
{
row[j + 1] = item.Value[j];
}
ds.Tables[0].Rows.Add(row);
}
OnProgressMessageUpdating(
90,
100,
$"Writing to file ...");
using (_cancellationTokenSource.Token.Register(Thread.CurrentThread.Abort))
{
if (!ExcelHelper.ExportToExcel(dlg.FileName, ds, out var reason))
{
MessageBox.Show($"Export failed, {reason}", "Export", MessageBoxButton.OK,
MessageBoxImage.Warning);
return;
}
}
#endif
});
sw.Stop();
Debug.WriteLine($"Export costs {sw.ElapsedMilliseconds}ms");
Debug.WriteLine($"Total Lines {timeValue.Count}");
if (_cancellationTokenSource?.Token.IsCancellationRequested == true)
return;
Exported?.Invoke(this, System.EventArgs.Empty);
MessageBox.Show($"Exporting succeed, file save as {dlg.FileName}", "Export", MessageBoxButton.OK,
MessageBoxImage.Information);
}
}
catch (ThreadAbortException)
{
// 操作取消。
}
catch (AggregateException ae)
{
var ex = ae.Flatten().InnerExceptions;
throw ex.FirstOrDefault() ?? ae;
}
catch (Exception ex)
{
MessageBox.Show($"Unable to export data, {ex.Message}", "Error", MessageBoxButton.OK,
MessageBoxImage.Error);
}
finally
{
Exported?.Invoke(this, System.EventArgs.Empty);
}
}
/// <summary>
/// 导出单条曲线数据。
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void BtnExport_OnPreviewMouseUp(object sender, MouseButtonEventArgs e)
{
if (!(ItemsSource is ChartingLineSeriesCollection collection))
return;
if (!(sender is TextBlock btn))
return;
if (!(btn.DataContext is SicFastLineSeries series))
return;
try
{
#if EXPORT_TO_CSV
var dlg = new SaveFileDialog
{
DefaultExt = ".xlsx", // Default file extension
Filter = "Excel数据表格文件(*.csv)|*.csv", // Filter files by extension
FileName = $"{series.DisplayName}_{DateTime.Now:yyyyMMdd_HHmmss}"
};
#else
var dlg = new SaveFileDialog
{
DefaultExt = ".xlsx", // Default file extension
Filter = "Excel数据表格文件(*.xlsx)|*.xlsx", // Filter files by extension
FileName = $"{DisplayName}_{DateTime.Now:yyyyMMdd_HHmmss}"
};
#endif
var result = dlg.ShowDialog(); // Show open file dialog box
if (result == DialogResult.OK) // Process open file dialog box results
{
_cancellationTokenSource = new CancellationTokenSource();
Exporting?.Invoke(this, System.EventArgs.Empty);
var sw = new Stopwatch();
sw.Restart();
#if EXPORT_TO_CSV
var columns = new List<string>();
#else
var ds = new DataSet();
ds.Tables.Add(new DataTable(cp.DataName));
ds.Tables[0].Columns.Add("Time");
ds.Tables[0].Columns[0].DataType = typeof(DateTime);
ds.Tables[0].Columns.Add(cp.DataName);
ds.Tables[0].Columns[1].DataType = typeof(double);
#endif
var ds = series?.GetDataSeries();
var points = ds?.Metadata.Cast<ParameterNodePoint>().ToList();
if (points == null)
{
MessageBox.Show($"Unable to find meta points from the series {series.DataName}.", "Error",
MessageBoxButton.OK,
MessageBoxImage.Error);
return;
}
var csvBuilder = new StringBuilder();
await Task.Run(() =>
{
using (_cancellationTokenSource.Token.Register(Thread.CurrentThread.Abort))
{
#if EXPORT_TO_CSV
csvBuilder.AppendLine($"Time,{ds.SeriesName}"); // table header
#endif
OnProgressMessageUpdating(
50,
100,
$"Exporting data ...");
for (var i = 0; i < points.Count; i++)
{
var p = points[i];
#if EXPORT_TO_CSV
csvBuilder.AppendLine($" {p.Time:yyyy/MM/dd HH:mm:ss.fff},{p.Value}");
#else
var row = ds.Tables[0].NewRow();
row[0] = p.Time;
row[1] = p.Value;
ds.Tables[0].Rows.Add(row);
#endif
}
OnProgressMessageUpdating(
90,
100,
$"Writing to file ...");
#if EXPORT_TO_CSV
File.WriteAllText(dlg.FileName, csvBuilder.ToString());
#else
if (!ExcelHelper.ExportToExcel(dlg.FileName, ds, out var reason))
{
MessageBox.Show($"Export failed, {reason}", "Export", MessageBoxButton.OK,
MessageBoxImage.Warning);
return;
}
#endif
}
});
sw.Stop();
Debug.WriteLine($"Export costs {sw.ElapsedMilliseconds}ms");
Debug.WriteLine($"Total Lines {points?.Count}");
Exported?.Invoke(this, System.EventArgs.Empty);
MessageBox.Show($"Export succeed, file save as {dlg.FileName}", "Export", MessageBoxButton.OK,
MessageBoxImage.Information);
}
}
catch (ThreadAbortException)
{
// 操作取消。
}
catch (Exception ex)
{
MessageBox.Show($"Unable to export data, {ex.Message}", "Error", MessageBoxButton.OK,
MessageBoxImage.Error);
}
finally
{
Exported?.Invoke(this, System.EventArgs.Empty);
}
}
/// <summary>
/// 删除全部曲线。
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void BtnDeleteAll_OnClick(object sender, RoutedEventArgs e)
{
if (!(ItemsSource is ChartingLineSeriesCollection collection))
return;
try
{
var list = collection.Cast<SicFastLineSeries>().ToList();
var args = new RenderableSeriesDeletingEventArgs(list);
Deleting?.Invoke(this, args);
if (args.Cancel)
return;
var total = collection.Count;
for (var i = total - 1; i >= 0; i--)
{
list[i].BackendParameterNode.IsSelected = false;
}
collection.Clear();
Deleted?.Invoke(this, args);
}
catch (Exception ex)
{
MessageBox.Show($"It's failed to delete all series, {ex.Message}", "Error", MessageBoxButton.OK,
MessageBoxImage.Error);
}
}
/// <summary>
/// 删除单条曲线。
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void BtnDelete_OnPreviewMouseUp(object sender, MouseButtonEventArgs e)
{
if (!(ItemsSource is ChartingLineSeriesCollection collection))
return;
if (!(sender is TextBlock btn))
return;
if (!(btn.DataContext is SicFastLineSeries series))
return;
try
{
var args = new RenderableSeriesDeletingEventArgs(
new List<SicFastLineSeries>(new[] { series }));
Deleting?.Invoke(this, args);
if (args.Cancel)
return;
series.BackendParameterNode.IsSelected = false;
collection.Remove(series);
Deleted?.Invoke(this, args);
}
catch (Exception ex)
{
MessageBox.Show($"It's failed to delete series, {ex.Message}", "Error", MessageBoxButton.OK,
MessageBoxImage.Error);
}
}
/// <summary>
/// 更换曲线颜色。
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void BtnChangeSeriesColor_OnPreviewMouseUp(object sender, MouseButtonEventArgs e)
{
var dlg = new System.Windows.Forms.ColorDialog();
if (dlg.ShowDialog() != System.Windows.Forms.DialogResult.OK)
return;
if (!(sender is Border bdr))
return;
if (bdr.DataContext is SicFastLineSeries series)
series.Stroke = new System.Windows.Media.Color()
{ A = dlg.Color.A, B = dlg.Color.B, G = dlg.Color.G, R = dlg.Color.R };
}
#endregion
}
}