MoS2/Framework/MECF.Framework.UI.Client/CenterViews/Editors/Recipe/RecipeCompareViewModel.cs

514 lines
17 KiB
C#
Raw Permalink Normal View History

2026-06-15 10:56:30 +08:00
using Aitex.Core.RT.Log;
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;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Dynamic;
using System.Linq;
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, ISupportMultipleSystem
{
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<string> Chambers { get; set; }
private string labSelectRecipeNames;
public string LabSelectRecipeNames
{
get => labSelectRecipeNames;
set
{
labSelectRecipeNames = value;
NotifyOfPropertyChange();
}
}
private RecipeData _recipeHeaders;
/// <summary>
/// Recipe表头
/// </summary>
public RecipeData RecipeHeaders
{
get => _recipeHeaders;
set
{
_recipeHeaders = value;
NotifyOfPropertyChange();
}
}
private RecipeData _comparRecipeHeader;
/// <summary>
/// Recipe表头
/// </summary>
public RecipeData ComparRecipeHeader
{
get => _comparRecipeHeader;
set
{
_comparRecipeHeader = value;
NotifyOfPropertyChange();
}
}
private RecipeData _comparRecipe1;
/// <summary>
/// 选择要Recipe-1
/// </summary>
public RecipeData ComparRecipe1
{
get => _comparRecipe1;
set
{
_comparRecipe1 = value;
NotifyOfPropertyChange();
}
}
private RecipeData _comparRecipe2;
/// <summary>
/// 选择要Recipe-2
/// </summary>
public RecipeData ComparRecipe2
{
get => _comparRecipe2;
set
{
_comparRecipe2 = value;
NotifyOfPropertyChange();
}
}
private double[] _scrollViewerOffset;
/// <summary>
/// ScrollViewer滑动偏移
/// </summary>
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 string CurrentChamberType { get; set; }
public string SelectedChamber { get; set; }
public ObservableCollection<FileNode> SelectRecipeNameList { get; set; } = new ObservableCollection<FileNode>();
public FileNode DGSelectRecipeName { get; set; }
public List<ProcessTypeFileItem> ProcessTypeFileList { get; set; }
public RecipeCompareViewModel()
{
_fileTypes = RecipeType.Process | RecipeType.Routine;
_isFolderOnly = false;
_isShowRootNode = false;
_recipeProvider = new RecipeProvider();
}
protected override void OnViewLoaded(object view)
{
View = view;
base.OnViewLoaded(view);
}
protected override void OnActivate()
{
base.OnActivate();
RefreshRecipe();
}
protected override void OnDeactivate(bool close)
{
base.OnDeactivate(close);
// 锁定编辑器
((RecipeCompareView)View).editorLocker.Lock();
}
protected override void OnInitialize()
{
base.OnInitialize();
CurrentChamberType = (string)QueryDataClient.Instance.Service.GetConfig("System.Recipe.SupportedChamberType");
BuildFileTree(_isShowRootNode, _isFolderOnly, _fileTypes);
UpdateRecipeFormat();
}
public void RefreshRecipe()
{
ReloadRecipeFileList(CurrentChamberType, CurrentProcessType, "", false);
}
private void ReloadRecipeFileList(string chamberType, RecipeType processType, string selectedFile,
bool selectionIsFolder)
{
var item = ProcessTypeFileList.FirstOrDefault(x => x.ProcessType == processType);
if (item == null)
{
LOG.Write("error reload recipe file list, type = " + processType);
}
var prefix = $"{CurrentChamberType}\\{SystemName}\\{item.ProcessType}";
var recipes = _recipeProvider.GetXmlRecipeList(prefix);
item.FileListByProcessType =
RecipeSequenceTreeBuilder.BuildFileNode(prefix, selectedFile, selectionIsFolder, recipes)[0].Files;
item.InvokePropertyChanged();
}
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<RecipeHistory> 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<TreeViewItem>(e.OriginalSource as DependencyObject) as TreeViewItem;
if (item != null)
{
item.Focus();
}
}
private TreeViewItem GetParentObjectEx<TreeViewItem>(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 ProcessType = new ObservableCollection<string>(((string)(scProcessType)).Split(','));
var processTypeFileList = new List<ProcessTypeFileItem>();
for (var i = 0; i < ProcessType.Count; i++)
{
Enum.TryParse(ProcessType[i], true, out RecipeType rType);
var type = new ProcessTypeFileItem
{
ProcessType = rType
};
var prefix = $"{CurrentChamberType}\\{SystemName}\\{ProcessType[i]}";
var recipes = recipeProvider.GetXmlRecipeList(prefix);
type.FileListByProcessType = RecipeSequenceTreeBuilder.BuildFileNode(prefix, "", false, recipes, isFolderOnly, isShowRootNode)[0].Files;
processTypeFileList.Add(type);
}
this.ProcessTypeFileList = processTypeFileList;
}
public void UpdateRecipeFormat()
{
var chamber = QueryDataClient.Instance.Service.GetConfig("System.Recipe.ChamberModules");
if (chamber == null)
{
chamber = "PM1";
}
Chambers = new ObservableCollection<string>(((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<string>(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}", SystemName, 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);
}
/// <summary>
/// 递归获取选择的Recipe
/// </summary>
/// <param name="fileNodes"></param>
private void GetSelectRecipeName(ObservableCollection<FileNode> 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 (!(Object.Equals(obj1, obj2)))
{
//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;
}
}
}