using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Linq; using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Input; using System.Windows.Media; using System.Xml; using Aitex.UI.RecipeEditor.Core; using Aitex.UI.RecipeEditor.View; using ExcelLibrary.SpreadSheet; using Microsoft.Win32; using Xceed.Wpf.DataGrid; namespace Aitex.UI.RecipeEditor { public class RecipeEditorControlViewModel : INotifyPropertyChanged { public List, Guid>> UndoList { get; set; } public List, Guid>> RedoList { get; set; } public bool IsUndoEnabled { get { return UndoList != null && UndoList.Count > 0; } } public bool IsRedoEnabled { get { return RedoList != null && RedoList.Count > 0; } } public ObservableCollection RecipeRows { get; set; } private ObservableCollection _previousRecipeRows { get; set; } private string _valueBeforeEditing { get; set; } private string _currentRecipeVariationName = string.Empty; private SmartCellData _currentEditingCellData { get; set; } public List> Errors { get; set; } public List> CreateRecipeList { get; set; } public List> OpenRecipeList { get; set; } public DataGridControl DataGridControl { get; set; } public int CurrentRunningStepNo { get; set; } public Visibility SingleAppElementVisibility { get; set; } public Visibility RecipeInfoTextVisibility { get; set; } public ICommand ShowDetailedErrInfoCommand { get; set; } public ICommand EditRecipeInfoCommand { get; set; } //public ICommand CellContentChangedCommand { get; set; } public ICommand RightClickCommand { get; set; } public ICommand OpenLocalRecipeCommand { get; set; } public ICommand ExpandGroupCommand { get; set; } public ICommand CollapseGroupCommand { get; set; } public ICommand SaveRecipeCommand { get; set; } public ICommand RecipeHelpDocCommand { get; set; } public ICommand RecipeExport2ExcelCommand { get; set; } public ICommand ToggleHideSameCommand { get; set; } public ICommand UndoCommand { get; set; } public ICommand RedoCommand { get; set; } public ICommand SmartCellEditorLoadedCommand { get; set; } public ICommand SmartCellEditorUnloadedCommand { get; set; } public RecipeHead RecipeHead { get; set; } public bool IsBusy { get; set; } public bool IsHideSameContent { get; set; } public string RecipeInfo { get; set; } public HashSet MaskedTechNames { get; set; } public HashSet MaskedCatalogNames { get; set; } public event EventHandler OnSaveRecipeFile; public event EventHandler OnLocalRecipeOpened; public event EventHandler OnLoadRecipeContent; public bool IsRecipeModified { get; set; } public string UserName { set; get; } //private List> SourceDilteInjectRestrictions { get; set; } private Dictionary _preDefinedRecipeVars { get; set; } private List> _validationRules { get; set; } private List _checkVariables { get; set; } private Dictionary RowVarNameDic { get; set; } private Dictionary> _copiedColumnDatas = new Dictionary>(); private Dictionary>> _recipeReadingFormat; private Dictionary/*each item's control name*/> _recipeSavingFormat; private string _currentRecipeFormatContent; public bool IsBarcodeEnabled { get; set; } public Visibility IsBarcodeVisibility { get { return IsBarcodeEnabled ? Visibility.Visible : Visibility.Hidden; } } /// /// class construction /// public RecipeEditorControlViewModel() { UndoList = new List, Guid>>(); RedoList = new List, Guid>>(); //if(SingleAppElementVisibility CreateRecipeList = new List>(); OpenRecipeList = new List>(); foreach (var item in GetDefinedRecipeVariations()) { CreateRecipeList.Add(new Tuple("配置:" + item, item)); OpenRecipeList.Add(new Tuple("转换为配置:" + item, item)); } RecipeInfoTextVisibility = Visibility.Collapsed; SingleAppElementVisibility = Visibility.Collapsed; RightClickCommand = new DelegatedCommand((o) => true, (o) => RightClickAction()); ExpandGroupCommand = new DelegatedCommand((o) => true, (o) => ExpandGroups()); CollapseGroupCommand = new DelegatedCommand((o) => true, (o) => CollapseGroups()); SaveRecipeCommand = new DelegatedCommand((o) => true, (o) => SaveRecipeFile()); OpenLocalRecipeCommand = new DelegatedCommand((o) => true, (o) => OpenLocalRecipe()); ShowDetailedErrInfoCommand = new DelegatedCommand((o) => true, (o) => ShowDetailedErrInformation()); EditRecipeInfoCommand = new DelegatedCommand((o) => true, (o) => EditRecipeInformation()); RecipeHelpDocCommand = new DelegatedCommand((o) => true, (o) => ShowHelpDocView()); RecipeExport2ExcelCommand = new DelegatedCommand((o) => true, (o) => Export2Excel()); ToggleHideSameCommand = new DelegatedCommand((o) => true, (o) => ToggleHideSameContent()); UndoCommand = new DelegatedCommand((o) => true, (o) => UndoOperation(o)); RedoCommand = new DelegatedCommand((o) => true, (o) => RedoOperation(o)); SmartCellEditorLoadedCommand = new DelegatedCommand((o) => true, (o) => SmartCellEditorLoaded(o)); SmartCellEditorUnloadedCommand = new DelegatedCommand((o) => true, (o) => SmartCellEditorUnloaded(o)); } /// /// When smart cell editor loaded /// /// private void SmartCellEditorLoaded(object obj) { _currentEditingCellData = obj as SmartCellData; if (_currentEditingCellData == null) return; _valueBeforeEditing = _currentEditingCellData.Value; _previousRecipeRows = CloneRecipeRowData(RecipeRows); } /// /// make a clone of current recipe row data /// /// private ObservableCollection CloneRecipeRowData(ObservableCollection oldData) { ObservableCollection newRecipeRows = new ObservableCollection(); foreach (var item in oldData) { var newRecipeRow = new RecipeRow() { CatalogName = item.CatalogName, FriendlyName = item.FriendlyName, TechnicalName = item.TechnicalName, RecipeItems = new ObservableCollection() }; newRecipeRows.Add(newRecipeRow); foreach (var item2 in item.RecipeItems) { var newRecipeItem = new SmartCellData() { Background = item2.Background, ErrInfo = item2.ErrInfo, FontWeight = item2.FontWeight, Foreground = item2.Foreground, IsHidden = item2.IsHidden, IsMasked = item2.IsMasked, IsRunning = item2.IsRunning, RecipeVariableDefine = item2.RecipeVariableDefine, Value = item2.Value }; newRecipeRow.RecipeItems.Add(newRecipeItem); } } return newRecipeRows; } /// /// When smart cell editor unloaded /// /// private void SmartCellEditorUnloaded(object obj) { if (_currentEditingCellData != null && _currentEditingCellData.Value != _valueBeforeEditing) { IsRecipeModified = true; var ret = OverrideSelectedCellsData(); if (ret < 2) AddUndoHistory(string.Format("第{0}步({1})", _currentEditingCellData.ColIndex + 1, _currentEditingCellData.RecipeVariableDefine.CatalogName + _currentEditingCellData.RecipeVariableDefine.FriendlyName), _previousRecipeRows); else AddUndoHistory(string.Format("第{0}步({1})等{2}个单元格数据", _currentEditingCellData.ColIndex + 1, _currentEditingCellData.RecipeVariableDefine.CatalogName + _currentEditingCellData.RecipeVariableDefine.FriendlyName, ret), _previousRecipeRows); } RefreshCellsDisplay(false); } /// /// Adding undo history /// /// /// private void AddUndoHistory(string description, ObservableCollection recipeRows) { System.Diagnostics.Debug.WriteLine(description); if (recipeRows == null) return; string shortDesc = ""; if (description.Length > 50) { shortDesc = description.Substring(0, 50); shortDesc += "..."; } else { shortDesc = description; } UndoList.Insert(0, new Tuple, Guid>(shortDesc, description, recipeRows, Guid.NewGuid())); UndoList = new List, Guid>>(UndoList.ToList()); RedoList = new List, Guid>>(); InvokePropertyChanged("UndoList"); InvokePropertyChanged("RedoList"); InvokePropertyChanged("IsUndoEnabled"); InvokePropertyChanged("IsRedoEnabled"); } /// /// Redo something here /// private void RedoOperation(object obj) { try { if (RedoList == null || RedoList.Count == 0) return; Guid redoId = Guid.Empty; if (obj == null) redoId = RedoList[0].Item4; else redoId = (Guid)obj; var itemIndex = RedoList.FindIndex((o) => o.Item4 == redoId); if (itemIndex < 0) return; UndoList.Reverse(); for (int i = 0; i <= itemIndex; i++) { if (i == 0) UndoList.Add(new Tuple, Guid>(RedoList[i].Item1, RedoList[i].Item2, CloneRecipeRowData(RecipeRows), RedoList[i].Item4)); else UndoList.Add(new Tuple, Guid>(RedoList[i].Item1, RedoList[i].Item2, RedoList[i - 1].Item3, RedoList[i].Item4)); } UndoList.Reverse(); UndoList = new List, Guid>>(UndoList.ToList()); RecipeRows = RedoList[itemIndex].Item3; RedoList.RemoveRange(0, itemIndex + 1); RedoList = new List, Guid>>(RedoList.ToList()); RefreshDataGrid(); RefreshCellsDisplay(false); InvokePropertyChanged("UndoList"); InvokePropertyChanged("RedoList"); InvokePropertyChanged("IsUndoEnabled"); InvokePropertyChanged("IsRedoEnabled"); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex.Message); MessageBox.Show("恢复动作失败!\r\n\r\n" + ex.Message, "警告", MessageBoxButton.OK, MessageBoxImage.Warning); } } /// /// Undo something here /// private void UndoOperation(object obj) { try { if (UndoList == null || UndoList.Count == 0) return; Guid undoId = Guid.Empty; if (obj == null) undoId = UndoList[0].Item4; else undoId = (Guid)obj; var itemIndex = UndoList.FindIndex((o) => o.Item4 == undoId); if (itemIndex < 0) return; RedoList.Reverse(); for (int i = 0; i <= itemIndex; i++) { if (i == 0) RedoList.Add(new Tuple, Guid>(UndoList[i].Item1, UndoList[i].Item2, CloneRecipeRowData(RecipeRows), UndoList[i].Item4)); else RedoList.Add(new Tuple, Guid>(UndoList[i].Item1, UndoList[i].Item2, UndoList[i - 1].Item3, UndoList[i].Item4)); } RedoList.Reverse(); RedoList = new List, Guid>>(RedoList.ToList()); RecipeRows = UndoList[itemIndex].Item3; UndoList.RemoveRange(0, itemIndex + 1); UndoList = new List, Guid>>(UndoList.ToList()); RefreshDataGrid(); RefreshCellsDisplay(false); InvokePropertyChanged("UndoList"); InvokePropertyChanged("RedoList"); InvokePropertyChanged("IsUndoEnabled"); InvokePropertyChanged("IsRedoEnabled"); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex.Message); MessageBox.Show("撤销动作失败!\r\n\r\n" + ex.Message, "警告", MessageBoxButton.OK, MessageBoxImage.Warning); } } /// /// hide or show same content before and after one step /// private void ToggleHideSameContent() { IsHideSameContent = !IsHideSameContent; RefreshCellsDisplay(false); } /// /// get recipe variations /// /// public List GetDefinedRecipeVariations() { var list1 = new List(); var dir = new System.IO.FileInfo(Process.GetCurrentProcess().MainModule.FileName).Directory; string cfgPath = dir + "\\Config\\"; var di = new System.IO.DirectoryInfo(cfgPath); if (di.Exists) { foreach (var item in di.GetFiles("*.xml")) { list1.Add(item.Name.Substring(0, item.Name.Length - item.Extension.Length)); } } return list1; } /// /// Export to excel /// private void Export2Excel() { try { if (RecipeRows == null) return; var dlg = new SaveFileDialog(); dlg.DefaultExt = ".xls"; dlg.Filter = "Excel文件 (.xls)|*.xls|所有文件 (*.*)|*.*"; if (dlg.ShowDialog() == true) { if (File.Exists(dlg.FileName)) File.Delete(dlg.FileName); Workbook workbook = new Workbook(); Worksheet worksheet = new Worksheet("First Sheet"); XmlDocument doc = new XmlDocument(); doc.LoadXml(_currentRecipeFormatContent); var catalogNodes = doc.SelectNodes("TableRecipeFormat/Catalog"); int groupRowIndex = 0; int stepRowIndex = 0; int catalogRowIndex = 0; foreach (XmlElement cNode in catalogNodes) { XmlNodeList sNodes = cNode.SelectNodes("Group/Step"); string catalogHeader = cNode.Attributes["DisplayName"].Value; worksheet.Cells[catalogRowIndex, 0] = new ExcelLibrary.SpreadSheet.Cell(catalogHeader); catalogRowIndex += sNodes.Count; XmlNodeList gNodes = cNode.SelectNodes("Group"); foreach (XmlElement gNode in gNodes) { XmlNodeList stepNodes = gNode.SelectNodes("Step"); string groupHeader = gNode.Attributes["DisplayName"].Value; worksheet.Cells[groupRowIndex, 1] = new ExcelLibrary.SpreadSheet.Cell(groupHeader); groupRowIndex += stepNodes.Count; foreach (XmlElement nd in stepNodes) { string stepHeader = nd.Attributes["DisplayName"].Value; worksheet.Cells[stepRowIndex, 2] = new ExcelLibrary.SpreadSheet.Cell(stepHeader); stepRowIndex++; } } } string recipeContent = GetRecipeContentString(); XmlDocument rcp = new XmlDocument(); rcp.LoadXml(recipeContent); XmlNodeList stepNodeList = rcp.SelectNodes("/TableRecipeData/Step"); for (int stepNo = 0; stepNo < stepNodeList.Count; stepNo++) { var dataDic = new Dictionary(); foreach (XmlAttribute att in (stepNodeList[stepNo] as XmlElement).Attributes) { dataDic.Add(att.Name, att.Value); } //fill sub node's attributes foreach (XmlElement subStep in stepNodeList[stepNo].ChildNodes) { foreach (XmlAttribute att in subStep.Attributes) { dataDic.Add(att.Name, att.Value); } foreach (XmlElement subsubStep in subStep.ChildNodes) { foreach (XmlAttribute att2 in subsubStep.Attributes) { dataDic.Add(att2.Name, att2.Value); } } } long length = dataDic.LongCount(); int step = 0; foreach (string key in dataDic.Keys) { worksheet.Cells.ColumnWidth[(ushort)step] = 4000; var cellContent = dataDic[key]; double v; if (double.TryParse(cellContent, out v)) { worksheet.Cells[step, stepNo + 3] = new ExcelLibrary.SpreadSheet.Cell(v); } else { worksheet.Cells[step, stepNo + 3] = new ExcelLibrary.SpreadSheet.Cell(cellContent); } step++; } } workbook.Worksheets.Add(worksheet); workbook.Save(dlg.FileName); MessageBox.Show(string.Format("成功导出到Excel文件!\r\n\r\n{0}", dlg.FileName), "导出", MessageBoxButton.OK, MessageBoxImage.Information); } } catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex.Message); MessageBox.Show("导出Excel文件发生异常!\r\n\r\n" + ex.Message, "导出", MessageBoxButton.OK, MessageBoxImage.Error); } } /// /// Show recipe information /// private void ShowHelpDocView() { var view = RecipeHelpView.Instance; view.Owner = Application.Current.MainWindow; view.DataContext = this; if (view.Visibility == Visibility.Visible) view.Hide(); else view.Show(); } /// /// Edit recipe information /// private void EditRecipeInformation() { RecipeInfoEditor editor = new RecipeInfoEditor() { RecipeHead = this.RecipeHead, Owner = Application.Current.MainWindow }; editor.ShowDialog(); InvokePropertyChanged("RecipeHead"); } public void EnableBarcode(bool enable) { IsBarcodeEnabled = enable; InvokePropertyChanged("IsBarcodeVisibility"); } public void SetCurrentUser(string user) { UserName = user; InvokePropertyChanged("UserName"); } /// /// Show recipe information /// private void ShowDetailedErrInformation() { var view = ErrorInformationDetailsView.Instance; view.Owner = Application.Current.MainWindow; view.DataContext = this; if (view.Visibility == Visibility.Visible) view.Hide(); else view.Show(); } /// /// Update selected cells data /// /// overrided cells number private int OverrideSelectedCellsData() { int overridedCellsNum = 0; if (_currentEditingCellData != null) { var finalCellData = _currentEditingCellData; bool containsEditingCell = false; foreach (var item in DataGridControl.SelectedCellRanges) { int startColIndex = Math.Min(item.ColumnRange.StartIndex, item.ColumnRange.EndIndex); int endColIndex = Math.Max(item.ColumnRange.StartIndex, item.ColumnRange.EndIndex); int startRowIndex = Math.Min(item.ItemRange.StartIndex, item.ItemRange.EndIndex); int endRowIndex = Math.Max(item.ItemRange.StartIndex, item.ItemRange.EndIndex); if (startColIndex > 0 && startColIndex <= RecipeRows[0].RecipeItems.Count && endColIndex > 0 && endColIndex <= RecipeRows[0].RecipeItems.Count && startRowIndex >= 0 && startRowIndex < RecipeRows.Count && endRowIndex >= 0 && endRowIndex < RecipeRows.Count) { for (int m = startRowIndex; m <= endRowIndex; m++) { for (int n = startColIndex; n <= endColIndex; n++) { int rowIndex = m; int colIndex = n - 1; if (RecipeRows[rowIndex].RecipeItems[colIndex] == _currentEditingCellData) { containsEditingCell = true; break; } } } } } if (containsEditingCell) { foreach (var item in DataGridControl.SelectedCellRanges) { int startColIndex = Math.Min(item.ColumnRange.StartIndex, item.ColumnRange.EndIndex); int endColIndex = Math.Max(item.ColumnRange.StartIndex, item.ColumnRange.EndIndex); int startRowIndex = Math.Min(item.ItemRange.StartIndex, item.ItemRange.EndIndex); int endRowIndex = Math.Max(item.ItemRange.StartIndex, item.ItemRange.EndIndex); if (startColIndex > 0 && startColIndex <= RecipeRows[0].RecipeItems.Count && endColIndex > 0 && endColIndex <= RecipeRows[0].RecipeItems.Count && startRowIndex >= 0 && startRowIndex < RecipeRows.Count && endRowIndex >= 0 && endRowIndex < RecipeRows.Count) { for (int m = startRowIndex; m <= endRowIndex; m++) { for (int n = startColIndex; n <= endColIndex; n++) { int rowIndex = m; int colIndex = n - 1; if (RecipeRows[rowIndex].RecipeItems[colIndex].RecipeVariableDefine.CellType == finalCellData.RecipeVariableDefine.CellType) { overridedCellsNum++; RecipeRows[rowIndex].RecipeItems[colIndex].Value = finalCellData.Value; RecipeRows[rowIndex].RecipeItems[colIndex].InvokePropertyChanged(); } } } } } } } return overridedCellsNum; } /// /// Open local recipe operation /// /// public void OpenLocalRecipe(string recipeVariationName = "") { var dlg = new OpenFileDialog(); dlg.DefaultExt = ".rcp"; dlg.Filter = "Recipe File (.rcp)|*.rcp|All (*.*)|*.*"; if (dlg.ShowDialog() == true) { try { XmlDocument rcpDoc = new XmlDocument(); rcpDoc.Load(dlg.FileName); string recipeVariation = recipeVariationName; if (string.IsNullOrEmpty(recipeVariation)) { var root = rcpDoc.SelectSingleNode("/TableRecipeData") as XmlElement; recipeVariation = root.Attributes["RecipeVersion"].Value; } var startupPath = System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName; string dir = System.IO.Path.GetDirectoryName(startupPath); List map = CustomXmlSerializer.Deserialize>(new System.IO.FileInfo(System.IO.Path.Combine(dir, "map.xml"))); Func predicate = item => item.Name.Equals(recipeVariation); System.IO.FileInfo fi = new System.IO.FileInfo(string.Format("{0}\\config\\{1}.xml", dir, map.Any(predicate) ? map.Single(predicate).No : recipeVariation)); if (fi.Exists) { XmlDocument doc = new XmlDocument(); doc.Load(fi.FullName); string formatXml = doc.SelectSingleNode("/Aitex/TableRecipeFormat").OuterXml; string recipeXml = rcpDoc.SelectSingleNode("/TableRecipeData").OuterXml; LoadRecipe(formatXml, recipeXml); if (OnLocalRecipeOpened != null) OnLocalRecipeOpened.Invoke(new Tuple(recipeVariation, dlg.FileName), null); } else { MessageBox.Show(string.Format("工艺程序版本{0}的描述文件不存在,无法正确识别该版本的工艺程序文件。", recipeVariation), "工艺程序打开失败", MessageBoxButton.OK, MessageBoxImage.Error); } } catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex.Message); MessageBox.Show(string.Format("open recipe {0} failed,please confirm the recipe file is exist and valid。\r\n{1}", dlg.FileName, ex.Message), "Open recipe failed", MessageBoxButton.OK, MessageBoxImage.Error); } } } /// /// Save recipe file /// private void SaveRecipeFile() { try { if (Errors != null && Errors.Count > 0) { var ret = MessageBox.Show(string.Format("Recipe have {0} error,continue save?", Errors.Count), "Save", MessageBoxButton.YesNo, MessageBoxImage.Warning, MessageBoxResult.No); if (ret != MessageBoxResult.Yes) return; } if (OnSaveRecipeFile != null) { IsRecipeModified = false; OnSaveRecipeFile.Invoke(GetRecipeContentString(), null); } } catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex.Message); MessageBox.Show("save recipe failed!\r\n\r\n" + ex.Message, "Save", MessageBoxButton.OK, MessageBoxImage.Warning); } } /// /// get string typed recipe content /// /// public string GetRecipeContentString() { if (string.IsNullOrEmpty(UserName)) { var dlg1 = new Aitex.UI.RecipeEditor.UserNameInput() { Owner = Application.Current.MainWindow }; var ret = dlg1.ShowDialog(); if (ret.HasValue && ret.Value) { UserName = dlg1.UserName; } } RecipeHead.LastRevisionTime = DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ss"); RecipeHead.LastModifiedBy = UserName; InvokePropertyChanged("RecipeHead"); if (RecipeRows == null || RecipeRows.Count == 0 || RecipeRows[0].RecipeItems == null || RecipeRows[0].RecipeItems.Count == 0) return string.Empty; //Dictionary>> _recipeReadingFormat; XmlDocument doc = new XmlDocument(); var rootNode = doc.CreateElement("TableRecipeData"); rootNode.SetAttribute("RecipeVersion", RecipeHead.RecipeVariation); rootNode.SetAttribute("CreatedBy", RecipeHead.CreatedBy); rootNode.SetAttribute("CreationTime", RecipeHead.CreationTime); rootNode.SetAttribute("LastRevisedBy", RecipeHead.LastModifiedBy); rootNode.SetAttribute("LastRevisionTime", RecipeHead.LastRevisionTime); if (!string.IsNullOrEmpty(RecipeHead.PressureMode)) { rootNode.SetAttribute("PressureMode", RecipeHead.PressureMode); } if (!string.IsNullOrEmpty(RecipeHead.BasePressure)) { rootNode.SetAttribute("BasePressure", RecipeHead.BasePressure); } if (!string.IsNullOrEmpty(RecipeHead.ElectrodeTemp)) { rootNode.SetAttribute("ElectrodeTemp", RecipeHead.ElectrodeTemp); } if (!string.IsNullOrEmpty(RecipeHead.PumpDownLimit)) { rootNode.SetAttribute("PumpDownLimit", RecipeHead.PumpDownLimit); } if (!string.IsNullOrEmpty(RecipeHead.PurgeActive)) { rootNode.SetAttribute("PurgeActive", RecipeHead.PurgeActive); } rootNode.SetAttribute("Barcode", RecipeHead.Barcode); rootNode.SetAttribute("Description", RecipeHead.Description); doc.AppendChild(rootNode); for (int stepIndex/* 0-> */ = 0; stepIndex < RecipeRows[0].RecipeItems.Count; stepIndex++) { var stepNode = doc.CreateElement("Step"); rootNode.AppendChild(stepNode); foreach (var module in _recipeSavingFormat) { var moduleName = module.Key; XmlElement subNode = stepNode; if (!string.IsNullOrEmpty(moduleName)) { string[] pathArr = moduleName.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries); for (int i = 0; i < pathArr.Length; i++) { var snd = subNode.SelectSingleNode(pathArr[i]) as XmlElement; if (snd == null) { var nd = doc.CreateElement(pathArr[i]); subNode.AppendChild(nd); subNode = nd; } else { subNode = snd; } } } foreach (var controlName in module.Value) { if (controlName == "StepNo") { subNode.SetAttribute(controlName, string.Format("第{0}步", stepIndex + 1)); } else { var recipeRow = RecipeRows.First((o) => o.TechnicalName == controlName); if (recipeRow == null) continue; /*to compatiable with old recipe editor, always fill 'black space' to loop step*/ if (controlName == "Loop" && string.IsNullOrEmpty(recipeRow.RecipeItems[stepIndex].Value)) subNode.SetAttribute(controlName, " "); else { subNode.SetAttribute(controlName, recipeRow.RecipeItems[stepIndex].Value); //bool isJump = recipeRow.RecipeItems[stepIndex].IsJump; //if (isJump) //{ // string deviceType = recipeRow.RecipeItems[stepIndex].RecipeVariableDefine.DeviceType; // if (deviceType == "MFC" || deviceType == "PC") // subNode.SetAttribute(controlName + ".IsJump", isJump.ToString()); //} } } } } } return doc.OuterXml; } /// /// expand groups /// private void ExpandGroups() { try { foreach (CollectionViewGroup item in DataGridControl.Items.Groups) { DataGridControl.ExpandGroup(item); } } catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex.Message); } } /// /// collapse groups /// private void CollapseGroups() { try { foreach (CollectionViewGroup item in DataGridControl.Items.Groups) { DataGridControl.CollapseGroup(item); } } catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex.Message); } } /// /// right click context menu /// private void RightClickAction() { DataGridControl.ContextMenu = null; //get selected column information List selectedColumns = new List(); foreach (var item in DataGridControl.SelectedCellRanges) { bool dir = item.ColumnRange.StartIndex <= item.ColumnRange.EndIndex; if (dir) { for (int i = item.ColumnRange.StartIndex; i <= item.ColumnRange.EndIndex; i++) { if (i > 0) if (!selectedColumns.Contains(i)) selectedColumns.Add(i); } } else { for (int i = item.ColumnRange.StartIndex; i >= item.ColumnRange.EndIndex; i--) { if (i > 0) if (!selectedColumns.Contains(i)) selectedColumns.Add(i); } } } selectedColumns.Sort(); //debug only /* string s = ""; foreach (var item in selectedColumns) { s += "," + item; } MessageBox.Show(s); * */ //generate context menu ContextMenu cms = new ContextMenu(); if (selectedColumns.Count > 0) { string colStrings = ""; foreach (var item in selectedColumns) { if (string.IsNullOrEmpty(colStrings)) colStrings = item.ToString(); else colStrings += "," + item.ToString(); } MenuItem mi; cms.Items.Add(new MenuItem() { Header = string.Format(" 当前选中第{0}步", colStrings), FontFamily = new FontFamily("Arial,SimSun"), IsEnabled = false, Background = Brushes.Gray, Foreground = Brushes.White }); //copy mi = new MenuItem() { Header = " 复制", FontFamily = new FontFamily("Arial,SimSun"), Tag = selectedColumns.ToList() }; mi.Click += (s, e) => { try { var list = (s as MenuItem).Tag as List; _copiedColumnDatas.Clear(); foreach (var colId in list) { _copiedColumnDatas.Add(colId/*index from 1*/, new List()); for (int i = 0; i < RecipeRows.Count; i++) { var oldCellData = RecipeRows[i].RecipeItems[colId - 1]; var newCellData = new SmartCellData() { Value = oldCellData.Value, RecipeVariableDefine = oldCellData.RecipeVariableDefine }; _copiedColumnDatas[colId].Add(newCellData); } } } catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex.Message); } }; cms.Items.Add(mi); if (selectedColumns.Count < RecipeRows[0].RecipeItems.Count) { mi = new MenuItem() { Header = " 剪切", FontFamily = new FontFamily("Arial,SimSun"), Tag = selectedColumns.ToList() }; mi.Click += (s, e) => { try { AddUndoHistory("剪切", CloneRecipeRowData(RecipeRows)); var list = (s as MenuItem).Tag as List; _copiedColumnDatas.Clear(); foreach (var colId in list) { _copiedColumnDatas.Add(colId/*index from 1*/, new List()); for (int i = 0; i < RecipeRows.Count; i++) { var oldCellData = RecipeRows[i].RecipeItems[colId - 1]; var newCellData = new SmartCellData() { Value = oldCellData.Value, RecipeVariableDefine = oldCellData.RecipeVariableDefine }; _copiedColumnDatas[colId].Add(newCellData); } } foreach (var item in RecipeRows) { var collection = new ObservableCollection(); for (int index = 0; index < item.RecipeItems.Count; index++) { if (list.Contains(index + 1)) continue; collection.Add(item.RecipeItems[index]); } item.RecipeItems = collection; } RefreshDataGrid(); RefreshCellsDisplay(false); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex.Message); } }; cms.Items.Add(mi); mi = new MenuItem() { Header = " 删除", FontFamily = new FontFamily("Arial,SimSun"), Tag = selectedColumns.ToList() }; mi.Click += (s, e) => { try { AddUndoHistory("删除", CloneRecipeRowData(RecipeRows)); var list = (s as MenuItem).Tag as List; foreach (var item in RecipeRows) { var collection = new ObservableCollection(); for (int index = 0; index < item.RecipeItems.Count; index++) { if (list.Contains(index + 1)) continue; collection.Add(item.RecipeItems[index]); } item.RecipeItems = collection; } RefreshDataGrid(); RefreshCellsDisplay(false); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex.Message); } }; cms.Items.Add(mi); } if (selectedColumns.Count == 1 && _copiedColumnDatas.Count > 0) { mi = new MenuItem() { Header = string.Format(" 插入第{0}步后", selectedColumns[0]/*, _copiedColumnDatas.Count*/), FontFamily = new FontFamily("Arial,SimSun"), Tag = selectedColumns[0] }; mi.Click += (s, e) => { try { AddUndoHistory("插入", CloneRecipeRowData(RecipeRows)); int insertColId = (int)(s as MenuItem).Tag; for (int rowIndex = 0; rowIndex < RecipeRows.Count; rowIndex++) { var collection = new ObservableCollection(); for (int index = 0; index < insertColId; index++) { collection.Add(RecipeRows[rowIndex].RecipeItems[index]); } foreach (var col in _copiedColumnDatas) { collection.Add(new SmartCellData() { Value = col.Value[rowIndex].Value, RecipeVariableDefine = col.Value[rowIndex].RecipeVariableDefine }); } for (int index = insertColId; index < RecipeRows[rowIndex].RecipeItems.Count; index++) { collection.Add(RecipeRows[rowIndex].RecipeItems[index]); } RecipeRows[rowIndex].RecipeItems = collection; } RefreshDataGrid(); RefreshCellsDisplay(false); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex.Message); } }; cms.Items.Add(mi); } } //show context menu DataGridControl.ContextMenu = cms; cms.Visibility = Visibility.Visible; } /// /// Parse recipe content with recipe format /// /// /// /// public void LoadRecipe(string recipeFormat, string recipeContent, int selectedStepNo = -1) { CurrentRunningStepNo = selectedStepNo; IsHideSameContent = false; IsRecipeModified = false; RecipeHead = new RecipeEditor.RecipeHead(); DataGridControl.ContextMenu = null; RecipeInfo = ""; IsBusy = true; InvokePropertyChanged("IsBusy"); UndoList.Clear(); RedoList.Clear(); InvokePropertyChanged("UndoList"); InvokePropertyChanged("RedoList"); InvokePropertyChanged("IsUndoEnabled"); InvokePropertyChanged("IsRedoEnabled"); try { RecipeRows = new ObservableCollection(); _recipeReadingFormat = new Dictionary>>(); _recipeSavingFormat = new Dictionary>(); //parse recipe format XmlDocument formatXml = new XmlDocument(); formatXml.LoadXml(recipeFormat); _currentRecipeFormatContent = formatXml.OuterXml; //reading recipe variation string recipeVariationName = formatXml.SelectSingleNode("TableRecipeFormat").Attributes["RecipeVersion"].Value; if (String.Compare(recipeVariationName, _currentRecipeVariationName) != 0) { //if variation name is different from previous loaded recipe -> clear all previous copied recipe columns _copiedColumnDatas.Clear(); } _currentRecipeVariationName = recipeVariationName; //reading recipe validation releated parameters _preDefinedRecipeVars = new Dictionary(); foreach (XmlElement nd in formatXml.SelectNodes("/TableRecipeFormat/Validation/Predefine/Item")) { _preDefinedRecipeVars.Add(nd.Attributes["VarName"].Value, nd.Attributes["Value"].Value); } //reading check variables _checkVariables = new List(); foreach (XmlElement nd in formatXml.SelectNodes("/TableRecipeFormat/Validation/CheckVariable/Var")) { string varName = nd.Attributes["Name"].Value; _checkVariables.Add(varName); } //reading validation rules _validationRules = new List>(); foreach (XmlElement nd in formatXml.SelectNodes("/TableRecipeFormat/Validation/Restriction/Rule")) { string varName = nd.Attributes["VarName"].Value; string checkCondition = nd.Attributes["CheckCondition"].Value.Replace("<", "<").Replace(">", ">"); string message = nd.Attributes["Message"].Value.Replace("'", "\""); _validationRules.Add(new Tuple(varName, checkCondition, message)); } //parsing recipe format xml file foreach (XmlElement catalogNode in formatXml.SelectNodes("/TableRecipeFormat/Catalog")) { string catalogName = catalogNode.Attributes["DisplayName"].Value; _recipeReadingFormat.Add(catalogName, new Dictionary>()); foreach (XmlElement groupNode in catalogNode.SelectNodes("Group")) { string groupName = groupNode.Attributes["DisplayName"].Value; _recipeReadingFormat[catalogName].Add(groupName, new List()); foreach (XmlElement stepNode in groupNode.SelectNodes("Step")) { string stepModuleName = stepNode.Attributes["ModuleName"].Value; string stepDeviceType = stepNode.Attributes["DeviceType"].Value; string stepDisplayName = stepNode.Attributes["DisplayName"].Value; string stepControlName = stepNode.Attributes["ControlName"].Value; string stepInputType = stepNode.Attributes["InputType"].Value; string stepDescription = stepNode.HasAttribute("Desc") ? stepNode.Attributes["Desc"].Value : ""; double stepMin = double.Parse(stepNode.HasAttribute("Min") ? stepNode.Attributes["Min"].Value : "0"); double stepMax = double.Parse(stepNode.HasAttribute("Max") ? stepNode.Attributes["Max"].Value : "0"); var selectionList = new List>(); foreach (XmlElement selectionNode in stepNode.SelectNodes("Item")) { selectionList.Add(new Tuple(selectionNode.Attributes["ControlName"].Value, selectionNode.Attributes["DisplayName"].Value)); } if (!_recipeSavingFormat.ContainsKey(stepModuleName)) _recipeSavingFormat.Add(stepModuleName, new List()); _recipeSavingFormat[stepModuleName].Add(stepControlName); if (stepControlName == "StepNo") continue; _recipeReadingFormat[catalogName][groupName].Add(new RecipeVariableDefine() { CellType = (CellType)Enum.Parse(typeof(CellType), stepInputType), Description = stepDescription, DeviceType = stepDeviceType, DropdownItemList = selectionList, MaxValue = stepMax, //MinValue = stepDeviceType.Equals("MFC",StringComparison.InvariantCultureIgnoreCase)? stepMax*2.0/100: stepMin, MinValue = stepMin, GroupName = groupName, CatalogName = catalogName, TechnicalName = stepControlName, FriendlyName = stepDisplayName }); } } } //parse recipe content XmlDocument contentXml = new XmlDocument(); contentXml.LoadXml(recipeContent); contentXml.DocumentElement.SetAttribute("RecipeVersion", _currentRecipeVariationName); //read recipe head //em. RecipeVersion="Ace" CreatedBy="tech" CreationTime="2013-02-02T12:36:02" LastRevisedBy="Peter" LastRevisionTime="2013-10-22T13:30:29" Description="5e46af80-49f9-4b4f-b04b-bc911fb292f8" RecipeHead.CreatedBy = contentXml.DocumentElement.HasAttribute("CreatedBy") ? contentXml.DocumentElement.Attributes["CreatedBy"].Value : "(无)"; RecipeHead.CreationTime = contentXml.DocumentElement.HasAttribute("CreationTime") ? contentXml.DocumentElement.Attributes["CreationTime"].Value : "(无)"; RecipeHead.LastModifiedBy = contentXml.DocumentElement.HasAttribute("LastRevisedBy") ? contentXml.DocumentElement.Attributes["LastRevisedBy"].Value : "(无)"; RecipeHead.LastRevisionTime = contentXml.DocumentElement.HasAttribute("LastRevisionTime") ? contentXml.DocumentElement.Attributes["LastRevisionTime"].Value : "(无)"; if (contentXml.DocumentElement.HasAttribute("PressureMode")) { RecipeHead.PressureMode = contentXml.DocumentElement.Attributes["PressureMode"].Value; } if (contentXml.DocumentElement.HasAttribute("BasePressure")) { RecipeHead.BasePressure = contentXml.DocumentElement.Attributes["BasePressure"].Value; } if (contentXml.DocumentElement.HasAttribute("PumpDownLimit")) { RecipeHead.PumpDownLimit = contentXml.DocumentElement.Attributes["PumpDownLimit"].Value; } if (contentXml.DocumentElement.HasAttribute("ElectrodeTemp")) { RecipeHead.ElectrodeTemp = contentXml.DocumentElement.Attributes["ElectrodeTemp"].Value; } if (contentXml.DocumentElement.HasAttribute("PurgeActive")) { RecipeHead.PurgeActive = contentXml.DocumentElement.Attributes["PurgeActive"].Value; } if (contentXml.DocumentElement.HasAttribute("Barcode")) { RecipeHead.Barcode = contentXml.DocumentElement.Attributes["Barcode"].Value; } RecipeHead.RecipeVariation = contentXml.DocumentElement.HasAttribute("RecipeVersion") ? contentXml.DocumentElement.Attributes["RecipeVersion"].Value : "(NULL)"; RecipeHead.Description = contentXml.DocumentElement.HasAttribute("Description") ? contentXml.DocumentElement.Attributes["Description"].Value : "(NULL)"; var allRecipeItems = new List>(); foreach (XmlElement stepNode in contentXml.SelectNodes("/TableRecipeData/Step")) { var stepDic = new Dictionary(); allRecipeItems.Add(stepDic); foreach (XmlAttribute att in stepNode.Attributes) { stepDic.Add(att.Name, att.Value); } foreach (XmlElement innerStep in stepNode.ChildNodes) { foreach (XmlAttribute att in innerStep.Attributes) { stepDic.Add(att.Name, att.Value); } foreach (XmlElement innerInnerStep in innerStep.ChildNodes) { foreach (XmlAttribute att in innerInnerStep.Attributes) { stepDic.Add(att.Name, att.Value); } } } } //generate view model RowVarNameDic = new Dictionary(); int rowIndex = 0; foreach (var catalog in _recipeReadingFormat.Keys) { foreach (var group in _recipeReadingFormat[catalog].Keys) { foreach (var var1 in _recipeReadingFormat[catalog][group]) { var singleRow = new RecipeRow() { CatalogName = catalog, FriendlyName = /*group + */var1.FriendlyName, TechnicalName = var1.TechnicalName }; for (int stepNo = 0; stepNo < allRecipeItems.Count; stepNo++) { var stepCell = new SmartCellData() { RecipeVariableDefine = var1 }; if (allRecipeItems[stepNo].ContainsKey(var1.TechnicalName)) { stepCell.Value = allRecipeItems[stepNo][var1.TechnicalName]; //stepCell.ShowsJumpControl = false; //if (var1.CellType == CellType.NumInput) //{ // if (var1.DeviceType == "MFC" || var1.DeviceType == "PC") // { // string jumpString = var1.TechnicalName + ".IsJump"; // stepCell.IsJump = allRecipeItems[stepNo].ContainsKey(jumpString) ? bool.Parse(allRecipeItems[stepNo][jumpString]) : false; // stepCell.ShowsJumpControl = true; // } //} } else stepCell.Value = "n/a"; singleRow.RecipeItems.Add(stepCell); } RecipeRows.Add(singleRow); RowVarNameDic.Add(var1.TechnicalName, rowIndex++); } } } //refresh datagrid RefreshDataGrid(); //refresh recipe information RefreshCellsDisplay(); if (OnLoadRecipeContent != null) OnLoadRecipeContent.Invoke(recipeContent, null); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex.Message); //LOG.Write("", ex); } IsBusy = false; InvokePropertyChanged("IsBusy"); } /// /// 检查变量ramp rate /// /// /// /// /// /// /// /// False:check ok, True: check failed public bool CheckRampRate(int stepNo, string rampEnable, string varName, string rampTime, double maxRampUpRate, double maxRampDownRate) { try { if (stepNo <= 0) return false; int rowIndex = RowVarNameDic[varName]; double currentValue = double.Parse(RecipeRows[rowIndex].RecipeItems[stepNo].Value); //if (varName == "AZone.Setpoint" || varName == "BZone.Setpoint" || varName == "CZone.Setpoint" || varName == "DZone.Setpoint") //{ // int heatControlModeIndex = RowVarNameDic["Heater.Mode"]; // string curStepHeatCtrlMode = RecipeRows[heatControlModeIndex].RecipeItems[stepNo].Value; // string lastStepHeatCtrlMode = RecipeRows[heatControlModeIndex].RecipeItems[stepNo - 1].Value; // if (curStepHeatCtrlMode != lastStepHeatCtrlMode) // { // if (!(lastStepHeatCtrlMode.Equals("CurrentControl") && curStepHeatCtrlMode.Equals("PyroTempControl"))) // return false; // int endbyIndex = RowVarNameDic["EndBy"]; // int endValueIndex = RowVarNameDic["EndValue"]; // string endby = RecipeRows[endbyIndex].RecipeItems[stepNo - 1].Value; // EndByCondition endbyEnum; // if (!Enum.TryParse(endby, out endbyEnum)) return false; // if (endbyEnum != EndByCondition.PmTempGt && endbyEnum != EndByCondition.PmTempLt) return false; // string end = RecipeRows[endValueIndex].RecipeItems[stepNo - 1].Value; // double endValue; // if (!double.TryParse(end, out endValue)) return false; // return CheckRampRate(rampEnable, rampTime, maxRampUpRate, maxRampDownRate, endValue, currentValue); // } //} double prevValue = double.Parse(RecipeRows[rowIndex].RecipeItems[stepNo - 1].Value); return CheckRampRate(rampEnable, rampTime, maxRampUpRate, maxRampDownRate, prevValue, currentValue); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex.Message); return true; } } bool CheckRampRate(string rampEnable, string rampTime, double maxRampUpRate, double maxRampDownRate, double prevValue, double currentValue) { bool isRampEnable = bool.Parse(rampEnable); //string[] timeStr = rampTime.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries); //string tt = string.Join(":", timeStr); TimeSpan timeSpan; TimeSpan.TryParse(rampTime, out timeSpan); double totalTimeSec = timeSpan.TotalSeconds; double diff = currentValue - prevValue; if (!isRampEnable || totalTimeSec <= 0) { if (diff != 0) return true; return false; } else { double rampRate = diff / totalTimeSec; return (rampRate > 0 && rampRate >= maxRampUpRate) || (rampRate < 0 && rampRate <= -maxRampDownRate); } } /// /// Refresh datagrid cell data display /// /// True: moving to current running step position public void RefreshCellsDisplay(bool moving2RunningStepPosition = true) { try { if (RecipeRows == null || RecipeRows.Count < 2) return; Errors = new List>(); Dictionary dicEndby = new Dictionary(); Dictionary dicPressureMode = new Dictionary(); Dictionary dicRfMode = new Dictionary(); Dictionary dicRfMatchProcessMode = new Dictionary(); for (int i = 0; i < RecipeRows.Count; i++) { for (int j = 0; j < RecipeRows[i].RecipeItems.Count; j++) { var cell = RecipeRows[i].RecipeItems[j]; cell.RowIndex = i; cell.ColIndex = j; cell.ErrInfo = string.Empty; bool logicEnable = true; if (cell.RecipeVariableDefine.TechnicalName == "EndBy") dicEndby[j] = cell.Value; if (cell.RecipeVariableDefine.TechnicalName == "ThrottleValve.SetMode") { dicPressureMode[j] = cell.Value; } if (cell.RecipeVariableDefine.TechnicalName == "PressureControl.SetTVMode") { dicPressureMode[j] = cell.Value; } if (cell.RecipeVariableDefine.TechnicalName == "Rf.SetMode") dicRfMode[j] = cell.Value; if (cell.RecipeVariableDefine.TechnicalName == "Rf.SetMatchProcessMode") dicRfMatchProcessMode[j] = cell.Value; if (cell.RecipeVariableDefine.TechnicalName == "Time") { logicEnable = dicEndby.ContainsKey(j) && (dicEndby[j] == "EndByStepTime" || dicEndby[j] == "EndByHeatUp"); } if (cell.RecipeVariableDefine.TechnicalName == "ThrottleValve.SetPosition") { logicEnable = dicPressureMode.ContainsKey(j) && dicPressureMode[j] == "TVPositionCtrl"; } if (cell.RecipeVariableDefine.TechnicalName == "ThrottleValve.SetPressure") { logicEnable = dicPressureMode.ContainsKey(j) && dicPressureMode[j] == "TVPressureCtrl"; } if (cell.RecipeVariableDefine.TechnicalName == "PressureControl.SetTVPosition") { logicEnable = dicPressureMode.ContainsKey(j) && dicPressureMode[j] == "TVPositionCtrl"; } if (cell.RecipeVariableDefine.TechnicalName == "PressureControl.SetTVPressure") { logicEnable = dicPressureMode.ContainsKey(j) && dicPressureMode[j] == "TVPressureCtrl"; } if (cell.RecipeVariableDefine.TechnicalName == "Rf.SetPulsingFrequency") { logicEnable = dicRfMode.ContainsKey(j) && dicRfMode[j] == "PulsingMode"; } if (cell.RecipeVariableDefine.TechnicalName == "Rf.SetPulsingDuty") { logicEnable = dicRfMode.ContainsKey(j) && dicRfMode[j] == "PulsingMode"; } if (cell.RecipeVariableDefine.TechnicalName == "Rf.SetPowerOnTime") { logicEnable = dicEndby.ContainsKey(j) && dicEndby[j] == "EndByRfTime"; } //if (cell.RecipeVariableDefine.TechnicalName == "Rf.SetMatchPositionC1") //{ // logicEnable = dicRfMatchProcessMode.ContainsKey(j);// && dicRfMatchProcessMode[j] == "TritonPresetMode"; //} //if (cell.RecipeVariableDefine.TechnicalName == "Rf.SetMatchPositionC2") //{ // logicEnable = dicRfMatchProcessMode.ContainsKey(j);// && dicRfMatchProcessMode[j] == "TritonPresetMode"; //} cell.IsMasked = !logicEnable; //update diff variable numbers if (cell.RecipeVariableDefine.TechnicalName == "Ramp") { //更新当前步骤中Ramp的变量数据 if (j == 0) { cell.Tag = 0; } else { int rampNum = 0; for (int s1 = 0; s1 < RecipeRows.Count; s1++) { if (RecipeRows[s1].CatalogName == "StepInfo")// RecipeRows[s1].RecipeItems[j].RecipeVariableDefine.CellType == CellType.ReadOnly) continue; string preValue = RecipeRows[s1].RecipeItems[j - 1].Value; string curValue = RecipeRows[s1].RecipeItems[j].Value; if (RecipeRows[s1].RecipeItems[j].RecipeVariableDefine.CellType == CellType.NumInput) { double pd, cd; if (double.TryParse(preValue, out pd) && double.TryParse(curValue, out cd)) if (pd != cd) rampNum++; } else if (String.Compare(preValue, curValue, false) != 0) { rampNum++; } } cell.Tag = rampNum; } cell.InvokePropertyChanged(); } #region mask (hide or show cell content) if ((MaskedTechNames != null && MaskedTechNames.Contains(cell.RecipeVariableDefine.TechnicalName)) || (MaskedCatalogNames != null && MaskedCatalogNames.Contains(cell.RecipeVariableDefine.CatalogName)) || !logicEnable) cell.IsMasked = true; else cell.IsMasked = false; #endregion mask (hide or show cell content) #region check cell data range switch (cell.RecipeVariableDefine.CellType) { case CellType.CheckBox: { if (string.Compare(cell.Value, "true", true) == 0 || string.Compare(cell.Value, "false", true) == 0) { cell.ErrInfo = ""; cell.Background = Brushes.Transparent; } else { string reason = string.Format("Value '{0}' not valid", cell.Value); Errors.Add(new Tuple(i, j, cell.RecipeVariableDefine.FriendlyName, reason)); cell.ErrInfo = reason; cell.Background = Brushes.Pink; } } break; case CellType.EditableSelection: { cell.ErrInfo = ""; cell.Background = Brushes.Transparent; } break; case CellType.NumInput: { double min = cell.RecipeVariableDefine.MinValue; double max = cell.RecipeVariableDefine.MaxValue; double v; if (!double.TryParse(cell.Value, out v)) { string reason = string.Format("Value '{0}' not valid", cell.Value); Errors.Add(new Tuple(i, j, cell.RecipeVariableDefine.FriendlyName, reason)); cell.ErrInfo = reason; cell.Background = Brushes.Pink; } else if (v > max || v < 0 || (v < min && v > max * 0.1 / 100)) { string reason = string.Format("Value'{0}',Out of Range {1}~{2}", cell.Value, min, max); Errors.Add(new Tuple(i, j, cell.RecipeVariableDefine.FriendlyName, reason)); cell.ErrInfo = reason; cell.Background = Brushes.Pink; } else { cell.ErrInfo = ""; cell.Background = Brushes.Transparent; } } break; case CellType.ReadOnlySelection: { if (cell.RecipeVariableDefine.DropdownItemList != null && cell.RecipeVariableDefine.DropdownItemList.Find((o) => o.Item1 == cell.Value) != null) { cell.ErrInfo = ""; cell.Background = Brushes.Transparent; } else { string reason = string.Format("Value'{0}' is not valid", cell.Value); Errors.Add(new Tuple(i, j, cell.RecipeVariableDefine.FriendlyName, reason)); cell.ErrInfo = reason; cell.Background = Brushes.Pink; } } break; case CellType.ReadOnly: case CellType.TextInput: cell.ErrInfo = ""; cell.Background = Brushes.Transparent; break; case CellType.TimeInput: { string[] arr = cell.Value.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries); int h, mi, s; if (arr.Length == 3 && int.TryParse(arr[0], out h) && int.TryParse(arr[1], out mi) && int.TryParse(arr[2], out s) && h >= 0 && mi >= 0 && mi <= 59 && s >= 0 && s <= 59) { cell.ErrInfo = ""; cell.Background = Brushes.Transparent; } else { string reason = string.Format("value'{0}' is not valid", cell.Value); Errors.Add(new Tuple(i, j, cell.RecipeVariableDefine.FriendlyName, reason)); cell.ErrInfo = reason; cell.Background = Brushes.Pink; } } break; } #endregion check cell data range #region mark data trend / hide same content if (j == 0) //step one { cell.Foreground = Brushes.Black; cell.FontWeight = FontWeights.Normal; cell.IsHidden = false; cell.InvokePropertyChanged(); cell.IsMasked = !logicEnable; continue; } var preCell = RecipeRows[i].RecipeItems[j - 1]; if (cell.RecipeVariableDefine.CellType == CellType.NumInput) { double preValue = 0; double.TryParse(preCell.Value.ToString(), out preValue); double curValue = 0; double.TryParse(cell.Value.ToString(), out curValue); if (curValue > preValue) { cell.Foreground = Brushes.Red; cell.FontWeight = FontWeights.Bold; } else if (curValue < preValue) { cell.Foreground = Brushes.Blue; cell.FontWeight = FontWeights.Bold; } else { cell.Foreground = Brushes.Black; cell.FontWeight = FontWeights.Normal; } } else { //not number type cell.Foreground = Brushes.Black; cell.FontWeight = FontWeights.Normal; } if (IsHideSameContent && cell.Value == preCell.Value) cell.IsHidden = true; else cell.IsHidden = false; //!logicEnable; cell.IsMasked = !logicEnable; //if (!logicEnable) //{ // cell.Background = Brushes.DarkGray; // cell.IsMasked = true; //} #endregion mark data trend / hide same content cell.InvokePropertyChanged(); } } #region check loop int loopRowId = -1; for (int i = 0; i < RecipeRows.Count; i++) { if (RecipeRows[i].TechnicalName == "Loop") { loopRowId = i; break; } } for (int j = 0; j < RecipeRows[loopRowId].RecipeItems.Count; j++) { var cell = RecipeRows[loopRowId].RecipeItems[j]; bool isLoopStart = Regex.IsMatch(cell.Value, @"^Loop\x20\d+$") || Regex.IsMatch(cell.Value, @"^循环\x20\d+$"); bool isLoopEnd = Regex.IsMatch(cell.Value, @"^Loop End$") || Regex.IsMatch(cell.Value, @"^循环结束"); bool isNullOrEmpty = string.IsNullOrWhiteSpace(cell.Value); if (!isLoopEnd && !isLoopStart && !isNullOrEmpty) { string reason = string.Format("Value '{0}' not valid", cell.Value); cell.ErrInfo = reason; cell.Background = Brushes.Pink; Errors.Add(new Tuple(loopRowId, j, cell.RecipeVariableDefine.FriendlyName, reason)); } if (isLoopEnd) { string reason = "Loop Start 缺失"; cell.ErrInfo = reason; cell.Background = Brushes.Pink; Errors.Add(new Tuple(loopRowId, j, cell.RecipeVariableDefine.FriendlyName, reason)); } else if (isLoopStart) { for (int k = j + 1; k < RecipeRows[loopRowId].RecipeItems.Count; k++) { cell = RecipeRows[loopRowId].RecipeItems[k]; bool isCurStepLoopStart = Regex.IsMatch(cell.Value, @"^Loop\x20\d+$") || Regex.IsMatch(cell.Value, @"^循环\x20\d+$"); bool isCurStepLoopEnd = Regex.IsMatch(cell.Value, @"^Loop End$") || Regex.IsMatch(cell.Value, @"^循环结束"); isNullOrEmpty = string.IsNullOrWhiteSpace(cell.Value); if (!isCurStepLoopEnd && !isCurStepLoopStart && !isNullOrEmpty) { string reason = string.Format("Value '{0}' not valid", cell.Value); cell.ErrInfo = reason; cell.Background = Brushes.Pink; Errors.Add(new Tuple(loopRowId, k, cell.RecipeVariableDefine.FriendlyName, reason)); } else if (isCurStepLoopStart) { string reason = "前面循环没有结束,不能设置新的Loop Start标志"; cell.ErrInfo = reason; cell.Background = Brushes.Pink; Errors.Add(new Tuple(loopRowId, k, cell.RecipeVariableDefine.FriendlyName, reason)); } else if (isCurStepLoopEnd) { //mark loop steps with blue background for (int m1 = j; m1 <= k; m1++) { var curCell = RecipeRows[loopRowId].RecipeItems[m1]; if (curCell.Background == Brushes.Transparent) curCell.Background = Brushes.LightGreen; curCell.InvokePropertyChanged(); } j = k; break; } if (k == RecipeRows[loopRowId].RecipeItems.Count - 1) { j = k; string reason = "Loop End 缺失"; cell.ErrInfo = reason; cell.Background = Brushes.Pink; Errors.Add(new Tuple(loopRowId, k, cell.RecipeVariableDefine.FriendlyName, reason)); } } } } #endregion check loop #region recipe parameter validation using (var lua = new LuaInterface.Lua()) { //is special recipe? //bool IsSpecialRecipe = true; //{ // int heaterModeRowIndex = RowVarNameDic["Heater.Mode"]; // int aZoneSetpointIndex = RowVarNameDic["AZone.Setpoint"]; // int bZoneSetpointIndex = RowVarNameDic["BZone.Setpoint"]; // int cZoneSetpointIndex = RowVarNameDic["CZone.Setpoint"]; // int dZoneSetpointIndex = RowVarNameDic.ContainsKey("DZone.Setpoint") ? RowVarNameDic["DZone.Setpoint"] : aZoneSetpointIndex; // for (int stepIndex = 0; stepIndex < RecipeRows[0].RecipeItems.Count; stepIndex++) // { // if (RecipeRows[heaterModeRowIndex].RecipeItems[stepIndex].Value != "CurrentControl" || // RecipeRows[aZoneSetpointIndex].RecipeItems[stepIndex].Value != "0" || // RecipeRows[bZoneSetpointIndex].RecipeItems[stepIndex].Value != "0" || // RecipeRows[cZoneSetpointIndex].RecipeItems[stepIndex].Value != "0" || // RecipeRows[dZoneSetpointIndex].RecipeItems[stepIndex].Value != "0") // { // IsSpecialRecipe = false; // break; // } // } //} ////set get ramp rate function //lua.RegisterFunction("CheckRampRate", this, GetType().GetMethod("CheckRampRate")); //lua.DoString(string.Format("IsProductionRecipe={0};",(!IsSpecialRecipe).ToString().ToLower())); for (int stepIndex = 0; stepIndex < RecipeRows[0].RecipeItems.Count; stepIndex++) { //set stepNo lua.DoString(string.Format("StepNo={0};", stepIndex)); //reading var from current recipe step foreach (var checkVar in _checkVariables) { if (!RowVarNameDic.ContainsKey(checkVar)) { System.Diagnostics.Debug.WriteLine("检查不到变量定义," + checkVar); continue; } try { int varId = RowVarNameDic[checkVar]; if (RecipeRows[varId].RecipeItems[stepIndex].RecipeVariableDefine.CellType == CellType.NumInput) lua.DoString(string.Format("{0}={1};", RecipeRows[varId].RecipeItems[stepIndex].RecipeVariableDefine.TechnicalName.Replace(".", "_"), RecipeRows[varId].RecipeItems[stepIndex].Value)); else lua.DoString(string.Format("{0}=\"{1}\";", RecipeRows[varId].RecipeItems[stepIndex].RecipeVariableDefine.TechnicalName.Replace(".", "_"), RecipeRows[varId].RecipeItems[stepIndex].Value)); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine("校验异常," + ex); } } //reading from recipe format file foreach (string key in _preDefinedRecipeVars.Keys) { string varValueString = _preDefinedRecipeVars[key]; double varValue; if (double.TryParse(varValueString, out varValue)) lua.DoString(string.Format("{0}={1};", key, varValue)); else lua.DoString(string.Format("{0}=\"{1}\";", key, varValueString)); } //do valation foreach (var rule in _validationRules) { lua.DoString(string.Format("if {0} then hasErr=1 else hasErr=0 end", rule.Item2)); bool hasError = ((int)lua.GetNumber("hasErr")) == 1; if (hasError) { lua.DoString(string.Format("message=string.format({0});", rule.Item3)); string reason = lua.GetString("message"); var rowId = RowVarNameDic[rule.Item1]; var cell = RecipeRows[rowId].RecipeItems[stepIndex]; cell.ErrInfo = reason; cell.Background = Brushes.Pink; Errors.Add(new Tuple(rowId, stepIndex, cell.RecipeVariableDefine.FriendlyName, reason)); cell.InvokePropertyChanged(); } } } } #endregion recipe parameter validation #region update material cell data int materialRowId = -1; for (int colId = 0; colId < RecipeRows[0].RecipeItems.Count; colId++) { string materialName = ""; for (int rowId = 0; rowId < RecipeRows.Count; rowId++) { string curMo = ""; if (materialRowId == -1 && RecipeRows[rowId].RecipeItems[colId].RecipeVariableDefine.TechnicalName == "Material") { materialRowId = rowId; } var cell = RecipeRows[rowId].RecipeItems[colId]; if (cell.RecipeVariableDefine.CellType == CellType.ReadOnlySelection && String.Compare(cell.Value, "flow", true) == 0) { string moName = cell.RecipeVariableDefine.TechnicalName.Split('.')[0].ToLower(); string displayName = cell.RecipeVariableDefine.FriendlyName.Replace("_", ".").Split('.')[0].ToLower(); if (moName.Contains("cp2mg_1") || displayName.Contains("cp2mg_1")) curMo = "Mg1"; else if (moName.Contains("cp2mg_2") || displayName.Contains("cp2mg_2")) curMo = "Mg2"; else if (moName.Contains("cp2mg_3") || displayName.Contains("cp2mg_3")) curMo = "Mg3"; else if (moName.Contains("cp2mg_4") || displayName.Contains("cp2mg_4")) curMo = "Mg4"; else if (moName.Contains("cp2mg") || displayName.Contains("cp2mg")) curMo = "Mg"; else if (moName.Contains("tmin") || displayName.Contains("tmin")) curMo = "In"; else if (moName.Contains("tmin_1") || displayName.Contains("tmin_1")) curMo = "In1"; else if (moName.Contains("tmin_2") || displayName.Contains("tmin_2")) curMo = "In2"; else if (moName.Contains("tmin_3") || displayName.Contains("tmin_3")) curMo = "In3"; else if (moName.Contains("tmin_4") || displayName.Contains("tmin_4")) curMo = "In4"; else if (moName.Contains("tmga") || displayName.Contains("tmga")) curMo = "TMG"; else if (moName.Contains("tmga_1") || displayName.Contains("tmga_1")) curMo = "TMG1"; else if (moName.Contains("tmga_2") || displayName.Contains("tmga_2")) curMo = "TMG2"; else if (moName.Contains("tmga_3") || displayName.Contains("tmga_3")) curMo = "TMG3"; else if (moName.Contains("tmga_4") || displayName.Contains("tmga_4")) curMo = "TMG4"; else if (moName.Contains("tega") || displayName.Contains("tega")) curMo = "TEG"; else if (moName.Contains("tega_1") || displayName.Contains("tega_1")) curMo = "TEG1"; else if (moName.Contains("tega_2") || displayName.Contains("tega_2")) curMo = "TEG2"; else if (moName.Contains("tega_3") || displayName.Contains("tega_3")) curMo = "TEG3"; else if (moName.Contains("tega_4") || displayName.Contains("tega_4")) curMo = "TEG4"; else if (moName.Contains("tmal") || displayName.Contains("tmal")) curMo = "TAl"; else if (moName.Contains("tmal_1") || displayName.Contains("tmal_1")) curMo = "TAl1"; else if (moName.Contains("tmal_2") || displayName.Contains("tmal_2")) curMo = "TAl2"; else if (moName.Contains("tmal_3") || displayName.Contains("tmal_3")) curMo = "TAl3"; else if (moName.Contains("tmal_4") || displayName.Contains("tmal_4")) curMo = "TAl4"; else if (moName.Contains("nh3source") || displayName.Contains("nh3source")) curMo = "NH3"; else if (moName.Contains("si")) curMo = "Si"; if (!string.IsNullOrEmpty(curMo)) { if (string.IsNullOrEmpty(materialName)) materialName = curMo; else materialName += "|" + curMo; } } } if (materialRowId >= 0) { RecipeRows[materialRowId].RecipeItems[colId].Value = materialName; RecipeRows[materialRowId].RecipeItems[colId].InvokePropertyChanged(); } } #endregion update material cell data #region update column header if (DataGridControl.Columns.Count - 1 == RecipeRows[0].RecipeItems.Count) { var redColHeader = (DataTemplate)DataGridControl.FindResource("columnRedTitleTemplate"); var blackColHeader = (DataTemplate)DataGridControl.FindResource("columnBlackTitleTemplate"); for (int j = 0; j < RecipeRows[0].RecipeItems.Count; j++) { DataGridControl.Columns[j + 1].Title = string.Format("{0}|{2}\r\n{1}", j + 1, RecipeRows[0].RecipeItems[j].Value, RecipeRows[2].RecipeItems[j].Value); if (Errors.Find((o) => o.Item2 == j) != null) DataGridControl.Columns[j + 1].TitleTemplate = redColHeader; else DataGridControl.Columns[j + 1].TitleTemplate = blackColHeader; } } #endregion update column header #region if running state, highlight current running step, move horizontal bar to current running step if (CurrentRunningStepNo > 0 && DataGridControl.Columns.Count > 2) { for (int rowId = 0; rowId < RecipeRows.Count; rowId++) { for (int colId = 0; colId < RecipeRows[0].RecipeItems.Count; colId++) { var cell = RecipeRows[rowId].RecipeItems[colId]; if (colId + 1 == CurrentRunningStepNo) { cell.IsRunning = true; } else { cell.IsRunning = false; } cell.InvokePropertyChanged("IsRunning"); } } if (moving2RunningStepPosition) { Task.Factory.StartNew(() => { System.Threading.Thread.Sleep(200); DataGridControl.Dispatcher.Invoke(new Action(() => { double singleWidth = DataGridControl.Columns[1].Width; double horizontalOffset = (CurrentRunningStepNo - 2) * singleWidth; if (DataGridControl.ScrollViewer != null) { if (horizontalOffset > DataGridControl.ScrollViewer.ScrollableWidth) horizontalOffset = DataGridControl.ScrollViewer.ScrollableWidth; if (horizontalOffset < 0) horizontalOffset = 0; DataGridControl.ScrollViewer.ScrollToHorizontalOffset(horizontalOffset); } })); }); } } #endregion if running state, highlight current running step, move horizontal bar to current running step //calc recipe time CalcRecipeTime(); //invoke property InvokePropertyChanged("Errors"); } catch (Exception ex) { MessageBox.Show("工艺程序校验出错!\r\n\r\n" + ex.Message, "出错", MessageBoxButton.OK, MessageBoxImage.Error); System.Diagnostics.Debug.WriteLine(ex.Message); } } /// /// reload datagrid content /// private void RefreshDataGrid() { try { //generate columns in Grid DataGridControl.CurrentColumn = null; if (DataGridControl.Columns.Count > 0) DataGridControl.Columns.Clear(); var template = (DataTemplate)DataGridControl.FindResource("CustomTemplate"); var rowTemplate = (DataTemplate)DataGridControl.FindResource("RowHeadTemplate"); DataGridControl.Columns.Add(new Xceed.Wpf.DataGrid.Column() { Width = 140, Title = " ", FieldName = ".", CellContentTemplate = rowTemplate }); var cellEditor = DataGridControl.DefaultCellEditors[typeof(SmartCellData)];// DefaultCellEditorSelector.SelectCellEditor(typeof(SmartCellData)); for (int index = 0; index < RecipeRows[0].RecipeItems.Count; index++) { //var col = new Xceed.Wpf.DataGrid.Column(); DataGridControl.Columns.Add(new Xceed.Wpf.DataGrid.Column() { Title = string.Format("【{0}】\r\n{1}", index + 1, RecipeRows[0].RecipeItems[index].Value), FieldName = string.Format("RecipeItems[{0}]", index), CellContentTemplate = template, AllowSort = false, Width = 120, MaxWidth = 120, CellEditor = cellEditor }); } InvokePropertyChanged("RecipeRows"); InvokePropertyChanged("RecipeHead"); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex.Message); } } /// /// calc recipe total time /// private void CalcRecipeTime() { int timeStepRowId = -1; int loopStepRowId = -1; for (int i = 0; i < RecipeRows.Count; i++) { if (RecipeRows[i].TechnicalName == "Time") timeStepRowId = i; if (RecipeRows[i].TechnicalName == "Loop") loopStepRowId = i; if (loopStepRowId != -1 && timeStepRowId != -1) break; } TimeSpan tspan = new TimeSpan(); for (int stepNo = 0; stepNo < RecipeRows[timeStepRowId].RecipeItems.Count; stepNo++) { string loopStr = RecipeRows[loopStepRowId].RecipeItems[stepNo].Value; bool isLoopStart = Regex.IsMatch(loopStr, @"^Loop\x20\d+$"); if (isLoopStart) { int loopNum = int.Parse(loopStr.ToLower().Replace("loop", "").Replace(" ", "")); TimeSpan ts = new TimeSpan(); for (int innerStepNo = stepNo; innerStepNo < RecipeRows[timeStepRowId].RecipeItems.Count; innerStepNo++) { loopStr = RecipeRows[loopStepRowId].RecipeItems[innerStepNo].Value; stepNo = innerStepNo; string timeDuration = RecipeRows[timeStepRowId].RecipeItems[innerStepNo].Value; string[] timeArr = timeDuration.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries); if (timeArr.Length == 3) { int h, mi, s; if (int.TryParse(timeArr[0], out h) && int.TryParse(timeArr[1], out mi) && int.TryParse(timeArr[2], out s)) { var tt = new TimeSpan(h, mi, s); ts += tt; } } bool isLoopEnd = Regex.IsMatch(loopStr, @"^Loop End$"); if (isLoopEnd) { tspan += new TimeSpan(ts.Ticks * loopNum); break; } } } else { string timeDuration = RecipeRows[timeStepRowId].RecipeItems[stepNo].Value; string[] timeArr = timeDuration.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries); if (timeArr.Length == 3) { int h, mi, s; if (int.TryParse(timeArr[0], out h) && int.TryParse(timeArr[1], out mi) && int.TryParse(timeArr[2], out s)) { var ts = new TimeSpan(h, mi, s); tspan += ts; } } } } RecipeInfo = string.Format("共{0}步,总时间{1}:{2}:{3}", RecipeRows[0].RecipeItems.Count, (int)tspan.TotalHours, tspan.Minutes.ToString("00"), tspan.Seconds.ToString("00")); InvokePropertyChanged("RecipeInfo"); } #region INotifyPropertyChanged public event PropertyChangedEventHandler PropertyChanged; public void InvokePropertyChanged(string propertyName) { if (PropertyChanged != null) { PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } #endregion INotifyPropertyChanged } public class RecipePredefine123 { /// /// 最大升温速度 C/sec /// public double MaxTempRampUpRate { get; set; } /// /// 最大降温速度 C/sec /// public double MaxTempRampDownRate { get; set; } /// /// 加热区允许的最大温差设定值 C /// public double MaxTempDT { get; set; } /// /// Heat Purge最小流量设定 sccm /// public double MinHeatPurge { get; set; } /// /// Spindle最低转速 rpm /// public double MinSpindleSpeed { get; set; } /// /// 腔体压力允许最低设定值 mbar /// public double MinPressureSetpoint { get; set; } /// /// 最大压力伺服速度 mbar/sec /// public double MaxPressRampRate { get; set; } /// /// 最大转盘旋转加速度 rpm/sec /// public double MaxSpindleRampRate { get; set; } /// /// 最大温度设定值 /// public double MaxTempSetpoint { get; set; } /// /// 最大电流设定值 /// public double AZoneMaxCurrentSetpoint { get; set; } public double BZoneMaxCurrentSetpoint { get; set; } public double CZoneMaxCurrentSetpoint { get; set; } public double DZoneMaxCurrentSetpoint { get; set; } } public class RecipeRow { private ObservableCollection _recipeItems = new ObservableCollection(); public RecipeRow(params SmartCellData[] vars) { foreach (var var in vars) _recipeItems.Add(var); } public string CatalogName { get; set; } public string FriendlyName { get; set; } public string TechnicalName { get; set; } public ObservableCollection RecipeItems { get { return _recipeItems; } set { _recipeItems = value; } } } /// /// Recipe head /// public class RecipeHead { public string RecipeVariation { get; set; } public string CreationTime { get; set; } public string LastRevisionTime { get; set; } public string CreatedBy { get; set; } public string LastModifiedBy { get; set; } public string PressureMode { get; set; } public string Description { get; set; } public string BasePressure { get; set; } public string PumpDownLimit { get; set; } public string ElectrodeTemp { get; set; } public string PurgeActive { get; set; } public string Barcode { get; set; } } }