NikSp
NikSp

Reputation: 1549

Kill a process that opened a file, after the file is closed by the user [WPF application]

I am trying to efficiently open-close-reopen a power bi file (.pbix) from a WPF application button click. My method starts by creating a process that opens the pbix file then kills the process when the file is closed and then when the button is clicked again creates a new process to re-open the file.

Kindly find below the code I use to execute the steps above.

namespace TestApp
{
    public class MainWindowViewModel : INotifyPropertyChanged
    {
        public int CheckFileIsOpen(string filenamepath)
        {
            try
            {
                using FileStream fs = new FileStream(filenamepath, FileMode.Open, FileAccess.ReadWrite, FileShare.None);
                return 0;
            }
            catch (Exception)
            {
                WindowEffect = new BlurEffect();
                Mouse.OverrideCursor = null;
                bool? Result = new CustomMessageBox($"File: {filenamepath.Split(@"\").Last()} in use!\nClose it and try again.", "File used by another process", MessageType.Error, MessageButtons.Ok).ShowDialog(); //this is a MessageBox object
                if (Result.Value)
                {
                    WindowEffect = null;
                    return 1;
                }
                else
                {
                    WindowEffect = null;
                    return 2;
                }
            }
        }

        private void OpenOnlineLocally(bool open_local)
        {
            Process p = new Process();
            string copy_name = "File_Copy.pbix";
            string path = AppDomain.CurrentDomain.BaseDirectory; //the directory the .exe file runs.
            try
            {
                Mouse.OverrideCursor = Cursors.Wait;
                if (open_local == true)
                {
                    int IsPBIFileOpen = CheckFileIsOpen($@"{path}{copy_name}");
                    if (new[] { 1, 2 }.Contains(IsPBIFileOpen))
                    {
                        return;
                    }
                    
                    //Open the file using the system process
                    p.StartInfo = new ProcessStartInfo($"{path}{copy_name}")
                    {
                        UseShellExecute = true
                    };
                    p.Start();
                }
                else
                {
                    OpenUrl("https://app.powerbi.com/...");
                }
            }
            finally
            {
                if (p.HasExited) { p.Kill(); } //kill the process if the user closed the .pbix file
            }
        }

        public ICommand ExportPowerBICommand //binded to a button click command in xaml
        {
            get { return new DelegateCommand<object>(FuncExportPowerBI); }
        }
        public void FuncExportPowerBI(object parameter)
        {
            Mouse.OverrideCursor = Cursors.Wait;
            try
            {
                OpenOnlineLocally(true);
            }
            finally
            {
                Mouse.OverrideCursor = null;
            }
        }
    }
}

The above code generates this error in the finally statement:

System.InvalidOperationException: 'No process is associated with this object.'

Some notes after experimentation:

  1. The process should be killed when the user closes the .pbix file (i.e. clicks the X icon on top right corner of the desktop app). If the process is not killed and the user re-clicks the button to re-open the file then I get an error that the file is already opened and used by another process.

  2. I prefer to avoid a solution that uses process.WaitForExit(), for two reasons. First, the application freezes while the file is used by the user. Second, it takes a couple of seconds for the desktop to realize that the process has exited so it can kill() it (not time efficient).

Upvotes: 0

Views: 479

Answers (1)

aepot
aepot

Reputation: 4824

Since you're running .NET 5, there's an asynchronous method Process.WaitForExitAsync(). Async operation will not block the UI.

I've made the changes to two methods

private async Task OpenOnlineLocally(bool open_local)
{
    Process p = new Process();
    string copy_name = "File_Copy.pbix";
    string dir = AppDomain.CurrentDomain.BaseDirectory; //the directory the .exe file runs.
    string path = Path.Combine(dir, copy_name);
    try
    {
        if (open_local == true)
        {
            int IsPBIFileOpen = CheckFileIsOpen(path);
            if (IsPBIFileOpen != 0)
            {
                return;
            }

            //Open the file using the system process
            p.StartInfo = new ProcessStartInfo(path)
            {
                UseShellExecute = true
            };
            p.Start();
            await p.WaitForExitAsync();
        }
        else
        {
            OpenUrl("https://app.powerbi.com/...");
        }
    }
    finally
    {
        if (!p.HasExited) { p.Kill(); } //kill the process if the user closed the .pbix file
    }
}

public async void FuncExportPowerBI(object parameter)
{
    Mouse.OverrideCursor = Cursors.Wait;
    try
    {
        await OpenOnlineLocally(true);
    }
    catch (Exception ex)
    {
        Debug.WriteLine(ex.Message); // handle possible exception here
    }
    Mouse.OverrideCursor = null;
}

Upvotes: 1

Related Questions