514 lines
17 KiB
C#
514 lines
17 KiB
C#
|
|
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;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|