I'm trying to send a pdf to the printer without the print dialog coming up using GhostScript.NET. My understanding is if I provide the exact name of the printer in the -sOutputFile switch, the user will not be prompted..
The exact name of my printer is 101-XER4250, and in debugging, the name that the processor receives is: "-sOutputFile=%printer%101-XER4250-E". Is there something that I am missing regarding this? Also if it's worth mentioning, I'm using a Xerox machine with PCL6 drivers.
Here's my example code:
private static void PrintWithGSNET(byte[] pdfFormBytes, string printer, int copies)
{
try
{
var fileName = #"c:\temp\" + $"{DateTime.Now:yyyyMMddhhmmssffff} - {Security.CurrentUser}";
using (var file = new FileStream(fileName, FileMode.Create, FileAccess.ReadWrite))
{
file.Write(pdfFormBytes, 0, pdfFormBytes.Length);
using (GhostscriptProcessor processor = new GhostscriptProcessor(GhostscriptVersionInfo.GetLastInstalledVersion(), true))
{
List<string> switches = new List<string>();
switches.Add("-empty");
switches.Add("-dPrinted");
switches.Add("-dBATCH");
switches.Add("-dPDFFitPage");
switches.Add("-dNOPAUSE");
switches.Add("-dNOSAFER");
switches.Add("-dNOPROMPT");
switches.Add("-dQUIET");
switches.Add("-sDEVICE=mswinpr2");
switches.Add("-sOutputFile=%printer%" + printer.Trim());
switches.Add("-dNumCopies=1");
switches.Add(fileName);
processor.StartProcessing(switches.ToArray(), null);
}
file.Close();
}
File.Delete(fileName);
}
catch (Exception ex)
{
throw new InvalidOperationException($"Error printing. [Printer: {printer.Trim()}] [Copies: {copies}", ex);
}
}
If you want the user should not notice that the file is being printed you can add the no cancel command
switches.Add("-dNoCancel");
Is it a network printer? I had to qualify my printer name with the server name.
If I set the printer to be "printerName" the dialog popped up. When I changed it to #"\servername\printerName" the dialog went away and it printed silently.
Related
I'm working with C# and adobe acrobat SDK.
When the program throws an error due to the pdf already being compressed I want to move the pdf.
However, C# complains that the file is being used by another process and I know it has to do with the SDK and not another program.
After some debugging I found out that compressPDFOperation.Execute is the culprit.
How can I close it so that I can move the file?
try {
// Initial setup, create credentials instance.
Console.WriteLine(".json: " + Directory.GetCurrentDirectory() + "/pdftools-api-credentials.json");
Credentials credentials = Credentials.ServiceAccountCredentialsBuilder()
.FromFile(Directory.GetCurrentDirectory() + "/pdftools-api-credentials.json")
.Build();
// Create an ExecutionContext using credentials and create a new operation instance.
ExecutionContext executionContext = ExecutionContext.Create(credentials);
CompressPDFOperation compressPDFOperation = CompressPDFOperation.CreateNew();
// Set operation input from a source file.
FileRef sourceFileRef = FileRef.CreateFromLocalFile(directory + #"\" + pdfname);
compressPDFOperation.SetInput(sourceFileRef);
// Execute the operation.
FileRef result = compressPDFOperation.Execute(executionContext);
// Save the result to the specified location.
//if pdf is part of a group, the group directory name will be stored in fileGroupDirectory
string fileGroupDirectory = directory.Replace(sourceDir, "");
result.SaveAs(finishedDir + fileGroupDirectory + pdfname);
}
catch (ServiceApiException ex)
{
Console.WriteLine("Exception encountered while executing operation", ex.Message);
if (ex.Message.Contains("The input file is already compressed"))
{
File.Move(file, finishedDir + fileGroupDirectory + fileName);
}
}
I've found a solution , it's not best practice but I don't know an other way to do it.
I've declared all the variables used to execute the compression (sourceFileRef, compressPdfOperation, ...) before the try catch statement and after result.SaveAs(...) I set those variables to null and run the garbage collection.
compressPDFOperation = null;
result = null;
sourceFileRef = null;
executionContext = null;
credentials = null;
GC.Collect();
I have an application that displays a pdf in a webBrowser control, using the following code
webBrowser1.Navigate(filename + "#toolbar=0");
It works perfectly if Adobe Reader is installed
I would like to check if Adobe Acrobat Reader is installed before displaying the window, or at least when trying to display the pdf.
I have adapted the following code from here Check Adobe Reader is installed (C#)?
As mentioned in the comments, unfortunately, it flags uninstalled versions as well.
I have also tried the 64 bit code in the same article but find errors I can't easily resolve and suspect would give the same result anyway as it simmply looks at the registry in a similar way.
using System;
using Microsoft.Win32;
namespace MyApp
{
class Program
{
static void Main(string[] args)
{
RegistryKey adobe = Registry.LocalMachine.OpenSubKey("Software").OpenSubKey("Adobe");
if(null == adobe)
{
var policies = Registry.LocalMachine.OpenSubKey("Software").OpenSubKey("Policies");
if (null == policies)
return;
adobe = policies.OpenSubKey("Adobe");
}
if (adobe != null)
{
RegistryKey acroRead = adobe.OpenSubKey("Acrobat Reader");
if (acroRead != null)
{
string[] acroReadVersions = acroRead.GetSubKeyNames();
Console.WriteLine("The following version(s) of Acrobat Reader are installed: ");
foreach (string versionNumber in acroReadVersions)
{
Console.WriteLine(versionNumber);
}
}
}
}
}
}
If Adobe pdf Reader is not loaded, a prompt appears to open(in any other installed reader), save the file or cancel.
I would like to be able to intercept this so as to Indicate that Adobe's reader is not available.
I have tried
private void webBrowser1_Navigating(object sender, WebBrowserNavigatingEventArgs e)
{
string url = e.Url.ToString();
if (url.StartsWith("res://ieframe.dll/navcancl.htm") && url.EndsWith("pdf"))
{
e.Cancel = true;
MessageBox.Show("Cannot open PDF!");
}
}
at the following https://social.msdn.microsoft.com/Forums/en-US/46aaeecd-5317-462a-ac36-9ebb30ba90e7/load-pdf-file-using-webbrowser-control-in-windows-form-c?forum=csharpgeneral
but found the open, save cancel event precedes the webBrowser1_Navigating.
I would appreciate any help in a reliable solution that will not flag uninstalled versions, or a seperate solution that will stop the open, save, cancel prompt, and allow me to create a message to install the reader
Thanks
The Simple Way is to use try / catch
in the try give the code.
The web browser will give error if there is an error so in the catch then say the user to install adobe reader.
Here is the sample
try
{
//your code goes her
}
catch(Exception ex)
{
if(ex.Message != null)
{
Console.WriteLine("Adobe Reader not Installed");
//this is where you say adobe reader is not installed
}
}
I am trying to print an xps document to printers (network printer, some virtual local printers, xps and non xps based) with the following code.
C# Source:
static void Main(string[] args)
{
PrintServer printServer = new PrintServer(#"\\printserver.csez.zohocorpin.com");
foreach (PrintQueue queue in printServer.GetPrintQueues())
{
Console.WriteLine("Printer: {0}, Port: {1}, ShareName: {2}, status: {3}, PrintingIsCancelled: {4}",
queue.Name, queue.QueuePort.Name, queue.ShareName, queue.QueueStatus, queue.PrintingIsCancelled);
Program program = new Program();
Thread printingThread = new Thread(() => program.Print_XPXFile(queue, #"D:\Assist\RemotePrint\Spool\Donalduck.xps"));
// Set the thread that will use PrintQueue.AddJob to single threading.
printingThread.SetApartmentState(ApartmentState.STA);
printingThread.Start();
printingThread.Join();
}
}
public void Print_XPXFile(PrintQueue pQueue, String FilePath)
{
// Create print server and print queue.
bool fastCopy = pQueue.IsXpsDevice;
FileInfo file = new FileInfo(FilePath);
if (!file.Exists)
{
Console.WriteLine("There is no such file.");
}
else
{
Console.WriteLine("Adding {0} to {1} queue. share name : {2}", FilePath, pQueue.Name, pQueue.ShareName);
try
{
// Print the Xps file while providing XPS validation and progress notifications.
PrintSystemJobInfo xpsPrintJob = pQueue.AddJob(file.Name, FilePath, fastCopy);
Console.WriteLine("Done adding.");
}
catch (PrintJobException e)
{
Console.WriteLine("\n\t{0} could not be added to the print queue.", file.Name);
if (e.InnerException.Message == "File contains corrupted data.")
{
Console.WriteLine("\tIt is not a valid XPS file."); // Use the isXPS Conformance Tool to debug it.
}
else
{
Console.WriteLine("\tmessage : {0}", e.InnerException.Message);
}
}
}
}
When printing to Microsoft XPS Document Writer, Microsoft Print to PDF, etc it works fine.
I found that it is working fine with all XPS based printers. I even installed a XPS sample printer driver and added a virtual local printer to confirm this claim and as expected it worked.
For non-xps based printers, it actually gets stuck in the AddJob function. It neither throws any exception, nor it moves to the next statement.
I developed the code based on this msdn resource.
What is the cause and solution?
All thoughts are welcome.
I can't seem to find the problem. But here is a more promising way to print XPS files:
public static void PrintXPSToDefaultPrinter(string FilePath)
{
try
{
// Create the print dialog object and set options
PrintDialog pDialog = new PrintDialog();
pDialog.PageRangeSelection = PageRangeSelection.AllPages;
pDialog.UserPageRangeEnabled = true;
FileInfo file = new FileInfo(FilePath);
XpsDocument xpsDocument = new XpsDocument(FilePath, FileAccess.ReadWrite);
FixedDocumentSequence fixedDocSeq = xpsDocument.GetFixedDocumentSequence();
pDialog.PrintDocument(fixedDocSeq.DocumentPaginator, file.Name);
}
catch (System.IO.IOException ex)
{
Console.WriteLine("The file is being used by some other process.");
}
catch (Exception ex)
{
Console.WriteLine("Exception occured : {0}", ex.Message);
}
}
You should call this in STA mode just like you have used:
static void Main(string[] args)
{
Thread printingThread = new Thread(() => PrintXPSToDefaultPrinter(#"D:\Assist\RemotePrint\Spool\Donalduck.xps"));
printingThread.SetApartmentState(ApartmentState.STA);
printingThread.Start();
}
You can also mention any printqueue u want in the pDialog.PrintQueue property.
Hope this helps. Enjoy man !!!
I, too, got stuck in the AddJob() method. The problem seemed to be more prevalent as Windows 10 platforms became involved.
Breaking the execution in debug, the stack trace showed the STA thread was blocked on a call to
MS.Internal.PrintWin32Thunk.XpsCompatiblePrinter.JobIdentifier.get
and this was further blocking on a low-level WaitOne() on some unknown synchronization object.
Even though details on this problem are thin (this is the only post I have found on the topic), THANKFULLY the accepted solution works extremely well (and I have been trying all kinds of things to print in WPF for years).
AddJob() is fully replaced in all respects. One can even control the PrintTicket more easily through the PrintDialog (to set paper size, orientation, one/two sided printing, etc.)
Trying to make Android chooser to display available actions for user to launch a PDF file which is stored in my local folder.
When I pass the file name like /data/user/0/myappappname/files/output.pdf , (which exsists, of course), I get a nice chooser with all the apps that can accept a pdf file. But when I pick any of them, I get an error (from external app) The document path is not valid. No exception is thrown.
Then I tried (for testing purposes) to set fname to something like /storage/emulated/0/Download/TLCL.pdf (file also exists), and everything works fine.
At first, I thought that this has something to do with file permissions (since first path is private to my app), but then I found flag ActivityFlags.GrantReadUriPermission built exactly for purpose of temporarily granting file access to other apps. Still same results.
Since this is a Xamarin.forms project, I am limited in choice of file creation locations (I use PCLStorage, which always writes to app-private, local folder), so I don't have an option of generating files in /Documents, /Downloads etc.
I am obviously doing something wrong. Any ideas appreciated.
Is there an option to get full path from system, including the /storage/emulated/0 part (or whatever that would be on other devices)? Maybe that would help?
Piece of code:
(mimeType is defined as "application/pdf" earlier)
public async Task<bool> LaunchFile(string fname, string mimeType)
{
var uri = Android.Net.Uri.Parse("file://" + fname );
var intent = new Intent(Intent.ActionView);
intent.SetDataAndType(uri, mimeType);
intent.SetFlags(ActivityFlags.ClearWhenTaskReset | ActivityFlags.NewTask | ActivityFlags.GrantReadUriPermission );
try
{
Forms.Context.StartActivity(Intent.CreateChooser(intent, "ChooseApp"));
return true;
}
catch (Exception ex)
{
Debug.WriteLine("LaunchFile: " + ex.Message);
return false;
}
My solution to this, which may not be exactly what you want, is to generate a file (in my case a zip file), export it to a public folder, and use that file for the chooser.
Using these:
private readonly string PublicDocsPath = Android.OS.Environment.ExternalStorageDirectory.AbsolutePath + "/AppName";
private readonly string PrivateDocsPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.ApplicationData);
and some basic functions:
public Stream GetOutputStream(string destFilePath)
{
string destFolderPath = Path.GetDirectoryName(destFilePath);
if (!Directory.Exists(destFolderPath))
Directory.CreateDirectory(destFolderPath);
return new FileStream(destFilePath, FileMode.Create, FileAccess.Write, FileShare.None);
}
public Stream GetInputStream(string sourceFilePath)
{
if (!File.Exists(sourceFilePath)) throw new FileNotFoundException();
string sourceFolderPath = Path.GetDirectoryName(sourceFilePath);
return new FileStream(sourceFilePath, FileMode.Open, FileAccess.Read, FileShare.Read);
}
You can copy your file to your public folder (or subfolders, you just have to assemble the path) and use that file for your chooser:
public void SendEmail(string subject, string body, string recipient, string mimeType, string attachmentFilePath, string activityTitle)
{
var emailIntent = new Intent(Intent.ActionSendMultiple);
if (string.IsNullOrEmpty(subject)) throw new ArgumentException();
emailIntent.PutExtra(Intent.ExtraSubject, subject);
if (!string.IsNullOrEmpty(recipient))
emailIntent.PutExtra(Intent.ExtraEmail, new[] { recipient });
if (!string.IsNullOrEmpty(body))
emailIntent.PutExtra(Intent.ExtraText, body);
if (!string.IsNullOrEmpty(attachmentFilePath))
{
var file = new Java.IO.File(attachmentFilePath);
file.SetReadable(true, true);
var uri = Android.Net.Uri.FromFile(file);
emailIntent.PutParcelableArrayListExtra(Intent.ExtraStream, new List<IParcelable>(){uri});
}
emailIntent.SetType(mimeType);
_activity.StartActivity(Intent.CreateChooser(emailIntent, activityTitle));
}
This chooser specifically lets the user send their file via email or google drive , but you can assemble it however you want. The attachmentFilePath of this function is the same as the string passed into the GetOutputStream function above.
we're using Acr.IO rather than PCLStorage and I recall that has a property that'll return the fullpath for you.
The code we're using is below, but I wonder if you're simply missing "file://" off the start of your path, as I noticed thats in our code, as well as this previous stackoverflow answer to a similar question as this one, open a PDF in Xamarin.Forms (Android)
We're using a dependency FileService on Android and using this code to open PDFs:
public void OpenNatively(string filePath) {
Android.Net.Uri uri;
if (filePath.StartsWithHTTP()) {
uri = Android.Net.Uri.Parse(filePath);
}
else {
uri = Android.Net.Uri.Parse("file:///" + filePath);
}
Intent intent = new Intent(Intent.ActionView);
var extension = filePath.Substring(filePath.LastIndexOf(".")+1);
if (extension == "ppt" || extension == "pptx") {
extension = "vnd.ms-powerpoint";
}
var docType = "application/" + extension;
intent.SetDataAndType(uri, docType);
intent.SetFlags(ActivityFlags.ClearWhenTaskReset | ActivityFlags.NewTask);
try {
Xamarin.Forms.Forms.Context.StartActivity(intent);
}
catch (Exception e) {
Toast.MakeText(Xamarin.Forms.Forms.Context, "No Application found to view " + extension.ToUpperInvariant() + " files.", ToastLength.Short).Show();
}
}
I try to open temporary HTML file with default browser and delete the file then:
var tempFileName = Path.ChangeExtension(Path.GetTempFileName(), "html");
// I save document to temp file here...
Process process = null;
try
{
process = Process.Start(tempFileName);
}
catch (Win32Exception)
{
}
catch (ObjectDisposedException)
{
}
catch (FileNotFoundException)
{
}
var worker = new BackgroundWorker();
worker.DoWork += (s, we) => {
if (process != null)
{
process.WaitForExit();
try
{
File.Delete(tempFileName);
}
catch (IOException)
{
}
}
};
worker.RunWorkerAsync();
Unfortunately, Process.Start returns null if a process is not started, but a running one is used (new tab is opened in Google Chrome). So I can't wait for that process to exit.
So, a general question is: how to do the task? How to show a temporary HTML file to a user and delete it after viewing?
If you use ProcessStartInfo and set UseShellExecute then you can start the user's default browser by "running" the HTML directly like you're trying to do now. I haven't tried it, but it should give you a Process back to determine when the user has closed the browser.
I would still prepare for a bunch of edge cases you have no control over. Such as if they leave the browser open but close the app that's watching the browser. At that point do you let the browser stay alive? Do you kill it? When do you delete the HTML file? It might be better to use the Web Browser control. Then you don't even have to worry about other processes or browser compatibility. You can even stream the HTML contents to the control and there is no file to delete later.
You can force a new browser instance, by first figuring out the default browser, and executing it manually:
public Process launchBrowser(string url)
{
string browserName = "iexplore.exe";
using (RegistryKey userChoiceKey = Registry.CurrentUser.OpenSubKey(#"Software\Microsoft\Windows\Shell\Associations\UrlAssociations\http\UserChoice"))
{
if (userChoiceKey != null)
{
object progIdValue = userChoiceKey.GetValue("Progid");
if (progIdValue != null)
{
if(progIdValue.ToString().ToLower().Contains("chrome"))
browserName = "chrome.exe";
else if(progIdValue.ToString().ToLower().Contains("firefox"))
browserName = "firefox.exe";
else if (progIdValue.ToString().ToLower().Contains("safari"))
browserName = "safari.exe";
else if (progIdValue.ToString().ToLower().Contains("opera"))
browserName = "opera.exe";
}
}
}
return Process.Start(new ProcessStartInfo(browserName, url));
}
Then you can get a handle to the process:
var process = launchBrowser("www.google.com");
process.WaitForExit();
try
{
//Do whatever
}
catch (IOException)
{
}
You can also read the html content into a Memory Stream or into a string variable using WebClient, and after close the Stream or WebClient, file will be released, ready to be deleted, as you no longer need it.
Them you have the html content in memory, just send it to browser.
Here some example if you need:
https://social.msdn.microsoft.com/Forums/vstudio/en-US/060ea8e0-cc63-44a3-b0dc-b531c29b8a0f/read-html-content-using-cnet?forum=csharpgeneral
Hope it helps.