This is the latest edition of my PDF tool, previously mentioned here, here, here, and inspired by my first attempt at a MVP here.
Major differences from previous editions:
- It uses the MVP pattern (as opposed to the god-tier UI that did everything).
- The background/separate thread is gone. I will consider adding it back later, but it was giving me pains with the new pattern.
- I "fixed" every last
using
directive. - I removed every last documentation comment.
- The processor is now a
static
class.
"Problems":
- The UI/View is still not entirely naive. It knows what scripts and fields are, and assigns them depending on which button the user presses. I don't know if there is a better alternative or not.
- The presenter seems to do a little too much direct modification to the UI. Again, not sure what can/should be done about that.
- The background thread is gone. This means that very large workloads have the potential to crash the application, due to the Winforms timeout. This can always be avoided by not running several thousand files at a time, but I feel I should do something to "fix" this.
- The "model" part of the MVP is largely nonexistent. There is simply no data that needs to be handled save a list of strings. The back end only handles actions, which largely do not affect the view.
PdfConversionAndTimeStampTool.cs
namespace PdfConversionAndTimeStampTool
{
using System.IO;
using Application = System.Windows.Forms.Application;
internal static class PdfConversionAndTimeStampTool
{
[System.STAThread]
private static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
var view = new PdfConversionAndTimeStampToolView();
var presenter = new PdfConversionAndTimeStampToolPresenter(view);
Directory.CreateDirectory(PdfProcessor.OutputPath);
Directory.CreateDirectory(PdfProcessor.ProcessingPath);
PdfProcessor.ClearProcessing();
Application.Run(view);
}
}
}
IPdfConversionAndTimeStampView.cs
using System;
using System.Collections.Generic;
namespace PdfConversionAndTimeStampTool
{
internal interface IPdfConversionAndTimeStampToolView :
IProgress<ProgressReport>
{
event Action FilesSelected;
event Action TaskRequested;
Field Field { get; }
List<string> FileNames { get; set; }
List<string> OpenFileNames { get; }
Script Script { get; }
void ClearFiles();
void ClearProgress();
void ShowMessage(string message);
void ToggleEnabled();
}
}
PdfConversionAndTimeStampToolView.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using static PdfConversionAndTimeStampTool.Properties.Resources;
namespace PdfConversionAndTimeStampTool
{
internal partial class PdfConversionAndTimeStampToolView : Form,
IPdfConversionAndTimeStampToolView
{
internal PdfConversionAndTimeStampToolView()
{
InitializeComponent();
BindComponent();
openFileDialog.Multiselect = true;
openFileDialog.Filter = OpenFileDialogFilter;
openFileDialog.Title = OpenFileDialogTitle;
}
public event Action FilesSelected;
public event Action TaskRequested;
public List<string> CheckedFileNames
{
get
{
return fileView.CheckedItems.OfType<string>().ToList();
}
}
public Field Field { get; set; }
public List<string> FileNames
{
get
{
return openFileDialog.FileNames.ToList();
}
set
{
foreach (string fileName in value)
{
fileView.Items.Add(fileName, isChecked: true);
}
}
}
public List<string> OpenFileNames
{
get
{
return fileView.Items.OfType<string>().ToList();
}
}
public Script Script { get; set; }
public void ClearFiles()
{
fileView.Items.Clear();
}
public void ClearProgress()
{
progressBar.Value = 0;
}
public void Report(ProgressReport progressReport)
{
progressBar.Value = progressReport.Percent;
}
public void ShowMessage(string message)
{
MessageBox.Show(message);
}
public void ToggleEnabled()
{
Enabled = !Enabled;
}
private void BindComponent()
{
selectFilesButton.Click += OnSelectFilesButtonClick;
convertOnlyButton.Click += OnTaskButtonClick;
convertAndTimeStampDefaultDayButton.Click += OnTaskButtonClick;
convertAndTimeStampDefaultMonthButton.Click += OnTaskButtonClick;
}
private void OnSelectFilesButtonClick(object sender, EventArgs e)
{
var DialogResult = openFileDialog.ShowDialog();
if (DialogResult == DialogResult.OK)
{
FilesSelected?.Invoke();
}
}
private void OnTaskButtonClick(object sender, EventArgs e)
{
if (CheckedFileNames.Any())
{
if (sender == convertOnlyButton)
{
Field = null;
Script = null;
}
else if (sender == convertAndTimeStampDefaultDayButton)
{
Field = Field.DefaultTimeStampField;
Script = Script.TimeStampOnPrintDefaultDayScript;
}
else if (sender == convertAndTimeStampDefaultMonthButton)
{
Field = Field.DefaultTimeStampField;
Script = Script.TimeStampOnPrintDefaultMonthScript;
}
else if (sender == addCustomFieldButton)
{
// TODO
// Field = new Field();
}
else if (sender == addCustomScriptButton)
{
// TODO
// Script = new Script();
}
TaskRequested?.Invoke();
}
else
{
ShowMessage("Please select at least one file for processing.");
}
}
}
}
PdfConversionAndTimeStampToolPresenter.cs
using System.Collections.Generic;
using System.IO;
namespace PdfConversionAndTimeStampTool
{
internal class PdfConversionAndTimeStampToolPresenter
{
private readonly IPdfConversionAndTimeStampToolView view;
internal PdfConversionAndTimeStampToolPresenter(
IPdfConversionAndTimeStampToolView view)
{
this.view = view;
this.view.FilesSelected += OnFilesSelected;
this.view.TaskRequested += OnTaskRequested;
}
private void OnFilesSelected()
{
var fileNames = new List<string>();
if (view.FileNames != null)
{
foreach (var fileName in view.FileNames)
{
if (fileName.FileNameIsContainedIn(view.OpenFileNames))
{
view.ShowMessage("File \"" +
Path.GetFileNameWithoutExtension(fileName) +
"\" is already open.");
}
else
{
fileNames.Add(FileProcessor.PrepareFile(fileName));
}
}
view.FileNames = fileNames;
}
}
private void OnTaskRequested()
{
view.ToggleEnabled();
FileProcessor.ProcessFiles(
view.OpenFileNames,
view,
view.Field,
view.Script);
view.ClearFiles();
view.ToggleEnabled();
view.ClearProgress();
view.ShowMessage("Files saved to " + FileProcessor.OutputPath);
}
}
}
FileProcessor.cs
using iTextSharp.text.pdf;
using Microsoft.Office.Core;
using Microsoft.Office.Interop.Word;
using System;
using System.Collections.Generic;
using System.IO;
using static PdfConversionAndTimeStampTool.Properties.Resources;
using static System.Environment;
namespace PdfConversionAndTimeStampTool
{
internal static class FileProcessor
{
private const int EveryOtherPage = 2;
private const int EveryPage = 1;
private const int FirstPageNumber = 1;
private const int SecondPageNumber = 2;
internal static string OutputPath
{
get
{
return Path.Combine(
GetFolderPath(SpecialFolder.MyDocuments),
RootFolderName);
}
}
internal static string ProcessingPath
{
get
{
return Path.Combine(
GetFolderPath(SpecialFolder.ApplicationData),
RootFolderName);
}
}
internal static void ClearProcessing()
{
var processingDirectory = new DirectoryInfo(ProcessingPath);
foreach (FileInfo file in processingDirectory.GetFiles())
{
file.Delete();
}
}
internal static string CopyFileToProcessing(string filename)
{
var processingPath = GetProcessingPath(filename);
File.Copy(filename, processingPath);
return processingPath;
}
internal static string PrepareFile(string fileName)
{
return CopyFileToProcessing(fileName);
}
internal static List<string> PrepareFiles(List<string> fileNames)
{
for (int i = 0; i < fileNames.Count; i++)
{
fileNames[i] = CopyFileToProcessing(fileNames[i]);
}
return fileNames;
}
internal static void ProcessFiles(
List<string> fileNames,
IProgress<ProgressReport> progressReport,
Field field = null,
Script script = null)
{
for (int i = 0; i < fileNames.Count; i++)
{
var currentFile = fileNames[i];
if (!IsPdf(currentFile))
{
currentFile = ConvertToPdf(currentFile);
}
if (field != null || script != null)
{
ProcessPdf(currentFile, field, script);
}
else
{
MovePdfToOutput(currentFile);
}
progressReport.Report(new ProgressReport
{
Total = fileNames.Count,
CurrentCount = i + 1
});
}
ClearProcessing();
}
private static void AddFieldToPage(
Field field,
int pageNumber,
PdfStamper pdfStamper,
PdfFormField parentField)
{
var textField = new TextField(
pdfStamper.Writer,
new iTextSharp.text.Rectangle(
field.TopLeftX,
field.TopLeftY,
field.BottomRightX,
field.BottomRightY),
null);
var childField = textField.GetTextField();
parentField.AddKid(childField);
childField.PlaceInPage = pageNumber;
}
private static void AddFieldToPdf(
Field field, PdfStamper pdfStamper, int numberOfPages)
{
var parentField = PdfFormField.CreateTextField(
pdfStamper.Writer, multiline:false, password:false, maxLen:0);
parentField.FieldName = field.Title;
int pageNumber = field.Pages == Pages.Last ?
numberOfPages : FirstPageNumber;
if (field.Pages == Pages.First || field.Pages == Pages.Last)
{
AddFieldToPage(
field,
pageNumber,
pdfStamper,
parentField);
}
else
{
int increment = field.Pages == Pages.All ?
EveryPage : EveryOtherPage;
if (field.Pages == Pages.Even)
{
pageNumber += 1;
}
for (; pageNumber <= numberOfPages; pageNumber += increment)
{
AddFieldToPage(
field,
pageNumber,
pdfStamper,
parentField);
}
}
pdfStamper.AddAnnotation(parentField, FirstPageNumber);
}
private static void AddScriptToPdf(
Script script, PdfStamper pdfStamper)
{
var pdfAction = PdfAction.JavaScript(
script.ScriptText, pdfStamper.Writer);
PdfName actionType = null;
switch (script.ScriptEvent)
{
case ScriptEvent.DidPrint:
actionType = PdfWriter.DID_PRINT;
break;
case ScriptEvent.DidSave:
actionType = PdfWriter.DID_SAVE;
break;
case ScriptEvent.WillPrint:
actionType = PdfWriter.WILL_PRINT;
break;
case ScriptEvent.WillSave:
actionType = PdfWriter.WILL_SAVE;
break;
}
pdfStamper.Writer.SetAdditionalAction(
actionType, pdfAction);
}
private static string ConvertToPdf(string filename)
{
var outputFilename = Path.GetFileNameWithoutExtension(filename)
+ PdfFileExtension;
var outputPath = Path.Combine(ProcessingPath, outputFilename);
var wordApplication = new Application();
wordApplication.Application.AutomationSecurity =
MsoAutomationSecurity.msoAutomationSecurityForceDisable;
var wordDocument = wordApplication.Documents.Open(filename);
var exportFormat = WdExportFormat.wdExportFormatPDF;
wordDocument.ExportAsFixedFormat(outputPath, exportFormat);
wordDocument.Close(false);
wordApplication.Quit();
return outputPath;
}
private static string GetOutputPath(string inputPath)
{
return Path.Combine(OutputPath, Path.GetFileName(inputPath));
}
private static string GetProcessingPath(string inputPath)
{
return Path.Combine(ProcessingPath, Path.GetFileName(inputPath));
}
private static bool IsPdf(string filename)
{
return string.Equals(
Path.GetExtension(filename),
PdfFileExtension,
StringComparison.InvariantCultureIgnoreCase);
}
private static string MovePdfToOutput(string filename)
{
var outputPath = GetOutputPath(filename);
File.Move(filename, outputPath);
return outputPath;
}
private static void ProcessPdf(
string filename, Field field, Script script)
{
using (var pdfReader = new PdfReader(filename))
{
using (var pdfStamper = new PdfStamper(
pdfReader,
new FileStream(GetOutputPath(filename), FileMode.Create)))
{
if (field != null)
{
AddFieldToPdf(
field,
pdfStamper,
pdfReader.NumberOfPages);
}
if (script != null)
{
AddScriptToPdf(script, pdfStamper);
}
}
}
}
}
}