Is there a C# equivalent to the `PrintDescriptionRef` class found in the C(++) API?I'm trying to convert the PrintDesc.cpp print example found in the SDK :
// Create a print description instance. PrintDescriptionPtr printDescPtr = PrintDescriptionRef::Create(); IPrintDescriptionP printDesc = printDescPtr->GetP(); // Initialize the print description to use the PDF printer driver // configuration and the active session. if (SUCCESS != printDesc->InitializeFromTCB (L"pdf.pltcfg")) ....
But am unable to create a PrintDescription because no such class is found, having referenced the following DLL's:- Bentley.PrintAPI- Bentley.PrintEngine- Bentley.PrinterConfigurationEditor- Bentley.PrintFoundation- Bentley.PrintHandlers- Bentley.PrintManager- Bentley.PrintOrganizerI've looked through MSTNPlatform.NET.chm and DgnPlatformNet.chm to see if there were any similarly named classes, but came up short, finding only IPrintDefinition related pages.I am trying to create an mdl addin that runs one of our standard pltcfg's in the middle of some other processes without requiring user input.Is there one such class in C#, or is there a different named class that can do this?
Remy,
Don't know if this will help, I'm still trying to wrap my mind about what I'm actually doing in this code but it is generating a plot. I'm basing the code off of the PrintSet.cpp file located in the SDK Examples.
I have the same references you list in your post with the addition of Bentley.PrintDefinitions.
Here's the code:
public static void PrintSheet(String unparsed) { Bentley.MstnPlatformNET.Print.PrintManager printManager = (Bentley.MstnPlatformNET.Print.PrintManager)PrintManagerProxy.GetLocalPrintManager(); DgnDocument dgnDocument = DgnDocument.CreateForLocalFile(@"C:\Worksets\FDOT\Test\Roadway\CESSRD01.dgn"); DgnFileOwner dgnFileOwner = DgnFile.Create(dgnDocument, DgnFileOpenMode.ReadOnly); StatusInt status = -1; dgnFileOwner.DgnFile.LoadDgnFile(out status); DgnFile dgnFile = dgnFileOwner.DgnFile; FileSpec fileSpec = new FileSpec(dgnDocument); IPrintStyleManager printStyleManager = printManager.CreatePrintStyleManager(); IPrintStyle printStyle = printStyleManager.GetPrintStyleByName("FDOT PDF"); IPrinter printer = printManager.CreateAndInitializeDefaultPrinter(null, null, null); IPrintSet printSet = printManager.CreatePrintSet(); printSet.InitializeFromPrinter(printer); printSet.Name = "WorksetPrint"; printSet.OutputFileNameExpression = new OutputFileNameExpression(StandardOutputFileNameExpression.Sequence_Set); printSet.PrintDefNameExpression = new PrintDefNameExpression(StandardPrintDefNameExpression.SourceFile); printSet.ApplyPrintStyle(printStyle); IPrintDefinition[] printDefinitionsToAdd = printSet.CreatePrintDefinitions(fileSpec, printStyle); printSet.Items.AddRange(printDefinitionsToAdd); foreach (IPrintDefinition printDefinition1 in printSet.GetPrintDefinitionList(null, true)) { CadPrintDefinition cadPrintDefinition = (CadPrintDefinition)printDefinition1; cadPrintDefinition.Maximize(); cadPrintDefinition.IsRasterized = false; } IPrinter printer1 = printSet.Printer; PublishingParams publishingParams = new PublishingParams(); if (printer1.SupportsPrintAsSet) { publishingParams.OutputFspec = new FileSpec(@"C:\temp\temp.pdf"); printSet.Publish(publishingParams); } dgnFile.Release(); }
Answer Verified By: Remy Moerland
Hi Mike,
Thank you for your answer and sample code, this gets very close to doing what I need it to do.
When I create a print style using out PDF PltCfg it creates a plot as expected (Well mostly, there's something wrong with colours, but I do get a pdf at the end) however when I try to switch to our PNG PltCfg the
if (printer1.SupportsPrintAsSet)
Do you know what controls this var?
I also tried expanding the statement with an else case, based on what is done in the PrintSet.cpp example you mentioned:
if (printer1.SupportsPrintAsSet) { publishingParams.OutputFspec = new FileSpec(@"C:\temp\tempTest.pdf"); publishingParams.OutputFolderSpec = new FolderSpec(@"C:\Temp\"); } else { publishingParams.UseSeparateJobs = true; publishingParams.OutputFolderSpec = new FolderSpec(@"C:\Temp\", true); } printSet.Publish(publishingParams);
System.AccessViolationException: 'Attempted to read or write protected memory. This is often an indication that other memory is corrupt.'
Thanks and regards,
Remy
It's been a long time since I was up to speed in this area but I believe the SDK includes documentation for the .NET Print API. The starting point is the IPrintManager class, from which everything else is obtained. The IPrintManager documentation includes a bit of sample C# code showing how to construct a very simple print set containing a single DGN and print it as a set. It should be similar to the PrintSet.cpp example in the SDK, only without the distracting C++ / CLI syntax.
More advanced features such as applying a print style, printing as a set, etc., can be discovered by exploring the methods and properties of the IPrinter, IPrintSet, and IPrintDefinition interfaces and other classes that are part of IPrintManager. The only required assembly reference is Bentley.PrintAPI.dll.
/*------------------------------------------------------------------------------------**//// <summary>The IPrintManager interface is the front end to the MicroStation printing/// system's .NET application programming interface.</summary>/// <remarks>An application must obtain an IPrintManager interface before performing/// any operation on printer, print set, or print definition objects.</remarks>/// <seealso cref="PrintManagerProxy"/>/// <example>This simple example shows how to obtain an IPrintManager interface from/// inside the currently running MicroStation process, create a print set, create one/// or more print definitions from a design file, modify the paper size and scale of/// the print definitions, create a folder in the print set, add the print definitions/// to the folder, and finally print the set./// <para>Required references include Bentley.PrintAPI.dll.</para>/// <code>/// using Bentley.MstnPlatformNET.Print;////// void TestMethod()/// {/// IPrintManager printManager = PrintManagerProxy.GetLocalPrintManager();////// IPrinter printer = printManager.CreatePrinter();/// printer.InitializeFromPltcfgFile (new FileSpec ("pdf.pltcfg"));////// IPrinterForm printerFormAnsiA = printer.FindFormByName ("ANSI A");////// IPrintSet printSet = printManager.CreatePrintSet();/// printSet.InitializeFromPrinter (printer);////// IPrintDefinition[] printDefinitions = printSet.CreatePrintDefinitions (new FileSpec ("My Design.dgn"), null);////// foreach (IPrintDefinition printDefinition in printDefinitions)/// {/// ICadPrintDefinition cadPrintDefinition = printDefinition as ICadPrintDefinition;/// cadPrintDefinition.PrinterForm = printerFormAnsiA;/// cadPrintDefinition.Maximize();/// }////// IFolder folder = printSet.CreateFolder ("My Folder");/// printSet.Items.Add (folder);////// folder.Items.AddRange (printDefinitions);////// PublishingParams publishingParams = new PublishingParams();/// publishingParams.UseSeparateJobs = false;/// publishingParams.OutputFspec = new FileSpec (@"C:\My PDF.pdf", true);/// printSet.Publish (publishingParams);////// printSet.Close();/// printer.Release();/// }/// </code>/// </example>/*-------------------------------------------------------------------------------------*/
The IPrinter.SupportsPrintAsSet property returns True if the printer driver supports printing as a set. It's an intrinsic property of the printer driver, not something you can change. In other words, the MicroStation PDF driver has support for creating multi-page PDF files. The system printer driver also has the capability. There is no ability to directly create a multi-page raster file, so if a printer driver configuration (.pltcfg) file associated with the LORIP printer driver (the one that supports PNG output) is selected, SupportsPrintAsSet will be False.
Here's another old C# test program that also demonstrates how to read and write a PSET file, control the print definition and output file names, plus create separate PDF files in a designed folder. It wasn't intended to be a published example so the coding style is not great. But it might be helpful.
private static bool Test (string pltcfgLfs, string sourceLfs) { bool isSuccess = false; IPrinter printer = null; IPrintSet printSet = null; IPrintDefinition[] printDefinitions;
IPrintManager printManager = PrintManagerProxy.GetLocalPrintManager(); if (null == printManager) goto cleanup;
printer = printManager.CreatePrinter();
FileSpec pltcfgFspec = new FileSpec (pltcfgLfs);
if (! printManager.FindPrinterConfigurationFile (pltcfgFspec, false)) goto cleanup;
if (false == printer.InitializeFromPltcfgFile (pltcfgFspec)) goto cleanup;
printSet = printManager.CreatePrintSet(); printSet.OnUserMessage += MyApp.OnUserMessageHandler;
if (false == printSet.InitializeFromPrinter (printer)) goto cleanup;
OutputFileNameExpression outputFileNameExpression = new OutputFileNameExpression(); outputFileNameExpression.Expression = OutputFileNameExpression.GetStandardExpression (StandardOutputFileNameExpression.Sequence_Set);
PrintDefNameExpression printDefNameExpression = new PrintDefNameExpression(); printDefNameExpression.Expression = PrintDefNameExpression.GetStandardExpression (StandardPrintDefNameExpression.SourceFile);
printSet.OutputFileNameExpression = outputFileNameExpression; printSet.PrintDefNameExpression = printDefNameExpression;
FileSpec sourceFspec = new FileSpec (sourceLfs);
printDefinitions = printSet.CreatePrintDefinitions (sourceFspec, null); if (null == printDefinitions) goto cleanup;
printSet.Items.AddRange (printDefinitions);
FileSpec psetFspec = new FileSpec (@"C:\Temp\PrintApiTest01.pset", true);
if (false == printSet.WritePrintSetFile (psetFspec, false)) goto cleanup;
if (false == printSet.ReadPrintSetFile (psetFspec)) goto cleanup;
PublishingParams publishingParams = new PublishingParams();
if ((printer.Destination == Destination.File) || (printer.Destination == Destination.Metafile)) { if (printer.SupportsPrintAsSet) { string outputHfs = @"C:\Temp\PrintApiTest01." + printer.DefaultExtension; publishingParams.OutputFspec = new FileSpec (outputHfs, true); } else { publishingParams.UseSeparateJobs = true; publishingParams.OutputFolderSpec = new FolderSpec (@"C:\Temp\", true); } }
if (false == printSet.Publish (publishingParams)) goto cleanup;
isSuccess = true;
cleanup: if (printSet != null) printSet.Close();
if (printer != null) printer.Release();
return isSuccess; }
No idea on your exception, but some mismanagement of the Printer object would be my first guess. That part can be tricky, all due to ugliness that arises from most but not all of the .NET Print API sitting atop a non-thread-safe native code SDK. Follow the example code patterns as best you can and you should be OK.
If you need to create a print set object with one printer driver configuration (i.e. pdf.pltcfg) then switch that print set to a different printer driver configuration (i.e. png.pltcfg), IIRC you will need to manually create a Printer object using the png.pltcfg file. Then call the IPrintSet.SetPrinter method. That will add a reference to the native code Plotter object underlying the IPrinter you constructed. At that point, you can close your IPrinter reference. That will drop the Plotter reference count back to 1, allowing the Plotter object to be released when the PrintSet is closed. Explicit closing of C# objects prevents the .NET garbage collector from calling non-trivial native-code destructors unpredictably on a separate thread. The PrintDescriptionRef class in C++ / CLI uses scoping logic to help enforce that. The design is admittedly poor.
Note that setting a new Printer in the print set is a big deal. All the print definitions in the set must be updated to the paper sizes supported by the new printer driver configuration. That may force changes to the print scale and other layout properties.
.
Andrew,Thanks for your information and examples.
I have implemented the example found in the IPrintManager class and found that this has the exact same result. During this I did however make the discovery that despite MicroStation crashing the plot would actually still be created if I waited long enough. Looking into this behavior I came across the following Property: printSet.UseWorkerMicroStation which is set to true by default.
Now if I set this property to false after creating my printSet MicroStation doesn't crash and creates the plot as expected after which I can continue using the MicroStation instance.
The same applied to the code sample you provided (causes a crash with UseWorkeRMicroStation, but still creates the plot, but works smoothly when it is set to false).
So right now my feeling is that the problem is between spawning a new MicroStation instance to create the plot and the PNG specific driver (since the issue doesn't surface when using e.g. pdf)I will do some more troubleshooting and probably start a new thread about that specific combination using a PNG plotter and the example code as given in IPrintManager.Regards,Remy
PrintSet.UseWorkerMicroStation = False is the mode in which the single-Print dialog plotdlg.ma application works. It removes a lot of complexity from the picture, but isn't designed for multi-page print set operations. Your mileage may vary if you try to create print sets containing multiple print definitions, and things may not go well if you cause a DGN to be loaded that automatically changes the WorkSet. If you limit your actions to things the single-Print dialog does, then you should be OK. But there's not all that much value over using the .NET Print API with 'UseWorkerMicroStation = False' than just using plotdlg.ma keyin commands.
I can't imagine that Print Organizer does not work today using png.pltcfg, so I have no idea on your exception. If you are not instructing the Print API to print as a set using a driver that doesn't support that, then I can only assume some other customization is involved. Hopefully someone currently involved with MicroStation printing may be able to offer some insight.
Andrew,Thanks for the info, I don't necessarily need multi-page printing, although it would definitely be helpful, so for now I can use this work around.To keep this question scoped to just the original question of being able to create a plot (which you've answered) i've created a separate question for this issue here: [C# MS V10.16.02.34] IPrintset.Publish crashes when plotting PNG with WorkerMicroStation - MicroStation Programming Forum - MicroStation Programming - Bentley Communities