[UI.Client]
移除PMProcessGasFlowCounterViewModel.cs。 [RT.Core] 新增ProcessDataStat相关对象,用于统计PM工艺过程中的数据。
This commit is contained in:
parent
1a2979f0c5
commit
b0c5451a43
|
@ -23,6 +23,7 @@
|
|||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
|
@ -32,6 +33,7 @@
|
|||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'DebugWithoutCopyFiles|AnyCPU'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
|
@ -40,7 +42,7 @@
|
|||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<DebugType>full</DebugType>
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<LangVersion>7.3</LangVersion>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
|
@ -124,6 +126,9 @@
|
|||
<Compile Include="IoProviders\Siemens\Transfer\SoftBasic.cs" />
|
||||
<Compile Include="Language\DefaultLanguage.cs" />
|
||||
<Compile Include="Language\English.cs" />
|
||||
<Compile Include="Managers\PDS\ProcessDataStatCounter.cs" />
|
||||
<Compile Include="Managers\PDS\ProcessDataStatPerRun.cs" />
|
||||
<Compile Include="Managers\PDS\ProcessDataStatManager.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="StringResources.cs" />
|
||||
<Compile Include="ThreadLock\ThreadLock.cs" />
|
||||
|
@ -140,6 +145,10 @@
|
|||
<Project>{2c9e1df3-1aba-4972-be60-41dd9b3c47a7}</Project>
|
||||
<Name>MECF.Framework.UI.Core</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Sicentury.Core\Sicentury.Core.csproj">
|
||||
<Project>{A78B3F87-4601-43F7-A941-EDB339CE35A9}</Project>
|
||||
<Name>Sicentury.Core</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Include="Backend\BackendMainView.xaml">
|
||||
|
|
|
@ -0,0 +1,119 @@
|
|||
using System;
|
||||
using Aitex.Core.RT.DataCenter;
|
||||
using Aitex.Core.RT.Log;
|
||||
using Aitex.Core.Util;
|
||||
using Sicentury.Core;
|
||||
|
||||
namespace MECF.Framework.RT.Core.Managers.PDS
|
||||
{
|
||||
/// <summary>
|
||||
/// 气体流量累加器。
|
||||
/// </summary>
|
||||
public class ProcessDataStatCounter : BindableBase
|
||||
{
|
||||
#region Variables
|
||||
|
||||
private DateTime _lastPollingTime = DateTime.MinValue;
|
||||
private readonly R_TRIG _trigDataPollingError = new();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public ProcessDataStatCounter(string name, string module)
|
||||
{
|
||||
Name = name;
|
||||
Module = module;
|
||||
|
||||
DataPath = new[]
|
||||
{
|
||||
$"{module}.GasRealTimeFlow.{name}_Run.FeedBack",
|
||||
$"{module}.GasRealTimeFlow.{name}_Vent.FeedBack",
|
||||
};
|
||||
}
|
||||
|
||||
public ProcessDataStatCounter(string name, string module, params string[] dataPath) : this(name, module)
|
||||
{
|
||||
DataPath = dataPath;
|
||||
Total = 0;
|
||||
}
|
||||
|
||||
public ProcessDataStatCounter(string name, string module, double initValue) : this(name, module)
|
||||
{
|
||||
Total = initValue;
|
||||
}
|
||||
|
||||
public ProcessDataStatCounter(string name, string module, double initValue, params string[] dataPath) : this(name, module, dataPath)
|
||||
{
|
||||
Total = initValue;
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// 返回当前统计数据的名称。
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 返回当前统计数据所属的模块。
|
||||
/// </summary>
|
||||
public string Module { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 返回当前数据统计时所使用的数据拉取路径。
|
||||
/// </summary>
|
||||
public string[] DataPath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 返回当前统计数据的累加值。
|
||||
/// </summary>
|
||||
public double Total { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// 累加气体流量。
|
||||
/// </summary>
|
||||
public void Accumulate()
|
||||
{
|
||||
var ts = DateTime.Now - _lastPollingTime;
|
||||
if(_lastPollingTime == DateTime.MinValue)
|
||||
ts = TimeSpan.FromSeconds(1);
|
||||
|
||||
foreach (var pdata in DataPath)
|
||||
{
|
||||
var sFlow = DATA.Poll(pdata).ToString();
|
||||
if (double.TryParse(sFlow, out var dFlow))
|
||||
{
|
||||
Total += dFlow * ts.TotalSeconds;
|
||||
_trigDataPollingError.RST = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_trigDataPollingError.CLK = true;
|
||||
if (_trigDataPollingError.Q)
|
||||
{
|
||||
LOG.Error($"Unable to polling data {DataPath} or the data({sFlow}) is not double value.");
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_lastPollingTime = DateTime.Now;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Name}: {Total:F1}";
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,270 @@
|
|||
#define _FAKE_DATA
|
||||
|
||||
using System;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Windows.Data;
|
||||
using Aitex.Core.RT.Log;
|
||||
using MECF.Framework.Common.DataCenter;
|
||||
using Sicentury.Core.Collections;
|
||||
#if FAKE_DATA
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
#endif
|
||||
|
||||
|
||||
namespace MECF.Framework.RT.Core.Managers.PDS
|
||||
{
|
||||
public class ProcessDataStatManager
|
||||
{
|
||||
#region Variables
|
||||
|
||||
/// <summary>
|
||||
/// 工艺数据统计列表中显示的最大行数
|
||||
/// </summary>
|
||||
private const int DISPLAYED_ROWS_MAX = 10;
|
||||
|
||||
private readonly object _syncObject = new();
|
||||
private bool _isCounting;
|
||||
|
||||
#if FAKE_DATA
|
||||
|
||||
private readonly Random _randomFlow;
|
||||
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public ProcessDataStatManager(string module)
|
||||
{
|
||||
Module = module;
|
||||
GasFlowCountCollection = new ObservableQueue<ProcessDataStatPerRun>(DISPLAYED_ROWS_MAX);
|
||||
GasFlowCountCollection.CollectionChanged += GasFlowCountCollectionOnCollectionChanged;
|
||||
|
||||
// 加载历史记录。
|
||||
ReadLastFlowCounts();
|
||||
|
||||
#if FAKE_DATA
|
||||
|
||||
_randomFlow = new Random();
|
||||
|
||||
//for (var i = 0; i < 10; i++)
|
||||
//{
|
||||
// var counter = new GasFlowPerRun();
|
||||
// counter.H2.AddFlow(_randomFlow.NextDouble());
|
||||
// counter.Ar.AddFlow(_randomFlow.NextDouble());
|
||||
// counter.PN2.AddFlow(_randomFlow.NextDouble());
|
||||
// counter.HCL.AddFlow(_randomFlow.NextDouble());
|
||||
// counter.SiH4.AddFlow(_randomFlow.NextDouble());
|
||||
// counter.C3H8.AddFlow(_randomFlow.NextDouble());
|
||||
// counter.TCS.AddFlow(_randomFlow.NextDouble());
|
||||
// counter.TMA.AddFlow(_randomFlow.NextDouble());
|
||||
// counter.StatisticsEnd = DateTime.Now;
|
||||
|
||||
// GasFlowCountCollection.Enqueue(counter);
|
||||
|
||||
// CurrentFlowCounter = counter;
|
||||
//}
|
||||
|
||||
// Add new Flow Counter every 5s
|
||||
Task.Run(() =>
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
Thread.Sleep(5000);
|
||||
if (CurrentFlowCounter != null)
|
||||
{
|
||||
// 结束当前的统计,随后开启新统计
|
||||
CurrentFlowCounter.StatisticsEnd = DateTime.Now;
|
||||
SaveFlowCount(CurrentFlowCounter);
|
||||
}
|
||||
|
||||
var counter = new GasFlowPerRun();
|
||||
GasFlowCountCollection.Enqueue(counter);
|
||||
CurrentFlowCounter = counter;
|
||||
}
|
||||
});
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
public string Module { get; private set; }
|
||||
|
||||
public ObservableQueue<ProcessDataStatPerRun> GasFlowCountCollection { get; }
|
||||
|
||||
public ProcessDataStatPerRun CurrentFlowCounter { get; private set; }
|
||||
|
||||
public ICollectionView GasFlowDetailList
|
||||
{
|
||||
get
|
||||
{
|
||||
var view = CollectionViewSource.GetDefaultView(GasFlowCountCollection.ToList());
|
||||
view.SortDescriptions.Add(new SortDescription(nameof(ProcessDataStatPerRun.Index),
|
||||
ListSortDirection.Ascending));
|
||||
return view;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
private void GasFlowCountCollectionOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
// 列表发生变化时重新排序。
|
||||
if (e.Action == NotifyCollectionChangedAction.Add)
|
||||
{
|
||||
var i = 1;
|
||||
GasFlowCountCollection.ToList().OrderByDescending(x => x.StatisticsStart).ToList().ForEach(x =>
|
||||
{
|
||||
x.Index = i;
|
||||
i++;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将统计值保存到数据库。
|
||||
/// </summary>
|
||||
/// <param name="runData"></param>
|
||||
private void SaveFlowCount(ProcessDataStatPerRun runData)
|
||||
{
|
||||
try
|
||||
{
|
||||
var sql = $"INSERT INTO process_flow_data " +
|
||||
$"(process_guid, module_name, recipe_name, wafer_guid, process_begin_time, process_end_time, h2, ar, pn2, hcl, sih4, c3h8, tcs, tma) " +
|
||||
$"VALUES " +
|
||||
$"('{runData.StatisticsUid:D}', '{Module}', '{runData.RecipeName}', '{runData.WaferId:D}', " +
|
||||
$"'{runData.StatisticsStart:yyyy/MM/dd HH:mm:ss.fff}', '{runData.StatisticsEnd:yyyy/MM/dd HH:mm:ss.fff}', " +
|
||||
$"{runData.H2}, {runData.Ar}, {runData.PN2}, {runData.HCL}, {runData.SiH4}, {runData.C2H4}, {runData.TCS}, {runData.TMA})";
|
||||
|
||||
QueryDataClient.Instance.Service.QueryData(sql);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LOG.Error($"Unable to save total gas flow to database, {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从数据库加载指定的记录。
|
||||
/// </summary>
|
||||
/// <param name="limit"></param>
|
||||
private void ReadLastFlowCounts(int limit = DISPLAYED_ROWS_MAX)
|
||||
{
|
||||
try
|
||||
{
|
||||
var sql =
|
||||
$"SELECT * FROM process_flow_data where module_name = '{Module}' " +
|
||||
$" ORDER BY process_begin_time DESC LIMIT {limit}";
|
||||
var dt = QueryDataClient.Instance.Service.QueryData(sql);
|
||||
if (dt != null && dt.Rows.Count > 0)
|
||||
{
|
||||
for (var i = 0; i < dt.Rows.Count; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
var row = dt.Rows[i];
|
||||
var uid = Guid.Parse(row["process_guid"].ToString());
|
||||
var recipeName = row["recipe_name"].ToString();
|
||||
var waferUid = row["wafer_guid"].ToString();
|
||||
var beginTime = DateTime.Parse(row["process_begin_time"].ToString());
|
||||
var endTime = DateTime.Parse(row["process_end_time"].ToString());
|
||||
var h2 = double.Parse(row["h2"].ToString());
|
||||
var ar = double.Parse(row["ar"].ToString());
|
||||
var pn2 = double.Parse(row["pn2"].ToString());
|
||||
var hcl = double.Parse(row["hcl"].ToString());
|
||||
var sih4 = double.Parse(row["sih4"].ToString());
|
||||
var cxhx = double.Parse(row["c3h8"].ToString());
|
||||
var tcs = double.Parse(row["tcs"].ToString());
|
||||
var tma = double.Parse(row["tma"].ToString());
|
||||
var heaterPowerConsumption = double.Parse(row["heater_power_consumption"].ToString());
|
||||
var counter = new ProcessDataStatPerRun(i + 1, uid, Module, recipeName, waferUid, beginTime, endTime, h2, ar, pn2,
|
||||
hcl, sih4, cxhx, tcs, tma, heaterPowerConsumption);
|
||||
|
||||
GasFlowCountCollection.Enqueue(counter);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LOG.Error($"Unable to load process flow count history at row {i}, {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LOG.Error($"Unable to load process flow count history, {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void Monitor()
|
||||
{
|
||||
lock (_syncObject)
|
||||
{
|
||||
if (CurrentFlowCounter != null && CurrentFlowCounter.StatisticsEnd.HasValue == false)
|
||||
{
|
||||
#if FAKE_DATA
|
||||
CurrentFlowCounter.H2.AddFlow(_randomFlow.NextDouble());
|
||||
CurrentFlowCounter.Ar.AddFlow(_randomFlow.NextDouble());
|
||||
CurrentFlowCounter.PN2.AddFlow(_randomFlow.NextDouble());
|
||||
CurrentFlowCounter.HCL.AddFlow(_randomFlow.NextDouble());
|
||||
CurrentFlowCounter.SiH4.AddFlow(_randomFlow.NextDouble());
|
||||
CurrentFlowCounter.CxHx.AddFlow(_randomFlow.NextDouble());
|
||||
CurrentFlowCounter.TCS.AddFlow(_randomFlow.NextDouble());
|
||||
CurrentFlowCounter.TMA.AddFlow(_randomFlow.NextDouble());
|
||||
#else
|
||||
|
||||
CurrentFlowCounter.Accumulate();
|
||||
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启动统计。
|
||||
/// </summary>
|
||||
public void Begin()
|
||||
{
|
||||
lock (_syncObject)
|
||||
{
|
||||
_isCounting = true;
|
||||
CurrentFlowCounter = new ProcessDataStatPerRun(Module);
|
||||
GasFlowCountCollection.Enqueue(CurrentFlowCounter);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 结束上次统计。
|
||||
/// </summary>
|
||||
public void End()
|
||||
{
|
||||
lock (_syncObject)
|
||||
{
|
||||
// Process结束
|
||||
if (CurrentFlowCounter != null)
|
||||
{
|
||||
CurrentFlowCounter.StatisticsEnd = DateTime.Now;
|
||||
|
||||
// 写数据库
|
||||
SaveFlowCount(CurrentFlowCounter);
|
||||
|
||||
}
|
||||
|
||||
_isCounting = false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,139 @@
|
|||
using System;
|
||||
using Sicentury.Core;
|
||||
|
||||
namespace MECF.Framework.RT.Core.Managers.PDS
|
||||
{
|
||||
public class ProcessDataStatPerRun : BindableBase
|
||||
{
|
||||
#region Variables
|
||||
|
||||
private DateTime _statStart;
|
||||
private DateTime? _statEnd;
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public ProcessDataStatPerRun(int index, Guid uid, string module, string recipeName, string waferUid, DateTime beginTime, DateTime endTime, double h2,
|
||||
double ar,
|
||||
double pn2, double hcl, double sih4, double cxhx, double tcs, double tma, double heaterPowerConsumption)
|
||||
{
|
||||
Index = index;
|
||||
Module = module;
|
||||
StatisticsUid = uid;
|
||||
RecipeName = recipeName;
|
||||
WaferId = waferUid;
|
||||
StatisticsStart = beginTime;
|
||||
StatisticsEnd = endTime;
|
||||
H2 = new ProcessDataStatCounter(nameof(H2), module, h2);
|
||||
Ar = new ProcessDataStatCounter(nameof(Ar), module, ar);
|
||||
PN2 = new ProcessDataStatCounter(nameof(PN2), module, pn2);
|
||||
HCL = new ProcessDataStatCounter(nameof(HCL), module, hcl);
|
||||
SiH4 = new ProcessDataStatCounter(nameof(SiH4), module, sih4);
|
||||
C2H4 = new ProcessDataStatCounter(nameof(C2H4), module, cxhx);
|
||||
TCS = new ProcessDataStatCounter(nameof(TCS), module, tcs);
|
||||
TMA = new ProcessDataStatCounter(nameof(TMA), module, tma);
|
||||
HeaterPowerConsumption = new ProcessDataStatCounter(nameof(HeaterPowerConsumption), module, new []
|
||||
{
|
||||
$"{module}.PSU1.OutputPowerFeedBack",
|
||||
$"{module}.PSU2.OutputPowerFeedBack",
|
||||
$"{module}.PSU3.OutputPowerFeedBack",
|
||||
$"{module}.SCR1.PowerFeedBack",
|
||||
$"{module}.SCR2.PowerFeedBack",
|
||||
$"{module}.SCR3.PowerFeedBack"
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public ProcessDataStatPerRun(string module)
|
||||
{
|
||||
StatisticsUid = Guid.NewGuid();
|
||||
Module = module;
|
||||
WaferId = string.Empty;
|
||||
RecipeName = string.Empty;
|
||||
StatisticsStart = DateTime.Now;
|
||||
|
||||
H2 = new ProcessDataStatCounter(nameof(H2), module);
|
||||
Ar = new ProcessDataStatCounter(nameof(Ar), module);
|
||||
PN2 = new ProcessDataStatCounter(nameof(PN2), module);
|
||||
HCL = new ProcessDataStatCounter(nameof(HCL), module);
|
||||
SiH4 = new ProcessDataStatCounter(nameof(SiH4), module);
|
||||
C2H4 = new ProcessDataStatCounter(nameof(C2H4), module);
|
||||
TCS = new ProcessDataStatCounter(nameof(TCS), module);
|
||||
TMA = new ProcessDataStatCounter(nameof(TMA), module);
|
||||
HeaterPowerConsumption = new ProcessDataStatCounter(nameof(HeaterPowerConsumption), module);
|
||||
}
|
||||
|
||||
public ProcessDataStatPerRun(string module, string waferId) : this(module)
|
||||
{
|
||||
WaferId = waferId;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
public int Index { get; set; }
|
||||
|
||||
public string Module { get; set; }
|
||||
|
||||
public string WaferId { get; }
|
||||
|
||||
public string RecipeName { get; }
|
||||
|
||||
public Guid StatisticsUid { get; }
|
||||
|
||||
public DateTime StatisticsStart
|
||||
{
|
||||
get => _statStart;
|
||||
set => Set(ref _statStart, value);
|
||||
}
|
||||
|
||||
public DateTime? StatisticsEnd
|
||||
{
|
||||
get => _statEnd;
|
||||
set => Set(ref _statEnd, value);
|
||||
}
|
||||
|
||||
public ProcessDataStatCounter H2 { get; }
|
||||
|
||||
public ProcessDataStatCounter Ar { get; }
|
||||
|
||||
public ProcessDataStatCounter PN2 { get; }
|
||||
|
||||
public ProcessDataStatCounter HCL { get; }
|
||||
|
||||
public ProcessDataStatCounter SiH4 { get; }
|
||||
|
||||
public ProcessDataStatCounter C2H4 { get; }
|
||||
|
||||
public ProcessDataStatCounter TCS { get; }
|
||||
|
||||
public ProcessDataStatCounter TMA { get; }
|
||||
|
||||
public ProcessDataStatCounter HeaterPowerConsumption { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// 累加工艺数据。
|
||||
/// </summary>
|
||||
public void Accumulate()
|
||||
{
|
||||
H2.Accumulate();
|
||||
Ar.Accumulate();
|
||||
PN2.Accumulate();
|
||||
HCL.Accumulate();
|
||||
SiH4.Accumulate();
|
||||
C2H4.Accumulate();
|
||||
TCS.Accumulate();
|
||||
TMA.Accumulate();
|
||||
HeaterPowerConsumption.Accumulate();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -1,526 +0,0 @@
|
|||
#define _FAKE_DATA
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Windows.Data;
|
||||
using Aitex.Core.RT.Log;
|
||||
using MECF.Framework.Common.DataCenter;
|
||||
using MECF.Framework.UI.Client.CenterViews.Modules.PM;
|
||||
using MECF.Framework.UI.Client.ClientBase;
|
||||
using Sicentury.Core;
|
||||
|
||||
#if FAKE_DATA
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
#endif
|
||||
|
||||
|
||||
namespace SicUI.Models.PMs
|
||||
{
|
||||
public class ObservableQueue<T> : Queue<T>, INotifyCollectionChanged
|
||||
{
|
||||
public event NotifyCollectionChangedEventHandler CollectionChanged;
|
||||
|
||||
private readonly object _syncRoot = new object();
|
||||
|
||||
#region Constructors
|
||||
|
||||
public ObservableQueue()
|
||||
{
|
||||
LimitSize = 0;
|
||||
}
|
||||
|
||||
public ObservableQueue(int capacity) : base(capacity)
|
||||
{
|
||||
LimitSize = capacity;
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
public int LimitSize { get; }
|
||||
|
||||
public new virtual void Enqueue(T obj)
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
base.Enqueue(obj);
|
||||
|
||||
if (LimitSize > 0 && base.Count > LimitSize)
|
||||
base.Dequeue();
|
||||
|
||||
CollectionChanged?.Invoke(this,
|
||||
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, obj));
|
||||
}
|
||||
}
|
||||
|
||||
public new virtual T Dequeue()
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
var obj = base.Dequeue();
|
||||
CollectionChanged?.Invoke(this,
|
||||
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, obj));
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public new virtual void Clear()
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
base.Clear();
|
||||
CollectionChanged?.Invoke(this,
|
||||
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
public class GasFlowCounter : BindableBase
|
||||
{
|
||||
#region Variables
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public GasFlowCounter(string gasName)
|
||||
{
|
||||
GasName = gasName;
|
||||
TotalFlow = 0;
|
||||
}
|
||||
|
||||
public GasFlowCounter(string gasName, double initValue) : this(gasName)
|
||||
{
|
||||
TotalFlow = initValue;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
public string GasName { get; }
|
||||
|
||||
public double TotalFlow { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
public void AddFlow(double increment)
|
||||
{
|
||||
TotalFlow += increment;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{TotalFlow:F1}";
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
public class PmGasFlowCountInfo : BindableBase
|
||||
{
|
||||
#region Variables
|
||||
|
||||
private DateTime _statStart;
|
||||
private DateTime? _statEnd;
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public PmGasFlowCountInfo(int index, Guid uid, string recipeName, string waferUid, DateTime beginTime, DateTime endTime, double h2,
|
||||
double ar,
|
||||
double pn2, double hcl, double sih4, double cxhx, double tcs, double tma)
|
||||
{
|
||||
Index = index;
|
||||
StatisticsUid = uid;
|
||||
RecipeName = recipeName;
|
||||
WaferId = waferUid;
|
||||
StatisticsStart = beginTime;
|
||||
StatisticsEnd = endTime;
|
||||
H2 = new GasFlowCounter(nameof(H2), h2);
|
||||
Ar = new GasFlowCounter(nameof(Ar), ar);
|
||||
PN2 = new GasFlowCounter(nameof(PN2), pn2);
|
||||
HCL = new GasFlowCounter(nameof(HCL), hcl);
|
||||
SiH4 = new GasFlowCounter(nameof(SiH4), sih4);
|
||||
CxHx = new GasFlowCounter(nameof(CxHx), cxhx);
|
||||
TCS = new GasFlowCounter(nameof(TCS), tcs);
|
||||
TMA = new GasFlowCounter(nameof(TMA), tma);
|
||||
|
||||
|
||||
}
|
||||
|
||||
public PmGasFlowCountInfo()
|
||||
{
|
||||
StatisticsUid = Guid.NewGuid();
|
||||
WaferId = string.Empty;
|
||||
RecipeName = string.Empty;
|
||||
StatisticsStart = DateTime.Now;
|
||||
|
||||
H2 = new GasFlowCounter(nameof(H2));
|
||||
Ar = new GasFlowCounter(nameof(Ar));
|
||||
PN2 = new GasFlowCounter(nameof(PN2));
|
||||
HCL = new GasFlowCounter(nameof(HCL));
|
||||
SiH4 = new GasFlowCounter(nameof(SiH4));
|
||||
CxHx = new GasFlowCounter(nameof(CxHx));
|
||||
TCS = new GasFlowCounter(nameof(TCS));
|
||||
TMA = new GasFlowCounter(nameof(TMA));
|
||||
}
|
||||
|
||||
public PmGasFlowCountInfo(string waferId) : this()
|
||||
{
|
||||
WaferId = waferId;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
public int Index { get; set; }
|
||||
|
||||
public string WaferId { get; }
|
||||
|
||||
public string RecipeName { get; }
|
||||
|
||||
public Guid StatisticsUid { get; }
|
||||
|
||||
public DateTime StatisticsStart
|
||||
{
|
||||
get => _statStart;
|
||||
set => Set(ref _statStart, value);
|
||||
}
|
||||
|
||||
public DateTime? StatisticsEnd
|
||||
{
|
||||
get => _statEnd;
|
||||
set => Set(ref _statEnd, value);
|
||||
}
|
||||
|
||||
public GasFlowCounter H2 { get; }
|
||||
|
||||
public GasFlowCounter Ar { get; }
|
||||
|
||||
public GasFlowCounter PN2 { get; }
|
||||
|
||||
public GasFlowCounter HCL { get; }
|
||||
|
||||
public GasFlowCounter SiH4 { get; }
|
||||
|
||||
public GasFlowCounter CxHx { get; }
|
||||
|
||||
public GasFlowCounter TCS { get; }
|
||||
|
||||
public GasFlowCounter TMA { get; }
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
public class PMProcessGasFlowCounterViewModel : UiViewModelBase
|
||||
{
|
||||
#region Variables
|
||||
|
||||
private const int MAX_ROWS = 10;
|
||||
|
||||
private readonly object _syncObject = new object();
|
||||
|
||||
private readonly PMProcessViewModel _pmProcessVm;
|
||||
private string _previousStatus;
|
||||
|
||||
|
||||
#if FAKE_DATA
|
||||
|
||||
private readonly Random _randomFlow;
|
||||
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public PMProcessGasFlowCounterViewModel(PMProcessViewModel vm)
|
||||
{
|
||||
_previousStatus = "Init";
|
||||
_pmProcessVm = vm;
|
||||
_pmProcessVm.PropertyChanged += VmOnPropertyChanged;
|
||||
|
||||
GasFlowCountCollection = new ObservableQueue<PmGasFlowCountInfo>(MAX_ROWS);
|
||||
GasFlowCountCollection.CollectionChanged += GasFlowCountCollectionOnCollectionChanged;
|
||||
|
||||
// 加载历史记录。
|
||||
ReadLastFlowCounts();
|
||||
|
||||
#if FAKE_DATA
|
||||
|
||||
_randomFlow = new Random();
|
||||
|
||||
//for (var i = 0; i < 10; i++)
|
||||
//{
|
||||
// var counter = new PmGasFlowCountInfo();
|
||||
// counter.H2.AddFlow(_randomFlow.NextDouble());
|
||||
// counter.Ar.AddFlow(_randomFlow.NextDouble());
|
||||
// counter.PN2.AddFlow(_randomFlow.NextDouble());
|
||||
// counter.HCL.AddFlow(_randomFlow.NextDouble());
|
||||
// counter.SiH4.AddFlow(_randomFlow.NextDouble());
|
||||
// counter.C3H8.AddFlow(_randomFlow.NextDouble());
|
||||
// counter.TCS.AddFlow(_randomFlow.NextDouble());
|
||||
// counter.TMA.AddFlow(_randomFlow.NextDouble());
|
||||
// counter.StatisticsEnd = DateTime.Now;
|
||||
|
||||
// GasFlowCountCollection.Enqueue(counter);
|
||||
|
||||
// CurrentFlowCounter = counter;
|
||||
//}
|
||||
|
||||
// Add new Flow Counter every 5s
|
||||
Task.Run(() =>
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
Thread.Sleep(5000);
|
||||
if (CurrentFlowCounter != null)
|
||||
{
|
||||
// 结束当前的统计,随后开启新统计
|
||||
CurrentFlowCounter.StatisticsEnd = DateTime.Now;
|
||||
SaveFlowCount(CurrentFlowCounter);
|
||||
}
|
||||
|
||||
var counter = new PmGasFlowCountInfo();
|
||||
GasFlowCountCollection.Enqueue(counter);
|
||||
CurrentFlowCounter = counter;
|
||||
}
|
||||
});
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
public ObservableQueue<PmGasFlowCountInfo> GasFlowCountCollection { get; }
|
||||
|
||||
public PmGasFlowCountInfo CurrentFlowCounter { get; private set; }
|
||||
|
||||
public ICollectionView GasFlowDetailList
|
||||
{
|
||||
get
|
||||
{
|
||||
var view = CollectionViewSource.GetDefaultView(GasFlowCountCollection.ToList());
|
||||
view.SortDescriptions.Add(new SortDescription(nameof(PmGasFlowCountInfo.Index),
|
||||
ListSortDirection.Ascending));
|
||||
return view;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
private void GasFlowCountCollectionOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
// 列表发生变化时重新排序。
|
||||
if (e.Action == NotifyCollectionChangedAction.Add)
|
||||
{
|
||||
var i = 1;
|
||||
GasFlowCountCollection.ToList().OrderByDescending(x => x.StatisticsStart).ToList().ForEach(x =>
|
||||
{
|
||||
x.Index = i;
|
||||
i++;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 监测Process的Status属性是否变化,以确定Process的开始和结束时机。
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void VmOnPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName == nameof(PMProcessViewModel.Status))
|
||||
{
|
||||
var currentStatus = _pmProcessVm.Status;
|
||||
|
||||
if (CheckIfProcessStarted(_previousStatus, currentStatus))
|
||||
{
|
||||
// Process启动
|
||||
|
||||
lock (_syncObject)
|
||||
{
|
||||
CurrentFlowCounter = new PmGasFlowCountInfo();
|
||||
GasFlowCountCollection.Enqueue(CurrentFlowCounter);
|
||||
}
|
||||
|
||||
}
|
||||
else if (CheckIfProcessStopped(_previousStatus, currentStatus))
|
||||
{
|
||||
lock (_syncObject)
|
||||
{
|
||||
// Process结束
|
||||
if (CurrentFlowCounter != null)
|
||||
{
|
||||
CurrentFlowCounter.StatisticsEnd = DateTime.Now;
|
||||
|
||||
// 写数据库
|
||||
SaveFlowCount(CurrentFlowCounter);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Debug.WriteLineIf(_previousStatus != currentStatus,
|
||||
$"PM Process Status: {_previousStatus} to {currentStatus}");
|
||||
|
||||
_previousStatus = currentStatus;
|
||||
}
|
||||
}
|
||||
|
||||
private bool CheckIfProcessStarted(string previousStatus, string currentStatus)
|
||||
{
|
||||
if (previousStatus == "ProcessIdle" &&
|
||||
currentStatus == "PreProcess")
|
||||
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool CheckIfProcessStopped(string previousStatus, string currentStatus)
|
||||
{
|
||||
if ((previousStatus != "ProcessIdle" &&
|
||||
currentStatus == "ProcessIdle") ||
|
||||
(previousStatus != "Error" &&
|
||||
currentStatus == "Error"))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将统计值保存到数据库。
|
||||
/// </summary>
|
||||
/// <param name="count"></param>
|
||||
private void SaveFlowCount(PmGasFlowCountInfo count)
|
||||
{
|
||||
try
|
||||
{
|
||||
var c = count;
|
||||
var sql = $"INSERT INTO process_flow_data " +
|
||||
$"(process_guid, module_name, recipe_name, wafer_guid, process_begin_time, process_end_time, h2, ar, pn2, hcl, sih4, c3h8, tcs, tma) " +
|
||||
$"VALUES " +
|
||||
$"('{c.StatisticsUid:D}', '{_pmProcessVm.SystemName}', '{_pmProcessVm.SelectedRecipe}', '{c.WaferId:D}', " +
|
||||
$"'{c.StatisticsStart:yyyy/MM/dd HH:mm:ss.fff}', '{c.StatisticsEnd:yyyy/MM/dd HH:mm:ss.fff}', " +
|
||||
$"{c.H2}, {c.Ar}, {c.PN2}, {c.HCL}, {c.SiH4}, {c.CxHx}, {c.TCS}, {c.TMA})";
|
||||
|
||||
QueryDataClient.Instance.Service.QueryData(sql);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LOG.Error($"Unable to save total gas flow to database, {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从数据库加载指定的记录。
|
||||
/// </summary>
|
||||
/// <param name="limit"></param>
|
||||
private void ReadLastFlowCounts(int limit = MAX_ROWS)
|
||||
{
|
||||
try
|
||||
{
|
||||
var sql =
|
||||
$"SELECT * FROM process_flow_data where module_name = '{_pmProcessVm.SystemName}' " +
|
||||
$" ORDER BY process_begin_time DESC LIMIT {limit}";
|
||||
var dt = QueryDataClient.Instance.Service.QueryData(sql);
|
||||
if (dt != null && dt.Rows.Count > 0)
|
||||
{
|
||||
for (var i = 0; i < dt.Rows.Count; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
var row = dt.Rows[i];
|
||||
var uid = Guid.Parse(row["process_guid"].ToString());
|
||||
var recipeName = row["recipe_name"].ToString();
|
||||
var waferUid = row["wafer_guid"].ToString();
|
||||
var beginTime = DateTime.Parse(row["process_begin_time"].ToString());
|
||||
var endTime = DateTime.Parse(row["process_end_time"].ToString());
|
||||
var h2 = double.Parse(row["h2"].ToString());
|
||||
var ar = double.Parse(row["ar"].ToString());
|
||||
var pn2 = double.Parse(row["pn2"].ToString());
|
||||
var hcl = double.Parse(row["hcl"].ToString());
|
||||
var sih4 = double.Parse(row["sih4"].ToString());
|
||||
var cxhx = double.Parse(row["c3h8"].ToString());
|
||||
var tcs = double.Parse(row["tcs"].ToString());
|
||||
var tma = double.Parse(row["tma"].ToString());
|
||||
var counter = new PmGasFlowCountInfo(i + 1, uid, recipeName, waferUid, beginTime, endTime, h2, ar, pn2,
|
||||
hcl,
|
||||
sih4, cxhx, tcs, tma);
|
||||
|
||||
GasFlowCountCollection.Enqueue(counter);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LOG.Error($"Unable to load process flow count history at row {i}, {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LOG.Error($"Unable to load process flow count history, {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Poll()
|
||||
{
|
||||
base.Poll();
|
||||
|
||||
lock (_syncObject)
|
||||
{
|
||||
if (CurrentFlowCounter != null && CurrentFlowCounter.StatisticsEnd.HasValue == false)
|
||||
{
|
||||
#if FAKE_DATA
|
||||
CurrentFlowCounter.H2.AddFlow(_randomFlow.NextDouble());
|
||||
CurrentFlowCounter.Ar.AddFlow(_randomFlow.NextDouble());
|
||||
CurrentFlowCounter.PN2.AddFlow(_randomFlow.NextDouble());
|
||||
CurrentFlowCounter.HCL.AddFlow(_randomFlow.NextDouble());
|
||||
CurrentFlowCounter.SiH4.AddFlow(_randomFlow.NextDouble());
|
||||
CurrentFlowCounter.CxHx.AddFlow(_randomFlow.NextDouble());
|
||||
CurrentFlowCounter.TCS.AddFlow(_randomFlow.NextDouble());
|
||||
CurrentFlowCounter.TMA.AddFlow(_randomFlow.NextDouble());
|
||||
#else
|
||||
|
||||
CurrentFlowCounter.H2.AddFlow(_pmProcessVm.H2Flow);
|
||||
CurrentFlowCounter.Ar.AddFlow(_pmProcessVm.ArFlow);
|
||||
CurrentFlowCounter.PN2.AddFlow(_pmProcessVm.PN2Flow);
|
||||
CurrentFlowCounter.HCL.AddFlow(_pmProcessVm.HCLFlow);
|
||||
CurrentFlowCounter.SiH4.AddFlow(_pmProcessVm.SiH4Flow);
|
||||
CurrentFlowCounter.CxHx.AddFlow(_pmProcessVm.C2H4Flow);
|
||||
CurrentFlowCounter.TCS.AddFlow(_pmProcessVm.TCSFlow);
|
||||
CurrentFlowCounter.TMA.AddFlow(_pmProcessVm.TMAFlow);
|
||||
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -12,7 +12,6 @@ using Caliburn.Micro.Core;
|
|||
using System.Windows.Media;
|
||||
using System.Windows.Threading;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using OpenSEMI.ClientBase;
|
||||
using Sicentury.Core;
|
||||
|
@ -22,11 +21,7 @@ using MECF.Framework.UI.Client.RecipeEditorLib.RecipeModel;
|
|||
using MECF.Framework.UI.Client.RecipeEditorLib.RecipeModel.Params;
|
||||
using Action = System.Action;
|
||||
using Newtonsoft.Json;
|
||||
using System.Dynamic;
|
||||
using Aitex.Core.RT.Device.PmDevices;
|
||||
using SicUI.Models.PMs;
|
||||
using SciChart.Core.Messaging;
|
||||
using MECF.Framework.RT.EquipmentLibrary.HardwareUnits.GasFlow;
|
||||
using MECF.Framework.Common.Aitex.Core.Common.DeviceData;
|
||||
|
||||
namespace MECF.Framework.UI.Client.CenterViews.Modules.PM
|
||||
|
@ -53,7 +48,6 @@ namespace MECF.Framework.UI.Client.CenterViews.Modules.PM
|
|||
|
||||
|
||||
public string title { get; set; } = "123";
|
||||
public PMProcessGasFlowCounterViewModel GasFlowCounterVm { get; private set; }
|
||||
|
||||
public PMProcessViewModel()
|
||||
{
|
||||
|
@ -407,10 +401,6 @@ namespace MECF.Framework.UI.Client.CenterViews.Modules.PM
|
|||
_progressRecipeStepChanged = new Progress<int>(RecipeStepInProcessChanged);
|
||||
|
||||
UpdateRecipeFormat();
|
||||
|
||||
GasFlowCounterVm = new PMProcessGasFlowCounterViewModel(this);
|
||||
GasFlowCounterVm.EnableTimer(true);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:SicUI"
|
||||
mc:Ignorable="d"
|
||||
Height="380" Width="600" Topmost="True" WindowStartupLocation="CenterScreen" >
|
||||
<Window.Resources>
|
||||
|
|
|
@ -236,7 +236,6 @@
|
|||
<DependentUpon>PMJobListView.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="CenterViews\JobList\PMJobListViewModel.cs" />
|
||||
<Compile Include="CenterViews\Modules\PM\PMProcessGasFlowCounterViewModel.cs" />
|
||||
<Compile Include="CenterViews\Modules\PM\PMProcessView.xaml.cs">
|
||||
<DependentUpon>PMProcessView.xaml</DependentUpon>
|
||||
</Compile>
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
|
||||
namespace Sicentury.Core.Collections
|
||||
{
|
||||
public class ObservableQueue<T> : Queue<T>, INotifyCollectionChanged
|
||||
{
|
||||
public event NotifyCollectionChangedEventHandler CollectionChanged;
|
||||
|
||||
private readonly object _syncRoot = new object();
|
||||
|
||||
#region Constructors
|
||||
|
||||
public ObservableQueue()
|
||||
{
|
||||
LimitSize = 0;
|
||||
}
|
||||
|
||||
public ObservableQueue(int capacity) : base(capacity)
|
||||
{
|
||||
LimitSize = capacity;
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
public int LimitSize { get; }
|
||||
|
||||
public new virtual void Enqueue(T obj)
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
base.Enqueue(obj);
|
||||
|
||||
if (LimitSize > 0 && base.Count > LimitSize)
|
||||
base.Dequeue();
|
||||
|
||||
CollectionChanged?.Invoke(this,
|
||||
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, obj));
|
||||
}
|
||||
}
|
||||
|
||||
public new virtual T Dequeue()
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
var obj = base.Dequeue();
|
||||
CollectionChanged?.Invoke(this,
|
||||
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, obj));
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public new virtual void Clear()
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
base.Clear();
|
||||
CollectionChanged?.Invoke(this,
|
||||
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -60,6 +60,7 @@
|
|||
<Compile Include="AttachedProperties\WatermarkAdorner.cs" />
|
||||
<Compile Include="AttachedProperties\WatermarkService.cs" />
|
||||
<Compile Include="BindableBase.cs" />
|
||||
<Compile Include="Collections\ObservableQueue.cs" />
|
||||
<Compile Include="Converters\DummyConverter.cs" />
|
||||
<Compile Include="Converters\MediaColorToSolidColorBrushConverter.cs" />
|
||||
<Compile Include="Converters\ParameterNodeStatisticToStringConverter.cs" />
|
||||
|
|
Loading…
Reference in New Issue