Manipulating "filename" of file dialog opened using a "print to pdf" option - c#

I have over a thousand e-mails that need to be converted to individual PDFs. The code I've written is able to process the faux-print job up to the "Save PDF As..." Dialog Box.
I need to manipulate the filename in that dialog to the original filename. I cannot find a way to post a string to the filename section of the dialogbox, since this is an unconventional method posting on a separate process.
How do I send this information to an active dialog window that is unrelated to the process the application is calling?
public static void Main(string[] args)
{
string folderIn = Path.GetDirectoryName(#"Z:\Files up to 3-6-13\");
string folderOut = Path.GetDirectoryName(#"c:\users\athomas\desktop\Output");
string[] fileList = Directory.GetFiles(folderIn);
foreach(var fileName in fileList)
{
Console.WriteLine(fileName);
Process p = new Process();
ProcessStartInfo info = new ProcessStartInfo(fileName);
info.Verb = "Print";
info.CreateNoWindow = true;
info.WindowStyle = ProcessWindowStyle.Hidden;
Process.Start(info);
// This is where I need to say something like
// SaveDialog.SaveAs(fileName.SafeFileName)
if(p.HasExited == false)
{
p.WaitForExit(1000);
}
}
Console.ReadLine();
}

You are asking basically about hacking around, which means there is no safe way to do that. The possible way may look like this:
1) You enumerate windowses shown on the screen, via EnumWindows
2) You use for every window GetWindowText to find a Title of the Dialog you are searching for.
3) Ocassionaly there may be more then one dialog, Murphy's law is always there, so may be even with the same title. You can reduce collision risk by
3.a Call GetWindowLong with HWND of "dialog" you found
3.b Call GetParent to get HWND of parent window, that you already know
If you can avoid or may not care about case of window style&title collision, just jump over point (3)
4) Once you found your dialog, run over its HWND EnumChildWindows to get all controls of it
5) Find the contrlol you interested in via some attribute (usually Class_ID)
6) Execute SetWindowtext to set the text you like.
As I said before, there are several places were this process can fail.
For example:
You may happen to have multiple dialogs on the screen in the same moment
You may download a new version of PDF processor, and in SaveAs dialog may have something changed, so your code will break.
But considering that you are hacking, you may assume your own risks.
An excellent tool, may be the best tool, for window investigation on Windows, is probably Spy ++, which is available within visual studio installation. Using it you can try to find some unique attributes of a textbox of that dialog where you want to put the text in. Having that in your hands you can reliably query dialog's children collection to find the textbox.

After you mentioned .msg, I did a Google Search on the .msg file format and I was surprised to find it was well documented. It is an OLESS file, and there are APIs for loading those. I even found a CodeProject C# sample that was able to load an email I just saved from Outlook 2013!
It will be a bit of work since you will have to write code to "render" message how you see fit. Ex: It gives you the body, the recipients, and the attachments. You would have to decide what to do with the attachments and code that up. Then find a PDF library. But at least it is a programmatic solution, rather than relying on hacking the UI.
Also: A Google search for .MSG to PDF returned loads of tools to do this. One is a forum post referring to an Adobe Product called "Acrobat PDFMaker" so maybe you don't even need to write code.

Sorry to post 2 answers. I have a solution to the exact problem of prompting for the file. These instructions vary slightly based on the version of Windows, but you will get the idea:
Add a new printer that uses PDF printer driver
When it asks for the "port" click "Add Port" then "Local Port"
Enter in a file name. Ex: "C:\Users\Myself\MyPdf.pdf"
Complete the printing wizard as usual.
I tested this with the XPS printer and it worked
When you print to this printer, it writes to that exact file: No prompting! You will have to monitor the file (I suggest FileSystemWatcher) or poll for when the file is closed or something like that. After that, you can move the file elsewhere.

Related

C# OpenFileDialog multiple filename filters including exclude

I have a requirement to allow users to open a specific file for processing. The open file dialog is currently
OpenFileDialog ofg = new OpenFileDialog
{
FileName = "BaseFileName*",
Filter = "CSV File (*.CSV)|*.csv",
Multiselect = false,
InitialDirectory = #"N:\Downloads"
};
However the process adds a suffix of _Processed along with timestamp data to the filename and I want to exclude these renamed files the next time the OpenFileDialog is used to prevent the user trying to reprocess the same file.
I have to leave the original files where they are for internal audit reasons.
So I need an additional filename filter of not equal to "_Processed".
Is there any way to do this with OpenFileDialog or does anyone know of a custom c#/.net component that can do this?
You are asking to omit specific items from the file dialog view.
According to MSDN, this is no longer possible as of Windows 7, but was possible previously.
The C# file dialogs (both WPF and WinForms) use the IFileDialog API.
Here is the function that could have made this work, but is no longer supported:
https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-ifiledialog-setfilter
As it is, you are stuck with checking the file for correctness after the user has already selected it and confirmed it with OK.
You can help the situation a little bit: If you enjoy pain, then you can copy the whole IFileDialog COM interop code from the .NET source code, and implement IFileDialogEvents. This way, when the user clicks "OK", you can deny the selection and display an error before the dialog closes, leaving the dialog open so the user can select a different file.
If you are sane and you don't want to do that, then you'll have to open the dialog again after the verification fails.
The easy way is just saving the processed data with another extension e.g. "BaseFileName_Processed_20105640640.cvs1", that way you keep the data and your file dialog will not show this file.
Another way could be to call the OpenFileDialog() in an if statement (and compare the return to DialogResult.OK), then split the file name for {'_','.'}, then run a loop to count the occurrences of the word Processed( >0), and possibly as a safety check determine whether a timestamp is present in one of the split strings. Finally, reload the FileOpenDialog in the same folder when the wrong file was selected.

C# and ExtendScript for batch processing of .incx files

I have a ton of .incx text documents clustered into their own individual subfolders that I need to iterate through and convert to plaintext as part of a C# winform app I've created. I have the latest version of InCopy and the ExtendScript Toolkit, and a .jsx script that works great to quietly and quickly create my plaintext files.
My problem/question is that there isn't much guidance on how to best launch this from within a C# class in a running 3rd party app, sending in relevant info. When I run my .jsx script, I need to send it a target folder from my app where it can find the .incx files.
The target folder(s) will be dynamic depending on other previous actions in my app.
I've found a few vague hints to solutions on Adobe's forums involving additional .vbs files and/or external temp files to hold arguments, but they're all pretty dated, so I thought I'd ask and see if anyone knew of a contemporary method. If anything is unclear, I'll respond right away to clarify.
Through a lot more Googling and my own trial and error, I have found my answer.
The best way I can find is to do all of my InCopy scripting in VBS and then use a Process instance to send in my arg(s) with cscript.
Example C#:
Process myScriptProc = new Process();
myScriptProc.StartInfo.FileName = #"cscript";
myScriptProc.StartInfo.WorkingDirectory = rootDir + "\\"; // rootDir being the path where my vbs lives
myScriptProc.StartInfo.Arguments = "MyScript.vbs " + filesPath; // filesPath is the arg sent to the script
myScriptProc.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
myScriptProc.Start();
myScriptProc.WaitForExit();
myScriptProc.Close();
MyScript.vbs
main
Function main()
Set myInCopy = CreateObject("InCopy.Application.CC.2015")
Set obj = CreateObject("Scripting.FileSystemObject")
myInCopy.ScriptPreferences.UserInteractionLevel = 1699640946
myFormat = 1952412773
myExtension = ".txt"
Set objFSO = CreateObject("Scripting.FileSystemObject")
objStartFolder = WScript.Arguments(0)
Set objFolder = objFSO.GetFolder(objStartFolder)
Set colFiles = objFolder.Files
For Each x In colFiles
If LCase(objFSO.GetExtensionName(x.name)) = "incx" Then
thisDoc = x
Set myDoc = myInCopy.open(thisDoc)
Set myStory = myInCopy.ActiveDocument.Stories.Item(1)
parts = split(x.Name, ".")
myFilePath = objStartFolder & "/" & parts(0) & myExtension
myStory.Export myFormat, myFilePath
myDoc.close()
obj.DeleteFile(thisDoc)
End If
Next
myInCopy.ScriptPreferences.UserInteractionLevel = 1699311169
End Function
I rewrote my JavaScript file in VBScript because judging from the tumbleweeds blowing through the Adobe forums, I was never going to get any answers as to why their documentation examples for calling DoJavaScriptFile produce object missing method errors.
The biggest hurdle I ran into after redoing my script in VB was that you have to use the super-secret enumerated decimal values for Adobe-specific things if you run the scripts externally. If you look at MyScript.vbs you'll see a few instances of what look like random 10 digit values. Those come from here:
http://jongware.mit.edu/idcs5js_html_3.0.3i/idcs5js/index_Enum%20Suite.html
Bless the guy who created that resource, because I couldn't find that information in any of Adobe's documentation to save my life.
TL;DR: If you're trying to automate using processes and scripts that run outside an Adobe app, do everything in VBScript, and beware the mystery decimal enumerations.
useless footnote:
MyScript.vbs here reads all *.incx files from the passed in directory, exports as plain .txt (with the same filename, into the same dir), and deletes the original.

How do I Use OpenFileDialog to select files or paths

I am writing a WPF / C# application, and would like to enable a user to select one (or multiple) files, or one (or multiple) folders, without having to select which option they use initially, but intentionally. In my opinion, the best way to acchieve this goal would be to have a standard FolderBrowserDialog, and as long as the user does not seelct a file, but browses to a path, clicking the open button should select that path.
Practically, this solution does not work, because OpenFileDialog does not allow empty selections, you can hit "open", but nothing will happen. There is one workaround descriped here which allows to enter a fake name like "Selected Folder." as filename. which can afterwards be filtered out, which is a workaround, but not a nice one:
http://www.codeproject.com/Articles/44914/Select-file-or-folder-from-the-same-dialog
This solution has two important weaknesses:
1.) you will have to filter for the fake name
2.) if you paste a filename manually, or select a file first, and then switch the selection to a folder instead, the fake name is not inserted automatically.
of course I am aware there is something like FolderBrowserDialog, which I omit using even if I only wanted to select folders and not files. The reason: this dialog has no possibility to paste paths from clipboard, and I find it annoying to navigate all the way, I rather copy paths from somewhere and paste them, which works perfectly fine in OpenFileDialog, but not in FolderBrowserDialog. Besides, FolderBrowserDialog does not allow to select files and folders.
I have googled a lot, but do not find satisfying solutions, although I am sure many people must obviously face this problem.
As mentioned, the most elegant way for me would be to make the OpenFileDialog simply allow empty Filename boxes when clicking Open - any way to acchieve this?
Thanks alot.
Letting a user select a directory OR a file using the same dialog is not practical nor intuitively possible.
However, if you want a solution for selecting a Folder, here it is :
If you don't want to create a custom dialog but still prefer a 100% WPF way and don't want to use separate DDLs, additional dependencies or outdated APIs, I came up with a very simple hack using WPF's Save As dialog for actually selecting a directory.
No using directive needed, you may simply copy-paste the code below !
It should still be very user-friendly and most people will never notice.
The idea comes from the fact that we can change the title of that dialog, hide files, and work around the resulting filename quite easily.
It is a big hack for sure, but maybe it will do the job just fine for your usage...
In this example I have a textbox object to contain the resulting path, but you may remove the related lines and use a return value if you wish...
// Create a "Save As" dialog for selecting a directory (HACK)
var dialog = new Microsoft.Win32.SaveFileDialog();
dialog.InitialDirectory = textbox.Text; // Use current value for initial dir
dialog.Title = "Select a Directory"; // instead of default "Save As"
dialog.Filter = "Directory|*.this.directory"; // Prevents displaying files
dialog.FileName = "select"; // Filename will then be "select.this.directory"
if (dialog.ShowDialog() == true) {
string path = dialog.FileName;
// Remove fake filename from resulting path
path = path.Replace("\\select.this.directory", "");
path = path.Replace(".this.directory", "");
// If user has changed the filename, create the new directory
if (!System.IO.Directory.Exists(path)) {
System.IO.Directory.CreateDirectory(path);
}
// Our final value is in path
textbox.Text = path;
}
The only issues with this hack are :
Acknowledge button still says "Save" instead of something like "Select directory", but in a case like mines I "Save" the directory selection so it still works...
Input field still says "File name" instead of "Directory name", but we can say that a directory is a type of file...
There is still a "Save as type" dropdown, but its value says "Directory (*.this.directory)", and the user cannot change it for something else, works for me...
Most people won't notice these, although I would definitely prefer using an official WPF way if microsoft would get their heads out of their asses, but until they do, that's my temporary fix.

How to restrict filename modification in OpenFileDialog in C#

I have a C# .NET 3.5 program that uses an OpenFileDialog to retrieve a file for importing. It has two filters (*Domain*.* and *.*) and the *Domain*.* is chosen as the FilterIndex. I'm seeing a case of a disappearing file from the filters when it is modified within the OpenFileDialog.
Here are the steps the user will take to reproduce the problem:
In the program, open the OpenFileDialog. Ensure that the filter is set to *Domain*.*.
Find a file in the directory that matches the filter and change the file name so that it no longer matches the filter. Note: the file will disappear.
Change the filter to show all files *.*. Note: the file is not listed even though you're supposedly showing *.*.
At this point, you can open the folder in Windows Explorer and see that the file with the changed name exists. However, in the OpenFileDialog, it is no longer visible regardless of the filter that is used. The only way to see that file again is to close the OpenFileDialog and then reopen it.
Here is my code that creates the OpenFileDialog.
using(OpenFileDialog domainFileDialog = new OpenFileDialog())
{
domainFileDialog.CheckFileExists = true;
domainFileDialog.CheckPathExists = true;
domainFileDialog.Filter = "Domain Files (*Domain*.*)|*Domain*.*|All files (*.*)|*.*";
domainFileDialog.FilterIndex = 1;
domainFileDialog.Multiselect = false;
domainFileDialog.RestoreDirectory = true;
domainFileDialog.ShowReadOnly = true;
domainFileDialog.SupportMultiDottedExtensions = true;
domainFileDialog.Title = "Choose the Domain File to Import...";
domainFileDialog.ValidateNames = true;
\\ Perform Import functionality...
}
It seems as though when you open the OpenFileDialog and modify an existing file that matches the more restrictive filter so that it no longer matches the filter, the file disappears from visibility even when all files *.* are displayed.
Since I have no idea why this is happening, (and I'm open for suggestions as to how to fix it) is there any way to restrict a user from creating or modifying any of the files in the OpenFileDialog in hopes of preventing this problem?
First, I'd suggest you consider whether it is worth caring that the user might do this. If the FOD does this, every windows application has this feature, but you don't hear users complaining about it. So is it really an issue that needs fixing?
It wouldn't surprise me if the problem's related to the filter being for more than just a file extension - it's rather unconventional to do this. Maybe try it with an extension only and see if this is the cause.
Have you tried pressing F5 to refresh its cached data? I don't know if this would work in a FOD as it does in an explorer window, but it might be interesting to give it a try.
Having said all that, it would be trivial to filter keypresses (F2) and mouse clicks (right button) )(with some forms you could derive off it, or you could drop in a MessageFilter) to make it impossible for a user to access any UI that allows them to rename a file in the FOD. It would piss me off, as a user, if you broke the FOD in this waym though - much more than a renamed file "disappearing" under rare circumstances, anyway.
http://msdn.microsoft.com/en-us/library/microsoft.win32.openfiledialog.aspx
The OpenFileDialog doesn't itself support any such functionality. I think your best bet at this point is writing your own open-file dialog window. You would gain a great deal of control that way and it wouldn't take too long.

Is it possible to save data in another process with a C# app?

I have the following code:
p.StartInfo.FileName = "notepad.exe";
p.StartInfo.Arguments = "somefile.txt";
p.Start();
When "somefile.txt" is opened in notepad, its text will be modified. I want to save the new text in a temp file with the C# app automatically.
Is it possible to access this text and save it with C#?
It is sorta possible, you could use P/Invoke to call the SendMessage() method and generate the WM_COMMAND messages for the Edit + Select-All and Edit + Copy menu commands. That puts all text on the clip board, readily accessible to your program. You'll need to use the Spy++ utility to find out what the command identifiers are. Looks like 25 and 769 when I look at it on Win7.
There's a more direct way to discover the commands. In Visual Studio choose File + Open + File and select c:\windows\notepad.exe. Open the Menu node and double-click the "1" resource. That opens the menu editor, select Edit + Select-All and look at the Properties window for the ID.
Of course, this technique is specific to Notepad. The command IDs will be different for another program. As will be the code you need to find the proper window handle. Using Process.MainWindowHandle is treacherous at best, you don't know what specific instance of the process you need to select.
It would be very difficult to look at the text in-memory in the notepad process. It could be done, but it depends on the internal memory layout of notepad and your process may need administrative privileges depending on how the memory pages are protected.
Why not use a FileSystemWatcher to detect when the file has been modified, and then read the file in directly using c#?
http://msdn.microsoft.com/en-us/library/system.io.filesystemwatcher.aspx
Copy the somefile.txt before you start notepad to a temporary file/folder. Start notepad using this temporary file. And wait for Notepad to exit.
Then you can examine if the file is changed (checksum?).
Probably delete the file after the processing, or your system will be full with temp files.
You can bring notepad window to front and send CTRL+S. You will need to use pinvoke for that.
I really don't understand why you would want to use notepad?
If you need a user to edit a text file, and then your app take some action on the text itself, why not just give the user the ability to edit the text right in your app?
What does notepad give you that the TextBox control doesn't have?
Try something like this
using System.Windows.Forms;
using System.IO;
TextBox myEditor = new TextBox();
myEditor.Multiline = true;
myEditor.ScrollBars = ScrollBars.Vertical;
myEditor.AcceptsReturn = true;
myEditor.AcceptsTab = true;
myEditor.WordWrap = true;
// Get the text from the file.
StreamReader sr = new StreamReader("SomeFile.txt"))
myEditor.Text = sr.ReadToEnd();

Categories

Resources