チュートリアル:Windows フォーム アプリケーションでのデータフローの使用
この記事では、Windows フォーム アプリケーションでイメージ処理を実行する、データフロー ブロックのネットワークを作成する方法を示します。
この例では、指定したフォルダーからイメージ ファイルを読み込み、複合イメージを作成して、結果を表示します。 例では、ネットワーク経由でイメージをルーティングするために、データフロー モデルを使用します。 このデータフロー モデルでは、プログラム内の独立したコンポーネント同士が、メッセージを送信することによって相互に通信します。 1 つのコンポーネントがメッセージを受信すると、何らかのアクションを実行した後に、結果を別のコンポーネントに渡します。 このモデルと制御フロー モデルを比較してください。制御フロー モデルでは、アプリケーションは制御構造 (条件付きステートメントやループなど) を使用してプログラムでの操作順序を制御します。
必須コンポーネント
このチュートリアルを開始する前に、「Dataflow (データフロー)」をお読みください。
注意
TPL データフロー ライブラリ (System.Threading.Tasks.Dataflow 名前空間) は、.NET と一緒には配布されません。 Visual Studio に System.Threading.Tasks.Dataflow 名前空間をインストールするには、プロジェクトを開き、[プロジェクト] メニューの [NuGet パッケージの管理] をクリックし、System.Threading.Tasks.Dataflow
パッケージをオンラインで検索します。 または、.NET Core CLI を使ってインストールするには、dotnet add package System.Threading.Tasks.Dataflow
を実行します。
セクション
このチュートリアルは、次のセクションで構成されています。
Windows フォーム アプリケーションの作成
このセクションでは、基本的な Windows フォーム アプリケーションを作成し、メイン フォームにコントロールを追加する方法を説明します。
Windows フォーム アプリケーションを作成するには
Visual Studio で、Visual C# または Visual Basic Windows フォーム アプリケーション プロジェクトを作成します。 このドキュメントでは、プロジェクトの名前を
CompositeImages
とします。メイン フォーム Form1.cs (Visual Basic の Form1.vb) のフォーム デザイナーで、ToolStrip コントロールを追加します。
ToolStrip コントロールに ToolStripButton コントロールを追加します。 DisplayStyle プロパティを Text に設定し、Text プロパティを「フォルダーの選択」に設定します。
ToolStrip コントロールに 2 つ目の ToolStripButton コントロールを追加します。 DisplayStyle プロパティを Text に、Text プロパティを「キャンセル」に、Enabled プロパティを
False
に設定します。PictureBox オブジェクトをメイン フォームに追加します。 Dock プロパティを Fillに設定します。
データフロー ネットワークの作成
このセクションでは、イメージ処理を実行するデータフロー ネットワークを作成する方法を説明します。
データフロー ネットワークを作成するには
System.Threading.Tasks.Dataflow.dll への参照をプロジェクトに追加します。
Form1.cs (Visual Basic の Form1.vb) に次の
using
(Visual Basic ではUsing
) ステートメントが含まれていることを確認します。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;
Form1
クラスに次のデータ メンバーを追加します。// The head of the dataflow network. ITargetBlock<string> headBlock = null; // Enables the user interface to signal cancellation to the network. CancellationTokenSource cancellationTokenSource;
次の
CreateImageProcessingNetwork
メソッドをForm1
クラスに追加します。 このメソッドによって、イメージ処理ネットワークが作成されます。// 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; }
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; }
CreateCompositeBitmap
メソッドを実装します。// 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; }
注意
CreateCompositeBitmap
メソッドの C# バージョンでは、ポインターを使って、System.Drawing.Bitmap オブジェクトの効率的な処理を実現します。 したがって、unsafe キーワードを使用するために、プロジェクト内の [アンセーフ コードの許可] オプションを有効にしてください。 Visual C# プロジェクトでアンセーフ コードを有効にする方法については、「[ビルド] ページ (プロジェクト デザイナー) (C#)」を参照してください。
ネットワークのメンバーを次の表に示します。
メンバー | 種類 | 説明 |
---|---|---|
loadBitmaps |
TransformBlock<TInput,TOutput> | フォルダーのパスを入力として取得し、Bitmap オブジェクトのコレクションを出力として生成します。 |
createCompositeBitmap |
TransformBlock<TInput,TOutput> | Bitmap オブジェクトのコレクションを入力として取得し、複合ビットマップを出力として生成します。 |
displayCompositeBitmap |
ActionBlock<TInput> | フォーム上に複合ビットマップを表示します。 |
operationCancelled |
ActionBlock<TInput> | 操作が取り消されたことを示すためにイメージを表示し、ユーザーが別のフォルダーを選択できるようにします。 |
データフロー ブロックを接続してネットワークを形成するため、この例では LinkTo メソッドを使います。 LinkTo メソッドには、ターゲット ブロックがメッセージを受け入れるか拒否するかを決定する Predicate<T> オブジェクトを受け取るオーバーロード バージョンが含まれます。 このフィルターのしくみによって、メッセージ ブロックは特定の値のみを受信できます。 この例では、ネットワークが、2 つに分岐します。 メイン分岐は、ディスクからイメージを読み込み、複合イメージを作成し、フォームにそのイメージを表示します。 もう 1 つの分岐では、現在の操作がキャンセルされます。 Predicate<T> オブジェクトは、メイン分岐に沿ったデータフロー ブロックで、特定のメッセージを拒否することによって、もう 1 つの分岐に切り替えることができます。 たとえば、ユーザーが操作をキャンセルした場合、データフロー ブロック createCompositeBitmap
で、null
(Visual Basic では Nothing
) が出力として生成されます。 データフロー ブロック displayCompositeBitmap
では、null
入力値が拒否されるため、メッセージが operationCancelled
に提供されます。 データフロー ブロック operationCancelled
はすべてのメッセージを受け入れるため、操作が取り消されたことを示すためにイメージを表示します。
次の図は、イメージ処理ネットワークを示しています。
displayCompositeBitmap
と operationCancelled
のデータフロー ブロックはユーザー インターフェイスで機能するので、これらの操作をユーザー インターフェイス スレッドで実行することが重要です。 これを実現するため、構築時にこれらのオブジェクトは TaskScheduler プロパティが TaskScheduler.FromCurrentSynchronizationContext に設定された ExecutionDataflowBlockOptions オブジェクトを提供します。 TaskScheduler.FromCurrentSynchronizationContext メソッドは、現行の同期コンテキストで作業を実行する TaskScheduler オブジェクトを作成します。 ユーザー インターフェイス スレッドで実行されるCreateImageProcessingNetwork
メソッドは、[フォルダーの選択] ボタンのハンドラーから呼び出されるため、displayCompositeBitmap
とoperationCancelled
のデータフロー ブロックのアクションも、ユーザー インターフェイス スレッドで実行されます。
CancellationToken プロパティはデータフロー ブロックの実行を完全にキャンセルするので、この例では、CancellationToken プロパティを設定する代わりに、共有キャンセル トークンを使います。 キャンセル トークンによって、この例では、ユーザーが 1 つまたは複数の操作をキャンセルしたときにも、同じデータフロー ネットワークを複数回再利用できます。 CancellationToken を使ってデータフロー ブロックの実行を完全に取り消す例については、「方法: データフロー ブロックをキャンセルする」をご覧ください。
ユーザー インターフェイスへのデータフロー ネットワークの接続
このセクションでは、ユーザー インターフェイスにデータフロー ネットワークを接続する方法を説明します。 複合イメージの作成と、操作のキャンセルは、[フォルダーの選択] と [キャンセル] の各ボタンから開始されます。 ユーザーがこのいずれかのボタンを選択すると、適切な操作が非同期的に開始されます。
ユーザー インターフェイスにデータフロー ネットワークを接続するには
メイン フォームのフォーム デザイナーで、[フォルダーの選択] ボタンの Click イベントのイベント ハンドラーを作成します。
[フォルダーの選択] ボタンの Click イベントを実装します。
// 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; } }
メイン フォームのフォーム デザイナーで、[キャンセル] ボタンの Click イベントのイベント ハンドラーを作成します。
[キャンセル] ボタンの Click イベントを実装します。
// 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(); }
完全な例
次の例は、このチュートリアルのコード全体を示しています。
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();
}
}
}
次の図は、一般的な \Sample Pictures\ フォルダーの典型的な出力を示しています。
関連項目
.NET
フィードバック
https://aka.ms/ContentUserFeedback」を参照してください。
以下は間もなく提供いたします。2024 年を通じて、コンテンツのフィードバック メカニズムとして GitHub の issue を段階的に廃止し、新しいフィードバック システムに置き換えます。 詳細については、「フィードバックの送信と表示