private OutputParams ProcessWindow(InputParams inputs, ref bool SkillIsPausable) { //This is the implementation of the "first" Method provided to the Skills (the "process method") //Remark : this method is quite complicated because it has to deal with windows and HMI, so it requires access to the UI thread. //Skill is currently not pausable SkillIsPausable = false; InputsForFakeWindow value; //First cast the inputs try { value = (InputsForFakeWindow)inputs.Values; } catch (System.InvalidCastException ex) { //If you can't cast the inputs, then there is a special error code for this return(new OutputParams(Skill.ERROR_VALUES_PROCESS_INVALID, null)); } //Then execute the code try { //Lets say skill is productive (even if it is not that true) inputs.IsProductive = true; //Update remaining time value.RemainingTimeMs = value.ExecutionTimeMs; inputs.RemainingDuration = (UInt32)value.RemainingTimeMs; //First Ask (the UI thread) to pop a new window InputsForFakeWindow argsFirst = new InputsForFakeWindow() { StartANewWindow = true, RemainingTimeMs = value.RemainingTimeMs, CloseTheWindow = false, ExecutionTimeMs = value.ExecutionTimeMs, NameOfSkill = value.NameOfSkill, Status = "EXECUTING" }; OnDisplayOnSkillWindow(argsFirst); //Then perform a loop, each second, after each second int subdivision = value.ExecutionTimeMs / 1000; //You can subscribe to the "Pausing" event, if you want to have notification of the Pausing. //The SEE mother class also subscribed to that event, to notify the OPC-UA about the state change inputs.Pausing += inputs_Pausing; if (subdivision >= 1) { //SKill is now pausable => turn SkillIsPausable to true SkillIsPausable = true; //there are more than 1 second of execution time for (int i = 0; i < subdivision; i++) { double remainingTime = (((double)value.ExecutionTimeMs) - i * 1000.0); inputs.StopOrPauseIfRequired(); //You can use this line of code to block or stop the execution, if a Stop or pause was required InputsForFakeWindow argsLoop = new InputsForFakeWindow() { StartANewWindow = false, RemainingTimeMs = (int)remainingTime, CloseTheWindow = false, ExecutionTimeMs = value.ExecutionTimeMs, NameOfSkill = value.NameOfSkill, Status = "EXECUTING" }; OnDisplayOnSkillWindow(argsLoop); //Update remaining time before sleeping inputs.RemainingDuration = (UInt32)remainingTime; //Sleep 1s System.Threading.Thread.Sleep(1000); inputs.StopOrPauseIfRequired(); //You can use this line of code to block or stop the execution, if a Stop or pause was required } double LastValue = (((double)value.ExecutionTimeMs) - subdivision * 1000.0); inputs.StopOrPauseIfRequired(); //You can use this line of code to block or stop the execution, if a Stop or pause was required InputsForFakeWindow argsLastLoop = new InputsForFakeWindow() { StartANewWindow = false, RemainingTimeMs = (int)LastValue, CloseTheWindow = false, ExecutionTimeMs = value.ExecutionTimeMs, NameOfSkill = value.NameOfSkill, Status = "EXECUTING" }; OnDisplayOnSkillWindow(argsLastLoop); //SKill is no longer pausable => turn SkillIsPausable to false SkillIsPausable = false; inputs.StopOrPauseIfRequired(); inputs.Pausing -= inputs_Pausing; //Update remaining time before sleeping inputs.RemainingDuration = (UInt32)LastValue; //Sleep the remaining time System.Threading.Thread.Sleep((int)LastValue); } else { //there are Less than 1 second of execution time //SKill is now pausable => turn SkillIsPausable to true SkillIsPausable = true; inputs.StopOrPauseIfRequired();//You can use this line of code to block or stop the execution, if a Stop or pause was required InputsForFakeWindow argsLess1 = new InputsForFakeWindow() { StartANewWindow = false, RemainingTimeMs = value.ExecutionTimeMs, CloseTheWindow = false, ExecutionTimeMs = value.ExecutionTimeMs, NameOfSkill = value.NameOfSkill, Status = "EXECUTING" }; OnDisplayOnSkillWindow(argsLess1); //SKill is no longer pausable => turn SkillIsPausable to false SkillIsPausable = false; inputs.StopOrPauseIfRequired(); inputs.Pausing -= inputs_Pausing; //Update remaining time before sleeping inputs.RemainingDuration = (UInt32)value.ExecutionTimeMs; //Sleep System.Threading.Thread.Sleep(value.ExecutionTimeMs); } //Display that skill execution is about to finish InputsForFakeWindow argsAboutToClose = new InputsForFakeWindow() { StartANewWindow = false, RemainingTimeMs = 0, CloseTheWindow = false, ExecutionTimeMs = value.ExecutionTimeMs, NameOfSkill = value.NameOfSkill, Status = "ABOUT TO FINISH : ANSWER QUESTION FIRST" }; OnDisplayOnSkillWindow(argsAboutToClose); //Then finnaly, you have to prepare the "OutputParams". //These "OutputParams" then returned in the overriden method GetOutputsOfExecutedSkill //So take care of what you are providing OutputParams outp = InterativeAskReturnedCode(value, ERROR_DURING_EXECUTION); InputsForFakeWindow argsClose = new InputsForFakeWindow() { StartANewWindow = false, RemainingTimeMs = 0, CloseTheWindow = false, ExecutionTimeMs = value.ExecutionTimeMs, NameOfSkill = value.NameOfSkill, Status = "FINISHED" }; OnDisplayOnSkillWindow(argsClose); //Check if you have an alternative post condition if (value.AlternativePostConditionAvailable && outp.ReturnCode == 0) { //Ask user if he wants to go to alternative post condition System.Windows.MessageBoxResult res = System.Windows.MessageBox.Show("Do you want to go to alternative post condition?", "Alternative", System.Windows.MessageBoxButton.YesNo); switch (res) { case System.Windows.MessageBoxResult.Yes: outp.GoToAlternativePostCondition = true; break; default: outp.GoToAlternativePostCondition = false; break; } } //you don't have to Update remaining time to "0" => it is already done in the SEE mother class return(outp); } catch (OperationCanceledException ex) { //External element asked for "emergency stop". //The "Closing Method" will be called (second method passed to the constructor of the skill) //You can do some memory freeing here. //And then, rethrow the exception throw; } catch (Exception ex) { return(new OutputParams(ERROR_DURING_EXECUTION, null, false, new SkillProException(ex.Message, ex))); } }