using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Data; using System.IO; using System.Linq; using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Windows; using System.Windows.Threading; using System.Xml; using Aitex.Core.RT.Log; using Caliburn.Micro.Core; using MECF.Framework.Common.Account.Extends; using MECF.Framework.Common.Account.Permissions; using MECF.Framework.Common.DataCenter; using MECF.Framework.UI.Client.CenterViews.Configs.Roles; using MECF.Framework.UI.Client.CenterViews.Editors; using MECF.Framework.UI.Client.CenterViews.Editors.Recipe; using MECF.Framework.UI.Client.ClientBase; using MECF.Framework.UI.Client.RecipeEditorLib.DGExtension.CustomColumn; using MECF.Framework.UI.Client.RecipeEditorLib.RecipeModel.Params; using Sicentury.Core; using Sicentury.Core.Collections; namespace MECF.Framework.UI.Client.RecipeEditorLib.RecipeModel { /// /// 配方对象。 /// /// /// 每个配方由配方步骤的数组组成。 /// public class RecipeData : PropertyChangedBase { #region Variables /// /// 当调用方法后引发此事件。 /// public event EventHandler OnValidated; /// /// 当参数访问白名单发生变化时引发此事件。 /// public event EventHandler OnAccessibleWhitelistChanged; private readonly IRecipeGasFlowCalculator _gasFlowCalculator; private readonly RecipeFormatBuilder _formatBuilder; private XmlDocument _doc; private bool _isSavedDesc; private string _name; private string _chamberType; private string _recipeVersion; private string _prefixPath; private string _creator; private DateTime _createTime; private string _description; private string _devisor; private DateTime _deviseTime; private int _recipeTotalTime; private bool _isHideParamValueNotInWhitelist; #endregion #region Constructors /// /// 创建配方对象的实例。 /// /// /// 配方步骤气体流量计算器,请参考:。 /// /// Recipe类型 public RecipeData(IRecipeGasFlowCalculator gasFlowCalculator, RecipeType type) { RecipeType = type; _formatBuilder = new RecipeFormatBuilder(); ValidationErrorInfo = new ObservableRangeCollection(); Steps = new RecipeStepCollection(this); Steps.OnSavedStateChanged += (sender, e) => { OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsChanged))); }; Steps.HighlightStateChanged += (sender, e) => { if (IsAccessibleWhitelistEditMode) { // 当参数高亮发生变化时,统计高亮的参数总数 var cnt = CountAccessibleWhitelistParams(); OnAccessibleWhitelistChanged?.Invoke(this, cnt); } }; Steps.GasFlowCalculated += (sender, args) => { // 当流量计算完成时,立即校验Recipe Validate(); }; Steps.StepTimeChanged += (sender, args) => { RecipeTotalTime = (int)Steps.GetTotalTime(); }; StepTolerances = new RecipeStepCollection(this); PopSettingSteps = new Dictionary(); PopEnable = new Dictionary(); ConfigItems = new RecipeStep(null); IsSavedDesc = true; _doc = new XmlDocument(); var node = _doc.CreateElement("Aitex"); node.AppendChild(_doc.CreateElement("TableRecipeData")); _doc.AppendChild(node); _gasFlowCalculator = gasFlowCalculator ?? new RecipeGasFlowCalculatorBase(); LOG.Info($"Recipe Gas Flow Calculator : {_gasFlowCalculator.GetType().FullName}"); } #endregion #region Properties public RecipeType RecipeType { get; } public bool IsChanged { get { var changed = !IsSavedDesc; if (!changed) changed = ChkChanged(Steps) || ChkChanged(PopSettingSteps); if (!changed) { foreach (var config in ConfigItems) { if (!config.IsSaved) { changed = true; break; } } } return changed; } } public bool IsSavedDesc { get => _isSavedDesc; set { _isSavedDesc = value; NotifyOfPropertyChange(); } } public string Name { get => _name; set { _name = value; NotifyOfPropertyChange(); } } public string FullName => $"{PrefixPath}\\{Name}"; public string RecipeChamberType { get => _chamberType; set { _chamberType = value; NotifyOfPropertyChange(); } } public string RecipeVersion { get => _recipeVersion; set { _recipeVersion = value; NotifyOfPropertyChange(); } } public string PrefixPath { get => _prefixPath; set { _prefixPath = value; NotifyOfPropertyChange(); } } public string Creator { get => _creator; set { _creator = value; NotifyOfPropertyChange(); } } public DateTime CreateTime { get => _createTime; set { _createTime = value; NotifyOfPropertyChange(); } } public string Description { get => _description; set { _description = value; NotifyOfPropertyChange(); } } public string Revisor { get => _devisor; set { _devisor = value; NotifyOfPropertyChange(); } } public DateTime ReviseTime { get => _deviseTime; set { _deviseTime = value; NotifyOfPropertyChange(); } } public int RecipeTotalTime { get => _recipeTotalTime; set { _recipeTotalTime = value; NotifyOfPropertyChange(); } } public List Columns { get; private set; } public RecipeStepCollection Steps { get; private set; } public Dictionary PopSettingSteps { get; private set; } public RecipeStepCollection StepTolerances { get; private set; } public RecipeStep ConfigItems { get; private set; } /// /// 返回当前Recipe所属的反应腔。 /// public string Module { get; private set; } public bool ToleranceEnable { get; set; } public Dictionary PopEnable { get; set; } public bool IsCompatibleWithCurrentFormat { get; set; } private bool _isAccessibleWhitelistEditMode; /// /// 设置或返回当前Recipe是否处于访问白名单编辑模式。 /// public bool IsAccessibleWhitelistEditMode { get=> _isAccessibleWhitelistEditMode; private set { _isAccessibleWhitelistEditMode = value; NotifyOfPropertyChange(); } } /// /// 设置或返回是否启用访问白名单。 /// /// /// 当启用访问白名单时,Recipe编辑器中的参数值将显示为***,仅在白名单中的参数显示具体数值。 /// public bool IsEnableAccessibleWhitelist { get => _isHideParamValueNotInWhitelist; set { _isHideParamValueNotInWhitelist = value; if (_isHideParamValueNotInWhitelist) { // 关闭白名单之外的所有参数的数值显示,用“***”代替。 HideValueButAccessibleWhitelist().Wait(); } else { // 恢复到角色设定的访问权限 var role = BaseApp.Instance.UserContext.Role; UpdatePermission(role); } } } /// /// 返回当前配方校验错误信息摘要。 /// public string ValidationErrorSummary { get; private set; } /// /// 返回当前配方校验失败的参数列表。 /// public ObservableRangeCollection ValidationErrorInfo { get; } /// /// 返回当前配方校验错误的参数的总数。 /// public int ValidationErrorCount { get; private set; } #endregion #region Methods private void SetAttribute(RecipeStep parameters, XmlElement popSettingStep) { if (parameters != null) foreach (var parameter1 in parameters) { if (parameter1.Visible != Visibility.Visible) continue; if (parameter1 is IntParam param) popSettingStep.SetAttribute(param.Name, param.Value.ToString()); else if (parameter1 is DoubleParam doubleParam) popSettingStep.SetAttribute(doubleParam.Name, doubleParam.Value.ToString()); else if (parameter1 is StringParam stringParam) popSettingStep.SetAttribute(stringParam.Name, stringParam.Value); else if (parameter1 is ComboxParam comboBoxParam) popSettingStep.SetAttribute(comboBoxParam.Name, comboBoxParam.Value); else if (parameter1 is LoopComboxParam loopComboxParam) popSettingStep.SetAttribute(loopComboxParam.Name, loopComboxParam.Value); else if (parameter1 is PositionParam positionParam) popSettingStep.SetAttribute(positionParam.Name, positionParam.Value); else if (parameter1 is BoolParam boolParam) popSettingStep.SetAttribute(boolParam.Name, boolParam.Value.ToString()); else if (parameter1 is StepParam stepParam) popSettingStep.SetAttribute(stepParam.Name, stepParam.Value.ToString()); else if (parameter1 is MultipleSelectParam selectParam) { var selected1 = new List(); selectParam.Options.Apply( opt => { if (opt.IsChecked) selected1.Add(opt.ControlName); } ); popSettingStep.SetAttribute(selectParam.Name, string.Join(",", selected1)); } } } private void LoopCellFeedback(Param cell) { var loopCell = cell as LoopComboxParam; var rowIndex = -1; var colIndex = -1; for (var i = 0; i < Steps.Count; i++) { for (var j = 0; j < Steps[i].Count; j++) { if (Steps[i][j] == loopCell) { rowIndex = i; colIndex = j; } } } if (rowIndex < 0 || colIndex < 0) return; for (var i = 0; i < Steps.Count; i++) { loopCell = Steps[i][colIndex] as LoopComboxParam; var loopStr = loopCell.Value; var isLoopStart = Regex.IsMatch(loopStr, @"^Loop\x20x\d+$"); var isLoopEnd = Regex.IsMatch(loopStr, @"^Loop End$"); var isNullOrEmpty = string.IsNullOrWhiteSpace(loopStr); if (!isLoopEnd && !isLoopStart && !isNullOrEmpty) { loopCell.IsLoopStep = true; loopCell.IsValidLoop = false; continue; } if (isLoopEnd) { loopCell.IsLoopStep = true; loopCell.IsValidLoop = false; continue; } if (isLoopStart) { if (i + 1 == Steps.Count) { loopCell.IsLoopStep = true; loopCell.IsValidLoop = true; } for (var j = i + 1; j < Steps.Count; j++) { var loopCell2 = Steps[j][colIndex] as LoopComboxParam; var loopStr2 = loopCell2.Value; var isLoopStart2 = Regex.IsMatch(loopStr2, @"^Loop\x20x\d+$"); var isLoopEnd2 = Regex.IsMatch(loopStr2, @"^Loop End$"); var isNullOrEmpty2 = string.IsNullOrWhiteSpace(loopStr2); if (!isLoopEnd2 && !isLoopStart2 && !isNullOrEmpty2) { for (var k = i; k < j + 1; k++) { (Steps[k][colIndex] as LoopComboxParam).IsLoopStep = true; (Steps[k][colIndex] as LoopComboxParam).IsValidLoop = false; } i = j; break; } if (isLoopStart2) { loopCell.IsLoopStep = true; loopCell.IsValidLoop = true; i = j - 1; break; } if (isLoopEnd2) { for (var k = i; k < j + 1; k++) { (Steps[k][colIndex] as LoopComboxParam).IsLoopStep = true; (Steps[k][colIndex] as LoopComboxParam).IsValidLoop = true; } i = j; break; } if (j == Steps.Count - 1) { loopCell.IsLoopStep = true; loopCell.IsValidLoop = true; i = j; break; } } continue; } loopCell.IsLoopStep = false; loopCell.IsValidLoop = false; } } private bool LoadHeader(XmlNode nodeHeader) { if (nodeHeader == null) return false; if (nodeHeader.Attributes["CreatedBy"] != null) Creator = nodeHeader.Attributes["CreatedBy"].Value; if (nodeHeader.Attributes["CreationTime"] != null) CreateTime = DateTime.Parse(nodeHeader.Attributes["CreationTime"].Value); if (nodeHeader.Attributes["LastRevisedBy"] != null) Revisor = nodeHeader.Attributes["LastRevisedBy"].Value; if (nodeHeader.Attributes["LastRevisionTime"] != null) ReviseTime = DateTime.Parse(nodeHeader.Attributes["LastRevisionTime"].Value); if (nodeHeader.Attributes["Description"] != null) Description = nodeHeader.Attributes["Description"].Value; var chamberType = string.Empty; if (nodeHeader.Attributes["RecipeChamberType"] != null) chamberType = nodeHeader.Attributes["RecipeChamberType"].Value; if (!string.IsNullOrEmpty(chamberType) && chamberType != RecipeChamberType) { LOG.Write($"{chamberType} is not accordance with {RecipeChamberType}"); return false; } var version = string.Empty; if (nodeHeader.Attributes["RecipeVersion"] != null) version = nodeHeader.Attributes["RecipeVersion"].Value; if (!string.IsNullOrEmpty(version) && version != RecipeVersion) { LOG.Write($"{version} is not accordance with {RecipeVersion}"); return false; } return true; } /// /// 加载Recipe。 /// /// /// /// 角色 /// /// private async Task LoadSteps(List columns, XmlNodeList steps, Role role, string processType = "", Dispatcher dispatcher = null) { // 当前步骤的前序步骤,用于创建参数链表。 RecipeStep frontStep = null; Steps.Clear(); PopSettingSteps.Clear(); StepTolerances.Clear(); var stepId = 1; RecipeTotalTime = 0; using (Steps.BeginLoading()) { foreach (XmlNode nodeStep in steps) { var step = CreateStep(columns, nodeStep, frontStep, stepId); step.Save(); frontStep = step; RecipeStep.SetPermission(step, role); var t = dispatcher?.BeginInvoke(DispatcherPriority.Background, (Action)(() => { Steps.Add(step); })); if (t != null) await t; else { // 确保在UI线程上执行 Application.Current?.Dispatcher.Invoke(() => { Steps.Add(step); }); } stepId++; } Validate(); } Steps.EndLoading(); } private void LoadConfigs(RecipeStep configDefine, XmlNode configNode) { ConfigItems.Clear(); foreach (var param in configDefine) { if (param is DoubleParam param1) { var config = new DoubleParam() { Name = param.Name, Value = param1.Value, DisplayName = param.DisplayName, Minimun = param1.Minimun, Maximun = param1.Maximun, Resolution = param1.Resolution }; if (configNode?.Attributes?[param1.Name] != null) config.Value = double.Parse(configNode.Attributes[param1.Name].Value); ConfigItems.Add(config); } if (param is StringParam paramString) { var config = new StringParam() { Name = param.Name, Value = paramString.Value, DisplayName = param.DisplayName, }; if (configNode?.Attributes?[paramString.Name] != null) config.Value = configNode.Attributes[paramString.Name].Value; ConfigItems.Add(config); } } } /// /// 计算指定配方步骤的气体流量。 /// /// 指定的配方步骤。 internal void CalculateGasFlow(RecipeStep step) { _gasFlowCalculator?.Calculate(step, Module); } public bool ChkChanged(RecipeStepCollection steps) { return steps.IsSaved == false; } public bool ChkChanged(Dictionary popSteps) { foreach (var steps in popSteps.Values) { if (ChkChanged(steps)) return true; } return false; } public void BuildFormat(string path, string chamber, Role role) { Columns = _formatBuilder.Build(path, chamber, true, RecipeType); UpdatePermission(role); } public void Clear() { Steps.Clear(); PopSettingSteps.Clear(); StepTolerances.Clear(); ConfigItems.Clear(); RecipeChamberType = ""; RecipeVersion = ""; IsSavedDesc = true; Module = ""; } /// /// 将当前配方标记为已保存状态。 /// public void MarkAsSaved() { Steps.Save(); StepTolerances.Save(); PopSettingSteps.Values.ToList().ForEach(x => x.Save()); ConfigItems.Save(); IsSavedDesc = true; } /// /// 从指定的配方文件加载配方。 /// /// /// /// /// /// /// public async Task LoadFile(string prefixPath, string recipeName, string recipeContent, string module, Role role, Dispatcher dispatcher = null) { IsCompatibleWithCurrentFormat = false; RecipeChamberType = _formatBuilder.RecipeChamberType; RecipeVersion = _formatBuilder.RecipeVersion; Name = recipeName; PrefixPath = prefixPath; Module = module; try { _doc = new XmlDocument(); _doc.LoadXml(recipeContent); if (!LoadHeader(_doc.SelectSingleNode("Aitex/TableRecipeData"))) return; var nodeSteps = _doc.SelectNodes($"Aitex/TableRecipeData/Module[@Name='{module}']/Step"); if (nodeSteps == null) nodeSteps = _doc.SelectNodes($"Aitex/TableRecipeData/Step"); var processType = ""; if (PrefixPath.Contains("Process")) { processType = "Process"; } await LoadSteps(Columns, nodeSteps, role, processType, dispatcher); ValidLoopData(); var nodeConfig = _doc.SelectSingleNode($"Aitex/TableRecipeData/Module[@Name='{module}']/Config"); if (nodeSteps == null) nodeConfig = _doc.SelectSingleNode($"Aitex/TableRecipeData/Config"); LoadConfigs(_formatBuilder.Configs, nodeConfig); IsCompatibleWithCurrentFormat = true; } catch (Exception ex) { LOG.Write(ex); } } public async Task ChangeChamber(List columnDefine, RecipeStep configDefine, string module, Role role, Dispatcher dispatcher) { Module = module; try { var nodeSteps = _doc.SelectNodes($"Aitex/TableRecipeData/Module[@Name='{module}']/Step") ?? _doc.SelectNodes($"Aitex/TableRecipeData/Step"); await LoadSteps(columnDefine, nodeSteps, role, dispatcher: dispatcher); ValidLoopData(); var nodeConfig = _doc.SelectSingleNode($"Aitex/TableRecipeData/Module[@Name='{module}']/Config"); if (nodeSteps == null) nodeConfig = _doc.SelectSingleNode($"Aitex/TableRecipeData/Config"); LoadConfigs(configDefine, nodeConfig); } catch (Exception ex) { LOG.Write(ex); } } public void SaveTo(string[] moduleList) { GetXmlString(); var nodeModule = _doc.SelectSingleNode($"Aitex/TableRecipeData/Module[@Name='{Module}']"); if (nodeModule == null) { LOG.Write("recipe not find modules," + Name); return; } var nodeData = nodeModule.ParentNode; foreach (var module in moduleList) { if (module == Module) { continue; } var child = _doc.SelectSingleNode($"Aitex/TableRecipeData/Module[@Name='{module}']"); if (child != null) nodeData.RemoveChild(child); var node = nodeModule.Clone() as XmlElement; node.SetAttribute("Name", module); nodeData.AppendChild(node); } } /// /// 创建一个Recipe空步骤。 /// /// Recipe列集合 /// /// 前序Recipe步骤 /// /// public RecipeStep CreateStep(List columns, XmlNode stepNode = null, RecipeStep frontStep = null, int? stepId = null) { var newStep = new RecipeStep(frontStep); foreach (var col in columns) { var value = string.Empty; if (!(col is ExpanderColumn) && stepNode != null && !(col is StepColumn) && !(col is PopSettingColumn)) { if (string.IsNullOrEmpty(col.ModuleName) && stepNode.Attributes[col.ControlName] != null) { value = stepNode.Attributes[col.ControlName].Value; } else { if (stepNode.Attributes[col.ControlName] != null && stepNode.SelectSingleNode(col.ModuleName) != null && stepNode.SelectSingleNode(col.ModuleName).Attributes[col.ControlName] != null) value = stepNode.SelectSingleNode(col.ModuleName).Attributes[col.ControlName].Value; } } Param param = null; switch (col) { case StepColumn _: param = stepId.HasValue ? new StepParam(stepId.Value) : new StepParam(); param.Name = col.ControlName; param.EnableTolerance = col.EnableTolerance; break; case RatioColumn colRatio: if (colRatio.MaxSets == 3) { param = new Sets3RatioParam(col.Default) { Name = col.ControlName, IsEnabled = col.IsEnable, EnableTolerance = col.EnableTolerance, }; // 如果XML中保存的Value格式正确,则将设置为保存的Value。 if (Sets3RatioParam.ValidateRatioString(value)) { ((Sets3RatioParam)param).Value = value; } } break; case TextBoxColumn _: param = new StringParam(string.IsNullOrEmpty(value) ? (string.IsNullOrEmpty(col.Default) ? "" : col.Default) : value) { Name = col.ControlName, IsEnabled = col.IsEnable, EnableTolerance = col.EnableTolerance, }; break; case NumColumn colNum: param = new IntParam( (int)GlobalDefs.TryParseToDouble(value, GlobalDefs.TryParseToDouble(colNum.Default, colNum.Minimun)), (int)colNum.Minimun, (int)colNum.Maximun) { Name = colNum.ControlName, IsEnabled = colNum.IsEnable, EnableTolerance = colNum.EnableTolerance, }; break; case DoubleColumn colDbl: param = new DoubleParam( GlobalDefs.TryParseToDouble(value, GlobalDefs.TryParseToDouble(colDbl.Default, colDbl.Minimun)), colDbl.Minimun, colDbl.Maximun) { Name = colDbl.ControlName, IsEnabled = colDbl.IsEnable, Resolution = colDbl.Resolution, EnableTolerance = colDbl.EnableTolerance, }; break; case FlowModeColumn colFlowMode: { //string displayValue; //if (!string.IsNullOrEmpty(value) && // colFlowMode.Options.FirstOrDefault(x => x.ControlName == value) != null) //{ // displayValue = colFlowMode.Options.First(x => x.ControlName == value).DisplayName; //} //else //{ // if (!string.IsNullOrEmpty(colFlowMode.Default) && // colFlowMode.Options.Any(x => x.DisplayName == col.Default)) // { // displayValue = colFlowMode.Default; // } // else // { // var selIndex = 0; // displayValue = displayValue = colFlowMode.Options[selIndex].DisplayName; // } //} param = new FlowModeParam(string.IsNullOrEmpty(value) ? (string.IsNullOrEmpty(colFlowMode.Default) ? colFlowMode.Options[0].DisplayName : colFlowMode.Default) : value) { Name = colFlowMode.ControlName, Options = colFlowMode.Options, IsEditable = colFlowMode.IsEditable, EnableTolerance = colFlowMode.EnableTolerance, }; break; } case ComboxColumn colCbx: { //string displayValue; //if (!string.IsNullOrEmpty(value) && // colCbx.Options.FirstOrDefault(x => x.ControlName == value) != null) //{ // displayValue = colCbx.Options.First(x => x.ControlName == value).DisplayName; //} //else //{ // if (!string.IsNullOrEmpty(colCbx.Default) && // colCbx.Options.Any(x => x.DisplayName == col.Default)) // { // displayValue = colCbx.Default; // } // else // { // var selIndex = 0; // displayValue = displayValue = colCbx.Options[selIndex].DisplayName; // } //} param = new ComboxParam(string.IsNullOrEmpty(value) ? (string.IsNullOrEmpty(colCbx.Default) ? colCbx.Options[0].DisplayName : colCbx.Default) : value) { Name = colCbx.ControlName, Options = colCbx.Options, IsEditable = colCbx.IsEditable, EnableTolerance = colCbx.EnableTolerance, }; break; } case LoopComboxColumn colLoopCbx: { var selIndex = 0; param = new LoopComboxParam(string.IsNullOrEmpty(value) ? colLoopCbx.Options[selIndex].DisplayName : value) { Name = colLoopCbx.ControlName, Options = colLoopCbx.Options, IsEditable = colLoopCbx.IsEditable, IsLoopStep = false, EnableTolerance = colLoopCbx.EnableTolerance, }; break; } case ExpanderColumn _: param = new ExpanderParam(); break; case PopSettingColumn _: param = new PopSettingParam() { Name = col.ControlName, DisplayName = col.DisplayName, UnitName = col.UnitName, EnableTolerance = col.EnableTolerance, }; break; } if (param == null) break; param.ColumnPermChangedCallback = col.PermissionChangedCallback; if (col is LoopComboxColumn) { param.ColumnPermChangedCallback = LoopCellFeedback; } param.DisplayName = col.DisplayName; newStep.Add(param); } // 创建Step时默认有权限 newStep.Permission = MenuPermissionEnum.MP_READ_WRITE; foreach (var param in newStep) { // 根据列权限设置参数访问权限 param.ColumnPermChangedCallback?.Invoke(param); } return newStep; } public bool CreateStepTolerance(ObservableCollection columns, Dictionary popSettingColumns, XmlNode stepNode, out RecipeStep step, out RecipeStep warning, out RecipeStep alarm, out Dictionary popSettingStep) { step = new RecipeStep(null); warning = new RecipeStep(null); alarm = new RecipeStep(null); popSettingStep = new Dictionary(); foreach (var col in columns) { var warningValue = string.Empty; var alarmValue = string.Empty; var stepValue = string.Empty; var popValues = new Dictionary>(); if (!(col is ExpanderColumn) && stepNode != null && !(col is StepColumn) && !(col is PopSettingColumn)) { var warningNode = stepNode.SelectSingleNode("Warning"); if (warningNode != null && warningNode.Attributes[col.ControlName] != null) { warningValue = warningNode.Attributes[col.ControlName].Value; } var alarmNode = stepNode.SelectSingleNode("Alarm"); if (alarmNode != null && alarmNode.Attributes[col.ControlName] != null) { alarmValue = alarmNode.Attributes[col.ControlName].Value; } if (string.IsNullOrEmpty(col.ModuleName) && stepNode.Attributes[col.ControlName] != null) { stepValue = stepNode.Attributes[col.ControlName].Value; } else { if (stepNode.Attributes[col.ControlName] != null && stepNode.SelectSingleNode(col.ModuleName) != null && stepNode.SelectSingleNode(col.ModuleName).Attributes[col.ControlName] != null) stepValue = stepNode.SelectSingleNode(col.ModuleName).Attributes[col.ControlName].Value; } } if (col is PopSettingColumn) { foreach (var key in popSettingColumns.Keys) { var popNode = stepNode.SelectSingleNode(key); if (popNode != null) { var values = new Dictionary(); foreach (var item in popSettingColumns[key]) { if (popNode.Attributes[item.Name] != null) values.Add(item.Name, popNode.Attributes[item.Name].Value); }; popValues.Add(key, values); } } } Param stepCell = new DoubleParam(GlobalDefs.TryParseToDouble(stepValue)) { Name = col.ControlName, DisplayName = col.DisplayName, UnitName = col.UnitName, IsEnabled = false, StepCheckVisibility = Visibility.Hidden, }; stepCell.Parent = step; step.Add(stepCell); if (col is PopSettingColumn) { for (var i = 0; i < popSettingColumns[col.ControlName].Count; i++) { var name = popSettingColumns[col.ControlName][i].Name; var value = popValues[col.ControlName].Where(x => x.Key == name).Count() > 0 ? popValues[col.ControlName].First(x => x.Key == name).Value : ""; if (popSettingColumns[col.ControlName][i] is DoubleParam) { stepCell = new DoubleParam(GlobalDefs.TryParseToDouble(value)) { Name = name, DisplayName = popSettingColumns[col.ControlName][i].DisplayName, IsEnabled = false, StepCheckVisibility = Visibility.Hidden, }; } if (popSettingColumns[col.ControlName][i] is StringParam) { stepCell = new StringParam(value) { Name = name, DisplayName = popSettingColumns[col.ControlName][i].DisplayName, IsEnabled = false, StepCheckVisibility = Visibility.Hidden, }; } if (popSettingColumns[col.ControlName][i] is ComboxParam) { stepCell = new ComboxParam(value) { Name = name, DisplayName = popSettingColumns[col.ControlName][i].DisplayName, Options = ((ComboxParam)popSettingColumns[col.ControlName][i]).Options, IsEditable = !col.IsReadOnly, EnableTolerance = col.EnableTolerance, }; } if (!popSettingStep.ContainsKey(col.ControlName)) { popSettingStep.Add(col.ControlName, new RecipeStep(null)); } stepCell.Parent = popSettingStep[col.ControlName]; popSettingStep[col.ControlName].Add(stepCell); } } Param warningCell = new DoubleParam(col.EnableTolerance ? (GlobalDefs.TryParseToDouble(warningValue)) : double.NaN) { Name = col.ControlName, DisplayName = col.DisplayName, UnitName = col.UnitName, IsEnabled = col.EnableTolerance && stepValue != "0", StepCheckVisibility = Visibility.Collapsed, }; warningCell.ColumnPermChangedCallback = col.PermissionChangedCallback; warningCell.Parent = warning; warning.Add(warningCell); //Param alarmCell = new DoubleParam(col.EnableTolerance ? (string.IsNullOrEmpty(alarmValue) ? "0" : alarmValue) : "*") Param alarmCell = new DoubleParam(col.EnableTolerance ? (GlobalDefs.TryParseToDouble(alarmValue)) : double.NaN) { Name = col.ControlName, DisplayName = col.DisplayName, UnitName = col.UnitName, IsEnabled = col.EnableTolerance && stepValue != "0", StepCheckVisibility = Visibility.Collapsed, }; alarmCell.ColumnPermChangedCallback = col.PermissionChangedCallback; alarmCell.Parent = alarm; alarm.Add(alarmCell); } return true; } public void ValidLoopData() { if (Steps.Count == 0) return; for (var j = 0; j < Steps[0].Count; j++) { if (Steps[0][j] is LoopComboxParam) { LoopCellFeedback(Steps[0][j]); } } } /// /// 校验当前Recipe的参数值。 /// /// /// 校验结果请参考 /// 属性、 /// 属性、 /// 属性。 /// /// public bool Validate() { RecipeTotalTime = (int)Steps.GetTotalTime(); ValidationErrorSummary = string.Empty; /*var lstError = new List();*/ ValidationErrorInfo.Clear(); Steps.Validate(); // 校验所有步骤 foreach (var step in Steps) { var invalidParams = step .Where(x => x.IsValid == false).ToList(); ValidationErrorInfo.AddRange(invalidParams .Select(x => new RecipeStepValidationInfo( step, x, x.ValidationError))); } // 校验通过 if (ValidationErrorInfo.Count <= 0) { ValidationErrorCount = 0; } else { // 校验失败,生成错误信息摘要 ValidationErrorCount = ValidationErrorInfo.Count; var errCnt = 0; var strInfo = new StringBuilder(); foreach (var t in ValidationErrorInfo) { strInfo.AppendLine(t.ToString()); errCnt++; if (errCnt > 10) break; } if (errCnt < ValidationErrorInfo.Count) strInfo.AppendLine("\r\n"); ValidationErrorSummary = strInfo.ToString(); } OnValidated?.Invoke(this, EventArgs.Empty); return ValidationErrorCount <= 0; } public RecipeStep CloneStep(List columns, RecipeStep sourceParams) { var targetParams = CreateStep(columns); for (var i = 0; i < sourceParams.Count; i++) { var srcParam = sourceParams[i]; // StepUid列不能复制,插入、粘贴的Step生成新的Uid if (srcParam is { Name: RecipeFormatBuilder.SPEC_COL_STEPUID } or {Name: RecipeFormatBuilder.SPEC_COL_STEPNO}) continue; if (sourceParams[i] is StringParam) { ((StringParam)targetParams[i]).Value = ((StringParam)sourceParams[i]).Value; } else if (sourceParams[i] is Sets3RatioParam) { ((Sets3RatioParam)targetParams[i]).Value = ((Sets3RatioParam)sourceParams[i]).Value; } else if (sourceParams[i] is IntParam) { ((IntParam)targetParams[i]).Value = ((IntParam)sourceParams[i]).Value; } else if (sourceParams[i] is ComboxParam) { ((ComboxParam)targetParams[i]).Value = ((ComboxParam)sourceParams[i]).Value; } else if (sourceParams[i] is LoopComboxParam) { ((LoopComboxParam)targetParams[i]).Value = ((LoopComboxParam)sourceParams[i]).Value; } else if (sourceParams[i] is BoolParam) { ((BoolParam)targetParams[i]).Value = ((BoolParam)sourceParams[i]).Value; } else if (sourceParams[i] is DoubleParam) { ((DoubleParam)targetParams[i]).Value = ((DoubleParam)sourceParams[i]).Value; } } return targetParams; } public string GetXmlString() { try { var nodeData = _doc.SelectSingleNode($"Aitex/TableRecipeData") as XmlElement; nodeData.SetAttribute("CreatedBy", Creator); nodeData.SetAttribute("CreationTime", CreateTime.ToString("yyyy-MM-dd HH:mm:ss")); nodeData.SetAttribute("LastRevisedBy", Revisor); nodeData.SetAttribute("LastRevisionTime", ReviseTime.ToString("yyyy-MM-dd HH:mm:ss")); nodeData.SetAttribute("Description", Description); nodeData.SetAttribute("RecipeChamberType", RecipeChamberType); nodeData.SetAttribute("RecipeVersion", RecipeVersion); var nodeModule = _doc.SelectSingleNode($"Aitex/TableRecipeData/Module[@Name='{Module}']"); if (nodeModule == null) { nodeModule = _doc.CreateElement("Module"); nodeData.AppendChild(nodeModule); } nodeModule.RemoveAll(); (nodeModule as XmlElement).SetAttribute("Name", Module); var i = 0; foreach (var parameters in Steps) { var nodeWarning = _doc.CreateElement("Warning"); var nodeAlarm = _doc.CreateElement("Alarm"); var nodePop = new Dictionary(); foreach (var key in PopEnable.Keys) { nodePop.Add(key, _doc.CreateElement(key)); } var nodeStep = _doc.CreateElement("Step"); foreach (var parameter in parameters) { if (ToleranceEnable) { if (parameter.EnableTolerance) { nodeWarning.SetAttribute(parameter.Name, "1"); nodeAlarm.SetAttribute(parameter.Name, "7"); } } if (parameter is IntParam intParam) { //去除Int型数据前面多余的"0" if (int.TryParse(intParam.Value.ToString(), out var result)) { if (result != 0) { intParam.Value = int.Parse(intParam.Value.ToString().TrimStart('0')); } } nodeStep.SetAttribute(intParam.Name, intParam.Value.ToString()); } else if (parameter is DoubleParam doubleParam) { //去除Double型数据前面多余的"0" //if (int.TryParse(((DoubleParam)parameter).Value, out var result)) //{ // if (result != 0) // { // ((DoubleParam)parameter).Value = ((DoubleParam)parameter).Value.TrimStart('0'); // } //} nodeStep.SetAttribute(doubleParam.Name, doubleParam.Value.ToString()); } else if (parameter is Sets3RatioParam param) nodeStep.SetAttribute(param.Name, param.Value.ToString()); else if (parameter is StringParam stringParam) nodeStep.SetAttribute(stringParam.Name, stringParam.Value.ToString()); else if (parameter is ComboxParam comboxParam) nodeStep.SetAttribute(comboxParam.Name, comboxParam.Options.First(x => x.DisplayName == ((ComboxParam)parameter).Value.ToString()).ControlName); else if (parameter is LoopComboxParam loopComboxParam) nodeStep.SetAttribute(loopComboxParam.Name, loopComboxParam.Value.ToString()); else if (parameter is PositionParam positionParam) nodeStep.SetAttribute(positionParam.Name, positionParam.Value.ToString()); else if (parameter is BoolParam boolParam) nodeStep.SetAttribute(boolParam.Name, boolParam.Value.ToString()); else if (parameter is StepParam stepParam) nodeStep.SetAttribute(stepParam.Name, stepParam.Value.ToString()); else if (parameter is MultipleSelectParam selectParam) { var selected = new List(); selectParam.Options.Apply( opt => { if (opt.IsChecked) selected.Add(opt.ControlName); } ); nodeStep.SetAttribute(selectParam.Name, string.Join(",", selected)); } else if (parameter is PopSettingParam) { SetAttribute(PopSettingSteps[parameter.Name][i], nodePop[parameter.Name]); } } if (ToleranceEnable) { nodeStep.AppendChild(nodeWarning); nodeStep.AppendChild(nodeAlarm); } foreach (var key in PopEnable.Keys) { if (PopEnable[key]) { nodeStep.AppendChild(nodePop[key]); } } nodeModule.AppendChild(nodeStep); i++; } var nodeConfig = _doc.CreateElement("Config"); foreach (var parameter in ConfigItems) { if (parameter.Visible == Visibility.Visible) { if (parameter is IntParam) nodeConfig.SetAttribute(parameter.Name, ((IntParam)parameter).Value.ToString()); else if (parameter is DoubleParam) { /*var strValue = ((DoubleParam)parameter).Value; var succed = double.TryParse(strValue, out var dValue); if (!succed) { MessageBox.Show($"The set value of {parameter.DisplayName} is {strValue}, not a valid value"); return null; }*/ var dValue = ((DoubleParam)parameter).Value; var config = ConfigItems.Where(m => m.Name == parameter.Name).FirstOrDefault(); if (config is DoubleParam param1) { if (param1.Minimun == 0 && param1.Maximun == 0) { //没有设定范围 } else if (dValue > param1.Maximun || dValue < param1.Minimun) { MessageBox.Show($"The set value of {parameter.DisplayName} is {dValue}, out of the range {param1.Minimun}~{param1.Maximun}"); return null; } } nodeConfig.SetAttribute(parameter.Name, ((DoubleParam)parameter).Value.ToString()); } else if (parameter is StringParam) nodeConfig.SetAttribute(parameter.Name, ((StringParam)parameter).Value.ToString()); else if (parameter is ComboxParam) nodeConfig.SetAttribute(parameter.Name, ((ComboxParam)parameter).Value.ToString()); else if (parameter is LoopComboxParam) nodeConfig.SetAttribute(parameter.Name, ((LoopComboxParam)parameter).Value.ToString()); else if (parameter is PositionParam) nodeConfig.SetAttribute(parameter.Name, ((PositionParam)parameter).Value.ToString()); else if (parameter is BoolParam) nodeConfig.SetAttribute(parameter.Name, ((BoolParam)parameter).Value.ToString()); else if (parameter is StepParam) nodeConfig.SetAttribute(parameter.Name, ((StepParam)parameter).Value.ToString()); else if (parameter is MultipleSelectParam) { var selected = new List(); ((MultipleSelectParam)parameter).Options.Apply( opt => { if (opt.IsChecked) selected.Add(opt.ControlName); } ); nodeConfig.SetAttribute(parameter.Name, string.Join(",", selected)); } } } nodeModule.AppendChild(nodeConfig); return _doc.OuterXml; } catch (Exception ex) { LOG.Write(ex.Message); return ""; } } public string ToXmlString() { var builder = new StringBuilder(); builder.Append(""); builder.Append(string.Format("", Creator, CreateTime.ToString("yyyy-MM-dd HH:mm:ss"), Revisor, ReviseTime.ToString("yyyy-MM-dd HH:mm:ss"), Description, RecipeChamberType, RecipeVersion)); foreach (var parameters in Steps) { builder.Append("(); ((MultipleSelectParam)parameter).Options.Apply( opt => { if (opt.IsChecked) selected.Add(opt.ControlName); } ); builder.Append(parameter.Name + "=\"" + string.Join(",", selected) + "\" "); } } } builder.Append("/>"); } builder.Append("(); ((MultipleSelectParam)parameter).Options.Apply( opt => { if (opt.IsChecked) selected.Add(opt.ControlName); } ); builder.Append(parameter.Name + "=\"" + string.Join(",", selected) + "\" "); } } } builder.Append("/>"); builder.Append(""); return builder.ToString(); } #region Step Operations public void AddStep() { Steps.Add(CreateStep(Columns)); Validate(); } public void InsertToRight(RecipeStep step) { if (step == null) return; if (!RecipeStep.ValidateStepNo(step.StepNo, out var no)) return; var index = no - RecipeFormatBuilder.STEP_INDEX_BASE; if (index < Steps.Count - 1) Steps.Insert(index + 1, CreateStep(Columns)); else Steps.Add(CreateStep(Columns)); } public void InsertToLeft(RecipeStep step) { if (step == null) return; if (!RecipeStep.ValidateStepNo(step.StepNo, out var no)) return; var index = no - RecipeFormatBuilder.STEP_INDEX_BASE; Steps.Insert(index, CreateStep(Columns)); } private readonly List _copiedSteps = new List(); public void CopySteps() { _copiedSteps.Clear(); if (!Steps.SelectedSteps.Any()) return; foreach (var step in Steps.SelectedSteps) { _copiedSteps.Add(CloneStep(Columns, step)); } } /// /// 粘贴Step。 /// 注意:async Task 标记的方法不能直接应用于Caliburn.Message.Attach 方法。 /// /// 是否粘贴到当前选中的Step的左侧。 /// public async Task Paste(bool isToLeft) { if (Steps.SelectedSteps.Count != 1) return; if (_copiedSteps.Count <= 0) return; var stepNo = Steps.SelectedSteps[0].StepNo; if (RecipeStep.ValidateStepNo(stepNo, out var vStepNo) == false) return; foreach (var t in _copiedSteps) { var cloned = CloneStep(Columns, t); var pos = isToLeft ? vStepNo - RecipeFormatBuilder.STEP_INDEX_BASE : vStepNo; await Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background, (Action)(() => { Steps.Insert(pos, cloned); })); } ValidLoopData(); Validate(); } public void DeleteSteps() { if (!Steps.SelectedSteps.Any()) return; for (var i = Steps.SelectedSteps.Count - 1; i >= 0; i--) { Steps.Remove(Steps.SelectedSteps[i]); } Validate(); } #endregion #region Import Export Methods private static byte[] GetMagicNumber() { return Encoding.ASCII.GetBytes("SIC"); } /// /// 导入Recipe /// /// /// public static void ImportRecipe(string fileName, out string xmlContent) { xmlContent = ""; using (var fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.None)) { using (var br = new BinaryReader(fs)) { var magicNumDesired = GetMagicNumber(); var magicNum = br.ReadBytes(3); // Magic Number incorrect if (!magicNum.SequenceEqual(magicNumDesired)) throw new Exception("format error, code -1."); // read hash data var lenHash = br.ReadInt32(); var hashInFile = br.ReadBytes(lenHash); // read base64 recipe content var lenContent = br.ReadInt32(); var buffContent = br.ReadBytes(lenContent); // check hash using (var sha = SHA256.Create()) { var hash = sha.ComputeHash(buffContent); if (!hash.SequenceEqual(hashInFile)) throw new Exception("format error, code -2."); } var strBase64 = Encoding.ASCII.GetString(buffContent); var bufRecipe = Convert.FromBase64String(strBase64); var xmlRecipe = Encoding.UTF8.GetString(bufRecipe); xmlContent = xmlRecipe; } } } /// /// 导出Recipe /// /// public void ExportRecipe(string fileName) { var xmlContent = GetXmlString(); // 未加密,仅用Base64对Recipe字串编码,可直接用WinHex查看内容,方便后期调试。 var ba = Encoding.UTF8.GetBytes(xmlContent); var base64 = Convert.ToBase64String(ba); ba = Encoding.ASCII.GetBytes(base64); using (var sha = SHA256.Create()) { var hash = sha.ComputeHash(ba); using (var fs = new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None)) { // discard the contents of the file by setting the length to 0 fs.SetLength(0); using (var bw = new BinaryWriter(fs)) { var magicNum = GetMagicNumber(); bw.Write(magicNum, 0, magicNum.Length); bw.Write(hash.Length); bw.Write(hash); bw.Write(ba.Length); bw.Write(ba); bw.Flush(); } } } } #endregion #region Param Accessible Whitelist Edit Mode private void ResetHighlight() { Steps.ResetHighlight(); } private async Task HideValueButAccessibleWhitelist() { Steps.ToList().ForEach(s=>s.Permission = MenuPermissionEnum.MP_NONE); await ShowAccessibleWhitelistParamValues(); } /// /// 恢复所有参数的访问权限到指定的角色。 /// /// public void UpdatePermission(Role role) { if (RecipeType != RecipeType.Process) return; // 更新RecipeFormatBuilder中Columns的权限 _formatBuilder.UpdateColumnsPermission(role); // 更新当前RecipeData中Columns的权限 foreach (var col in Columns) { RecipeFormatBuilder.SetPermission(col, role); } // 更新当前RecipeData中Steps的权限 foreach (var step in Steps) { foreach (var param in step) { param.ColumnPermChangedCallback?.Invoke(param); } RecipeStep.SetPermission(step, role); } } /// /// 从数据库读取参数访问白名单。 /// /// private Task GetAccessibleWhitelist(string recipeFullName) { var cmd = $"select * from recipe_cell_access_permission_whitelist where \"recipeName\" = '{recipeFullName}'"; var dt = QueryDataClient.Instance.Service.QueryData(cmd); return Task.FromResult(dt); } /// /// 高亮访问白名单中的参数。 /// private async Task HighlightParamsInAccessibleWhitelist() { var dt = await GetAccessibleWhitelist(FullName); if (dt != null && dt.Rows.Count > 0) { foreach (DataRow dataRow in dt.Rows) { var stepUid = dataRow["stepUid"].ToString(); var controlName = dataRow["columnName"].ToString(); var step = Steps.FirstOrDefault(s => s.StepUid == stepUid); var param = step?.FirstOrDefault(p => p.Name == controlName); Application.Current?.Dispatcher.BeginInvoke(() => { param?.Highlight(); }, DispatcherPriority.Background); } } } /// /// 计算访问白名单中的参数的数量。 /// /// private int CountAccessibleWhitelistParams() { var total = Steps.ToList().Sum(x => x.GetHighlightedParams().Count); return total; } /// /// 保存Cell访问权限白名单。 /// private async Task SaveAccessibleWhitelist() { var cmdStr = new StringBuilder(); var insertExp = "INSERT into recipe_cell_access_permission_whitelist (\"uid\", \"recipeName\",\"stepUid\",\"columnName\",\"whoSet\",\"whenSet\") VALUES "; var whenSet = DateTime.Now; // 枚举所有高亮的Recipe参数。 foreach (var step in Steps) { var lst = step.GetHighlightedParams(); lst.ForEach(p => { cmdStr.AppendLine($"({(new RecipeCellAccessPermissionWhitelistInfo(FullName, step.StepUid, p.Name, whenSet))}),"); }); } // 是否有需要保存的Cell信息?如果没有,cmdStr留空,则之前的白名单会被删除。 if (cmdStr.Length > 0) cmdStr.Insert(0, insertExp); // 刷新白名单 QueryDataClient.Instance.Service.ExcuteTransAction(new List(new string[] { // 删除原有的白名单 $"delete from recipe_cell_access_permission_whitelist where \"recipeName\" = '{FullName}'", // 写入新的白名单 cmdStr.ToString().TrimEnd('\n').TrimEnd('\r').TrimEnd(',') })); await Task.CompletedTask; } /// /// 显示访问白名单中的参数的数值。 /// private async Task ShowAccessibleWhitelistParamValues() { var dt = await GetAccessibleWhitelist(FullName); if (dt != null && dt.Rows.Count > 0) { foreach (DataRow dataRow in dt.Rows) { var stepUid = dataRow[RecipeFormatBuilder.SPEC_COL_STEPUID].ToString(); var controlName = dataRow["columnName"].ToString(); var step = Steps.FirstOrDefault(s => s.StepUid == stepUid); var param = step?.FirstOrDefault(p => p.Name == controlName); if (param != null) { Application.Current?.Dispatcher.BeginInvoke(() => { param.ColumnPermission = MenuPermissionEnum.MP_READ_WRITE; param.StepPermission = MenuPermissionEnum.MP_READ_WRITE; }, DispatcherPriority.Background); } } } } /// /// 进入访问白名单编辑模式。 /// /// public async Task<(bool isSucceeded, string reason)> EnterAccessibleWhitelistEditMode() { ChkChanged(Steps); if (IsChanged) { var reason = "the recipe has been changed."; return await Task.FromResult((false, reason)); } ResetHighlight(); await HighlightParamsInAccessibleWhitelist(); IsAccessibleWhitelistEditMode = true; return await Task.FromResult((true, "")); } /// /// 退出访问白名单编辑模式。 /// public async Task LeaveAccessibleWhitelistEditMode() { await SaveAccessibleWhitelist(); ResetHighlight(); if (IsEnableAccessibleWhitelist) await HideValueButAccessibleWhitelist(); IsAccessibleWhitelistEditMode = false; await Task.CompletedTask; } /// /// 删除Cell访问权限白名单。 /// public void DeleteAccessibleWhiteList() { // 刷新白名单 QueryDataClient.Instance.Service.ExcuteTransAction(new List(new string[] { // 删除原有的白名单 $"delete from recipe_cell_access_permission_whitelist where \"recipeName\" = '{FullName}'", })); } #endregion #endregion } }