I have a couple of .exe files that I run as follows :
public void RunCalculator(Calculator calculator)
{
var query = Path.Combine(EpiPath, calculator.ExeName + ".exe");
if (File.Exists(Path.Combine(EpiPath, "ffs.exe")))
{
var p = new Process();
p.StartInfo.FileName = query;
p.StartInfo.WorkingDirectory = EpiPath;
p.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
p.StartInfo.UseShellExecute = false;
p.StartInfo.CreateNoWindow = true;
p.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
p.StartInfo.Arguments = String.Join(" ", calculator.Arguments);
p.Start();
p.WaitForExit();
}
else throw new InvalidOperationException();
}
This code works, but there's still some flickering being caused from the exe-s being ran multiple times. Is there ANY way to remove the flickering because it's really annoying for the users to experience it since it happens for a few second (there are quite a few exe's being ran).
I tried using a task to do it on a different thread but since each exe depends on the work on the previous ( it writes to a file ) I get an IO exception.
It also seems that the exe files only work if their ProcessPriority is set to Realtime.
Edit:
Afew more details about what I tried recently : As @Jack Hughes suggested I tried using a background worker to solve this problem :
Here's what I did :
I call RunWorkerAsync() function on the background worker which on it's turn calls the RunCalculator function for each of the calculators in order.The flickering still persists.
Edit2 : I have created a detailed repository which contains my way of running the exe files, the exe files and the ProcessHelper class which Old Fox suggested. You can find instructions on how to use the repository inside the README file.
Link to repository : https://github.com/interdrift/epiwin-flick
I had the same "flickering" problem as you describe when I created an ActiveX
extension using C#. The extension had to start an hidden Console Application
, however every time I started the app the console appeared for a few ms.
To solve this I tried many things such as: taking the source code of Process
class and debug it, UAC
check, VM
's vs Real machines and etc.
The solution I found was to use Win32Api
. The following snippet starts a new process without the "flickering":
public class ProcessHelper
{
public const Int32 USE_STD_HANDLES = 0x00000100;
public const Int32 STD_OUTPUT_HANDLE = -11;
public const Int32 STD_ERROR_HANDLE = -12;
//this flag instructs StartProcessWithLogonW to consider the value StartupInfo.showWindow when creating the process
public const Int32 STARTF_USESHOWWINDOW = 0x00000001;
public static ProcessStartResult StartProcess(string exe,
string[] args = null,
bool isHidden = false,
bool waitForExit = false,
uint waitTimeout = 0)
{
string command;
var startupInfo = CreateStartupInfo(exe, args, isHidden, out command);
ProcessInformation processInfo;
var processSecAttributes = new SecurityAttributes();
processSecAttributes.Length = Marshal.SizeOf(processSecAttributes);
var threadSecAttributes = new SecurityAttributes();
threadSecAttributes.Length = Marshal.SizeOf(threadSecAttributes);
CreationFlags creationFlags = 0;
if (isHidden)
{
creationFlags = CreationFlags.CreateNoWindow;
}
var started = Win32Api.CreateProcess(exe,
command,
ref processSecAttributes,
ref threadSecAttributes,
false,
Convert.ToInt32(creationFlags),
IntPtr.Zero,
null,
ref startupInfo,
out processInfo);
var result = CreateProcessStartResult(waitForExit, waitTimeout, processInfo, started);
return result;
}
private static StartupInfo CreateStartupInfo(string exe, string[] args, bool isHidden, out string command)
{
var startupInfo = new StartupInfo();
startupInfo.Flags &= USE_STD_HANDLES;
startupInfo.StdOutput = (IntPtr) STD_OUTPUT_HANDLE;
startupInfo.StdError = (IntPtr) STD_ERROR_HANDLE;
if (isHidden)
{
startupInfo.ShowWindow = 0;
startupInfo.Flags = STARTF_USESHOWWINDOW;
}
var argsWithExeName = new string[args.Length + 1];
argsWithExeName[0] = exe;
args.CopyTo(argsWithExeName, 1);
var argsString = ToCommandLineArgsString(argsWithExeName);
command = argsString;
return startupInfo;
}
private static string ToCommandLineArgsString(Array array)
{
var argumentsBuilder = new StringBuilder();
foreach (var item in array)
{
if (item != null)
{
var escapedArgument = item.ToString().Replace("\"", "\"\"");
argumentsBuilder.AppendFormat("\"{0}\" ", escapedArgument);
}
}
return argumentsBuilder.ToString();
}
private static ProcessStartResult CreateProcessStartResult(bool waitForExit, uint waitTimeout,
ProcessInformation processInfo, bool started)
{
uint exitCode = 0;
var hasExited = false;
if (started && waitForExit)
{
var waitResult = Win32Api.WaitForSingleObject(processInfo.Process, waitTimeout);
if (waitResult == WaitForSingleObjectResult.WAIT_OBJECT_0)
{
Win32Api.GetExitCodeProcess(processInfo.Process, ref exitCode);
hasExited = true;
}
}
var result = new ProcessStartResult()
{
ExitCode = (int) exitCode,
Started = started,
HasExited = hasExited
};
return result;
}
}
[Flags]
public enum CreationFlags
{
CreateSuspended = 0x00000004,
CreateNewConsole = 0x00000010,
CreateNewProcessGroup = 0x00000200,
CreateNoWindow = 0x08000000,
CreateUnicodeEnvironment = 0x00000400,
CreateSeparateWowVdm = 0x00000800,
CreateDefaultErrorMode = 0x04000000,
}
public struct ProcessInformation
{
public IntPtr Process { get; set; }
public IntPtr Thread { get; set; }
public int ProcessId { get; set; }
public int ThreadId { get; set; }
}
public class ProcessStartResult
{
public bool Started { get; set; }
public int ExitCode { get; set; }
public bool HasExited { get; set; }
public Exception Error { get; set; }
}
[StructLayout(LayoutKind.Sequential)]
public struct SecurityAttributes
{
public int Length;
public IntPtr SecurityDescriptor;
public int InheritHandle;
}
public struct StartupInfo
{
public int Cb;
public String Reserved;
public String Desktop;
public String Title;
public int X;
public int Y;
public int XSize;
public int YSize;
public int XCountChars;
public int YCountChars;
public int FillAttribute;
public int Flags;
public UInt16 ShowWindow;
public UInt16 Reserved2;
public byte Reserved3;
public IntPtr StdInput;
public IntPtr StdOutput;
public IntPtr StdError;
}
public static class WaitForSingleObjectResult
{
/// <summary>
/// The specified object is a mutex object that was not released by the thread that owned the mutex
/// object before the owning thread terminated. Ownership of the mutex object is granted to the
/// calling thread and the mutex state is set to nonsignaled
/// </summary>
public const UInt32 WAIT_ABANDONED = 0x00000080;
/// <summary>
/// The state of the specified object is signaled.
/// </summary>
public const UInt32 WAIT_OBJECT_0 = 0x00000000;
/// <summary>
/// The time-out interval elapsed, and the object's state is nonsignaled.
/// </summary>
public const UInt32 WAIT_TIMEOUT = 0x00000102;
}
public class Win32Api
{
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool GetExitCodeProcess(IntPtr process, ref UInt32 exitCode);
[DllImport("Kernel32.dll", SetLastError = true)]
public static extern UInt32 WaitForSingleObject(IntPtr handle, UInt32 milliseconds);
[DllImport("kernel32.dll")]
public static extern bool CreateProcess
(string lpApplicationName,
string lpCommandLine,
ref SecurityAttributes lpProcessAttributes,
ref SecurityAttributes lpThreadAttributes,
bool bInheritHandles,
Int32 dwCreationFlags,
IntPtr lpEnvironment,
string lpCurrentDirectory,
[In] ref StartupInfo lpStartupInfo,
out ProcessInformation lpProcessInformation);
}
Edit:
I tried the above code with startupInfo.ShowWindow = 7
(which should launch the application without stealing the focus), some of the apps still stealing the focus, therefore I deduced that some of them use a kind of Bring to front method
....
I played a little bit with a your code in a WPF
window, then I found that if the UI
is in Sleep
the app doesn't loss the focus:
private void Button_Click(object sender, RoutedEventArgs e)
{
Task.Factory.StartNew(() =>
{
//Provide path to epi
string epiPath = @"c:/EPISUITE41";
Level3ntCalculator cl = new Level3ntCalculator();
var runner = new Calculators.Epi.Runners.ProcessRunner(epiPath);
runner.WriteXSmilesFiles("CCCCC1CCCCCCC1");
cl.Calculate(runner);
});
Thread.Sleep(2000);
}
This is not a good solution! However you need to do the sleep only when you start one of your processes... So I tried to use this information:
private void Button_Click(object sender, RoutedEventArgs e)
{
var ac = new Action(() => Application.Current.Dispatcher.Invoke(
() =>
{
Thread.Sleep(50);
}));
Task.Factory.StartNew(() =>
{
//Provide path to epi
string epiPath = @"c:/EPISUITE41";
Level3ntCalculator cl = new Level3ntCalculator();
var runner = new Calculators.Epi.Runners.ProcessRunner(epiPath, ac);
runner.WriteXSmilesFiles("CCCCC1CCCCCCC1");
cl.Calculate(runner);
});
}
And then I changed ProcessRunner
:
public void RunCalculator(Calculator calculator)
{
//bla bla bla...
new Thread(new ThreadStart(_action)).Start();
p.Start();
//...
}
In this snippet you execute Thread.Sleep
in the UI
thread only for a very short of time(the user will never know...).
It's a bad solution... However it solved the problem.
Place the work into a background thread so that you are not blocking the main GUI thread.
Something like this:
public class MainWindow : Window
{
private readonly BackgroundWorker worker = new BackgroundWorker();
public MainWindow()
{
this.Loaded += MyWindow_Loaded;
worker.DoWork += worker_DoWork;
worker.RunWorkerCompleted += worker_RunWorkerCompleted;
}
private void MyWindow_Loaded(object sender, RoutedEventArgs e)
{
// Start the background worker. May be better started off on a button or something else app specific
worker.RunWorkerAsync();
}
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
// run all .exes here...
// Note: you cannot access the GUI elements directly from the background thread
RunCalculator();
RunOtherExes();
}
private void worker_RunWorkerCompleted(object sender,
RunWorkerCompletedEventArgs e)
{
//update ui once worker complete its work
}
private void RunCalculator()
{
var p = new Process();
p.StartInfo.FileName = query;
p.StartInfo.WorkingDirectory = path;
p.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
p.StartInfo.CreateNoWindow = true;
p.StartInfo.Arguments = String.Join(" ", calculator.Arguments);
p.Start();
p.WaitForExit();
}
private void RunOtherExes()
{
// ...
}
}
You can also update the GUI thread with progress from the background thread too if you need that.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With