Using IProgressEventHandler to report progress when saving a file
📌 Overview
This article explains how to use the new IProgressEventHandler API to report progress when saving a file in Mako. It also compares this new event-based mechanism with the existing tick-based IProgressTick approach, described in Progress Monitoring in Mako, which is commonly used for progress reporting in custom transforms.
ℹ️ Key Concepts: IProgressEventHandler vs IProgressTick
Feature | IProgressTick | IProgressEventHandler |
|---|---|---|
Reporting Style | Tick-based (integer/float increments) | Event-based (specific operation events) |
Typical Use | Custom transforms | Saving files |
Callback Signature |
|
|
Events Supported | None (just ticks) | Page write start/end, node write start/end |
Integration |
|
|
Granularity | Coarse (progress bar style) | Fine (event-driven, detailed) |
💪 Usage
Use IProgressTick for simple, linear progress reporting (e.g., custom transforms where only a percentage or tick count is needed).
Use IProgressEventHandler for more detailed, event-driven reporting (e.g., when you want to track the start/end of pages or nodes during file save operations).
⌨️ Code Examples
Event-Based Progress Reporting with IProgressEventHandler - extracts from Visual Studio Projects/ProgressMonitoring, Java Projects/ProgressMonitoring and Python Projects/ProgressMonitoring.
class ProgressEventHandler {
public:
// This section should be customised
ProgressEventHandler() : m_pageCount(0), m_nodeCount(0), m_nodeDepth(0) {}
void handleEvent(IProgressEventHandler::Event evt) {
int id;
switch (evt) {
case IProgressEventHandler::eEvtPageWriteStart:
m_pageCount++;
m_nodeCount = 0;
m_nodeDepth = 0;
m_nodes.clear();
printf("start of page %d\n", m_pageCount);
break;
case IProgressEventHandler::eEvtPageWriteEnd:
printf("end of page %d, got %d node events\n", m_pageCount, m_nodeCount);
if (m_nodeDepth != 0) printf("mismatch in node write start/end %d\n", m_nodeDepth);
break;
case IProgressEventHandler::eEvtNodeWriteStart:
id = ++m_nodeCount;
m_nodeDepth++;
m_nodes.push_back(id);
if (id % 500 == 0) {
printf("start of node %d\n", id);
}
break;
case IProgressEventHandler::eEvtNodeWriteEnd:
m_nodeDepth--;
if (m_nodes.empty())
printf("mismatch, empty nodes\n");
else {
id = m_nodes.back();
m_nodes.pop_back();
if (id % 500 == 0) {
printf("end of node %d\n", id);
}
}
break;
default:
break;
}
}
// Everything below this part can be reused
static std::shared_ptr<ProgressEventHandler> create() {
std::shared_ptr<ProgressEventHandler> progressEventHandler = std::make_shared<ProgressEventHandler>();
progressEventHandler->m_progressEventHandler = IProgressEventHandler::create(callback, progressEventHandler.get());
return progressEventHandler;
}
int m_pageCount;
int m_nodeCount;
int m_nodeDepth;
std::deque<int> m_nodes;
IProgressEventHandlerPtr m_progressEventHandler;
protected:
static void callback(void* priv, IProgressEventHandler::Event evt) {
ProgressEventHandler* progressEventHandler = (ProgressEventHandler*)priv;
if (progressEventHandler)
progressEventHandler->handleEvent(evt);
}
};
class ProgressEventHandler : IProgressEventHandlerCallback
{
private int m_pageCount;
private int m_nodeCount;
private int m_nodeDepth;
private readonly Stack<int> m_nodes = new Stack<int>();
public ProgressEventHandler()
{
m_pageCount = 0;
m_nodeCount = 0;
m_nodeDepth = 0;
}
public override void handleEvent(IProgressEventHandler.Event evt)
{
int id;
switch (evt)
{
case IProgressEventHandler.Event.eEvtPageWriteStart:
m_pageCount++;
m_nodeCount = 0;
m_nodeDepth = 0;
m_nodes.Clear();
Console.WriteLine($"start of page {m_pageCount}");
break;
case IProgressEventHandler.Event.eEvtPageWriteEnd:
Console.WriteLine($"end of page {m_pageCount}, got {m_nodeCount} node events");
if (m_nodeDepth != 0)
Console.WriteLine($"mismatch in node write start/end {m_nodeDepth}");
break;
case IProgressEventHandler.Event.eEvtNodeWriteStart:
id = ++m_nodeCount;
m_nodeDepth++;
m_nodes.Push(id);
if (id % 500 == 0)
Console.WriteLine($"start of node {id}");
break;
case IProgressEventHandler.Event.eEvtNodeWriteEnd:
m_nodeDepth--;
if (m_nodes.Count == 0)
{
Console.WriteLine("mismatch, empty nodes");
}
else
{
id = m_nodes.Pop();
if (id % 500 == 0)
Console.WriteLine($"end of node {id}");
}
break;
default:
break;
}
}
public static ProgressEventHandler Create()
{
var handler = new ProgressEventHandler();
handler.ProgressEvent = IProgressEventHandler.create(handler.getCallbackFunc(), handler.getPriv());
return handler;
}
public IProgressEventHandler ProgressEvent { get; private set; }
}
public static class ProgressEventHandler extends IProgressEventHandlerCallback
{
private int m_pageCount;
private int m_nodeCount;
private int m_nodeDepth;
private final Stack<Integer> m_nodes = new Stack<>();
private IProgressEventHandler progressEventHandler;
public ProgressEventHandler()
{
m_pageCount = 0;
m_nodeCount = 0;
m_nodeDepth = 0;
}
@Override
public void handleEvent(IProgressEventHandler.Event evt)
{
int id;
switch (evt)
{
case eEvtPageWriteStart:
m_pageCount++;
m_nodeCount = 0;
m_nodeDepth = 0;
m_nodes.clear();
System.out.printf("start of page %d%n", m_pageCount);
break;
case eEvtPageWriteEnd:
System.out.printf("end of page %d, got %d node events%n", m_pageCount, m_nodeCount);
if (m_nodeDepth != 0)
System.out.printf("mismatch in node write start/end %d%n", m_nodeDepth);
break;
case eEvtNodeWriteStart:
id = ++m_nodeCount;
m_nodeDepth++;
m_nodes.push(id);
if (id % 500 == 0)
System.out.printf("start of node %d%n", id);
break;
case eEvtNodeWriteEnd:
m_nodeDepth--;
if (m_nodes.isEmpty())
{
System.out.println("mismatch, empty nodes");
}
else
{
id = m_nodes.pop();
if (id % 500 == 0)
System.out.printf("end of node %d%n", id);
}
break;
default:
break;
}
}
public static ProgressEventHandler create()
{
ProgressEventHandler handler = new ProgressEventHandler();
handler.progressEventHandler = IProgressEventHandler.create(
handler.getCallbackFunc(),
handler.getPriv());
return handler;
}
public IProgressEventHandler getProgressEventHandler()
{
return progressEventHandler;
}
}
class ProgressEventHandler:
class Callback(IProgressEventHandlerCallback):
def __init__(self):
super().__init__()
self.m_pageCount = 0
self.m_nodeCount = 0
self.m_nodeDepth = 0
self.m_nodes = []
def handleEvent(self, evt):
if evt == IProgressEventHandler.eEvtPageWriteStart:
self.m_pageCount += 1
self.m_nodeCount = 0
self.m_nodeDepth = 0
self.m_nodes.clear()
print(f"start of page {self.m_pageCount}")
elif evt == IProgressEventHandler.eEvtPageWriteEnd:
print(f"end of page {self.m_pageCount}, got {self.m_nodeCount} node events")
if self.m_nodeDepth != 0:
print(f"mismatch in node write start/end {self.m_nodeDepth}")
elif evt == IProgressEventHandler.eEvtNodeWriteStart:
self.m_nodeCount += 1
self.m_nodeDepth += 1
self.m_nodes.append(self.m_nodeCount)
if self.m_nodeCount % 500 == 0:
print(f"start of node {self.m_nodeCount}")
elif evt == IProgressEventHandler.eEvtNodeWriteEnd:
self.m_nodeDepth -= 1
if not self.m_nodes:
print("mismatch, empty nodes")
else:
node_id = self.m_nodes.pop()
if node_id % 500 == 0:
print(f"end of node {node_id}")
def __init__(self):
super().__init__()
self.callback = ProgressEventHandler.Callback()
cbFunc = self.callback.getCallbackFunc()
cbPriv = self.callback.getPriv()
self.progressEventHandler = IProgressEventHandler.create(cbFunc, cbPriv)
Integration Example: Saving a File with Progress Events
// Use a ProgressEventHandler for the output to get more detailed information
std::shared_ptr<ProgressEventHandler> outputProgressHandler = ProgressEventHandler::create();
// Set ProgressEventHandler for output progress monitor
IProgressMonitorPtr outputProgressMonitor = IProgressMonitor::create(abort);
outputProgressMonitor->setProgressEventHandler(outputProgressHandler->m_progressEventHandler);
const IOutputPtr output = IOutput::create(jawsMako, cvtParams.m_outputFileFormat, outputProgressMonitor);
// Use a ProgressEventHandler for the output to get more detailed information
var outputHandler = ProgressEventHandler.Create();
// Set ProgressEventHandler for output progress monitor
IProgressMonitorPtr outputProgressMonitor = IProgressMonitor.create(abort);
outputProgressMonitor.setProgressEventHandler(outputHandler.ProgressEvent);
IOutput output = IOutput.create(jawsMako, cvtParams.OutputFileFormat, outputProgressMonitor);
// Use a ProgressEventHandler for the output to get more detailed information
var outputHandler = ProgressEventHandler.create();
// Set ProgressEventHandler for output progress monitor
IProgressMonitor outputProgressMonitor = IProgressMonitor.create(abort);
outputProgressMonitor.setProgressEventHandler(outputHandler.getProgressEventHandler());
IOutput output = IOutput.create(jawsMako, cvtParams.outputFileFormat, outputProgressMonitor);
# Use a ProgressEventHandler for the output to get more detailed information
output_handler = ProgressEventHandler()
# Set ProgressEventHandler for output progress monitor
output_progress_monitor = IProgressMonitor.create(abort)
output_progress_monitor.setProgressEventHandler(output_handler.progressEventHandler)
output = IOutput.create(jaws_mako, cvt_params.output_file_format, output_progress_monitor)
☑️ Conclusion
The IProgressEventHandler API provides a more flexible and granular way to report progress when saving files. For legacy or simple scenarios, IProgressTick remains available. Choose the mechanism that best fits your reporting needs.
📚 Additional Resources
If you need additional help, see our API documentation for detailed information on class/method usage, or raise a support ticket via our customer portal.