Skip to content

Detailed WinForms Walkthrough ‐ Progress Reporting with MySqlBackup.NET

adriancs edited this page Aug 5, 2025 · 3 revisions

Part 1-2: Complete WinForms Walkthrough - Progress Reporting with MySqlBackup.NET

Main Content:


Let's build some more comprehensive UI controls on WinForms

WinForms MySqlBackup.NET Progress Reporting UI Design

Let's write a slightly more comprehensive intermediary progress status caching layer class. This will store almost everything we'll need. Serves as the bridge between the main MySqlBackup.NET process and the UI.

class TaskResultProgressReport
{
    public int TaskType { get; set; }
    public DateTime TimeStart { get; set; } = DateTime.MinValue;
    public DateTime TimeEnd { get; set; } = DateTime.MinValue;
    public TimeSpan TimeUsed
    {
        get
        {
            if (TimeStart != DateTime.MinValue && TimeEnd != DateTime.MinValue)
            {
                return TimeEnd - TimeStart;
            }
            return TimeSpan.Zero;
        }
    }

    public bool IsRunning { get; set; } = false;
    public bool IsCompleted { get; set; } = false;
    public bool IsCancelled { get; set; } = false;
    public bool RequestCancel { get; set; } = false;
    public bool HasError { get; set; } = false;
    public string ErrorMsg { get; set; } = "";

    public int TotalTables { get; set; } = 0;
    public int CurrentTableIndex { get; set; } = 0;
    public string CurrentTableName { get; set; } = "";
    public long TotalRows { get; set; } = 0L;
    public long CurrentRows { get; set; } = 0L;
    public long TotalRowsCurrentTable { get; set; } = 0L;
    public long CurrentRowsCurrentTable { get; set; } = 0L;

    public long TotalBytes { get; set; }
    public long CurrentBytes { get; set; }

    public string TaskName
    {
        get
        {
            if (TaskType == 1)
                return "Backup";
            else if (TaskType == 2)
                return "Restore";
            return "---";
        }
    }

    public int Percent_TotalRows
    {
        get
        {
            if (CurrentRows == 0L || TotalRows == 0L)
                return 0;

            if (CurrentRows >= TotalRows)
                return 100;

            if (CurrentRows > 0L && TotalRows > 0L)
                return (int)((double)CurrentRows * 100.0 / (double)TotalRows);

            return 0;
        }
    }

    public int Percent_TotalRowsTable
    {
        get
        {
            if (CurrentRowsCurrentTable == 0L || TotalRowsCurrentTable == 0L)
                return 0;

            if (CurrentRowsCurrentTable >= TotalRowsCurrentTable)
                return 100;

            if (CurrentRowsCurrentTable > 0L && TotalRowsCurrentTable > 0L)
                return (int)((double)CurrentRowsCurrentTable * 100.0 / (double)TotalRowsCurrentTable);

            return 0;
        }
    }

    public int Percent_TotalTables
    {
        get
        {
            if (CurrentTableIndex == 0 || TotalTables == 0)
                return 0;

            if (CurrentTableIndex >= TotalTables)
                return 100;

            if (CurrentTableIndex > 0 && TotalTables > 0)
                return (int)((double)CurrentTableIndex * 100.0 / (double)TotalTables);

            return 0;
        }
    }

    public int Percent_TotalBytes
    {
        get
        {
            if (CurrentBytes == 0L || TotalBytes == 0L)
                return 0;

            if (CurrentBytes >= TotalBytes)
                return 100;

            if (CurrentBytes >= 0L && TotalBytes >= 0L)
                return (int)((double)CurrentBytes * 100.0 / (double)TotalBytes);

            return 0;
        }
    }

    public string TimeStartDisplay
    {
        get
        {
            if (TimeStart != DateTime.MinValue)
            {
                return TimeStart.ToString("yyyy-MM-dd HH:mm:ss");
            }

            return "---";
        }
    }

    public string TimeEndDisplay
    {
        get
        {
            if (TimeEnd != DateTime.MinValue)
            {
                return TimeEnd.ToString("yyyy-MM-dd HH:mm:ss");
            }

            return "---";
        }
    }

    public string TimeUsedDisplay
    {
        get
        {
            if (TimeUsed != TimeSpan.Zero)
            {
                return $"{TimeUsed.Hours}h {TimeUsed.Minutes}m {TimeUsed.Seconds}s {TimeUsed.Milliseconds}ms";
            }

            return "---";
        }
    }

    public void Reset()
    {
        TaskType = 0;
        TimeStart = DateTime.MinValue;
        TimeEnd = DateTime.MinValue;
        IsRunning = false;
        IsCompleted = false;
        IsCancelled = false;
        RequestCancel = false;
        HasError = false;
        ErrorMsg = string.Empty;
        TotalTables = 0;
        CurrentTableIndex = 0;
        TotalRows = 0;
        CurrentRows = 0;
        TotalRowsCurrentTable = 0;
        CurrentRowsCurrentTable = 0;
        TotalBytes = 0;
        CurrentBytes = 0;
    }
}

We'll declare the class of TaskResultProgressReport at the global class level with volatile modifier.

In C#, the volatile keyword is a modifier that makes the field thread safe, which it can safely be accessed (read and write) by multiple threads.

In our case, it is indeed going to be read and write by two main threads, one thread represents MySqlBackup.NET which will write status data to it, and another thread represents the main UI which it will read the values to update the UI components.

public partial class FormProgressReport : baseForm
{
    // global class level data caching
    volatile TaskResultProgressReport taskInfo = new TaskResultProgressReport();
    
    // a global timer serves as another thread for updating the progress values
    Timer timerUpdateProgress = new Timer();
    
    public FormProgressReport()
    {
        InitializeComponent();
        
        // 200 milliseconds
        // update 5 times per second
        timerUpdateProgress.Interval = 200;
        
        // run the update for each time elapsed
        timerUpdateProgress.Tick += TimerUpdateProgress_Tick;
    }
}

The timer (3rd thread) triggered for updating the UI:

private void TimerUpdateProgress_Tick(object sender, EventArgs e)
{
    // obtaining values from global caching data and update the UI components

    lbTaskType.Text = taskInfo.TaskName;
    lbTimeStart.Text = taskInfo.TimeStartDisplay;
    lbTimeEnd.Text = taskInfo.TimeEndDisplay;
    lbTimeUsed.Text = taskInfo.TimeUsedDisplay;

    lbIsCompleted.Text = taskInfo.IsCompleted.ToString();
    lbIsCancelled.Text = taskInfo.IsCancelled.ToString();
    lbHasError.Text = taskInfo.HasError.ToString();
    lbErrorMsg.Text = taskInfo.ErrorMsg;

    if (taskInfo.TaskType == 1)
    {
        lbTotalRows.Text = $"{taskInfo.CurrentRows} / {taskInfo.TotalRows} ({taskInfo.Percent_TotalRows}%)";
        lbTotalTables.Text = $"{taskInfo.CurrentTableIndex} / {taskInfo.TotalTables} ({taskInfo.Percent_TotalTables}%)";
        lbRowsCurrentTable.Text = $"{taskInfo.CurrentRowsCurrentTable} / {taskInfo.TotalRowsCurrentTable} ({taskInfo.Percent_TotalRowsTable}%)";

        progressBar_TotalRows.Value = taskInfo.Percent_TotalRows;
        progressBar_TotalTables.Value = taskInfo.Percent_TotalTables;
        progressBar_RowsCurrentTable.Value = taskInfo.Percent_TotalRowsTable;
    }
    else
    {
        lbTotalBytes.Text = $"{taskInfo.CurrentBytes} / {taskInfo.TotalBytes} ({taskInfo.Percent_TotalBytes}%)";
        progressBar_TotalBytes.Value = taskInfo.Percent_TotalBytes;
    }

    // task completion detection
    // this is where UI thread will call the timer to stop updating values

    if (taskInfo.IsCompleted)
    {
        // stop the timer
        timerUpdateProgress.Stop();
        
        // displaying the message somewhere in the UI
        // require your own implementation
        WriteStatus($"{taskInfo.TaskName} task is stopped/completed");
    }

    if (taskInfo.HasError)
    {
        // displaying additional message somewhere in the UI
        WriteStatus($"{taskInfo.TaskName} task has error");
        WriteStatus($"{taskInfo.ErrorMsg}");
    }

    if (taskInfo.IsCancelled)
    {
        WriteStatus($"{taskInfo.TaskName} is cancelled by user");
    }
}

The UI is being updated...

WinForms MySqlBackup.NET Progress Reporting UI Updating

Next, the two main buttons that will begin the process in another background thread:

private void btBackup_Click(object sender, EventArgs e)
{
    string sqlFilePath = @"D:\backup.sql";
    
    // reset all the UI into initial state
    ResetUI();
    
    int taskType = 1; // backup
    
    // run the task in background thread
    _ = Task.Run(() => { BeginTask(taskType, txtConstr.Text, sqlFilePath); });
    
    // start the timer to update the UI
    timerUpdateProgress.Start();
}

private void btRestore_Click(object sender, EventArgs e)
{
    string sqlFilePath = @"D:\backup.sql"; 
    
    // reset all the UI into initial state
    ResetUI();
    
    int taskType = 2; // restore
    
    // run the task in another thread (run in background)
    _ = Task.Run(() => { BeginTask(taskType, txtConstr.Text, sqlFilePath); });
    
    // start the timer 
    timerUpdateProgress.Start();
}

Running the backup and restore task in background:

void BeginTask(int taskType, string constr, string sqlFile)
{
    // first, mark the task is busy running
    taskInfo.IsRunning = true;
    
    // reset all values
    taskInfo.Reset();

    taskInfo.TaskType = taskType;

    taskInfo.TimeStart = DateTime.Now;

    try
    {
        using (var conn = new MySqlConnection(constr))
        using (var cmd = conn.CreateCommand())
        using (var mb = new MySqlBackup(cmd))
        {
            conn.Open();

            if (taskType == 1)
            {
                mb.ExportInfo.IntervalForProgressReport = 200;
                mb.ExportProgressChanged += Mb_ExportProgressChanged;
                mb.ExportToFile(sqlFile);
            }
            else if (taskType == 2)
            {
                // for better real-time accuracy
                mb.ImportInfo.EnableParallelProcessing = false;
                mb.ImportInfo.IntervalForProgressReport = 200;
                mb.ImportProgressChanged += Mb_ImportProgressChanged;
                mb.ImportFromFile(sqlFile);
            }
        }
    }
    catch (Exception ex)
    {
        taskInfo.HasError = true;
        taskInfo.ErrorMsg = ex.Message;
    }

    // reaching this line, MySqlBackup.NET has already finished it's process
    // it could be either a cancelled,
    // or a complete success task

    // identifying the cancellation request
    if (taskInfo.RequestCancel)
    {
        // officially mark the task as cancelled
        taskInfo.IsCancelled = true;
    }

    taskInfo.TimeEnd = DateTime.Now;
    
    // officially announcing the completion, 
    // so that the UI thread can use this reference to stop the timer
    taskInfo.IsCompleted = true;

    // task completed
    taskInfo.IsRunning = false;
}

// writing all the progress status values to the global caching class

private void Mb_ExportProgressChanged(object sender, ExportProgressArgs e)
{
    taskInfo.TotalRows = e.TotalRowsInAllTables;
    taskInfo.CurrentRows = e.CurrentRowIndexInAllTables;

    taskInfo.TotalTables = e.TotalTables;
    taskInfo.CurrentTableIndex = e.CurrentTableIndex;

    taskInfo.TotalRowsCurrentTable = e.TotalRowsInCurrentTable;
    taskInfo.CurrentRowsCurrentTable = e.CurrentRowIndexInCurrentTable;

    taskInfo.CurrentTableName = e.CurrentTableName;

    // detecting the cancellation signal
    if (taskInfo.RequestCancel)
    {
        // signal MySqlBackup.NET to terminate
        ((MySqlBackup)sender).StopAllProcess();
    }
}

private void Mb_ImportProgressChanged(object sender, ImportProgressArgs e)
{
    taskInfo.TotalBytes = e.TotalBytes;
    taskInfo.CurrentBytes = e.CurrentBytes;

    // detecting the cancellation signal
    if (taskInfo.RequestCancel)
    {
        // signal MySqlBackup.NET to terminate
        ((MySqlBackup)sender).StopAllProcess();
    }
}

The termination request:

private void btStop_Click(object sender, EventArgs e)
{
    if (taskInfo.IsRunning)
    {
        // mark the boolean flag to signal the termination/cancellation
        taskInfo.RequestCancel = true;
    }
}

Done.

Full Implementation Source Code

Clone this wiki locally