Sic.Framework/MECF.Framework.UI.Client/CenterViews/Editors/Recipe/RecipeEditorViewModel.cs

1826 lines
60 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using Aitex.Core.RT.Log;
using Caliburn.Micro;
using Caliburn.Micro.Core;
using MECF.Framework.Common.DataCenter;
using MECF.Framework.UI.Client.CenterViews.Editors.Sequence;
using MECF.Framework.UI.Client.ClientBase;
using OpenSEMI.ClientBase;
using OpenSEMI.ClientBase.Command;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
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.Threading;
using System.IO;
using MECF.Framework.UI.Client.RecipeEditorLib.DGExtension.CustomColumn;
using MECF.Framework.UI.Client.RecipeEditorLib.RecipeModel;
using MECF.Framework.UI.Client.RecipeEditorLib.RecipeModel.Params;
using Microsoft.Win32;
using MECF.Framework.UI.Client.CenterViews.Configs.Roles;
namespace MECF.Framework.UI.Client.CenterViews.Editors.Recipe
{
public class RecipeEditorViewModel : UiViewModelBase, IHandle<UserMode> //BaseModel
{
#region Variables
private readonly IRecipeGasFlowCalculator _recipeGasFlowCalculator;
private RecipeData _currentRecipe;
private bool _isLoading;
private int _errorsCount;
private Window _winValidationInfo;
private EditMode _editMode;
private readonly RecipeFormatBuilder _columnBuilder;
private readonly RecipeProvider _recipeProvider;
private ObservableCollection<ProcessTypeFileItem> _processTypeFileList;
private Point? _lastPosOfValidationWin = null;
#endregion
#region Constructors
public RecipeEditorViewModel()
{
var eventAggregator = IoC.Get<IEventAggregator>();
eventAggregator?.Subscribe(this);
_recipeGasFlowCalculator = IoC.Get<IRecipeGasFlowCalculator>();
_columnBuilder = new RecipeFormatBuilder();
_recipeProvider = new RecipeProvider();
}
#endregion
#region Commands
private ICommand _RenameFolderCommand;
public ICommand RenameFolderCommand
{
get
{
if (_RenameFolderCommand == null)
_RenameFolderCommand = new BaseCommand(() => RenameFolder());
return _RenameFolderCommand;
}
}
private ICommand _DeleteFolderCommand;
public ICommand DeleteFolderCommand
{
get
{
if (_DeleteFolderCommand == null)
_DeleteFolderCommand = new BaseCommand(() => DeleteFolder());
return _DeleteFolderCommand;
}
}
private ICommand _NewFolderCommand;
public ICommand NewFolderCommand
{
get
{
if (_NewFolderCommand == null)
_NewFolderCommand = new BaseCommand(() => NewFolder());
return _NewFolderCommand;
}
}
private ICommand _NewFolderRootCommand;
public ICommand NewFolderRootCommand
{
get
{
if (_NewFolderRootCommand == null)
_NewFolderRootCommand = new BaseCommand(() => NewFolderRoot());
return _NewFolderRootCommand;
}
}
private ICommand _NewRecipeCommand;
public ICommand NewRecipeCommand
{
get
{
if (_NewRecipeCommand == null)
_NewRecipeCommand = new BaseCommand(() => NewRecipe());
return _NewRecipeCommand;
}
}
private ICommand _NewRecipeRootCommand;
public ICommand NewRecipeRootCommand
{
get
{
if (_NewRecipeRootCommand == null)
_NewRecipeRootCommand = new BaseCommand(() => NewRecipeRoot());
return _NewRecipeRootCommand;
}
}
private ICommand _RenameRecipeCommand;
public ICommand RenameRecipeCommand
{
get
{
if (_RenameRecipeCommand == null)
_RenameRecipeCommand = new BaseCommand(() => RenameRecipe());
return _RenameRecipeCommand;
}
}
private ICommand _DeleteRecipeCommand;
public ICommand DeleteRecipeCommand
{
get
{
if (_DeleteRecipeCommand == null)
_DeleteRecipeCommand = new BaseCommand(() => DeleteRecipe());
return _DeleteRecipeCommand;
}
}
private ICommand _SaveAsRecipeCommand;
public ICommand SaveAsRecipeCommand
{
get
{
if (_SaveAsRecipeCommand == null)
_SaveAsRecipeCommand = new BaseCommand(() => SaveAsRecipe());
return _SaveAsRecipeCommand;
}
}
#endregion
#region Properties
public ObservableCollection<ProcessTypeFileItem> ProcessTypeFileList
{
get => _processTypeFileList;
set
{
_processTypeFileList = value;
NotifyOfPropertyChange(nameof(ProcessTypeFileList));
}
}
public RecipeData CurrentRecipe
{
get => _currentRecipe;
set
{
_currentRecipe = value;
NotifyOfPropertyChange();
}
}
public bool IsPermission => Permission == 3; //&& RtStatus != "AutoRunning";
public FileNode CurrentFileNode { get; set; }
private bool IsChanged => _editMode == EditMode.Edit || CurrentRecipe.IsChanged;
public Dictionary<string, List<EditorDataGridTemplateColumnBase>> DicColunms { get; set; }
public bool EnableNew { get; set; }
public bool EnableReName { get; set; }
public bool EnableCopy { get; set; }
public bool EnableDelete { get; set; }
public bool EnableSave { get; set; }
public bool EnableImportExport { get; set; }
public bool EnableStep { get; set; }
public bool EnableReload { get; set; }
public bool EnableSaveToAll { get; set; }
public bool EnableSaveTo { get; set; }
public bool EnableLeftTabPanel { get; set; }
public bool EnableRefreshRecipeList { get; set; }
public bool EnableFilterTreeList { get; set; }
public bool EnableCellPermButton { get; set; }
/// <summary>
/// 返回是否显示单元格访问权限编辑按钮。
/// </summary>
public bool IsShowCellAccessPermEditButton { get; private set; }
public int ErrorsCount
{
set
{
// 如果错误数没发生变化不要修改Badge的值以免触发Badge动画。
if (value == _errorsCount)
return;
if(value == 0)
((RecipeEditorView)View).txtErrorCount.Badge = null;
else
((RecipeEditorView)View).txtErrorCount.Badge = value;
_errorsCount = value;
}
}
private int _countAccessibleWhitelist;
private int AccessibleWhitelistCount
{
set
{
if (value == _countAccessibleWhitelist)
return;
if (value == 0)
((RecipeEditorView)View).txtCellAccessPremCount.Badge = null;
else
((RecipeEditorView)View).txtCellAccessPremCount.Badge = value;
_countAccessibleWhitelist = value;
}
}
/// <summary>
/// 是否正在加载配方。
/// </summary>
public bool IsLoading
{
get => _isLoading;
private set
{
_isLoading = value;
NotifyOfPropertyChange(nameof(IsLoading));
}
}
public List<RecipeStep> SelectedRecipeSteps { get; set; }
public ObservableCollection<string> ChamberType { get; set; }
public int ChamberTypeIndexSelection { get; set; }
public int ProcessTypeIndexSelection { get; set; }
public string CurrentChamberType => ChamberType[ChamberTypeIndexSelection];
public string CurrentProcessType => ProcessTypeFileList[ProcessTypeIndexSelection].ProcessType;
public Visibility MultiChamberVisibility { get; set; }
public ObservableCollection<string> Chambers { get; set; }
public string SelectedChamber { get; set; }
public object View { get; set; }
#endregion
protected override void OnInitialize()
{
base.OnInitialize();
var chamberType = QueryDataClient.Instance.Service.GetConfig("System.Recipe.SupportedChamberType");
if (chamberType == null)
{
ChamberType = new ObservableCollection<string>() { "Default" };
}
else
{
ChamberType = new ObservableCollection<string>(((string)(chamberType)).Split(','));
}
ChamberTypeIndexSelection = 0;
var processType = "Process,Routine,Clean";
ProcessTypeFileList = new ObservableCollection<ProcessTypeFileItem>();
var recipeProcessType = ((string)processType).Split(',');
for (var i = 0; i < recipeProcessType.Length; i++)
{
var type = new ProcessTypeFileItem();
type.ProcessType = recipeProcessType[i];
var prefix = $"{ChamberType[ChamberTypeIndexSelection]}\\{recipeProcessType[i]}";
var recipes = _recipeProvider.GetXmlRecipeList(prefix);
type.FileListByProcessType =
RecipeSequenceTreeBuilder.BuildFileNode(prefix, "", false, recipes)[0].Files;
type.FilterFileListByProcessType = type.FileListByProcessType;
ProcessTypeFileList.Add(type);
}
DicColunms = new Dictionary<string, List<EditorDataGridTemplateColumnBase>>();
UpdateRecipeFormat();
}
protected override void OnActivate()
{
//初始化RoleManager
var roleManager = new RoleManager();
roleManager.Initialize();
//得到当前登录的RoleItem
var role = roleManager.GetRoleByName(BaseApp.Instance.UserContext.RoleName);
CurrentRecipe.ReloadRolePermission(role);
UpdateView();
// 根据角色权限设置判断是否显示按钮。
IsShowCellAccessPermEditButton = GetCapeModeButtonPerm();
NotifyOfPropertyChange(nameof(IsShowCellAccessPermEditButton));
base.OnActivate();
}
/// <summary>
/// 获取角色权限配置中“Recipe.Behaviour.AllowEditCellAccessPerm”的配置。
/// </summary>
/// <returns></returns>
private bool GetCapeModeButtonPerm()
{
var roleID = BaseApp.Instance.UserContext.RoleID;
var val = RoleAccountProvider.Instance.GetMenuPermission(roleID, "Recipe.Behaviour.AllowEditCellAccessPerm");
return val != (int)MenuPermissionEnum.MP_NONE;
}
protected override void OnDeactivate(bool close)
{
base.OnDeactivate(close);
if (IsChanged)
{
if (DialogBox.ShowDialog(DialogButton.Yes | DialogButton.No, DialogType.CONFIRM,
$"Recipe {CurrentRecipe.Name} content is changed, do you want to save it?") == DialogButton.Yes)
{
SaveRecipe();
}
}
_winValidationInfo?.Close();
// 锁定编辑器
((RecipeEditorView)View).editorLocker.Lock();
}
protected override void OnViewLoaded(object view)
{
View = view;
base.OnViewLoaded(view);
}
public void TabSelectionChanged()
{
UpdateRecipeFormat();
OnViewLoaded(View);
}
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];
CurrentRecipe = new RecipeData(_recipeGasFlowCalculator);
CurrentRecipe.BuildFormat($"{CurrentChamberType}\\{CurrentProcessType}", SelectedChamber, BaseApp.Instance.UserContext.RoleName);
CurrentRecipe.OnValidated += (sender, args) =>
{
ErrorsCount = CurrentRecipe.ValidationErrorCount;
};
CurrentRecipe.OnAccessibleWhitelistChanged += (sender, count) =>
{
AccessibleWhitelistCount = count;
};
_editMode = EditMode.None;
MultiChamberVisibility = Chambers.Count > 1 ? Visibility.Visible : Visibility.Collapsed;
}
/// <summary>
/// Chamber变更时重新加载Recipe。
/// </summary>
public async void ChamberSelectionChanged()
{
if (IsChanged)
{
var selection = DialogBox.ShowDialog(DialogButton.Yes | DialogButton.No,
DialogType.CONFIRM,
$"Recipe {CurrentRecipe.Name} is changed, do you want to save it?");
if (selection == DialogButton.Yes)
{
CurrentRecipe.Revisor = BaseApp.Instance.UserContext.LoginName;
CurrentRecipe.ReviseTime = DateTime.Now;
Save(CurrentRecipe, false);
}
}
IsLoading = true;
await CurrentRecipe.ChangeChamber(CurrentRecipe.Columns
, _columnBuilder.Configs, SelectedChamber, GetLoadingDispatcher());
IsLoading = false;
}
/// <summary>
/// 左侧Recipe列表选择改变时重新加载Recipe。
/// </summary>
/// <param name="node"></param>
public async void TreeSelectChanged(FileNode node)
{
if (IsChanged)
{
var selection = DialogBox.ShowDialog(DialogButton.Yes | DialogButton.No,
DialogType.CONFIRM,
$"Recipe {CurrentRecipe.Name} is changed, do you want to save it?");
if (selection == DialogButton.Yes)
{
CurrentRecipe.Revisor = BaseApp.Instance.UserContext.LoginName;
CurrentRecipe.ReviseTime = DateTime.Now;
Save(CurrentRecipe, false);
}
}
CurrentFileNode = node;
if (node != null && node.IsFile)
{
await LoadRecipe(node.PrefixPath, node.FullPath);
}
else
{
ClearData();
_editMode = EditMode.None;
}
UpdateView();
}
#region folder
public void NewFolder()
{
if (IsChanged)
{
var selection = DialogBox.ShowDialog(DialogButton.Yes | DialogButton.No | DialogButton.Cancel,
DialogType.CONFIRM, $"Recipe {CurrentRecipe.Name} is changed, do you want to save it?");
if (selection == DialogButton.Cancel)
return;
if (selection == DialogButton.Yes)
{
CurrentRecipe.Revisor = BaseApp.Instance.UserContext.LoginName;
CurrentRecipe.ReviseTime = DateTime.Now;
Save(CurrentRecipe, false);
}
}
var dialog = new InputFileNameDialogViewModel("Input New Folder Name");
dialog.FileName = "new folder";
var wm = new WindowManager();
var dialogReturn = wm.ShowDialog(dialog);
if (!dialogReturn.HasValue || !dialogReturn.Value)
return;
var name = dialog.FileName.Trim();
if (string.IsNullOrEmpty(name))
{
DialogBox.ShowWarning("Folder name should not be empty");
return;
}
var prefix = ChamberType[ChamberTypeIndexSelection] + "\\" +
ProcessTypeFileList[ProcessTypeIndexSelection].ProcessType;
var processType = string.Empty;
var newFolder = string.Empty;
if (CurrentFileNode != null)
{
prefix = CurrentFileNode.PrefixPath;
var folder = CurrentFileNode.FullPath;
if (CurrentFileNode.IsFile)
{
folder = folder.Substring(0, folder.LastIndexOf("\\") + 1);
if (!string.IsNullOrEmpty(folder))
newFolder = folder;
}
else
{
newFolder = folder + "\\";
}
}
newFolder = newFolder + name;
if (IsExist(newFolder, false))
{
DialogBox.ShowWarning($"Can not create folder {newFolder}, Folder with the same name already exist.");
return;
}
if (newFolder.Length > 200)
{
DialogBox.ShowWarning($"Can not create folder {newFolder}, Folder name too long, should be less 200.");
return;
}
_recipeProvider.CreateRecipeFolder(prefix, newFolder);
ReloadRecipeFileList(CurrentChamberType, CurrentProcessType, newFolder, true);
}
public void NewFolderRoot()
{
if (IsChanged)
{
var selection = DialogBox.ShowDialog(DialogButton.Yes | DialogButton.No | DialogButton.Cancel,
DialogType.CONFIRM, $"Recipe {CurrentRecipe.Name} is changed, do you want to save it?");
if (selection == DialogButton.Cancel)
return;
if (selection == DialogButton.Yes)
{
CurrentRecipe.Revisor = BaseApp.Instance.UserContext.LoginName;
CurrentRecipe.ReviseTime = DateTime.Now;
Save(CurrentRecipe, false);
}
}
var dialog = new InputFileNameDialogViewModel("Input New Folder Name");
dialog.FileName = "new folder";
var wm = new WindowManager();
var dialogReturn = wm.ShowDialog(dialog);
if (!dialogReturn.HasValue || !dialogReturn.Value)
return;
var name = dialog.FileName.Trim();
if (string.IsNullOrEmpty(name))
{
DialogBox.ShowWarning("Folder name should not be empty");
return;
}
if (IsExist(name, false))
{
DialogBox.ShowWarning($"Can not create folder {name}, Folder with the same name already exist.");
return;
}
if (name.Length > 200)
{
DialogBox.ShowWarning($"Can not create folder {name}, Folder name too long, should be less 200.");
return;
}
var prefix = ChamberType[ChamberTypeIndexSelection] + "\\" +
ProcessTypeFileList[ProcessTypeIndexSelection].ProcessType;
_recipeProvider.CreateRecipeFolder(prefix, name);
ReloadRecipeFileList(CurrentChamberType, CurrentProcessType, name, true);
}
public void DeleteFolder()
{
if (CurrentFileNode == null || CurrentFileNode.IsFile)
return;
if (CurrentFileNode.Files.Count > 0)
{
DialogBox.ShowWarning(
$"Can not delete non-empty folder, Remove the files or folders under \r\n{CurrentFileNode.FullPath}.");
return;
}
var selection = DialogBox.ShowDialog(DialogButton.Yes | DialogButton.No, DialogType.CONFIRM,
$"Are you sure you want to delete \r\n {CurrentFileNode.FullPath}?");
if (selection == DialogButton.No)
return;
var nextFocus = CurrentFileNode.Parent.FullPath;
var isFolder = true;
if (CurrentFileNode.Parent.Files.Count > 1)
{
for (var i = 0; i < CurrentFileNode.Parent.Files.Count; i++)
{
if (CurrentFileNode.Parent.Files[i] == CurrentFileNode)
{
if (i == 0)
{
nextFocus = CurrentFileNode.Parent.Files[i + 1].FullPath;
isFolder = !CurrentFileNode.Parent.Files[i + 1].IsFile;
}
else
{
nextFocus = CurrentFileNode.Parent.Files[i - 1].FullPath;
isFolder = !CurrentFileNode.Parent.Files[i - 1].IsFile;
}
}
}
}
_recipeProvider.DeleteRecipeFolder(CurrentFileNode.PrefixPath, CurrentFileNode.FullPath);
ReloadRecipeFileList(CurrentChamberType, CurrentProcessType, nextFocus, isFolder);
}
public void RenameFolder()
{
if (CurrentFileNode == null || CurrentFileNode.IsFile)
return;
var dialog = new InputFileNameDialogViewModel("Input New Folder Name");
dialog.FileName = CurrentFileNode.Name;
var wm = new WindowManager();
var dialogReturn = wm.ShowDialog(dialog);
if (!dialogReturn.HasValue || !dialogReturn.Value)
return;
var name = dialog.FileName.Trim();
if (string.IsNullOrEmpty(name))
return;
var newFolder = CurrentFileNode.FullPath.Substring(0, CurrentFileNode.FullPath.LastIndexOf("\\") + 1);
if (!string.IsNullOrEmpty(newFolder))
newFolder = newFolder + name;
else
newFolder = name;
if (newFolder == CurrentFileNode.FullPath)
return;
if (IsExist(newFolder, false))
{
DialogBox.ShowWarning($"Can not rename to {newFolder}, Folder with the same name already exist.");
return;
}
if (newFolder.Length > 200)
{
DialogBox.ShowWarning($"Can not create folder {newFolder}, Folder name too long, should be less 200.");
return;
}
_recipeProvider.RenameFolder(CurrentFileNode.PrefixPath, CurrentFileNode.FullPath, newFolder);
ReloadRecipeFileList(CurrentChamberType, CurrentProcessType, newFolder, true);
}
#endregion
#region recipe
public void NewRecipe()
{
if (IsChanged)
{
var selection = DialogBox.ShowDialog(DialogButton.Yes | DialogButton.No | DialogButton.Cancel,
DialogType.CONFIRM, $"Recipe {CurrentRecipe.Name} is changed, do you want to save it?");
if (selection == DialogButton.Cancel)
return;
if (selection == DialogButton.Yes)
{
CurrentRecipe.Revisor = BaseApp.Instance.UserContext.LoginName;
CurrentRecipe.ReviseTime = DateTime.Now;
Save(CurrentRecipe, false);
}
}
var dialog = new InputFileNameDialogViewModel("Input New Name");
dialog.FileName = "";
var wm = new WindowManager();
var dialogReturn = wm.ShowDialog(dialog);
if (!dialogReturn.HasValue || !dialogReturn.Value)
return;
var recipeName = dialog.FileName.Trim();
if (string.IsNullOrEmpty(dialog.FileName))
{
DialogBox.ShowWarning("Recipe file name should not be empty");
return;
}
var prefix = CurrentChamberType + "\\" + CurrentProcessType;
var processType = string.Empty;
if (CurrentFileNode != null)
{
var folder = CurrentFileNode.FullPath;
if (CurrentFileNode.IsFile)
{
folder = folder.Substring(0, folder.LastIndexOf("\\") + 1);
//if (!string.IsNullOrEmpty(folder))
// folder = folder;
}
else
{
folder = folder + "\\";
}
recipeName = folder + recipeName;
}
if (IsExist(recipeName, true))
{
DialogBox.ShowWarning($"Can not create {recipeName}, Recipe with the same name already exist.");
return;
}
if (recipeName.Length > 200)
{
DialogBox.ShowWarning($"Can not create folder {recipeName}, Folder name too long, should be less 200.");
return;
}
var recipe = new RecipeData(_recipeGasFlowCalculator);
recipe.Name = recipeName;
recipe.PrefixPath = prefix;
recipe.Creator = BaseApp.Instance.UserContext.LoginName;
recipe.CreateTime = DateTime.Now;
recipe.Revisor = BaseApp.Instance.UserContext.LoginName;
recipe.ReviseTime = DateTime.Now;
recipe.Description = string.Empty;
if (!Save(recipe, true))
return;
var types = prefix.Split('\\');
ReloadRecipeFileList(types[0], types[1], recipeName, false);
}
public void NewRecipeRoot()
{
if (IsChanged)
{
var selection = DialogBox.ShowDialog(DialogButton.Yes | DialogButton.No | DialogButton.Cancel,
DialogType.CONFIRM, $"Recipe {CurrentRecipe.Name} is changed, do you want to save it?");
if (selection == DialogButton.Cancel)
return;
if (selection == DialogButton.Yes)
{
CurrentRecipe.Revisor = BaseApp.Instance.UserContext.LoginName;
CurrentRecipe.ReviseTime = DateTime.Now;
Save(CurrentRecipe, false);
}
}
var dialog = new InputFileNameDialogViewModel("Input New Recipe Name");
dialog.FileName = "new recipe";
var wm = new WindowManager();
var dialogReturn = wm.ShowDialog(dialog);
if (!dialogReturn.HasValue || !dialogReturn.Value)
return;
var recipeName = dialog.FileName.Trim();
if (string.IsNullOrEmpty(dialog.FileName))
{
DialogBox.ShowWarning("Recipe file name should not be empty");
return;
}
if (IsExist(recipeName, true))
{
DialogBox.ShowWarning($"Can not create {recipeName}, Recipe with the same name already exist.");
return;
}
if (recipeName.Length > 200)
{
DialogBox.ShowWarning($"Can not create folder {recipeName}, Folder name too long, should be less 200.");
return;
}
var recipe = new RecipeData(_recipeGasFlowCalculator);
recipe.Name = recipeName;
recipe.PrefixPath = CurrentChamberType + "\\" + CurrentProcessType;
recipe.Creator = BaseApp.Instance.UserContext.LoginName;
recipe.CreateTime = DateTime.Now;
recipe.Revisor = BaseApp.Instance.UserContext.LoginName;
recipe.ReviseTime = DateTime.Now;
recipe.Description = string.Empty;
if (!Save(recipe, true))
return;
ReloadRecipeFileList(CurrentChamberType, CurrentProcessType, recipeName, false);
}
private void ReloadRecipeFileList(string chamberType, string 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 = $"{ChamberType[ChamberTypeIndexSelection]}\\{item.ProcessType}";
var recipes = _recipeProvider.GetXmlRecipeList(prefix);
item.FileListByProcessType =
RecipeSequenceTreeBuilder.BuildFileNode(prefix, selectedFile, selectionIsFolder, recipes)[0].Files;
item.FilterFileListByProcessType = item.FileListByProcessType;
item.InvokePropertyChanged();
}
private bool IsExist(string fullPath, bool isFile)
{
for (var i = 0; i < ProcessTypeFileList.Count; i++)
{
if (ProcessTypeFileList[i].ProcessType == CurrentProcessType)
{
if (ProcessTypeFileList[i].FileListByProcessType.Count == 0)
return false;
return FindFile(fullPath, ProcessTypeFileList[i].FileListByProcessType[0].Parent, isFile);
}
}
return true;
}
private bool FindFile(string path, FileNode root, bool isFile)
{
if (root.FullPath == path && !isFile)
{
return true;
}
foreach (var node in root.Files)
{
if (isFile && node.IsFile && node.FullPath == path)
return true;
if (!node.IsFile && FindFile(path, node, isFile))
return true;
}
return false;
}
public void SaveAsRecipe()
{
if (CurrentFileNode == null || !CurrentFileNode.IsFile)
return;
if (IsChanged)
{
var selection = DialogBox.ShowDialog(DialogButton.Yes | DialogButton.No | DialogButton.Cancel,
DialogType.CONFIRM, $"Recipe {CurrentRecipe.Name} is changed, do you want to save it?");
if (selection == DialogButton.Cancel)
return;
if (selection == DialogButton.Yes)
{
CurrentRecipe.Revisor = BaseApp.Instance.UserContext.LoginName;
CurrentRecipe.ReviseTime = DateTime.Now;
Save(CurrentRecipe, false);
}
}
var dialog = new InputFileNameDialogViewModel("Input New Recipe Name");
dialog.FileName = CurrentFileNode.Name;
var wm = new WindowManager();
var dialogReturn = wm.ShowDialog(dialog);
if (!dialogReturn.HasValue || !dialogReturn.Value)
return;
var recipeName = dialog.FileName.Trim();
if (string.IsNullOrEmpty(dialog.FileName))
{
DialogBox.ShowWarning("Recipe file name should not be empty");
return;
}
var prefix = CurrentChamberType + "\\" + CurrentProcessType;
var processType = string.Empty;
var folder = CurrentFileNode.FullPath;
if (CurrentFileNode.IsFile)
{
folder = folder.Substring(0, folder.LastIndexOf("\\") + 1);
}
if (!string.IsNullOrEmpty(folder))
recipeName = folder + "\\" + recipeName;
if (CurrentFileNode.FullPath == recipeName)
return;
if (IsExist(recipeName, true))
{
DialogBox.ShowWarning($"Can not copy to {recipeName}, Recipe with the same name already exist.");
return;
}
if (recipeName.Length > 200)
{
DialogBox.ShowWarning($"Can not create folder {recipeName}, Folder name too long, should be less 200.");
return;
}
CurrentRecipe.Creator = BaseApp.Instance.UserContext.LoginName;
CurrentRecipe.CreateTime = DateTime.Now;
CurrentRecipe.Revisor = BaseApp.Instance.UserContext.LoginName;
CurrentRecipe.ReviseTime = DateTime.Now;
CurrentRecipe.Description = CurrentRecipe.Description + ". Renamed from " + CurrentFileNode.Name;
_recipeProvider.SaveAsRecipe(prefix, recipeName, CurrentRecipe.GetXmlString());
ReloadRecipeFileList(CurrentChamberType, CurrentProcessType, recipeName, false);
}
public void RenameRecipe()
{
if (CurrentFileNode == null || !CurrentFileNode.IsFile)
return;
if (IsChanged)
{
var selection = DialogBox.ShowDialog(DialogButton.Yes | DialogButton.No | DialogButton.Cancel,
DialogType.CONFIRM, $"Recipe {CurrentRecipe.Name} is changed, do you want to save it?");
if (selection == DialogButton.Cancel)
return;
if (selection == DialogButton.Yes)
{
CurrentRecipe.Revisor = BaseApp.Instance.UserContext.LoginName;
CurrentRecipe.ReviseTime = DateTime.Now;
Save(CurrentRecipe, false);
}
}
var dialog = new InputFileNameDialogViewModel("Input New Recipe Name");
dialog.FileName = CurrentFileNode.Name;
var wm = new WindowManager();
var dialogReturn = wm.ShowDialog(dialog);
if (!dialogReturn.HasValue || !dialogReturn.Value)
return;
var recipeName = dialog.FileName.Trim();
if (string.IsNullOrEmpty(dialog.FileName))
{
DialogBox.ShowWarning("Recipe file name should not be empty");
return;
}
var prefix = CurrentChamberType + "\\" + CurrentProcessType;
var processType = string.Empty;
var newName = CurrentFileNode.FullPath.Substring(0, CurrentFileNode.FullPath.LastIndexOf("\\") + 1);
if (!string.IsNullOrEmpty(newName))
newName = newName + recipeName;
else
newName = recipeName;
if (newName == CurrentFileNode.FullPath)
return;
if (IsExist(newName, true))
{
DialogBox.ShowWarning($"Can not rename to {newName}, Recipe with the same name already exist.");
return;
}
if (newName.Length > 200)
{
DialogBox.ShowWarning($"Can not create folder {newName}, Folder name too long, should be less 200.");
return;
}
_recipeProvider.RenameRecipe(prefix, CurrentFileNode.FullPath, newName);
ReloadRecipeFileList(CurrentChamberType, CurrentProcessType, newName, false);
}
public void DeleteRecipe()
{
if (CurrentFileNode == null || !CurrentFileNode.IsFile)
return;
var selection = DialogBox.ShowDialog(DialogButton.Yes | DialogButton.No, DialogType.CONFIRM,
$"Are you sure you want to delete \r\n {CurrentFileNode.FullPath}?");
if (selection == DialogButton.No)
return;
var nextFocus = CurrentFileNode.Parent.FullPath;
var isFolder = true;
if (CurrentFileNode.Parent.Files.Count > 1)
{
for (var i = 0; i < CurrentFileNode.Parent.Files.Count; i++)
{
if (CurrentFileNode.Parent.Files[i] == CurrentFileNode)
{
if (i == 0)
{
nextFocus = CurrentFileNode.Parent.Files[i + 1].FullPath;
isFolder = !CurrentFileNode.Parent.Files[i + 1].IsFile;
}
else
{
nextFocus = CurrentFileNode.Parent.Files[i - 1].FullPath;
isFolder = !CurrentFileNode.Parent.Files[i - 1].IsFile;
}
}
}
}
_recipeProvider.DeleteRecipe(CurrentFileNode.PrefixPath, CurrentFileNode.FullPath);
CurrentRecipe.DeleteAccessibleWhiteList();
ReloadRecipeFileList(CurrentChamberType, CurrentProcessType, nextFocus, isFolder);
}
public void RefreshRecipe()
{
ReloadRecipeFileList(CurrentChamberType, CurrentProcessType, "", false);
}
public async void ReloadRecipe()
{
if (_editMode == EditMode.Normal || _editMode == EditMode.Edit)
{
if (CurrentRecipe == null)
return;
if (CurrentRecipe.IsChanged)
{
var ret = DialogBox.ShowDialog(DialogButton.Yes | DialogButton.No,
DialogType.CONFIRM,
$"Recipe {CurrentRecipe.Name} has changed, discard and reload?");
if (ret == DialogButton.No)
return;
}
await LoadRecipe(CurrentRecipe.PrefixPath, CurrentRecipe.Name);
CurrentRecipe.Validate();
UpdateView();
foreach (var step in CurrentRecipe.Steps)
step.Save();
}
}
public void SaveToAll()
{
CurrentRecipe.Validate();
if (!CurrentRecipe.IsCompatibleWithCurrentFormat)
{
DialogBox.ShowWarning($"Saving failed, {CurrentRecipe.Name} is not a valid recipe file");
return;
}
//var selection = DialogBox.ShowDialog(DialogButton.Yes | DialogButton.No,
// DialogType.CONFIRM,
// $"Do you want to save to all chambers? \r\n This will replace all the other chambers recipe content");
//if (selection == DialogButton.No)
// return;
CurrentRecipe.SaveTo(Chambers.ToArray());
Save(CurrentRecipe, false);
}
public void SaveTo()
{
CurrentRecipe.Validate();
if (!CurrentRecipe.IsCompatibleWithCurrentFormat)
{
DialogBox.ShowWarning($"Saving failed, {CurrentRecipe.Name} is not a valid recipe file.");
return;
}
var dialog =
new SaveToDialogViewModel("Select the chamber to copy to", SelectedChamber, Chambers.ToList());
var wm = new WindowManager();
var dialogReturn = wm.ShowDialog(dialog);
if (!dialogReturn.HasValue || !dialogReturn.Value)
return;
var chambers = new List<string>();
foreach (var dialogChamber in dialog.Chambers)
{
if (dialogChamber.IsEnabled && dialogChamber.IsChecked)
chambers.Add(dialogChamber.Name);
}
if (chambers.Count == 0)
return;
CurrentRecipe.SaveTo(chambers.ToArray());
Save(CurrentRecipe, false);
}
/// <summary>
/// 导入Recipe。
/// </summary>
public void ImportRecipe()
{
try
{
IsLoading = true;
// 选择文件
var openFileDialog = new OpenFileDialog()
{
Filter = "Sic Recipe File|*.srp",
Title = "Import Recipe",
DefaultExt = ".srp",
Multiselect = true
};
var prefix = ProcessTypeFileItem.GetProcessFilesPrefix(ProcessTypeFileItem.ProcessFileTypes.Process);
if (openFileDialog.ShowDialog() != true)
return;
#region
var dialog = new RecipeSelectDialogViewModel(true, true, ProcessTypeFileItem.ProcessFileTypes.Process)
{
DisplayName = "Select Folder to Import ..."
};
var wm = new WindowManager();
var bret = wm.ShowDialog(dialog);
#endregion
if (bret != true)
return;
var recipeFolderToImport
= dialog.DialogResult;
// 从文件夹名移除Prefix
recipeFolderToImport = recipeFolderToImport.Replace(prefix + "\\", "");
var fns = openFileDialog.FileNames;
// 是否覆盖所有Recipe该选项通过Overwrite对话框给值。
var isOverwriteAll = false;
foreach (var fn in fns)
{
try
{
var recipePath = $"{recipeFolderToImport}\\{Path.GetFileNameWithoutExtension(fn)}";
// 检查Recipe是否已存在
if (IsExist(recipePath.TrimStart('\\'), true) && !isOverwriteAll)
{
var ret = DialogBox.ShowDialog(
DialogButton.Yes | DialogButton.YesToAll | DialogButton.Cancel,
DialogType.WARNING,
$"{recipePath} has existed, overwrite?\r\n\r\nATTENTION: Press 'Yes To All' to overwrite all files.");
if (ret == DialogButton.Cancel)
return;
if (ret == DialogButton.YesToAll)
isOverwriteAll = true;
}
RecipeData.ImportRecipe(fn, out var xmlContent);
// 保存文件。
_recipeProvider.WriteRecipeFile(prefix, recipePath, xmlContent);
}
catch (Exception ex)
{
DialogBox.ShowError($"Unable to import {fn}, {ex}");
}
}
// 刷新Recipe列表
ReloadRecipeFileList("Sic", "Process", "", false);
}
finally
{
IsLoading = false;
}
}
/// <summary>
/// 到处Recipe。
/// </summary>
public void ExportRecipe()
{
if (CurrentRecipe == null)
{
MessageBox.Show("No recipe selected.", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
return;
}
if (string.IsNullOrEmpty(CurrentRecipe.Name))
{
MessageBox.Show("No recipe loaded.", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
return;
}
// 选择保存路径和文件名
var dialog = new SaveFileDialog
{
Filter = "Sic Recipe File|*.srp",
Title = "Export Recipe",
DefaultExt = ".srp",
FileName = CurrentRecipe.Name.Split('\\').Last()
};
var ret = dialog.ShowDialog();
if (!ret.HasValue || ret == false)
return;
// 保存文件
try
{
CurrentRecipe.ExportRecipe(dialog.FileName);
DialogBox.ShowInfo($"Export done! {dialog.FileName}");
}
catch (Exception ex)
{
DialogBox.ShowError($"Unable to export recipe, {ex}");
}
}
#endregion
#region Steps
public void SaveRecipe()
{
/*if (Save(CurrentRecipe, false))
MessageBox.Show($"The recipe has been saved successfully!", "Succeeded", MessageBoxButton.OK,
MessageBoxImage.Information);*/
Save(CurrentRecipe, false);
}
public void PopSetting(string controlName, Param paramData)
{
var stepNum = Convert.ToInt32(((StepParam)paramData.Parent[1]).Value);
var dialog = new PublicPopSettingDialogViewModel();
dialog.DisplayName = paramData.DisplayName;
var step = CurrentRecipe.PopSettingSteps[controlName][stepNum - 1];
var controlParameters = new RecipeStep(null);
var brandParameters = new ObservableCollection<BandParam>();
foreach (var item in step)
{
if (item.Name.Contains("Band"))
{
var name = item.Name.Replace("Wavelength", "").Replace("Bandwidth", "");
var displayName = item.DisplayName.Replace("Wavelength", "").Replace("Bandwidth", "");
if (brandParameters.All(x => x.Name != name))
{
brandParameters.Add(new BandParam()
{
Name = name,
DisplayName = displayName
});
}
if (item.Name.Contains("Wavelength"))
{
brandParameters.First(x => x.Name == name).WavelengthDoubleParam = item;
}
else if (item.Name.Contains("Bandwidth"))
{
brandParameters.First(x => x.Name == name).BandwidthDoubleParam = item;
}
}
else
controlParameters.Add(item);
}
dialog.Parameters = step;
dialog.ControlParameters = controlParameters;
dialog.BandParameters = brandParameters;
var wm = new WindowManager();
var bret = wm.ShowDialog(dialog);
if (bret == true)
{
CurrentRecipe.PopSettingSteps[controlName][stepNum - 1] = dialog.Parameters;
}
}
private bool Save(RecipeData recipe, bool createNew)
{
if (string.IsNullOrEmpty(recipe.Name))
{
MessageBox.Show("Recipe name can't be empty", "Error",
MessageBoxButton.OK, MessageBoxImage.Error);
return false;
}
if (CurrentRecipe.Validate() == false)
{
var mbr = DialogBox.ShowDialog(
DialogButton.Yes | DialogButton.No,
DialogType.WARNING,
$"Are you sure to continue to save with errors?\r\n\r\n" +
$"{CurrentRecipe.ValidationErrorSummary}");
if (mbr == DialogButton.No)
return false;
}
var result = false;
recipe.Revisor = BaseApp.Instance.UserContext.LoginName;
recipe.ReviseTime = DateTime.Now;
result = _recipeProvider.WriteRecipeFile(recipe.PrefixPath, recipe.Name, recipe.GetXmlString());
if (result)
{
recipe.MarkAsSaved();
_editMode = EditMode.Normal;
UpdateView();
}
else
{
MessageBox.Show("Save failed!");
}
return result;
}
public void AddStep()
{
CurrentRecipe.AddStep();
if (_editMode != EditMode.New && _editMode != EditMode.ReName)
_editMode = EditMode.Edit;
UpdateView();
}
public void InsertStepToLeft()
{
if (CurrentRecipe == null)
return;
if (CurrentRecipe.Steps.SelectedSteps.Count != 1)
return;
var step = CurrentRecipe.Steps.SelectedSteps[0];
CurrentRecipe.InsertToLeft(step);
if (_editMode != EditMode.New && _editMode != EditMode.ReName)
_editMode = EditMode.Edit;
UpdateView();
}
public void InsertStepToRight()
{
if (CurrentRecipe == null)
return;
if(CurrentRecipe.Steps.SelectedSteps.Count != 1)
return;
var step = CurrentRecipe.Steps.SelectedSteps[0];
CurrentRecipe.InsertToRight(step);
if (_editMode != EditMode.New && _editMode != EditMode.ReName)
_editMode = EditMode.Edit;
UpdateView();
}
/// <summary>
/// 拷贝所有选中的Steps。
/// </summary>
public void CopyStep()
{
CurrentRecipe?.CopySteps();
}
/// <summary>
/// 在左侧粘贴Step。
/// </summary>
public async void PasteStepToLeft()
{
await Paste(true);
}
/// <summary>
/// 在右侧粘贴Step。
/// </summary>
public async void PasteStepToRight()
{
await Paste(false);
}
/// <summary>
/// 粘贴Step。
/// <para>注意async Task 标记的方法不能直接应用于Caliburn.Message.Attach 方法。</para>
/// </summary>
/// <param name="isToLeft">是否粘贴到当前选中的Step的左侧。</param>
/// <returns></returns>
private async Task Paste(bool isToLeft)
{
if (CurrentRecipe == null)
return;
await CurrentRecipe.Paste(isToLeft);
if (_editMode != EditMode.New && _editMode != EditMode.ReName)
_editMode = EditMode.Edit;
UpdateView();
}
/// <summary>
/// 在左侧复制Step。
/// </summary>
public async void DuplicateStepToLeft()
{
await Duplicate(true);
}
/// <summary>
/// 在右侧复制Step。
/// </summary>
public async void DuplicateStepToRight()
{
await Duplicate(false);
}
/// <summary>
/// 复制选中的Step。
/// </summary>
/// <param name="isToLeft">是否复制到选中的Step的左侧。</param>
private async Task Duplicate(bool isToLeft)
{
if (SelectedRecipeSteps == null || SelectedRecipeSteps.Count != 1)
return;
CopyStep();
await Paste(isToLeft);
}
/// <summary>
/// 删除步骤。
/// </summary>
public void DeleteStep()
{
if (CurrentRecipe == null)
return;
CurrentRecipe.DeleteSteps();
if (_editMode != EditMode.New && _editMode != EditMode.ReName)
_editMode = EditMode.Edit;
UpdateView();
}
private void CreateValidationDetailWindow()
{
_winValidationInfo = new RecipeEditorValidationDetailWindow(
CurrentRecipe.ValidationErrorInfo,
_lastPosOfValidationWin);
_winValidationInfo.DataContext = this;
_winValidationInfo.Closed += (sender, args) =>
{
_lastPosOfValidationWin = new Point(_winValidationInfo.Left, _winValidationInfo.Top);
_currentRecipe.Steps.ResetHighlight();
};
}
public void ShowValidationDetailWindow()
{
CurrentRecipe.Validate();
if (Application.Current.Windows.OfType<RecipeEditorValidationDetailWindow>().Any())
_winValidationInfo.Activate();
else
{
CreateValidationDetailWindow();
_winValidationInfo.Show();
}
}
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;
}
public void TreeRightMouseDown(MouseButtonEventArgs e)
{
var item = GetParentObjectEx<TreeViewItem>(e.OriginalSource as DependencyObject) as TreeViewItem;
if (item != null)
{
item.Focus();
}
}
/// <summary>
/// 滚动到并高亮指定的参数单元格。
/// </summary>
/// <param name="param"></param>
public void FocusToParam(IParam param)
{
((RecipeEditorView)View).dgCustom.FocusToParam(param);
}
#endregion
#region Cell-Access-Perm-Edit Mode
public void SwitchCellAccessPermEditMode()
{
var mode = CurrentRecipe.IsAccessibleWhitelistEditMode;
if (mode)
{
CurrentRecipe.LeaveAccessibleWhitelistEditMode();
_editMode = EditMode.Normal;
}
else
{
if (!CurrentRecipe.EnterAccessibleWhitelistEditMode(out var reason))
{
DialogBox.ShowError($"Unable to enter cell accessible whitelist edit mode, {reason}");
}
_editMode = EditMode.EditWhitelist;
}
UpdateView();
}
#endregion
private void ClearData()
{
_editMode = EditMode.None;
CurrentRecipe.Clear();
CurrentRecipe.Name = string.Empty;
CurrentRecipe.Description = string.Empty;
}
/// <summary>
/// 根据配置获取Cascade方式呈现Recipe的Dispatcher或返回null。
/// </summary>
/// <returns></returns>
private static Dispatcher GetLoadingDispatcher()
{
// 判断Step呈现方式
var isCascadeLoading = (bool)QueryDataClient.Instance.Service.GetConfig("System.RecipeCascadeLoading");
var dispatcher = isCascadeLoading ? Dispatcher.CurrentDispatcher : null;
return dispatcher;
}
private async Task LoadRecipe(string prefixPath, string recipeName)
{
IsLoading = true;
CurrentRecipe.Clear();
CurrentRecipe.Steps.Save(); // 重置为已保存状态
ErrorsCount = 0;
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;
}
await CurrentRecipe.LoadFile(prefixPath, recipeName, recipeContent, SelectedChamber,
GetLoadingDispatcher());
_editMode = EditMode.Normal;
IsLoading = false;
}
private void UpdateView()
{
var isFileSelected = CurrentFileNode != null && CurrentFileNode.IsFile;
EnableImportExport = true;
EnableLeftTabPanel = true;
EnableFilterTreeList = true;
EnableRefreshRecipeList = true;
EnableNew = true;
EnableCellPermButton = isFileSelected;
EnableReload = isFileSelected;
EnableNew = isFileSelected;
EnableReName = isFileSelected;
EnableCopy = isFileSelected;
EnableDelete = isFileSelected;
EnableSave = isFileSelected;
EnableStep = isFileSelected;
EnableSaveTo = isFileSelected;
EnableSaveToAll = isFileSelected;
if (_editMode == EditMode.None)
{
EnableNew = true;
EnableReName = false;
EnableCopy = false;
EnableDelete = false;
EnableStep = false;
EnableSave = false;
EnableReload = true;
}
//else if (CurrentRecipe.IsAccessibleWhitelistEditMode)
else if (_editMode == EditMode.EditWhitelist)
{
EnableNew = false;
EnableReName = false;
EnableCopy = false;
EnableDelete = false;
EnableStep = false;
EnableSave = false;
EnableReload = false;
EnableSaveTo = false;
EnableSaveToAll = false;
EnableLeftTabPanel = false;
EnableFilterTreeList = false;
EnableRefreshRecipeList = false;
}
NotifyOfPropertyChange(nameof(EnableNew));
NotifyOfPropertyChange(nameof(EnableReName));
NotifyOfPropertyChange(nameof(EnableCopy));
NotifyOfPropertyChange(nameof(EnableDelete));
NotifyOfPropertyChange(nameof(EnableSave));
NotifyOfPropertyChange(nameof(EnableImportExport));
NotifyOfPropertyChange(nameof(EnableStep));
NotifyOfPropertyChange(nameof(EnableSaveTo));
NotifyOfPropertyChange(nameof(EnableSaveToAll));
NotifyOfPropertyChange(nameof(EnableLeftTabPanel));
NotifyOfPropertyChange(nameof(EnableFilterTreeList));
NotifyOfPropertyChange(nameof(EnableRefreshRecipeList));
NotifyOfPropertyChange(nameof(EnableCellPermButton));
NotifyOfPropertyChange(nameof(CurrentRecipe));
}
private string _currentCriteria = string.Empty;
public string CurrentCriteria
{
get => _currentCriteria;
set
{
if (value == _currentCriteria)
return;
_currentCriteria = value;
NotifyOfPropertyChange(nameof(CurrentCriteria));
ApplyFilter();
}
}
private void ApplyFilter()
{
ProcessTypeFileList[ProcessTypeIndexSelection].FilterFileListByProcessType =
new ObservableCollection<FileNode>(ProcessTypeFileList[ProcessTypeIndexSelection].FileListByProcessType
.Where(d => d.Name.IndexOf(CurrentCriteria, StringComparison.OrdinalIgnoreCase) >= 0));
}
public void ClearFilter()
{
CurrentCriteria = "";
}
/// <summary>
/// 当主窗口状态Login状态发生变化时处理当前窗口状态。
/// </summary>
/// <param name="message"></param>
public void Handle(UserMode message)
{
switch (message)
{
case UserMode.None:
break;
case UserMode.Normal:
break;
case UserMode.Lock:
case UserMode.Logoff:
case UserMode.Exit:
case UserMode.Shutdown:
case UserMode.Breakdown:
_winValidationInfo?.Close();
break;
default:
// ignore
break;
}
}
}
}