Instruções: Utilizar o Fluxo de Dados numa Aplicação Windows Forms
Este artigo demonstra como criar uma rede de blocos de fluxo de dados que efetuam o processamento de imagens numa aplicação Windows Forms.
Este exemplo carrega ficheiros de imagem da pasta especificada, cria uma imagem composta e apresenta o resultado. O exemplo utiliza o modelo de fluxo de dados para encaminhar imagens através da rede. No modelo de fluxo de dados, os componentes independentes de um programa comunicam entre si ao enviar mensagens. Quando um componente recebe uma mensagem, executa alguma ação e, em seguida, transmite o resultado para outro componente. Compare-o com o modelo de fluxo de controlo, no qual uma aplicação utiliza estruturas de controlo, por exemplo, instruções condicionais, ciclos, etc., para controlar a ordem das operações num programa.
Pré-requisitos
Leia o Fluxo de Dados antes de iniciar estas instruções.
Nota
A Biblioteca de Fluxos de Dados TPL (o System.Threading.Tasks.Dataflow espaço de nomes) não é distribuída com .NET. Para instalar o System.Threading.Tasks.Dataflow espaço de nomes no Visual Studio, abra o seu projeto, selecione Gerir Pacotes NuGet no menu Projeto e procure o System.Threading.Tasks.Dataflow
pacote online. Em alternativa, para instalá-lo com a CLI do .NET Core, execute dotnet add package System.Threading.Tasks.Dataflow
.
Secções
Estas instruções contêm as seguintes secções:
Criar a Aplicação Windows Forms
Esta secção descreve como criar a aplicação de Windows Forms básica e adicionar controlos ao formulário principal.
Para Criar a Aplicação Windows Forms
No Visual Studio, crie um projeto visual C# ou Visual Basic Windows Forms Application. Neste documento, o projeto tem o nome
CompositeImages
.No estruturador de formulários do formulário principal, Form1.cs (Form1.vb para Visual Basic), adicione um ToolStrip controlo.
Adicione um ToolStripButton controlo ao ToolStrip controlo. Defina a DisplayStyle propriedade como Text e a Text propriedade como Escolher Pasta.
Adicione um segundo ToolStripButton controlo ao ToolStrip controlo. Defina a DisplayStyle propriedade como Text, a Text propriedade como Cancelar e a Enabled propriedade como
False
.Adicione um PictureBox objeto ao formulário principal. Defina a Dock propriedade como Fill.
Criar a Rede de Fluxo de Dados
Esta secção descreve como criar a rede de fluxo de dados que executa o processamento de imagens.
Para Criar a Rede de Fluxo de Dados
Adicione uma referência ao System.Threading.Tasks.Dataflow.dll ao seu projeto.
Certifique-se de que Form1.cs (Form1.vb para Visual Basic) contém as seguintes
using
instruções (Using
no Visual Basic):using System; using System.Collections.Generic; using System.Drawing; using System.Drawing.Imaging; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using System.Threading.Tasks.Dataflow; using System.Windows.Forms;
Adicione os seguintes membros de dados à
Form1
classe :// The head of the dataflow network. ITargetBlock<string> headBlock = null; // Enables the user interface to signal cancellation to the network. CancellationTokenSource cancellationTokenSource;
Adicione o seguinte método,
CreateImageProcessingNetwork
, àForm1
classe . Este método cria a rede de processamento de imagens.// Creates the image processing dataflow network and returns the // head node of the network. ITargetBlock<string> CreateImageProcessingNetwork() { // // Create the dataflow blocks that form the network. // // Create a dataflow block that takes a folder path as input // and returns a collection of Bitmap objects. var loadBitmaps = new TransformBlock<string, IEnumerable<Bitmap>>(path => { try { return LoadBitmaps(path); } catch (OperationCanceledException) { // Handle cancellation by passing the empty collection // to the next stage of the network. return Enumerable.Empty<Bitmap>(); } }); // Create a dataflow block that takes a collection of Bitmap objects // and returns a single composite bitmap. var createCompositeBitmap = new TransformBlock<IEnumerable<Bitmap>, Bitmap>(bitmaps => { try { return CreateCompositeBitmap(bitmaps); } catch (OperationCanceledException) { // Handle cancellation by passing null to the next stage // of the network. return null; } }); // Create a dataflow block that displays the provided bitmap on the form. var displayCompositeBitmap = new ActionBlock<Bitmap>(bitmap => { // Display the bitmap. pictureBox1.SizeMode = PictureBoxSizeMode.StretchImage; pictureBox1.Image = bitmap; // Enable the user to select another folder. toolStripButton1.Enabled = true; toolStripButton2.Enabled = false; Cursor = DefaultCursor; }, // Specify a task scheduler from the current synchronization context // so that the action runs on the UI thread. new ExecutionDataflowBlockOptions { TaskScheduler = TaskScheduler.FromCurrentSynchronizationContext() }); // Create a dataflow block that responds to a cancellation request by // displaying an image to indicate that the operation is cancelled and // enables the user to select another folder. var operationCancelled = new ActionBlock<object>(delegate { // Display the error image to indicate that the operation // was cancelled. pictureBox1.SizeMode = PictureBoxSizeMode.CenterImage; pictureBox1.Image = pictureBox1.ErrorImage; // Enable the user to select another folder. toolStripButton1.Enabled = true; toolStripButton2.Enabled = false; Cursor = DefaultCursor; }, // Specify a task scheduler from the current synchronization context // so that the action runs on the UI thread. new ExecutionDataflowBlockOptions { TaskScheduler = TaskScheduler.FromCurrentSynchronizationContext() }); // // Connect the network. // // Link loadBitmaps to createCompositeBitmap. // The provided predicate ensures that createCompositeBitmap accepts the // collection of bitmaps only if that collection has at least one member. loadBitmaps.LinkTo(createCompositeBitmap, bitmaps => bitmaps.Count() > 0); // Also link loadBitmaps to operationCancelled. // When createCompositeBitmap rejects the message, loadBitmaps // offers the message to operationCancelled. // operationCancelled accepts all messages because we do not provide a // predicate. loadBitmaps.LinkTo(operationCancelled); // Link createCompositeBitmap to displayCompositeBitmap. // The provided predicate ensures that displayCompositeBitmap accepts the // bitmap only if it is non-null. createCompositeBitmap.LinkTo(displayCompositeBitmap, bitmap => bitmap != null); // Also link createCompositeBitmap to operationCancelled. // When displayCompositeBitmap rejects the message, createCompositeBitmap // offers the message to operationCancelled. // operationCancelled accepts all messages because we do not provide a // predicate. createCompositeBitmap.LinkTo(operationCancelled); // Return the head of the network. return loadBitmaps; }
Implemente o
LoadBitmaps
método .// Loads all bitmap files that exist at the provided path. IEnumerable<Bitmap> LoadBitmaps(string path) { List<Bitmap> bitmaps = new List<Bitmap>(); // Load a variety of image types. foreach (string bitmapType in new string[] { "*.bmp", "*.gif", "*.jpg", "*.png", "*.tif" }) { // Load each bitmap for the current extension. foreach (string fileName in Directory.GetFiles(path, bitmapType)) { // Throw OperationCanceledException if cancellation is requested. cancellationTokenSource.Token.ThrowIfCancellationRequested(); try { // Add the Bitmap object to the collection. bitmaps.Add(new Bitmap(fileName)); } catch (Exception) { // TODO: A complete application might handle the error. } } } return bitmaps; }
Implemente o
CreateCompositeBitmap
método .// Creates a composite bitmap from the provided collection of Bitmap objects. // This method computes the average color of each pixel among all bitmaps // to create the composite image. Bitmap CreateCompositeBitmap(IEnumerable<Bitmap> bitmaps) { Bitmap[] bitmapArray = bitmaps.ToArray(); // Compute the maximum width and height components of all // bitmaps in the collection. Rectangle largest = new Rectangle(); foreach (var bitmap in bitmapArray) { if (bitmap.Width > largest.Width) largest.Width = bitmap.Width; if (bitmap.Height > largest.Height) largest.Height = bitmap.Height; } // Create a 32-bit Bitmap object with the greatest dimensions. Bitmap result = new Bitmap(largest.Width, largest.Height, PixelFormat.Format32bppArgb); // Lock the result Bitmap. var resultBitmapData = result.LockBits( new Rectangle(new Point(), result.Size), ImageLockMode.WriteOnly, result.PixelFormat); // Lock each source bitmap to create a parallel list of BitmapData objects. var bitmapDataList = (from bitmap in bitmapArray select bitmap.LockBits( new Rectangle(new Point(), bitmap.Size), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb)) .ToList(); // Compute each column in parallel. Parallel.For(0, largest.Width, new ParallelOptions { CancellationToken = cancellationTokenSource.Token }, i => { // Compute each row. for (int j = 0; j < largest.Height; j++) { // Counts the number of bitmaps whose dimensions // contain the current location. int count = 0; // The sum of all alpha, red, green, and blue components. int a = 0, r = 0, g = 0, b = 0; // For each bitmap, compute the sum of all color components. foreach (var bitmapData in bitmapDataList) { // Ensure that we stay within the bounds of the image. if (bitmapData.Width > i && bitmapData.Height > j) { unsafe { byte* row = (byte*)(bitmapData.Scan0 + (j * bitmapData.Stride)); byte* pix = (byte*)(row + (4 * i)); a += *pix; pix++; r += *pix; pix++; g += *pix; pix++; b += *pix; } count++; } } //prevent divide by zero in bottom right pixelless corner if (count == 0) break; unsafe { // Compute the average of each color component. a /= count; r /= count; g /= count; b /= count; // Set the result pixel. byte* row = (byte*)(resultBitmapData.Scan0 + (j * resultBitmapData.Stride)); byte* pix = (byte*)(row + (4 * i)); *pix = (byte)a; pix++; *pix = (byte)r; pix++; *pix = (byte)g; pix++; *pix = (byte)b; } } }); // Unlock the source bitmaps. for (int i = 0; i < bitmapArray.Length; i++) { bitmapArray[i].UnlockBits(bitmapDataList[i]); } // Unlock the result bitmap. result.UnlockBits(resultBitmapData); // Return the result. return result; }
Nota
A versão C# do
CreateCompositeBitmap
método utiliza ponteiros para permitir o System.Drawing.Bitmap processamento eficiente dos objetos. Por conseguinte, tem de ativar a opção Permitir código não seguro no seu projeto para utilizar a palavra-chave não segura . Para obter mais informações sobre como ativar código não seguro num projeto do Visual C#, consulte Build Page, Project Designer (C#).
A tabela seguinte descreve os membros da rede.
Membro | Tipo | Description |
---|---|---|
loadBitmaps |
TransformBlock<TInput,TOutput> | Assume um caminho de pasta como entrada e produz uma coleção de Bitmap objetos como saída. |
createCompositeBitmap |
TransformBlock<TInput,TOutput> | Utiliza uma coleção de Bitmap objetos como entrada e produz um mapa de bits composto como saída. |
displayCompositeBitmap |
ActionBlock<TInput> | Apresenta o mapa de bits composto no formulário. |
operationCancelled |
ActionBlock<TInput> | Apresenta uma imagem para indicar que a operação foi cancelada e permite que o utilizador selecione outra pasta. |
Para ligar os blocos de fluxo de dados para formar uma rede, este exemplo utiliza o LinkTo método . O LinkTo método contém uma versão sobrecarregada que utiliza um Predicate<T> objeto que determina se o bloco de destino aceita ou rejeita uma mensagem. Este mecanismo de filtragem permite que os blocos de mensagens recebam apenas determinados valores. Neste exemplo, a rede pode ramificar de uma de duas formas. O ramo principal carrega as imagens do disco, cria a imagem composta e apresenta essa imagem no formulário. O ramo alternativo cancela a operação atual. Os Predicate<T> objetos permitem que os blocos de fluxo de dados ao longo do ramo principal mudem para o ramo alternativo ao rejeitar determinadas mensagens. Por exemplo, se o utilizador cancelar a operação, o bloco createCompositeBitmap
de fluxo de dados produz null
(Nothing
no Visual Basic) como resultado. O bloco displayCompositeBitmap
de fluxo de dados rejeita os valores null
de entrada e, por conseguinte, a mensagem é disponibilizada a operationCancelled
. O bloco operationCancelled
de fluxo de dados aceita todas as mensagens e, por conseguinte, apresenta uma imagem para indicar que a operação foi cancelada.
A ilustração seguinte mostra a rede de processamento de imagens:
Uma vez que os displayCompositeBitmap
blocos de fluxo de dados e operationCancelled
atuam na interface de utilizador, é importante que estas ações ocorram no thread da interface de utilizador. Para tal, durante a construção, cada um destes objetos fornece um ExecutionDataflowBlockOptions objeto que tem a TaskScheduler propriedade definida como TaskScheduler.FromCurrentSynchronizationContext. O TaskScheduler.FromCurrentSynchronizationContext método cria um TaskScheduler objeto que executa o trabalho no contexto de sincronização atual. Uma vez que o CreateImageProcessingNetwork
método é chamado a partir do processador do botão Escolher Pasta , que é executado no thread da interface de utilizador, as ações dos displayCompositeBitmap
blocos de fluxo de dados e operationCancelled
também são executadas no thread da interface de utilizador.
Este exemplo utiliza um token de cancelamento partilhado em vez de definir a CancellationToken propriedade porque a propriedade cancela permanentemente a CancellationToken execução do bloco de fluxo de dados. Um token de cancelamento permite que este exemplo reutilize a mesma rede de fluxo de dados várias vezes, mesmo quando o utilizador cancela uma ou mais operações. Para obter um exemplo que utiliza CancellationToken para cancelar permanentemente a execução de um bloco de fluxo de dados, veja Como: Cancelar um Bloco de Fluxo de Dados.
Ligar a Rede de Fluxo de Dados à Interface de Utilizador
Esta secção descreve como ligar a rede de fluxo de dados à interface de utilizador. A criação da imagem composta e o cancelamento da operação são iniciados a partir dos botões Escolher Pasta e Cancelar . Quando o utilizador escolhe um destes botões, a ação adequada é iniciada de forma assíncrona.
Para ligar a Rede de Fluxo de Dados à Interface de Utilizador
No estruturador de formulários do formulário principal, crie um processador de eventos para o Click evento para o botão Escolher Pasta .
Implemente o Click evento para o botão Escolher Pasta .
// Event handler for the Choose Folder button. private void toolStripButton1_Click(object sender, EventArgs e) { // Create a FolderBrowserDialog object to enable the user to // select a folder. FolderBrowserDialog dlg = new FolderBrowserDialog { ShowNewFolderButton = false }; // Set the selected path to the common Sample Pictures folder // if it exists. string initialDirectory = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.CommonPictures), "Sample Pictures"); if (Directory.Exists(initialDirectory)) { dlg.SelectedPath = initialDirectory; } // Show the dialog and process the dataflow network. if (dlg.ShowDialog() == DialogResult.OK) { // Create a new CancellationTokenSource object to enable // cancellation. cancellationTokenSource = new CancellationTokenSource(); // Create the image processing network if needed. headBlock ??= CreateImageProcessingNetwork(); // Post the selected path to the network. headBlock.Post(dlg.SelectedPath); // Enable the Cancel button and disable the Choose Folder button. toolStripButton1.Enabled = false; toolStripButton2.Enabled = true; // Show a wait cursor. Cursor = Cursors.WaitCursor; } }
No estruturador de formulários do formulário principal, crie um processador de eventos para o Click evento para o botão Cancelar .
Implemente o Click evento para o botão Cancelar .
// Event handler for the Cancel button. private void toolStripButton2_Click(object sender, EventArgs e) { // Signal the request for cancellation. The current component of // the dataflow network will respond to the cancellation request. cancellationTokenSource.Cancel(); }
O Exemplo Completo
O exemplo seguinte mostra o código completo para estas instruções.
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
using System.Windows.Forms;
namespace CompositeImages
{
public partial class Form1 : Form
{
// The head of the dataflow network.
ITargetBlock<string> headBlock = null;
// Enables the user interface to signal cancellation to the network.
CancellationTokenSource cancellationTokenSource;
public Form1()
{
InitializeComponent();
}
// Creates the image processing dataflow network and returns the
// head node of the network.
ITargetBlock<string> CreateImageProcessingNetwork()
{
//
// Create the dataflow blocks that form the network.
//
// Create a dataflow block that takes a folder path as input
// and returns a collection of Bitmap objects.
var loadBitmaps = new TransformBlock<string, IEnumerable<Bitmap>>(path =>
{
try
{
return LoadBitmaps(path);
}
catch (OperationCanceledException)
{
// Handle cancellation by passing the empty collection
// to the next stage of the network.
return Enumerable.Empty<Bitmap>();
}
});
// Create a dataflow block that takes a collection of Bitmap objects
// and returns a single composite bitmap.
var createCompositeBitmap = new TransformBlock<IEnumerable<Bitmap>, Bitmap>(bitmaps =>
{
try
{
return CreateCompositeBitmap(bitmaps);
}
catch (OperationCanceledException)
{
// Handle cancellation by passing null to the next stage
// of the network.
return null;
}
});
// Create a dataflow block that displays the provided bitmap on the form.
var displayCompositeBitmap = new ActionBlock<Bitmap>(bitmap =>
{
// Display the bitmap.
pictureBox1.SizeMode = PictureBoxSizeMode.StretchImage;
pictureBox1.Image = bitmap;
// Enable the user to select another folder.
toolStripButton1.Enabled = true;
toolStripButton2.Enabled = false;
Cursor = DefaultCursor;
},
// Specify a task scheduler from the current synchronization context
// so that the action runs on the UI thread.
new ExecutionDataflowBlockOptions
{
TaskScheduler = TaskScheduler.FromCurrentSynchronizationContext()
});
// Create a dataflow block that responds to a cancellation request by
// displaying an image to indicate that the operation is cancelled and
// enables the user to select another folder.
var operationCancelled = new ActionBlock<object>(delegate
{
// Display the error image to indicate that the operation
// was cancelled.
pictureBox1.SizeMode = PictureBoxSizeMode.CenterImage;
pictureBox1.Image = pictureBox1.ErrorImage;
// Enable the user to select another folder.
toolStripButton1.Enabled = true;
toolStripButton2.Enabled = false;
Cursor = DefaultCursor;
},
// Specify a task scheduler from the current synchronization context
// so that the action runs on the UI thread.
new ExecutionDataflowBlockOptions
{
TaskScheduler = TaskScheduler.FromCurrentSynchronizationContext()
});
//
// Connect the network.
//
// Link loadBitmaps to createCompositeBitmap.
// The provided predicate ensures that createCompositeBitmap accepts the
// collection of bitmaps only if that collection has at least one member.
loadBitmaps.LinkTo(createCompositeBitmap, bitmaps => bitmaps.Count() > 0);
// Also link loadBitmaps to operationCancelled.
// When createCompositeBitmap rejects the message, loadBitmaps
// offers the message to operationCancelled.
// operationCancelled accepts all messages because we do not provide a
// predicate.
loadBitmaps.LinkTo(operationCancelled);
// Link createCompositeBitmap to displayCompositeBitmap.
// The provided predicate ensures that displayCompositeBitmap accepts the
// bitmap only if it is non-null.
createCompositeBitmap.LinkTo(displayCompositeBitmap, bitmap => bitmap != null);
// Also link createCompositeBitmap to operationCancelled.
// When displayCompositeBitmap rejects the message, createCompositeBitmap
// offers the message to operationCancelled.
// operationCancelled accepts all messages because we do not provide a
// predicate.
createCompositeBitmap.LinkTo(operationCancelled);
// Return the head of the network.
return loadBitmaps;
}
// Loads all bitmap files that exist at the provided path.
IEnumerable<Bitmap> LoadBitmaps(string path)
{
List<Bitmap> bitmaps = new List<Bitmap>();
// Load a variety of image types.
foreach (string bitmapType in
new string[] { "*.bmp", "*.gif", "*.jpg", "*.png", "*.tif" })
{
// Load each bitmap for the current extension.
foreach (string fileName in Directory.GetFiles(path, bitmapType))
{
// Throw OperationCanceledException if cancellation is requested.
cancellationTokenSource.Token.ThrowIfCancellationRequested();
try
{
// Add the Bitmap object to the collection.
bitmaps.Add(new Bitmap(fileName));
}
catch (Exception)
{
// TODO: A complete application might handle the error.
}
}
}
return bitmaps;
}
// Creates a composite bitmap from the provided collection of Bitmap objects.
// This method computes the average color of each pixel among all bitmaps
// to create the composite image.
Bitmap CreateCompositeBitmap(IEnumerable<Bitmap> bitmaps)
{
Bitmap[] bitmapArray = bitmaps.ToArray();
// Compute the maximum width and height components of all
// bitmaps in the collection.
Rectangle largest = new Rectangle();
foreach (var bitmap in bitmapArray)
{
if (bitmap.Width > largest.Width)
largest.Width = bitmap.Width;
if (bitmap.Height > largest.Height)
largest.Height = bitmap.Height;
}
// Create a 32-bit Bitmap object with the greatest dimensions.
Bitmap result = new Bitmap(largest.Width, largest.Height,
PixelFormat.Format32bppArgb);
// Lock the result Bitmap.
var resultBitmapData = result.LockBits(
new Rectangle(new Point(), result.Size), ImageLockMode.WriteOnly,
result.PixelFormat);
// Lock each source bitmap to create a parallel list of BitmapData objects.
var bitmapDataList = (from bitmap in bitmapArray
select bitmap.LockBits(
new Rectangle(new Point(), bitmap.Size),
ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb))
.ToList();
// Compute each column in parallel.
Parallel.For(0, largest.Width, new ParallelOptions
{
CancellationToken = cancellationTokenSource.Token
},
i =>
{
// Compute each row.
for (int j = 0; j < largest.Height; j++)
{
// Counts the number of bitmaps whose dimensions
// contain the current location.
int count = 0;
// The sum of all alpha, red, green, and blue components.
int a = 0, r = 0, g = 0, b = 0;
// For each bitmap, compute the sum of all color components.
foreach (var bitmapData in bitmapDataList)
{
// Ensure that we stay within the bounds of the image.
if (bitmapData.Width > i && bitmapData.Height > j)
{
unsafe
{
byte* row = (byte*)(bitmapData.Scan0 + (j * bitmapData.Stride));
byte* pix = (byte*)(row + (4 * i));
a += *pix; pix++;
r += *pix; pix++;
g += *pix; pix++;
b += *pix;
}
count++;
}
}
//prevent divide by zero in bottom right pixelless corner
if (count == 0)
break;
unsafe
{
// Compute the average of each color component.
a /= count;
r /= count;
g /= count;
b /= count;
// Set the result pixel.
byte* row = (byte*)(resultBitmapData.Scan0 + (j * resultBitmapData.Stride));
byte* pix = (byte*)(row + (4 * i));
*pix = (byte)a; pix++;
*pix = (byte)r; pix++;
*pix = (byte)g; pix++;
*pix = (byte)b;
}
}
});
// Unlock the source bitmaps.
for (int i = 0; i < bitmapArray.Length; i++)
{
bitmapArray[i].UnlockBits(bitmapDataList[i]);
}
// Unlock the result bitmap.
result.UnlockBits(resultBitmapData);
// Return the result.
return result;
}
// Event handler for the Choose Folder button.
private void toolStripButton1_Click(object sender, EventArgs e)
{
// Create a FolderBrowserDialog object to enable the user to
// select a folder.
FolderBrowserDialog dlg = new FolderBrowserDialog
{
ShowNewFolderButton = false
};
// Set the selected path to the common Sample Pictures folder
// if it exists.
string initialDirectory = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.CommonPictures),
"Sample Pictures");
if (Directory.Exists(initialDirectory))
{
dlg.SelectedPath = initialDirectory;
}
// Show the dialog and process the dataflow network.
if (dlg.ShowDialog() == DialogResult.OK)
{
// Create a new CancellationTokenSource object to enable
// cancellation.
cancellationTokenSource = new CancellationTokenSource();
// Create the image processing network if needed.
headBlock ??= CreateImageProcessingNetwork();
// Post the selected path to the network.
headBlock.Post(dlg.SelectedPath);
// Enable the Cancel button and disable the Choose Folder button.
toolStripButton1.Enabled = false;
toolStripButton2.Enabled = true;
// Show a wait cursor.
Cursor = Cursors.WaitCursor;
}
}
// Event handler for the Cancel button.
private void toolStripButton2_Click(object sender, EventArgs e)
{
// Signal the request for cancellation. The current component of
// the dataflow network will respond to the cancellation request.
cancellationTokenSource.Cancel();
}
~Form1()
{
cancellationTokenSource.Dispose();
}
}
}
A ilustração seguinte mostra o resultado típico da pasta \Sample Pictures\ comum.