using Caliburn.Micro; using DocumentFormat.OpenXml.EMMA; using MECF.Framework.Common.DataCenter; using MECF.Framework.UI.Client.CenterViews.Configs.Roles; using MECF.Framework.UI.Client.CenterViews.Editors.Sequence; using MECF.Framework.UI.Client.ClientBase; using MECF.Framework.UI.Client.RecipeEditorLib.RecipeModel; using MECF.Framework.UI.Client.RecipeEditorLib.RecipeModel.Params; using MECF.Framework.UI.Core.Accounts; using OpenSEMI.ClientBase; using OpenSEMI.ClientBase.Command; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Dynamic; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Threading; using static System.Windows.Forms.VisualStyles.VisualStyleElement.Window; namespace MECF.Framework.UI.Client.CenterViews.Editors.Recipe { public class RecipeCompareViewModel : UiViewModelBase { private readonly RecipeType _fileTypes; private readonly bool _isFolderOnly; private readonly bool _isShowRootNode; private FileNode _currentFileNode; private readonly RecipeProvider _recipeProvider; public object View { get; set; } public ObservableCollection Chambers { get; set; } private string labSelectRecipeNames; public string LabSelectRecipeNames { get => labSelectRecipeNames; set { labSelectRecipeNames = value; NotifyOfPropertyChange(); } } private RecipeData _recipeHeaders; /// /// Recipe表头 /// public RecipeData RecipeHeaders { get => _recipeHeaders; set { _recipeHeaders = value; NotifyOfPropertyChange(); } } private RecipeData _comparRecipeHeader; /// /// Recipe表头 /// public RecipeData ComparRecipeHeader { get => _comparRecipeHeader; set { _comparRecipeHeader = value; NotifyOfPropertyChange(); } } private RecipeData _comparRecipe1; /// /// 选择要Recipe-1 /// public RecipeData ComparRecipe1 { get => _comparRecipe1; set { _comparRecipe1 = value; NotifyOfPropertyChange(); } } private RecipeData _comparRecipe2; /// /// 选择要Recipe-2 /// public RecipeData ComparRecipe2 { get => _comparRecipe2; set { _comparRecipe2 = value; NotifyOfPropertyChange(); } } private double[] _scrollViewerOffset; /// /// ScrollViewer滑动偏移 /// public double[] ScrollViewerOffset { get => _scrollViewerOffset; set { _scrollViewerOffset = value; NotifyOfPropertyChange(); } } public RecipeHistory RecipeHistory { get; set; } private ICommand _recipeHistoryCommand; public ICommand RecipeHistoryCommand { get { if (_recipeHistoryCommand == null) _recipeHistoryCommand = new BaseCommand(() => RecipeHistoryViewShow()); return _recipeHistoryCommand; } } public int ProcessTypeIndexSelection { get; set; } public RecipeType CurrentProcessType => ProcessTypeFileList[ProcessTypeIndexSelection].ProcessType; public int ChamberTypeIndexSelection { get; set; } public ObservableCollection ChamberType { get; set; } public string CurrentChamberType => ChamberType[ChamberTypeIndexSelection]; public string SelectedChamber { get; set; } public ObservableCollection SelectRecipeNameList { get; set; } = new ObservableCollection(); public FileNode DGSelectRecipeName { get; set; } public List ProcessTypeFileList { get; set; } public RecipeCompareViewModel() { _fileTypes = RecipeType.Process | RecipeType.Routine; _isFolderOnly = false; _isShowRootNode = false; _recipeProvider = new RecipeProvider(); var chamberType = QueryDataClient.Instance.Service.GetConfig("System.Recipe.SupportedChamberType"); if (chamberType == null) { ChamberType = new ObservableCollection() { "Default" }; } else { ChamberType = new ObservableCollection(((string)(chamberType)).Split(',')); } BuildFileTree(_isShowRootNode, _isFolderOnly, _fileTypes); UpdateRecipeFormat(); } protected override void OnViewLoaded(object view) { View = view; base.OnViewLoaded(view); } protected override void OnDeactivate(bool close) { base.OnDeactivate(close); // 锁定编辑器 ((RecipeCompareView)View).editorLocker.Lock(); } protected override void OnInitialize() { base.OnInitialize(); } private void StoryboardClose() { var getString = (Storyboard)((RecipeCompareView)View).FindResource("MenuClose"); getString.Begin(); } private void RecipeHistoryViewShow() { if (_currentFileNode == null) return; var dialog = new RecipeHistoryViewModel(_currentFileNode.FullPath.Replace("\\", "-")); var wm = new WindowManager(); dynamic settings = new ExpandoObject(); settings.WindowStartupLocation = WindowStartupLocation.CenterOwner; settings.Title = "Recipe历史数据对比,选择一个(选择项和当前使用项对比),选择两个(两个选择项对比)"; settings.ResizeMode = ResizeMode.NoResize; settings.ShowInTaskbar = false; StoryboardClose(); var bret = wm.ShowDialog(dialog, null, settings); if ((bool)bret) { if (dialog.SelectRecipeMemorieList.Count == 1) RecipeSelectAndHistory(dialog.SelectRecipeMemorieList[0]); else if (dialog.SelectRecipeMemorieList.Count == 2) RecipeBothHistory(dialog.SelectRecipeMemorieList); } } private async void RecipeSelectAndHistory(RecipeHistory recipeHistory) { RecipeHistory = recipeHistory; LabSelectRecipeNames = _currentFileNode.FullPath + " / " + RecipeHistory.RecipeName + ": " +RecipeHistory.CreateTime; await LoadRecipe(ComparRecipe1, _currentFileNode.PrefixPath, _currentFileNode.FullPath); await LoadRecipe(ComparRecipe2, RecipeHistory); StartComper(ComparRecipe1, ComparRecipe2); } private async void RecipeBothHistory(List recipeHistoryList) { LabSelectRecipeNames = recipeHistoryList[0].RecipeName + ": " + recipeHistoryList[0].CreateTime + " / " + recipeHistoryList[1].RecipeName + ": " + recipeHistoryList[1].CreateTime; await LoadRecipe(ComparRecipe1, recipeHistoryList[0]); await LoadRecipe(ComparRecipe2, recipeHistoryList[1]); StartComper(ComparRecipe1, ComparRecipe2); } public void TreeSelectChanged(FileNode file) { _currentFileNode = file; RecipeHistoryViewShow(); } public void TreeRightMouseDown(MouseButtonEventArgs e) { var item = GetParentObjectEx(e.OriginalSource as DependencyObject) as TreeViewItem; if (item != null) { item.Focus(); } } private TreeViewItem GetParentObjectEx(DependencyObject obj) where TreeViewItem : FrameworkElement { var parent = VisualTreeHelper.GetParent(obj); while (parent != null) { if (parent is TreeViewItem) { return (TreeViewItem)parent; } parent = VisualTreeHelper.GetParent(parent); } return null; } private void BuildFileTree(bool isShowRootNode, bool isFolderOnly, RecipeType fileType) { var recipeProvider = new RecipeProvider(); var scProcessType = QueryDataClient.Instance.Service.GetConfig("System.Recipe.SupportedProcessType").ToString(); var supportedRecipeType = new List(new[] { RecipeType.Process, RecipeType.Routine }); if (!string.IsNullOrEmpty(scProcessType)) { supportedRecipeType.Clear(); if (fileType.HasFlag(RecipeType.Process) && scProcessType.Contains(RecipeType.Process.ToString())) supportedRecipeType.Add(RecipeType.Process); if (fileType.HasFlag(RecipeType.Routine) && scProcessType.Contains(RecipeType.Routine.ToString())) supportedRecipeType.Add(RecipeType.Routine); } var processTypeFileList = new List(); for (var i = 0; i < supportedRecipeType.Count; i++) { var type = new ProcessTypeFileItem { ProcessType = supportedRecipeType[i] }; var prefix = $"Sic\\{supportedRecipeType[i]}"; var recipes = recipeProvider.GetXmlRecipeList(prefix); type.FileListByProcessType = RecipeSequenceTreeBuilder.BuildFileNode(prefix, "", false, recipes, isFolderOnly, isShowRootNode)[0].Files; processTypeFileList.Add(type); } this.ProcessTypeFileList = processTypeFileList; } protected override void OnActivate() { base.OnActivate(); } public void UpdateRecipeFormat() { var chamber = QueryDataClient.Instance.Service.GetConfig("System.Recipe.ChamberModules"); if (chamber == null) { chamber = "PM1"; } Chambers = new ObservableCollection(((string)chamber).Split(',')); if (Chambers.Count > 1) { for (var i = 0; i < Chambers.Count; i++) { var isPmInstall = (bool)QueryDataClient.Instance.Service.GetConfig( $"System.SetUp.Is{Chambers[i].ToString()}Installed"); { if (!isPmInstall) { Chambers.RemoveAt(i); } } } } if (Chambers.Count == 0) { Chambers = new ObservableCollection(new string[] { "PM1" }); } SelectedChamber = Chambers[0]; #region Create RecipeData ComparRecipeHeader = CreateRecipe(); ComparRecipeHeader.AddStep(); ComparRecipe1 = CreateRecipe(); ComparRecipe2 = CreateRecipe(); #endregion Create RecipeData } private RecipeData CreateRecipe() { var role = AccountClient.Instance.Service.GetRoleByID(BaseApp.Instance.UserContext.Role.RoleId); var recipeData = new RecipeData(null, CurrentProcessType); recipeData.BuildFormat($"{CurrentChamberType}\\{CurrentProcessType}", SelectedChamber, role); //得到当前登录的RoleItem //var role = _roleManager.GetRoleByName(BaseApp.Instance.UserContext.RoleName); return recipeData; } private async Task LoadRecipe(RecipeData recipeData, string prefixPath, string recipeName) { recipeData.Clear(); recipeData.Steps.Save(); // 重置为已保存状态 var recipeContent = _recipeProvider.ReadRecipeFile(prefixPath, recipeName); if (string.IsNullOrEmpty(recipeContent)) { MessageBox.Show($"{prefixPath}\\{recipeName} is empty, please confirm the file is valid.", "Error", MessageBoxButton.OK, MessageBoxImage.Error); return; } var role = AccountClient.Instance.Service.GetRoleByID(BaseApp.Instance.UserContext.Role.RoleId); await recipeData.LoadFile(prefixPath, recipeName, recipeContent, SelectedChamber, role, GetLoadingDispatcher()); } private async Task LoadRecipe(RecipeData recipeData,RecipeHistory recipeHistory) { recipeData.Clear(); recipeData.Steps.Save(); // 重置为已保存状态 var role = AccountClient.Instance.Service.GetRoleByID(BaseApp.Instance.UserContext.Role.RoleId); await recipeData.LoadFile(recipeHistory, SelectedChamber, role, GetLoadingDispatcher()); } private static Dispatcher GetLoadingDispatcher() { // 判断Step呈现方式 var isCascadeLoading = (bool)QueryDataClient.Instance.Service.GetConfig("System.RecipeCascadeLoading"); var dispatcher = isCascadeLoading ? Dispatcher.CurrentDispatcher : null; return dispatcher; } public async void ComperSelectRecipe() { SelectRecipeNameList.Clear(); foreach (var ProcessTypeFile in ProcessTypeFileList) { GetSelectRecipeName(ProcessTypeFile.FileListByProcessType); } if (SelectRecipeNameList.Count != 2) { DialogBox.ShowError("Only 2 comparisons are supported"); return; } StoryboardClose(); LabSelectRecipeNames = SelectRecipeNameList[0].Name + " / " + SelectRecipeNameList[1].Name; await LoadRecipe(ComparRecipe1, GetRecipeFullPath(SelectRecipeNameList[0]), SelectRecipeNameList[0].Name); await LoadRecipe(ComparRecipe2, GetRecipeFullPath(SelectRecipeNameList[1]), SelectRecipeNameList[1].Name); StartComper(ComparRecipe1, ComparRecipe2); } /// /// 递归获取选择的Recipe /// /// private void GetSelectRecipeName(ObservableCollection fileNodes) { foreach (var item in fileNodes) { if (item.Files != null && item.Files.Count > 0) GetSelectRecipeName(item.Files); if (item.IsSelected) SelectRecipeNameList.Add(item); } } private string GetRecipeFullPath(FileNode fileNodes) { return fileNodes.PrefixPath + (fileNodes.Parent.FullPath == "" ? "" : "\\" + fileNodes.Parent.FullPath); } public void StartComper(RecipeData recipeData1, RecipeData recipeData2) { int stepsCount = 0;//缓存较短Recipe步数 RecipeData recipeDataExcess;//缓存较长步数Recipe if (recipeData1.Steps.Count <= recipeData2.Steps.Count) { stepsCount = recipeData1.Steps.Count; recipeDataExcess = recipeData2; } else { stepsCount = recipeData2.Steps.Count; recipeDataExcess = recipeData1; } for (int i = 0; i < stepsCount; i++) { RecipeStep recipe1step = recipeData1.Steps[i]; RecipeStep recipe2step = recipeData2.Steps[i]; for (int j = 0; j < recipe1step.Count; j++) { object obj1 = GetValue(recipe1step[j]);//获取单元格中的数值,object类型 object obj2 = GetValue(recipe2step[j]); if (!(Comparer.Default.Compare(obj1, obj2) == 0)) { //recipe1step[j].Highlight();//不同部分高亮 //recipe2step[j].Highlight(); recipe1step[j].CompareDifferent(); recipe2step[j].CompareDifferent(); } } } //步数多余部分高亮显示 使用缓存步数和缓存Recipe for (int i = stepsCount; i < recipeDataExcess.Steps.Count; i++) { for (int j = 0; j < recipeDataExcess.Steps[i].Count; j++) { recipeDataExcess.Steps[i][j].CompareDifferent(); } } } private object GetValue(Param param) { object Value = null; if (param is IValueParam vp) Value = vp.GetValue(); return Value; } } }