Download as pdf
Download as pdf
You are on page 1of 13
220017 Walkthrough Using Datatow in a Windows Foems Applicaton Walkthrough: Using Dataflow in a Windows Forms Application -NET Framework (current version) This document demonstrates how to create a network of dataflow blocks that perform image processing in a Windows Forms application. This example loads image files from the specified folder, creates a composite image, and displays the result. The example uses ‘the dataflow model to route images through the network. In the dataflow model, independent components of a program communicate with one another by sending messages. When a component receives a message, it performs some action and then. passes the result to another component. Compare this with the control flow model, in which an application uses control structures, for example, conditional statements, loops, and so on, to control the order of operations in a program Prerequisites Read Dataflow before you start this walkthrough. ‘Tip The TPL Dataflow Library (System.Threading,Tasks Dataflow namespace) is not distributed with the NET Framework 4.5. To install the System. Threading. Tasks.Dataflow namespace, open your project in Visual Studio 2012, choose Manage NuGet Packages from the Project menu, and search online for the Microsoft .Tpl .Dataflow package The TPL Dataflow Library (System. Threading. asks Dataflow namespace) is not distributed with the NET Framework 4.5. To install the System. Threacling.Tasks.Dataflow namespace, open your project in Visual Studio 2012, choose Manage NuGet Packages from the Project menu, and search online for the Microsoft. Tpl.Dataflow package Sections This walkthrough contains the following sections: © Creating the Windows Forms Application Creating the Dataflow Network © Connecting the Dataflow Network to the User Interface # The Complete Example Creating the Windows Forms Application ‘This section describes how to create the basic Windows Forms application and add controls to the main form. To Create the Windows Forms Application 1. In Visual Studio, create a Visual C# or Visual Basic Windows Forms Application project. In this document, the project is named Compositelnages, 2. On the form designer for the main form, Form1.cs (Form/.vb for Visual Basic), add a ToolStrip control hpssimsch microsoft comen-usibrarynh228606{deprirtr v=vs. 110) aspx 13 220017 Walkthrough Using Datatow in a Windows Foems Applicaton 3, Add a ToolStripButton control to the ToolStrip control, Set the DisplayStyle property to Text and the Text property to Choose Folder. 4, Add a second ToolStripButton control to the ToolStrip control, Set the DisplayStyle property to Text, the Text property to Cancel, and the Enabled property to False. 5, Add a PictureBox object to the main form, Set the Dock property to Fill Creating the Dataflow Network ‘This section describes how to create the dataflow network that performs image processing To Create the Dataflow Network 1, Add a reference to System.Threading,Tasks.Dataflow.dll to your project. 2. Ensure that Form1.cs (Form1.vb for Visual Basic) contains the following using (Using in Visual Basic) statements: ce using System using System.Collections.Generic; using System.Drawing; using System. Drawing. Imaging; using System.10; using System.Ling; using System. Threading; using System. Threading. Tasks; using System. Threading. Tasks .Dataflow; using System.Windows Forms; 3. Add the following data members to the Form class: {ce | // The head of the dataflow network. ITargetBlockcstring> headBlock = null; // Enables the user interface to signal cancellation to the network. Cancel lationTokenSource cancellationTokenSource; 4, Add the following method, CreatetnageProcessingNetwork, to the Fornd class This method creates the image processing network. // Creates the image processing dataflow network and returns the 11 head node of the network. TTargetBlock CreateInagePracessingNetwork() t uw // Create the dataflow blocks that form the network. uw // Create a dataflow block that takes a folder path as input // and returns a collection of Bitmap objects. var loadBitmaps = new TransformBlock>(path { try ra return LoadBitmaps(path); } catch (OperationCanceledexception) thipssimsch microsoft comen-usibraryhh228606{ eprint v=vs. 110). aspx ang. 220017 Walkthrough Using Datatow in a Windows Foems Applicaton // Handle cancellation by passing the empty collection // to the next stage of the network. return Enumerable.Empty(); x ys // Create a dataflow block that takes a collection of Bitmap objects // and returns a single conposite bitmap. var createCompositeBitmap = new TransfornBlock, Bitmap> (bitmaps => { try { + catch (OperationCanceledéxception) { return CreateConpositeBitmap(bitmaps) ; // Handle cancellation by passing null to the next stage 11 of the network. return nulls } ns // Create a dataflow block that displays the provided bitmap on the form. var displayCompositeBitnap = new ActionBlock(bitmap => « // Display the bitmap. pictureBox1.Sizetiode = PictureBoxSizeMode.StretchInage; pictureBox1.Inage = bitmap; // Enable the user to select another folder. toolstripButtont.Enabled = true; ‘toolstripButton2.Enabled = false; Cursor = DefaultCursor; b // Specify a task scheduler from the current synchronization context // so that the action runs on the UT thread. new ExecutionDataflowBlockoptions « TaskScheduler = TaskScheduler.FronCurrentSynchronizationContext () ns // 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(delegate { // Display the error image to indicate that the operation // was cancelled. pictureBox1.SizeMode = PictureBoxSizeMode.CenterImages pictureBoxl.Inage = pictureBoxl.Errorinage; // Enable the user to select another folder. toolStripButton1.Enabled = true; ‘toolstripButton2.Enabled = false; Cursor = DefaultCursor; h // Specify a task scheduler from the current synchronization context // so that the action runs on the UI thread. new ExecutionDataflowBlockoptions « TaskScheduler = TaskScheduler.FronCurrentSynchronizationContext() ns “ul // Connect the network. “ // Link loadBitmaps to createconpositesitmap. // The provided predicate ensures that createConpositesitmap accepts the // collection of bitmaps only if that collection has at least one menber. loadaitmaps.LinkTo(createConpositeBitmap, bitmaps => bitmaps.count() > 6); thipssimsch microsoft comen-usibraryhh228606{ eprint v=vs. 110). aspx 313 220017 Walkthrough Using Datatow in a Windows Foems Applicaton // Also Link loadBitmaps to operationCancelled. // When createConpositeBitmap rejects the message, loadBitraps // offers the message to operationCancelled. // operationCancelled accepts all messages because we do not provide a // predicate. loadaitmaps .LinkTo(operationCancelled) ; // Link createConpositeBitmap to displayCompositeBitnap. // The provided predicate ensures that displayConpositeBitmap accepts the // bitmap only if it is non-null. createConpositeBitnap.LinkTo(displayConpositeBitmap, bitmap => bitmap != null); // Miso Link createCompositeBitnap to operationCancelled. // when displayConpositeBitmap rejects the message, createConpositeBitmap // offers the message to operationCancelled. // operationCancelled accepts all messages because we do not provide a // predicate. createConpositeBitnap. LinkTo(operationCancelled) ; // Return the head of the network. return loadBitmaps; Implement the LoadBitraps method. ce // Loads all bitnap files that exist at the provided path. Ténumerable LoadBitaaps(string path) fe ListBitnap> bitmaps = new List(); // (oad a variety of image types. foreach (string bitmapType in new stringl] { "*.bnp", “*-gif", "*.Jpe", 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() 5 try t // Add the Bitmap object to the collection. bitmaps.Add(new Bitmap(fileName) ); y catch (Exception) { 3 // T000: A complete application might handle the error. y + return bitmaps; Implement the CreateConpositesitmap method, // 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 inage. Bitmap CreateConpositeBitmap(IEnunerable bitmaps) { Bitmap[] bitmapArray = bitmaps.ToArray(); // Conpute the maximum width and height components of all thipssimsch microsoft comen-usibraryhh228606{ eprint v=vs. 110). aspx 413 220017 Walkthrough Using Datatow in a Windows Foems Applicaton // bitmaps in the collection. Rectangle largest = new Rectangle(); foreach (var bitmap in bitmapArray) « if (bitmap.width > largest.width) largest Width = bitnap.Width; if (bitmap.Height > largest.Height) largest Height = bitmap.Height; d // 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 resultBitmapbata = result.LockBits( new Rectangle(new Point(), result.Size), InageLockMode.writeOnly, result.PixelFornat) 5 // Lock each source bitmap to create a parallel list of BitmapData objects. var bitmapDataList = (from bitmap in bitnapArray ‘select bitmap.LockBits( new Rectangle(new Point(), bitmap.Size), InageLockMode.ReadOnly, PixelFornat.Format32bppArgb) ) -ToList(); // Compute each column in parallel. Parallel.For(@, largest.Width, new Paralleloptions « CancellationToken = cancellationTokenSource.Token // Compute each row. for (int j = @ 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. inta=@ r=@, g=0 b= 9 // For each bitmap, compute the sun of all color conponents. foreach (var bitmapData in bitmapDatalist) { // Ensure that we stay within the bounds of the image. if (bitnapData.Width > i @& bitmapbata.Height > 3) t unsafe t bytet row = (byte*)(bitmapbata.Scand + (j * bitmapbata.Stride)); byte pix = (byte*)(row + (4 * 4))5 a t= tpixs pixtts "pix; pixt+s “pix; pixtts “pix; 8 > } count++s } unsafe ra 11 Compute the average of each color component. count; count; count; count; // Set the result pixel. byte* row = (byte*)(resultBitmapData.Scané + (j * resultaitmapbata.Stride)); byte* pix = (byte*)(row + (4 * 1))3 thipssimsch microsoft comen-usibraryhh228606{ eprint v=vs. 110). aspx 13. 220017 Walkthrough Using Datatow in a Windows Foems Applicaton *pix = (byte)a; pixt+s *pix = (byte)r; pixess *pix = (byte)gs pixt+s *pix = (byte)b; y De // Unlock the source bitmaps. for (int i = @; 1 < bitmapArray. Length; i++) { d bitmaparray[i].UnlockBits(bitmapbataList(i]); // Unlock the result bitmap. result.UnlockBits(resultBitmapData) ; // Return the result. return result; Note The C# version of the CreateCompositeBitnap method uses pointers to enable efficient processing of the System Drawing Bitmap objects. Therefore, you must enable the Allow unsafe code option in your project in order to use the unsafe keyword. For more information about how to enable unsafe code in a Visual C# projec, see Build Page, Project Designer (CH). ‘The following table describes the members of the network. Member Type loadsitmaps TransformBlock«Tinpul, | Takes a folder path as input and produces a collection of Bitmap objects as TOutput> output, createComposite | TransformBlock bitmap as output. displayComposit | ActionBlock | Displays the composite bitmap on the form eBitmap operationCancel | ActionBlock Displays an image to indicate that the operation is canceled and enables led the user to select another folder. ‘To connect the dataflow blocks to form a network, this example uses the LinkTo method, The LinkTo method contains an overloaded version that takes a Predicate object that determines whether the target black accepts or rejects a message. This filtering mechanism enables message blocks to receive only certain values. In this example, the network can branch in one of two. ways. The main branch loads the images fram disk, creates the composite image, and displays that image on the form. The alternate branch cancels the current operation. The Precicate objects enable the dataflow blocks along the main branch to switch to the alternative branch by rejecting certain messages. For example, if the user cancels the operation, the dataflow block createCompositeBitmap produces null (Nothing in Visual Basic) as its output. The dataflow block di splayConpositeBitmap rejects nul] input values, and therefore, the message is offered to operationCancelled. The dataflow block operationCancel led accepts all messages and therefore, displays an image to indicate that the operation is canceled. ‘The following illustration shows the image processing network. ((operationCencelled 5) # TransformBlock(TInput, TOutout) © ActionBlock(Tinput) hpssimsch microsoft comen-usibrarynh228606{deprirtr v=vs. 110) aspx ang, 220017 Walkthrough Using Datatow in a Windows Foems Applicaton Because the di splayCompositeBitmap and operationCancel led dataflow blocks act on the user interface, itis important that ‘these actions occur on the user-interface thread. To accomplish this, during construction, these objects each provide a ExecutionDataflowBlockOptions object that has the TaskScheduler property set to TaskScheduler.FromCurrentSynchronizationContext. The TaskScheduler.FromCurrentSynchronizationContext method creates a TaskScheduler object that performs work on the current synchronization context. Because the CreateInageProcessingNetwork ‘method is called from the handler of the Choose Folder button, which runs on the user-interface thread, the actions for the displayCompositeBitmap and operationCancel led dataflow blocks also run on the user-interface thread. This example uses a shared cancellation token instead of setting the CancellationToken properly because the CancellationToken property permanently cancels dataflow block execution. A cancellation token enables this example to reuse the same dataflow hetwork multiple times, even when the user cancels one or more operations. For an example that uses CancellationToken to permanently cancel the execution of a dataflow block, see How to: Cancel a Dataflow Block, Connecting the Dataflow Network to the User Interface ‘This section describes how to connect the dataflow network to the user interface. The creation of the composite image and cancellation of the operation are initiated from the Cheese Folder and Cancel buttons. When the user chooses either of these buttons, the appropriate action is initiated in an asynchronous manner. ‘To Connect the Dataflow Network to the User Interface 1. On the form designer for the main form, create an event handler for the Click event for the Choose Folder button, 2. Implement the Click event for the Cheese Folder button, ce // Event handler for the Choose Folder button. private void toolStripButtont_Click(object sender, EventArgs e) € // Create 2 FolderBrowserdialog object to enable the user to // select 2 folder. FolderBrowserDialog dlg = new FolderBrowserDialog { ShowNenFolderButton = false % // Set the selected path to the common Sample Pictures folder // Af it exists. string initialDirectory = Path.Conbine( Environment .GetFolderPath (Environment .SpecialFolder .ConmonPictures), “Sample Pictures"); if (Directory. Exists(initialDirectory)) dig.SelectedPath = initialbirectory; // 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. if (headBlock == null) { headBlock = CreateInageProcessingNetwork(); } // Post the selected path to the network. headBlock.Post (dig.SelectedPath); // Enable the Cancel button and disable the Choose Folder button. toolstripButton1.Enabled = false; toolstripButton2. Enabled = true; // Show a wait cursor. thipssimsch microsoft comen-usibraryhh228606{ eprint v=vs. 110). aspx m3 220017 Walkthrough Using Datatow in a Windows Foems Applicaton Cursor = Cursors.WaitCursor; 3. On the form designer for the main form, create an event handler for the Click event for the Cancel button, 4, Implement the Click event for the Caneel button. fe] // Event handler for the Cancel button. private void toolstripsutton2_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(); The Complete Example ‘The following example shows the complete code for this walkthrough, { c# using System; using System.Collections.Generic; using System.Drawing; using System.Drawing. Inagin using System.10; using System.Lings using System. Threading; using System. Threading. Tasks; using System. Threading. Tasks Dataflow; using System.Windows. Forms; namespace CompositeTnages « public partial class Form1 : Form « // The head of the dataflow network. TrargetBlock headBlock = null; // Enables the user interface to signal cancellation to the network. CancellationTokenSource cancellationTokenSource; public Form() t InitializeConponent(); x // Creates the image processing dataflow network and returns the // head node of the network. TTargetBlock CreateImageProcessingNetwork() { uv // Create the dataflow blocks that form the network. Ws // Create a dataflow block that takes a folder path as input // and returns a collection of Bitmap objects. var loadBitmaps = new TransformBlock>(path { try { return LoadBitmaps(path); hnips:imsch microsoft comen-usibrarynh228606{ eprint v=vs. 110). aspx ans s22n017 Walkthrough Using Datatow in a Windows Foems Applicaton y catch (OperationCanceledéxception) t // Handle cancellation by passing the enpty collection // to the next stage of the network. return Enumerable.Empty(); y 3 // Create a dataflow block that takes a collection of Bitmap objects // and returns a single composite bitmap. var createConpositeBitmap = new TransformBlock, Bitmap>(bitmaps => ¢ try t ? catch (OperationCanceledException) { return CreateCompositeBitmap(bitmaps) ; // Handle cancellation by passing null to the next stage 11 of the network. return nulls y Ys // Create a dataflow block that displays the provided bitmap on the form. var displayConpositesitmap = new ActionBlock(bitnap { J/ Display the bitmap. pictureBox1.SizeMode = PictureBoxSizeMode. StretchImages pictureBox1.Image = bitmap; J/ Enable the user to select another folder. toolstripButtoni. 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 ExecutionDataflow8lockOptions t TaskScheduler = TaskScheduler.FromCurrentSynchronizationContext() 3 // 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(delegate t // Display the error image to indicate that the operation 11 was cancelled. pictureBox1.Sizeode = PictureBoxSizeMode.CenterInages pictureBox1.Inage = pictureBoxl.£rrorInage; // Enable the user to select another folder. toolStripButtoni. Enabled = trues toolStripButton2. Enabled = false; Cursor = DefaultCursors » /1 Specify a task scheduler from the current synchronization context JJ so that the action runs on the UT thread. new ExecutionDataflowSlockoptions { TaskScheduler = TaskScheduler.FromCurrentSynchronizationcontext() 3 uy // Connect the network. u // Link loadBitmaps to createConpositesitmap. // The provided predicate ensures that createConpositeBitmap accepts the // collection of bitmaps only if that collection has at least one menber. loadBitmaps.LinkTo(createConpositesitmap, bitmaps => bitmaps.count() > 8); hnpssimsch microsoft coment ary 228606{ eprint v=vs. 110). aspx ons. s22n017 Walkthrough Using Datatow in a Windows Foems Applicaton // Also link loadBitnaps to operationCancelled. // when createCompositesitmap rejects the message, loadsitmaps // offers the message to operationCancelled. // operationCancelled accepts all messages because we do not provide a // predicate. loadBitmaps.LinkTo(operationCancelled) ; // Link createCompositesitmap to éisplayCompositeBitmap. // The provided predicate ensures that displayCompositeBitmap accepts the // bitmap only if it is non-null. createCompositeBitmap.LinkTo(displayCompositesitmap, bitrap => bitmap != null); // Also link createConpositeBitmap to operationCancelled. J/ When displayCompositeBitmap rejects the message, createConpositeBitmap J/ offers the message to operationCancelled. // operationCancelled accepts all messages because we do not provide a // predicate. createCompositeBitnap.LinkTo(operationCancelled) ; // Return the head of the network. return loadBitmaps; 3 // Loads all bitmap files that exist at the provided path. TEnunerable LoadBitmaps(string path) t ListeBitmap> bitmaps = new List(); // Load 2 variety of image types. foreach (string bitnapType in new string] ( "*.bmp", “*.gif", Spe", “*.png", “*.tif” }) JI Load each bitmap for the current extension. foreach (string fileNane in Directory.GetFiles(path, bitmapType) ) t // Throw OperationCanceledexception if cancellation is requested. cancel lat ionTokenSource. Token, ThrowI fCancellationRequested(); try { // Add the Bitnap object to the collection. bitmaps.Add(new Bitmap(FileName) ); ? catch (Exception) t /{ TODO: A complete application might handle the error. ? y + return bitmaps; + // Creates a conposite bitmap from the provided collection of Bitnap objects. // This method conputes the average color of each pixel anong all bitmaps 1/ to create the composite inage. Bitmap CreateConpositeBitmap(IEnunerable bitmaps) { Bitnap[] bitnaparray = bitnaps.ToArray()5 // Compute the maximum width and height components of all // bitmaps in the collection Rectangle largest = new Rectangle(); foreach (var bitmap in bitmaparray) t Af (bitmap.width > largest.Width) Jargest.Width = bitmap.Widths if (bitmap.Height > largest Height) Jargest.Height = bitmap.Height; } // Create a 32-bit Bitmap object with the greatest dimensions. Bitmap result = new Bitmap(largest.width, largest.Height, PixelFornat Format 32bppArgb) ; hnpssimsch microsoft coment ary 228606{ eprint v=vs. 110). aspx sais s22n017 Walkthrough Using Datatow in a Windows Foems Applicaton // Lock the result Bitmap. var resultBitnapData = result.LockBits( nen Rectangle(new Point(), result.Size), TmageLockMode.Writeonly, result.PixelFornat); // Lock each source bitmap to create a parallel list of Bitmapbata objects. var bitmapDataList = (from bitmap in bitmaparray select bitmap. LockBits( new Rectangle(new Point(), bitmap.Size), TnageLockNode.ReadOnly, PixelFormat.Format32bppArgb)) -Tolist(); // Compute each column in parallel. Parallel.For(®, largest.Width, new Paralleloptions { CancellationToken = cancellationTokenSource. Token 11 Conpute each row. for (int j= 0; 5 < largest.Meight; j++) { // Counts the nunber of bitnaps whose dimensions // contain the current location. int count = @; // The sum of all alpha, red, green, and blue components. inta=0,r=0,g=0, b= // For each bitmap, compute the sum of all color components. foreach (var bitmapData in bitmapDataList) { J/ Ensure that we stay within the bounds of the image. if (bitmapData.Width > i 8& bitmapData.Height > j) « unsafe « byte* row = (byte*)(bitmapData.Scan® + (j * bitnapData.Stride)); byte* pix = (byte*)(row + (4 * 4))5 a += *pix; pixess Pots *pix; pixess gt *pixs pixess bts *pix; I countess y ? unsafe t // Compute the average of each color component. a /= count; count count count // Set the result pixel. byte* row = (byte*)(resultBitmapbata.Scand + (j * resultBitmapData.Stride)); byte* pix = (byte*)(row + (4 * 4))5 spix = (byte)as pixtss spix = (byte)rs pixtts spix = (byte)gs pixtss spix = (byte)bs ? ys // Unlock the source bitmaps for (int 4 = @; i < bitmapArray.Length; i++) t bitmapArray[1] .UnlockBits(bitmapDatalist[i]); y hnpssimsch microsoft coment ary 228606{ eprint v=vs. 110). aspx ans. s22n017 Walkthrough Using Datatow in a Windows Foems Applicaton // Unlock the result bitmap. result .UnlockBits(resultsitmapbata) ; // Return the result. return result; // Event handler for the Choose Folder button. private void toolStripButtont_Click(object sender, EventArgs e) ¢ /1 Create a FolderBrowserDialog object to enable the user to // select a folder. FolderBrowserDialog dig = new FolderBrowserDialog { ShowNewFoldersutton = false // Set the selected path to the common Sample Pictures folder U1 if it exists. string initialDirectory = Path.Combine( Environment .GetFolderPath (Environment .SpecialFolder.ConmonPictures),, "Sample Pictures"); Lf (Directory. Exists(initialDirectory)) { dig.SelectedPath = initialDirectory; } // Show the dialog and process the dataflow network. Af (dlg-Show0ialog() == DialogResult.oK) // Create a new CancellationTokenSource object to enable JI cancellation. cancellationTokenSource = new CancellationTokenSource(); JI Create the image processing network if needed. if (headBlock == nu11) £ headBlock = CreateTnageProcessingNetwork(); ? // Post the selected path to the network. headBlock.Post(dig.SelectedPath) ; // Enable the Cancel button and disable the Choose Folder button. ‘toolstripsuttont. Enabled = false; toolStripButton2.Enabled = trues 11 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 1] the dataflow network will respond to the cancellation request. cancellationTokenSource.Cancel(); } ~Form() t cancellationTokenSource.Dispose(); } ‘The following illustration shows typical output for the common \Sample Pictures\ folder. hpssimsch microsoft comen-usibrarynh228606{deprirtr v=vs. 110) aspx ans 220017 Walkthrough Using Datatow in a Windows Foems Applicaton Fee] iF Forma Choose Folder Cancel Next Steps See Also Dataflow © 2017 Microsoft hpssimsch microsoft comen-usibrarynh228606{deprirtr v=vs. 110) aspx 113

You might also like