Download with progress and cancellation
Keep the interface responsive while a file downloads safely.
StartDownload returns an owned operation. WaitAsync produces the byte count, while a Timer samples progress. TotalBytes can be unknown, so use an indeterminate progress bar in that case. QueryUnload cancels and delays closure; Finally restores controls and stops the timer on every exit path.
Step 1Prepare a safe practice project#
Extract the archive into a new writable folder, separate from your own application. Open HttpDownloadDemo.mcproj in McBasic™. Keep every declared source, layout, resource and migration file together.
To reconstruct it yourself, create a DesktopApp project and add the files declared below. In the complete project, follow these steps as a guided rebuild: replace each named source file once and Check after all its procedures are present.
Exact project configuration
{
"schemaVersion": 1,
"projectId": "b728c63f-2ee1-4ab6-a2ec-deba701fa709",
"name": "HttpDownloadDemo",
"kind": "DesktopApp",
"language": {
"dialect": "McBasic1",
"requireExplicit": true,
"compare": "Binary",
"defaultArrayBase": 0
},
"application": {
"bundleIdentifier": "com.mcbasic.samples.httpdownloaddemo",
"version": "0.1.0",
"buildNumber": "1",
"startupFormId": "form-main",
"shutdownMode": "OnStartupFormClosed"
},
"forms": [
{
"id": "form-main",
"typeName": "frmMain",
"layout": "Forms/frmMain.form.json",
"code": "Forms/frmMain.bas"
}
],
"modules": [],
"classes": [],
"tests": [],
"references": [],
"resources": [],
"targets": [
"osx-arm64"
],
"capabilities": []
}Step 2Build the Form and set its properties#
frmMain
Select the Form first and set its properties. Add containers before their children. These are initial Designer values, not statements to paste into a procedure.
| Form property | Value |
|---|---|
Caption | "HTTP Download — McBasic" |
Width | 610 |
Height | 310 |
MinWidth | 610 |
MinHeight | 310 |
| Type | Name | Parent |
|---|---|---|
| TextBox | txtUrl | Form |
| TextBox | txtDestination | Form |
| CommandButton | cmdDownload | Form |
| CommandButton | cmdCancel | Form |
| ProgressBar | barDownload | Form |
| Label | lblProgress | Form |
| Timer | tmrProgress | Component tray |
txtUrl · exact Inspector values
| Property | Value |
|---|---|
Left | 24 |
Top | 24 |
Width | 550 |
Height | 34 |
Text | "http://127.0.0.1:8080/binary" |
TabIndex | 0 |
| Anchor edges | Left, Top |
txtDestination · exact Inspector values
| Property | Value |
|---|---|
Left | 24 |
Top | 70 |
Width | 550 |
Height | 34 |
Text | "download.bin" |
TabIndex | 1 |
| Anchor edges | Left, Top |
cmdDownload · exact Inspector values
| Property | Value |
|---|---|
Left | 24 |
Top | 120 |
Width | 150 |
Height | 34 |
Caption | "Download" |
TabIndex | 2 |
| Anchor edges | Left, Top |
cmdCancel · exact Inspector values
| Property | Value |
|---|---|
Left | 194 |
Top | 120 |
Width | 150 |
Height | 34 |
Caption | "Cancel" |
Enabled | False |
TabIndex | 3 |
| Anchor edges | Left, Top |
barDownload · exact Inspector values
| Property | Value |
|---|---|
Left | 24 |
Top | 178 |
Width | 550 |
Height | 24 |
Minimum | 0 |
Maximum | 100 |
Value | 0 |
Indeterminate | False |
| Anchor edges | Left, Top |
lblProgress · exact Inspector values
| Property | Value |
|---|---|
Left | 24 |
Top | 218 |
Width | 550 |
Height | 38 |
Caption | "Ready. Existing destination is replaced only after success." |
| Anchor edges | Left, Top |
tmrProgress · exact Inspector values
| Property | Value |
|---|---|
Enabled | False |
Interval | 100 |
Step 3Connect the events explicitly#
frmMain
| Object | Event | Procedure | Parameters |
|---|---|---|---|
cmdDownload | Click | cmdDownload_Click | Click() |
cmdCancel | Click | cmdCancel_Click | Click() |
tmrProgress | Timer | tmrProgress_Timer | Timer() |
| Form | QueryUnload | Form_QueryUnload | (ByRef Cancel As Integer, ByVal UnloadMode As Integer) |
Step 4Enter the complete behavior#
Open the named source file. Enter declarations first, then every procedure. Expand a procedure for its full body, or copy the complete file. Helpers are required: do not stop after entering the first Click handler.
Forms/frmMain.bas
Enter these declarations at file scope, before the procedures.
Option Explicit
Private active As HttpDownload
Private busy As Boolean
Private closeRequested As BooleanPrivate Async Sub cmdDownload_Click()
Follow the success, failure and cleanup paths together. Do not omit a guard or Finally block when entering this procedure.
Private Async Sub cmdDownload_Click()
Dim saved As LongLong
If busy Then Exit Sub
busy = True
cmdDownload.Enabled = False
cmdCancel.Enabled = True
barDownload.Indeterminate = False
barDownload.Value = 0
lblProgress.Caption = "Starting..."
Try
Using cancellation As CancellationSource = Http.CreateCancellationSource()
Set active = Http.StartDownload(txtUrl.Text, txtDestination.Text, 10000, cancellation, True)
tmrProgress.Enabled = True
Using owned As HttpDownload = active
saved = Await owned.WaitAsync()
End Using
End Using
barDownload.Indeterminate = False
barDownload.Value = 100
lblProgress.Caption = "Complete: " & CStr(saved) & " bytes"
Catch error As McException
If error.Number = 18 Then
lblProgress.Caption = "Cancelled; prior file preserved."
Else
lblProgress.Caption = "Error " & CStr(error.Number) & "; prior file preserved."
End If
Finally
tmrProgress.Enabled = False
barDownload.Indeterminate = False
busy = False
cmdDownload.Enabled = True
cmdCancel.Enabled = False
If closeRequested Then Unload Me
End Try
End SubPrivate Sub cmdCancel_Click()
Enter the complete action, including its input checks and calls to helper procedures.
Private Sub cmdCancel_Click()
If busy Then active.Cancel()
End SubPrivate Sub tmrProgress_Timer()
Enter this helper in the named source file, outside every other procedure. Its callers are included in the same project.
Private Sub tmrProgress_Timer()
If Not busy Then Exit Sub
lblProgress.Caption = CStr(active.BytesReceived) & "/" & CStr(active.TotalBytes) & " bytes"
If active.TotalBytes > 0 Then
barDownload.Indeterminate = False
barDownload.Value = CLng(CDbl(active.BytesReceived) / CDbl(active.TotalBytes) * 100)
Else
barDownload.Indeterminate = True
End If
End SubPrivate Sub Form_QueryUnload(ByRef Cancel As Integer, ByVal UnloadMode As Integer)
This is the lifetime boundary. Preserve the cancellation signature where present and release resources owned by this Form.
Private Sub Form_QueryUnload(ByRef Cancel As Integer, ByVal UnloadMode As Integer)
If busy Then
Cancel = 1
closeRequested = True
tmrProgress.Enabled = False
lblProgress.Caption = "Cancelling before close..."
active.Cancel()
End If
End SubCopy the complete Forms/frmMain.bas file
Replace the file once; do not append duplicate procedures.
Option Explicit
Private active As HttpDownload
Private busy As Boolean
Private closeRequested As Boolean
Private Async Sub cmdDownload_Click()
Dim saved As LongLong
If busy Then Exit Sub
busy = True
cmdDownload.Enabled = False
cmdCancel.Enabled = True
barDownload.Indeterminate = False
barDownload.Value = 0
lblProgress.Caption = "Starting..."
Try
Using cancellation As CancellationSource = Http.CreateCancellationSource()
Set active = Http.StartDownload(txtUrl.Text, txtDestination.Text, 10000, cancellation, True)
tmrProgress.Enabled = True
Using owned As HttpDownload = active
saved = Await owned.WaitAsync()
End Using
End Using
barDownload.Indeterminate = False
barDownload.Value = 100
lblProgress.Caption = "Complete: " & CStr(saved) & " bytes"
Catch error As McException
If error.Number = 18 Then
lblProgress.Caption = "Cancelled; prior file preserved."
Else
lblProgress.Caption = "Error " & CStr(error.Number) & "; prior file preserved."
End If
Finally
tmrProgress.Enabled = False
barDownload.Indeterminate = False
busy = False
cmdDownload.Enabled = True
cmdCancel.Enabled = False
If closeRequested Then Unload Me
End Try
End Sub
Private Sub cmdCancel_Click()
If busy Then active.Cancel()
End Sub
Private Sub tmrProgress_Timer()
If Not busy Then Exit Sub
lblProgress.Caption = CStr(active.BytesReceived) & "/" & CStr(active.TotalBytes) & " bytes"
If active.TotalBytes > 0 Then
barDownload.Indeterminate = False
barDownload.Value = CLng(CDbl(active.BytesReceived) / CDbl(active.TotalBytes) * 100)
Else
barDownload.Indeterminate = True
End If
End Sub
Private Sub Form_QueryUnload(ByRef Cancel As Integer, ByVal UnloadMode As Integer)
If busy Then
Cancel = 1
closeRequested = True
tmrProgress.Enabled = False
lblProgress.Caption = "Cancelling before close..."
active.Cancel()
End If
End SubStep 5Keep resources and storage reproducible#
No external resource or migration files are declared. Any inline migration, seed data or local JSON is present in the code above. Runtime application data and private preferences are not included in the archive.
Step 6Check, run and test the result#
Save all files and run Check. Resolve diagnostics before Run. Developer CLI users can work from the extracted folder:
mcbasic check HttpDownloadDemo.mcproj
mcbasic run HttpDownloadDemo.mcprojAcceptance checklist
- Choose an HTTPS file you control and an existing writable destination directory.
- Start a download and confirm controls switch to the busy state.
- Cancel and confirm the prior destination file is preserved.
- Start again and confirm a successful byte count.
- Close during download and confirm cancellation finishes before the Form closes.
Step 7Make one change and repeat the tests#
Use a deliberately slow test endpoint to verify unknown-length progress and cancellation.
Save a working Git checkpoint first. Change one behavior, Check again and repeat the acceptance checklist. Compare with the downloaded solution if a regression appears.
20f4bfed6b · Build manifest