共用方式為


逐步解說:在 Windows Forms 應用程式中使用資料流程

本文示範如何建立資料流程區塊網路,以在Windows Forms應用程式中執行影像處理。

這個範例會從指定的資料夾載入映像檔案、建立複合映像,以及顯示結果。 這個範例會使用資料流程模型,透過網路路由映像。 在資料流程模型中,程式的獨立元件會透過傳送訊息,彼此進行通訊。 當元件收到訊息時,它會執行某些動作,然後將結果傳遞給另一個元件。 比較資料流程模型與控制流程模型,在其中應用程式會使用控制項結構,例如,條件陳述式、迴圈等等,來控制程式中作業的順序。

必要條件

在開始進行這個逐步解說之前,請先閱讀資料流程

注意

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 Forms 應用程式

本節描述如何建立基本 Windows Forms 應用程式,並且將控制項新增至主要表單。

若要建立 Windows Forms 應用程式

  1. 在 Visual Studio 中,建立 Visual C# 或 Visual Basic Windows Forms 應用程式專案。 在本文件中,專案命名為 CompositeImages

  2. 在主要表單 Form1.cs (在 Visual Basic 中為 Form1.vb) 的表單設計工具上,新增 ToolStrip 控制項。

  3. ToolStripButton 控制項加入 ToolStrip 控制項。 將 DisplayStyle 屬性設為 Text,並將 Text 屬性設為 Choose Folder

  4. 將第二個 ToolStripButton 控制項加入 ToolStrip 控制項。 將 DisplayStyle 屬性設為 Text,將 Text 屬性設為 Cancel,然後將 Enabled 屬性設為 False

  5. PictureBox 物件加入主要表單。 將 Dock 屬性設定為 Fill

建立資料流程網路

本節描述如何建立會執行映像處理的資料流程網路。

若要建立資料流程網路

  1. 將 System.Threading.Tasks.Dataflow.dll 參考新增至您的專案。

  2. 確定 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;
    
  3. 將下列資料成員新增至 Form1 類別:

    // The head of the dataflow network.
    ITargetBlock<string> headBlock = null;
    
    // Enables the user interface to signal cancellation to the network.
    CancellationTokenSource cancellationTokenSource;
    
  4. 將下列 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;
    }
    
  5. 實作 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;
    }
    
  6. 實作 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;
    }
    

    注意

    C# 版本的 CreateCompositeBitmap 方法會使用指標,以便有效處理 System.Drawing.Bitmap 物件。 因此,您必須在專案中啟用 [容許 Unsafe 程式碼] 選項,以便使用 unsafe 關鍵字。 如需如何在 Visual C# 專案中啟用不安全程式碼的詳細資訊,請參閱專案設計工具、建置頁 (C#)

下表描述網路的成員。

member 類型 描述
loadBitmaps TransformBlock<TInput,TOutput> 採用資料夾路徑做為輸入,並且產生 Bitmap 物件的集合作為輸出。
createCompositeBitmap TransformBlock<TInput,TOutput> 採用 Bitmap 物件的集合做為輸入,並且產生複合點陣圖做為輸出。
displayCompositeBitmap ActionBlock<TInput> 在表單上顯示複合點陣圖。
operationCancelled ActionBlock<TInput> 顯示映像以表示作業已取消,讓使用者選取另一個資料夾。

為了連線資料流程區塊以形成網路,此範例會使用 LinkTo 方法。 LinkTo 方法包含採用 Predicate<T> 物件的多載版本,該物件會判斷目標區塊是否接受或拒絕訊息。 這個篩選機制可讓訊息區塊僅接收特定的值。 在此範例中,網路可以使用兩種方式之一進行分支。 主要分支會從磁碟載入映像、建立複合映像,以及在表單上顯示該映像。 替代分支會取消目前的作業。 Predicate<T> 物件會啟用主要分支上的資料流程區塊,以藉由拒絕特定訊息來切換至替代分支。 例如,如果使用者取消作業,資料流程區塊 createCompositeBitmap 會產生 null (在 Visual Basic 中為 Nothing) 作為其輸出。 資料流程區塊 displayCompositeBitmap 會拒絕 null 輸入值,因此,訊息會提供給 operationCancelled。 資料流程區塊 operationCancelled 接受所有訊息,因此,會顯示映像表示作業取消。

下圖顯示影像處理網路:

顯示影像處理網路的圖例。

由於 displayCompositeBitmapoperationCancelled資料流程區塊會在使用者介面上進行處理,因此這個動作一定要在使用者介面執行緒上發生。 為了要完成這項作業,這些物件在建構時會提供 ExecutionDataflowBlockOptions 物件,而且其 TaskScheduler 屬性會設定為 TaskScheduler.FromCurrentSynchronizationContextTaskScheduler.FromCurrentSynchronizationContext 方法會建立 TaskScheduler 物件,該物件會在目前的同步處理內容上執行工作。 因為 CreateImageProcessingNetwork 方法是從 [選擇資料夾] 按鈕的處理常式呼叫,它會在使用者介面執行緒上執行,displayCompositeBitmapoperationCancelled 資料流程區塊的動作也會在使用者介面執行緒上執行。

此範例使用共用取消權杖,而非設定 CancellationToken 屬性,因為 CancellationToken 屬性會永久取消資料流程區塊的執行。 取消語彙基元可讓此範例重複使用相同的資料流程網路多次,即使使用者取消一或多個作業。 如需使用 CancellationToken 以永久取消資料流程區塊執行的範例,請參閱如何:取消資料流程區塊

連接資料流程網路與使用者介面

本節描述如何連接資料流程網路與使用者介面。 建立複合映像和取消作業是從 [選擇資料夾] 和 [取消] 按鈕起始。 當使用者選擇任一個按鈕時,會以非同步方式起始適當的動作。

若要連接資料流程網路與使用者介面

  1. 在主要表單的表單設計工具上,為 Choose Folder 按鈕的 Click 事件建立事件處理常式。

  2. 實作 Choose Folder 按鈕的 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;
       }
    }
    
  3. 在主要表單的表單設計工具上,為 Cancel 按鈕的 Click 事件建立事件處理常式。

  4. 實作 Cancel 按鈕的 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\ 資料夾的一般輸出。

Windows Forms 應用程式

另請參閱