My WPF app can open and edit single documents. I am looking for a tidy approach to allow multiple instances of my WPF app to run but to only allow a given document to be open in one instance of the app. If a user tries to open a document which is already opened in another instance, I need to pop up a dialog to tell them and allow them to switch to the other app instance if required.
Thanks
Dan
One approach is to attempt to take an exclusive lock on the file when you open it. When your other application instance attempts to open the file, an IOException will be raised. You can catch this exception and show a dialog to your user stating that the file is already opened in another application. This scenario should be covered anyway, as the file could be open in a different application that is not yours.
Related
Searched a lot, but without luck - so here goes
My C# winforms application creates temp files which are opened using the default registered application (let's call them viewer apps). Once the user is done viewing those files, I want to delete them.
Currently, I register for an Application.ApplicationExit event, to delete the file. This approach covers most of the situations but not all. Sometimes the user still has the viewing application open while exiting my app, so the success of my File.Delete depends on whether the viewer has opened the file with FileShare.Delete or not - which is out of my control.
This is what I have found so far, but fall short of what I want
FileOptions.DeleteOnClose does not help, since my app will already be closed in some cases and the temp file will still be needed. Also, when I create the file like this: new FileStream(fn, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.ReadWrite | FileShare.Delete, 4096, FileOptions.DeleteOnClose), the viewer apps like say adobe reader & notepad, still complain about file in use by my application The process cannot access the file because it is being used by another process
MoveFileEx with MOVEFILE_DELAY_UNTIL_REBOOT dwFlags works, but it would wait till a reboot to delete it - I would rather have it deleted once the use is done, since reboots can be few and far between and forcing reboots IMO is not the most user friendly approach. On a side note, does windows automatically clear the %temp% folder on restart? Or is there any temp folder that windows automatically clears on restart?
I can write another background process which constantly tries to delete the temp files till it succeeds, but I would like to avoid deploying one more piece of software to accomplish this. It can be done using a windows service or scheduled task or adding command line switches to my existing app and making it run in "delete mode" in background or through task scheduler. All of it decrease my ease of deployment and use along with increasing my footprint on client's computer
In a nutshell, I am wondering if there is any Win32 API or .NET Framework API that will delete a file as soon as there are no processes with open handle to that file?
EDIT:
The information in the temp files are reasonably private (think your downloaded bank account statements) and hence the need for immediate deletion after viewing as opposed to waiting for a reboot or app restart
Summary of all Answers and Comments
After doing some more experiments with inputs from Scott Chamberlain's answer, and other comments on this question, the best path seems to be to force the end users to close the viewer app before closing my application, if the viewer app disallows deletion (FileShare.Delete) of the temp file. The below factors played a role in the decision
The best option is FileOptions.DeleteOnClose, but this only works if all files open before or after this call use FileShare.Delete option to open the file.
Viewer apps can frequently open files without FileShare.Delete option.
Some viewers close the handle immediately after reading/displaying the file contents (like notepad), whereas some other apps (like Adobe Reader) retain such handle till the file is closed in the viewer
Keeping sensitive files on disk for any longer than required is definitely not a good way to proceed. So waiting till reboot should only be used as a fail-safe and not as the main strategy.
The costs of maintaining another process to do the temp file cleanup, far exceeds the slight user inconvenience when they are forced to "close" the viewer before proceeding further.
This answer is based on my comments in the question.
Try write the file without the delete, close the file, let the editor open the file, then open a new filestream as a read with DeleteOnClose with an empty body in the using section.
If that 2nd opening does not fail it will behave exactly like you wanted, it will delete the file as soon as there are no processes with open handle to that file. If the 2nd opening for the delete does fail you can use MoveFileEx as a fallback failsafe.
What I'm trying to do is use SolidWorks' eDrawings (as an example) to open a specified .dwg file that the user selects from a list my application generates.
I am able to do this without issue, but what I'd like to do at this point is, if the eDrawings process is already running, change the .dwg it is displaying to the new one the user has selected.
I already have the Process object for the eDrawings application, and I have used process.CloseMainWindow() successfully on it, so I know my app is targeting the correct process.
I just need to know how to instruct it to change files. Is this going to be something specific to eDrawings (and/or unavailable from the .NET framework itself) ?
Maybe something like
Process myProcess = Process.GetProcessesByName("eDrawings")[0];
myProcess.CurrentFile = myNewFile;
Can this be done?
EDIT:
For emphasis on the solution I seek, I want the eDrawings program to load the new .dwg file without closing and opening a new instance of itself.
The point of trying to do this is to get around the excessive load times every time the application opens.
If I understood your question properly and you try to let other processes to access your main application process you might want to read about Interop Marshaling: http://msdn.microsoft.com/en-us/library/eaw10et3.aspx
I am currently hosting an IE Browser control in a .NET (2.0) Form and using it to load Office files such as Excel and Word thusly:
_ieCtrl.Navigate("C:\\test.xls", False);
The hosting and loading works well except whenever I navigate to a file I am presented with a dialog that asks whether I want to save or open the file. (This is standard IE file-download behavior.) I always want to open it of course and I do not want the dialog to show.
Another issue is that when I close the window that hosts the IE control and the Office doc the document does not close and remains open on disk. This means that subsequent attempts to open the same file via my app or the native office app will fail because of the sharing violation.
Is there a programmatic way of avoiding this dialog and cleaning up resources afterward? I am asking for a programmatic answer because web research has only yielded solutions that entail modifying OS-level settings.
Bounty NOTE:
I am open to any solution to this issue that will allow me to:
Host an Excel spreadsheet inside my application
Work rather transparently (avoid usability issues like the one described above)
Avoid having to make any OS-specific changes that may affect other applications (especially icluding IE)
Is zero additional cost (no licensed 3rd party libs please) Code Project and other open source resources are OK
Not mess around with the DSO Framer ActiveX control, unless a stable version is developed/discovered
Is your intention for the user to be able to work with the Excel file in an Excel-ish way (i.e. columns, rows, formulas, etc.), possibly saving it back? If this is the case, I can't see how you can solve this problem well without relying on COM Interop with the Excel object model or by integrating third-party libraries to work with the Excel sheet. I know you said no paid solutions, but there are some feature-rich 3rd-party controls out there just for working with Excel files within applications.
I noticed in your comment to SLaks that the final product is a "dashboard of sorts". If your intention is to design a a custom dashboard application, have you considered parsing the Excel file(s) to extract the data and then presenting it in a logical manner within your application. This removes the need to directly display and work with the Excel file while still allowing you to work with the data inside that file. If you are trying to get the data outside of the file, here are two approaches among many:
You might consider using the Excel object model and COM interop to read the data from the Excel file into your application. Granted, this includes a dependency on Excel being installed, but it is a possibility. This article has some great code for getting started with reading Excel files in this way.
A better way might be to use a library that doesn't have a dependency on Excel being installed on the local system. This answer suggests using the Excel Data Reader library, available on CodePlex.
I know this answer side-steps your original answer of "hosting MS Office documents in [a] custom app," but in case what you're really interested in is the data inside those Excel files, hopefully this answer will prove helpful.
This is a horrible hack and should only be considered as a last resort: SendKeys.Send("{O}");
http://msdn.microsoft.com/en-us/library/system.windows.forms.sendkeys%28VS.71%29.aspx
Something similar to
_ieCtrl.Navigate("C:\\test.xls", False);
(code to sleep or wait may be needed here)
SendKeys.Send("{O}");
Basically, you send the "o" key to the dialog so it presses the "open" option. You are simulating a keyboard presses to click the "open" button. It is hackey because
1) you may need to wait in between
calls. If you send the o key before
the dialog is up it will be missed.
Hopefully the navigate call is finished when the dialog pops (dont know behavior of control in c#). You may need to experiment with the time since different computers will open faster\slower
2) If the dialog is not shown on a
computer, you will be inserting "o"s
into it. This may cause problems when
exiting because it may popup another dialog to try and save
the changes. May be able to prevent this by opening it in read-only mode
3) Different versions or windows may need different sendkeys commands. For example, you may need to send "o" and them the "{enter}" key
4) Probably more :)
If you want to open the file in a separate Excel instance (not embedded in the WebBrowser control), you can simply call
Process.Start(#"C:\Test.xls");
Office was never meant to run in embedded mode, not in a web page or in an ActiveX Document host. Microsoft had time and time again given us the warning. From pulling dsoframer from the knowledge base to skipping the BrowserFlags registry key in Office 2007.
Move to Office add-ins, Excel Web Access or Office Web Apps as quickly as you can.
I want to open files, which are doubleclicked in Explorer, in the same instance of my .Net application, e.g. in a new tab. How can I do this?
For now each file starts new instance of my application. Many programs can open files in the same instance, for example, Opera and Notepad++, so there is an easy way for sure.
You may take a look at this post which illustrates a technique that could be used to have a single instance WinForms application.
Might be an easier way to do it but the way I've done it is that if an instance is started with a filename as a parameter then it checks if there are any other instances and if so passes on the filename to that instance and the close itself down.
In case you want to do the same, but in WPF rather than WinForms, the howto is explained here: What is the correct way to create a single-instance application?
Example using TCP-sockets:
http://pieterjan.pro/?a=Projecten_csharp_DrawIt.php
start TCPListener on the form
connect TCPClient in the main of the second instance
Send ActivationArguments through the TCP-connection to the form
Works for multiple files at once as well, and even for multiple files at the first time (when application not started yet)
The most important code blocks are:
The constructor of the MainForm (Hoofdscherm) where the server is started and the port number is written to a file. The first file is opened as well.
The Main-function (Program.cs) where the second, third, ... instance connects to the TcpListener in the first instance and sends the filename through the socket
The source code is available on the button "Broncode"
I am wondering if someone could point me in the right direction. You know how for example, in most IDEs, if you open a source file with "open with", it runs the program and opens it up? and then if you open another one, it opens it in a new tab in the same process?
My question is NOT how to add a program to the shell commands, but rather:
How would a C# application "receive" a PDF file for example?
How would the application open the file in the same process when another file is run with it (not having to instances of the program)?
When the second program instance starts up, before loading any interface components, it checks to see if another program instance is already running. If so, it communicates to it in some fashion (program specific: this can be sockets, inter process interrupts, shared memory, etc..) that it should open this new file.
After communicating this to the first instance, the second program instance will just terminate since it's no longer needed.
Your program would have to be able to talk to other instances of itself, and say "hey, I'm already open, what are you trying to do, let me do it for you."
Here is a nicely detailed post that explains the proper implementation:
http://www.iridescence.no/post/CreatingaSingleInstanceApplicationinC.aspx
This thread contains a discussion and sample for handling the command line arguments (this is how files are "passed to" your application): http://www.devnewsgroups.net/group/microsoft.public.dotnet.framework/topic62109.aspx
Microsoft programs usually have a ddeexec key in the shell configuration; this will cause the shell to send a DDE command to the already running app, if it exists.