C# OpenFileDialog open zip folder containing single file? - c#

I have an application which currently provides the user with the ability to view a PDF file inside the application by using File->Open, browsing to the location of the PDF file using a Microsoft.Win32.OpenFileDialog, and then displaying that PDF file in a System.Windows.Controls.WebBrowser in the GUI.
I am creating the OpenFileDialog and setting the file extensions it can open using:
/*Create Open File dialog */
Microsoft.Win32.OpenFileDialog OFDlg = new Microsoft.Win32.OpenFileDialog();
/*Set filter for file extension and default file extension */
OFDlg.DefaultExt = ".pdf";
OFDlg.Filter = "PDF Documents (.pdf)|*.pdf";
I now want to extend this, so that the user can open a ZIP folder containing a single PDF document, and display that PDF document in the same way that I am above.
I tried changing the filter to allow .zip files, i.e.
OFDlg.DefaultExt = ".pdf|.zip";
OFDlg.Filter = "PDF Documents (.pdf)|*.pdf|ZIP|*.zip";
but when I browse to the location of the .zip file in the OpenFileDialog, the .zip folder is not displayed there- only normal folders and PDF documents (other documents in that directory, such as .doc & .xls are not displayed in the OpenFileDialog).
My reason for wanting to be able to open the contents of a .zip file directly from the .zip, rather than navigating to that file itself, is so that I can add public/private key encryption to the .zip, so that its contents can only be read securely.
I know that there could in theory be problems if the .zip contains more than one file, but I intend to send each encrypted file in its own zip folder, so it can be assumed that any zip file that the user is trying to open contains a single .pdf, and nothing else.
So my questions are:
How can I make .zip folders visible from the OpenFileDialog?
How can I make the selection of that .zip folder automatically open and display its contents (a single PDF file) in the System.Window.Controls.WebBrowser that I am currently using to display PDFs in my GUI?
Edit 1
I tried changing my OpenFile() method to the following code:
/*Set filter for file extension and default file extension */
OFDlg.DefaultExt = ".pdf";
OFDlg.DefaultExt = ".zip";
OFDlg.Filter = "PDF Documents (.pdf)|*.pdf";
OFDlg.Filter = "ZIP Folders (.ZIP)|*.zip";
but when I now run my application, and browse to the same location, although the .zip folder is now shown in the OpenFileDialog, the .pdf files no longer are... and if I double click the .zip folder, my application breaks, and I get a runtime error on the line
PdfPanel.OpenFile(docFP);
which says:
An unhandled exception of type 'System.AccessViolationException' occurred in MoonPdfLib.dll
Additional information: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
I guess that's to do with the MoonPDF library that I'm using to read the PDFs being unable to handle the .zip extension?
How would I resolve this to be able to open the PDF inside the ZIP?
Edit 2
Ok, so I've resolved the issue about only being able to see either the PDF files or the .ZIP folders (not both at the same time), by moving the functionality into two separate methods- one to display the PDF direct from the PDF's filepath, and another to display the PDF from the path of the .ZIP folder holding it.
The method for displaying the PDFs directly currently works (it is essentially the code in the first bit of code I've quoted). However the method for displaying the PDFs from the ZIP currently doesn't work...
I understand the reason for this- it's because I am passing a .zip folder to the OpenFile method... The code for this method currently looks like this:
private void openZipMenuItem_click(object sender, RoutedEventArgs e)
{
Microsoft.Win32.OpenFileDialog OZipDlg = new Microsoft.Win32.OpenFileDialog();
OZipDlg.DefaultExt = ".zip";
OZipDlg.Filter = "ZIP Folder (.zip)|*.zip";
Nullable<bool> result = OZipDlg.ShowDialog();
if (result == true)
{
/*Open document */
string filename = OZipDlg.FileName;
//fnTextBox.Text = filename;
zipFP = OZipDlg.FileName;
/*browser.Navigate(docFP); ERF (27/05/2016 # 0935) Comment this line- I want to use PdfPanel to open docFP, not browser */
Console.WriteLine("Panel height: " + PdfPanel.ActualHeight);
PdfPanel.OpenFile(zipFP);
}
}
When I try to call this function to open a .zip, I get a runtime exception which says:
AccessViolationException was unhandled
An unhandled exception of type 'System.AccessViolationException' occurred in MoonPdfLib.dll
Additional information: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
I understand that I can't display a Zip folder in the PdfPanel (which is a MoonPdfPanel that I am using from the MoonPdfLibrary), so I know that I will get an exception here.
How can I pass the contents of zipFP to the call to PdfPanel.OpenFile(), rather than passing zipFP itself to it?
Edit 3
Ok, so my code is currently extracting the PDF file successfully from the ZIP folder when I open it- I can see that it is copied to the directory I have specified. I am now trying to get the PDF to be displayed automatically in the PDF Panel on my application- I've done this by adding the following code:
try{
string extractPath = #"C:\Documents";
using(ZipArchivev zip = ZipFile.Open(zipFP, ZipArchiveMode.Read))
foreach(ZipArchiveEntry entry in zip.Entries){
try{
ZipFile.ExtractToDirectory(zipFP, extractPath);
Console.WriteLine("zipFP: " + zipFP);
}catch(System.IOException){
Console.WriteLine("File already exists...");
}
}
string ExtractedPDF = string.Concat(extractPath, zipFP);
PdfPanel.OpenFile(ExtractedPDF);
}catch(AccessViolationException ex){
Console.WriteLine("Can't display a zip in the PDF panel..." + ex.InnerException);
}
But when my code tries to execute the line PdfPanel.OpenFile(ExtracedPDF);, I get an exception that says:
FileNotFoundException was unhandled | An unhandled exception of type 'System.IO.FileNotFoundException' occurred in MoonPdfLib.dll'
I understand that this is happening because the variable I am trying to display in the PDFPanel, ExtractedPDF actually holds the path of the folder containing the PDF, and not the PDF itself- How do I give it the name of the PDF file, when I don't actually know what the PDF file will be called?

Here is something works similar to your requests, the logic behind the code is:
Only display zip and pdf files in the OpenFileDialog
If user selected a pdf file, show it in the panel
If user selected a zip file, change the directory of the OpenFileDialog to the zip file(treat it like a folder)
Example code (working code....):
OpenFileDialog ofd = new OpenFileDialog();
ofd.Filter = "PDF files (.pdf)|*.pdf;*.zip";
ofd.ShowDialog();
//reopen OpenFileDialog if it is zip file. this part can be improved.
if (ofd.FileName.EndsWith(".zip"))
{
ofd.InitialDirectory = ofd.FileName;
ofd.ShowDialog();
}
//if it's a PDF, note that you don't really need this check,
//as the only file can reache here will be a PDF,
//and it can be the temporary file that inside a zip.
if(ofd.FileName.EndsWith(".pdf"))
{
//show it in your PdfPanel
}
Edit, based on your new comments and added code. you need to change your code to the following as your current code is mistaken directory with the file:
try{
string extractPath = #"C:\Documents";
string ExtractedPDF ="";
using(ZipArchivev zip = ZipFile.Open(zipFP, ZipArchiveMode.Read))
foreach(ZipArchiveEntry entry in zip.Entries){
try{
ExtractedPDF= Path.Combine(extractPath, entry.FullName);
entry.ExtractToFile(ExtractedPDF,true);
}catch(System.IOException){
Console.WriteLine("error during extraction...");
}
}
if( System.IO.File.Exists(ExtractedPDF))
{
PdfPanel.OpenFile(ExtractedPDF);
}
}catch(AccessViolationException ex){
Console.WriteLine("Can't display a zip in the PDF panel..." + ex.InnerException);
}

If you want to support multiple file formats in an open file dialog, you need to add a third (or better first) option, that aggregates all supported file extensions:
OFDlg.Filter = "Supported file formats|*.pdf;*.zip|PDF Documents|*.pdf|ZIP files|*.zip";

First, regarding showing the files in the open file dialog. Your initial method for doing this was correct. Your updated code now first sets the filter to show PDFs, then replaces that filter with one that shows zip files. The standard file open dialog isn't designed to show different file types at the same time. The right way to handle that is to give the user the option for which file types they want to show.
Typically, an "All files" option is added as well (with . as the search pattern). This way if the file type the user wants to open isn't available in the list, they can see it regardless.
As for opening the PDF file that is in the zip file, you need to take are of extracting the PDF file yourself. This question has some options for how to do that.

Related

C# LoadFile throwing unhandled exception when loading .txt, .rtf

I'm learning C# and went through a text editor tutorial. The final result works pretty good, except there is something strange happening I do not understand.
When I write/save/load files all in the text editor they work fine. But whenever I write a file in a different editor/download a text file from the internet somewhere, the file fails to load.
When I load the file, I get
"An unhandled exception of type 'System.ArgumentException' occurred in System.Windows.Forms.dll"
And when I look at "View Details" is says
"File format is not valid."
Even though there is text in the file (when viewed in a different text editor), the text property has nothing in it, a result of the file format being incorrect.
I'm pretty confused why it would load files made in the text editor itself (with the same extension) but not from somewhere else. I'm really not sure how to begin debugging this one. My save file/open file methods are listed below.
Open File
private void Open()
{
openFileDialog1.Filter = "RTF|*.rtf|Text Files|*.txt|VB Files|*.vb|C# Files|*.cs|All Files|*.*";
if (openFileDialog1.ShowDialog() == System.Windows.Forms.DialogResult.OK && openFileDialog1.FileName.Length > 0)
{
GetCurrentDocument.LoadFile(openFileDialog1.FileName, RichTextBoxStreamType.RichText);
}
}
Save File
private void Save()
{
saveFileDialog1.FileName = tabControl1.SelectedTab.Name;
saveFileDialog1.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
saveFileDialog1.Filter = "RTF|.rtf";
saveFileDialog1.Title = "Save";
if (saveFileDialog1.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
if (saveFileDialog1.FileName.Length > 0)
{
GetCurrentDocument.SaveFile(saveFileDialog1.FileName, RichTextBoxStreamType.RichText);
}
}
}
Help would be much appreciated, thanks!
It's not just the extension of the file that determines it's type. This version of the method allows loading both "regular" RTF files and also ASCII files.
The RichTextBoxStreamType Enumeration provides a few different possibilities. If you are trying to load a file created using a different editor, you might need to use RichTextBoxStreamType.PlainText instead of RichTextBoxStreamType.RichText.

Delete .doc duplication during PDF conversion c#

I currently have a program that merges a folder consisting of word docs into one combined file via user input with a FileBrowserDialog. Once files are selected, a 'combine' button applies the code shown below which sources the folder containing the documents, output location and name of the file created.
string fileDate = DateTime.Now.ToString("dd-MM-yy");
string fileTime = DateTime.Now.ToString("HH.mm.ss");
string outcomeFolder = outputFolder;
string outputFileType = ".docx";
string outputFile = "Combined Folder " + fileDate + " # " + fileTime + outputFileType;
string outputFileName = Path.Combine(outcomeFolder, outputFile);
// Combines the file name, output path selected and the yes / no for pagebreaks.
MsWord.Merge(sourceFiles, outputFileName, pageBreaker);
// Message displaying how many files are combined.
MessageBox.Show("A total of " + sourceFiles.Length.ToString() + " documents have been merged", "Success", MessageBoxButtons.OK, MessageBoxIcon.Information);}
The MsWord referenced calls a separate .CS file which combines the folder components, output name and a boolean to enable page-breaks. The MsWord also automatically saves the word .doc to the user specified location once the contents of the folder are successfully combined. MsWord.Merge(sourceFiles, outputFileName, pageBreaker);
The issue i'm wanting to address is, when I enable this check box:
if (convert2PDFBox.Checked)
Microsoft.Office.Interop.Word.Application officeApp = new Microsoft.Office.Interop.Word.Application();
officeApp.Documents.Open(outputFileName);
outputFileType = ".pdf";
officeApp.ActiveDocument.SaveAs(outputFileName + outputFileType, WdSaveFormat.wdFormatPDF);
officeApp.Quit();
I want the program to solely create a PDF of the combined folder and not 2 seperate .doc and .PDF files, which it currently does. Since the MsWord.save function is called separately and is essential to the overall function of my program, I was wondering is there a possibility of deleting the initially combined file once conversion of the PDF takes place? e.g. "combinedDocument".Delete - Essentially allowing the copy to take place however not presenting the user with the initial .doc - only the .PDF
Though the issue is small, I would love to get it addressed and welcome any suggestions or advice with this manner. I can also provide any additional information if needed, thank you.
tl;dr - merging program creates an amalgamated Word .doc, which i want to change solely to a PDF when a checkbox is enabled instead of creating a .doc and PDF.
I finally resolved my issue - What I decided to do was manipulate my existing MsWord.cs and create a separate PDF.cs call for my main form:
Rather than save the Word .doc when being merged, I instead used: wordApplication.ActiveDocument.SaveAs(outputFile, Word.WdSaveFormat.wdFormatPDF);
which saved the merged content thus far as a .pdf
This however presented errors with Microsoft Word as I was then prompted to 'Save File As' due to the merged file never actually being saved in a .Doc / .Docx format
I then altered the closing statement of the call,
// Close Word application
wordApplication.Quit(
false, // save changes
By setting the 'Save Changes' setting to False, it removed the 'Save As' prompt which allowed the Word doc. to be dismissed without needing to be saved, thus leaving only the initial PDF created. I then applied the two separate File type calls to each checkbox presented, which allowed the user to enable the outcome format of the merged files.
Thank you for the suggestions regarding the issue.

Detect when OpenFileDialog returns a downloaded URL/URI

I'm using OpenFileDialog (.Net Framework 4, Windows 10) and I've noticed that it will allow the user to specify a URL as the file name (e.g., http://somewebsite/picture.jpg). This is very useful for my application, so I don't intend to disable it. The way it works is downloading the file into the user's temp directory and returning the temporary file name in the dialog's Filename property. This is nice, except for the fact that the user starts to build up garbage in his/her temp directory.
I would like to tell when a file was downloaded by the OpenFileDialog class (as opposed to a previously existing file), so I can clean up by deleting the file after use. I could check if the file's directory is the temp directory, but that's not very good since the user might have downloaded the file him/herself.
I've tried intercepting the FileOK event and inspect the Filename property to see if it is an HTTP/FTP URI, but despite what the documentation says ("Occurs when the user selects a file name by either clicking the Open button of the OpenFileDialog") it is fired after the file is downloaded, so I don't get access to the URL: the Filename property already has the temporary file name.
EDIT: This is an example of what I'like to do:
Dim dlgOpenFile As New System.Windows.Forms.OpenFileDialog
If dlgOpenFile.ShowDialog(Me) <> Windows.Forms.DialogResult.OK Then Return
''//do some stuff with dlgOpenFile.Filename
If dlgOpenFile.WasAWebResource Then
Dim finfo = New IO.FileInfo(dlgOpenFile.Filename)
finfo.Delete()
End If
In this example, I've imagined a property to dlgOpenFile "WasAWebResource" that would tell me if the file was downloaded or originally local. If it's the first case, I'll delete it.
There's no obvious way to do this, but as a workaround, how about checking where the file lives? It looks like by default this dialog downloads files to the users Temporary Internet Files directory, so you could introduce some code that looks something like this:
FileDialog dialog = new OpenFileDialog();
if (dialog.ShowDialog() == DialogResult.OK)
{
string temporaryInternetFilesDir = Environment.GetFolderPath(System.Environment.SpecialFolder.InternetCache);
if (!string.IsNullOrEmpty(temporaryInternetFilesDir) &&
dialog.FileName.StartsWith(temporaryInternetFilesDir, StringComparison.InvariantCultureIgnoreCase))
{
// the file is in the Temporary Internet Files directory, very good chance it has been downloaded...
}
}

C# WPF - Only allow certain file extensions

I have an OpenFileDialog and I only want to allow .txt as a valid file for the users.
I know I can add a Filter to the OpenFileDialog like so:
var dialog = new OpenFileDialog();
dialog.DefaultExt = ".txt";
dialog.Filter = "Text Files (*.txt)|*.txt";
var result = dialog.ShowDialog();
// Do something with the result
The problem however, is that I can still directly say something like "test.jpg" in the OpenFileDialog and then it opens this uploads this .jpg file. (Obviously it goes wrong somewhere later, but that doesn't matter for now.) I just want to know how I can restrict the user to only add ".txt" files, nothing else? (By directly validation it inside the OpenFileDialog, instead of doing it somewhere later.)
You cant do that only in OpenFileDialog and even if you could its a bad limitation.
Using the *.txt example there are multiple files extensions that are plain text inside, *.bat or all the codding file extensions *.cs, *.js, etc...
You should not limit the user on what file he can put on it.
For more complex file types if your program cant handle the file passed by the user you should show an error not prevent the user from passing the file.

Where are paths previously used in the program stored (e.g. FileDialog paths)?

According to this msdn article one of the sources for InitialDirectory property used in FileDialog is:
A path that was previously used in the program, perhaps retained from the last directory or file operation.
...
So if you selected your first file from folder x, the next time you try to select a file it will open up the FileDialog with with folder x selected (saving you having to navigate there).
Playing around with notepad this seems to carry across opening a file, saving a file, opening a file and even when printing with "Microsoft XPS Document Writer" which brings up it's own dialog.
So my question is where is this value stored between dialogs? I would like to be able to see what it is and potentially change it? The specific area i would like to change it is in the "Microsoft XPS Document Writer" printer which brings up it's own dialog. So it's not as simple as just setting the initalDirectory Value.
It's stored in the registry, somewhere in HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\ComDlg32\ (LastVisitedPidlMRU).
You should take a look at this link:
MRU locations are what you are looking at!
Here's a way for accessing it:
var openFileDialog1 = new OpenFileDialog();
string path = openFileDialog1.InitialDirectory;
// you can change path if you want
openFileDialog1.InitialDirectory = path;
// after you are donw you can display you dialog
if (openFileDialog1.ShowDialog() == DialogResult.OK)
{
// do something
}
Another way is to use Directory.SetCurrentDirectory method which sets the application's current working directory
And from Microsoft website, it is stored at this location in the registry:
//The MRU lists for Windows Explorer-style dialog boxes are stored by file type for each user in the following registry key:
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\ComDlg32\OpenSaveMRU
hope this helps

Categories