Browse Source

Merge pull request #310 from voidstar69/V3

Progress updates to client code

Former-commit-id: aab837f658c761e8766ce1adc23a8d722c33bab2
Former-commit-id: 0ac3c70f0d21890c08532fabbb0f67e16a798e2e
Former-commit-id: 65ecdf82f8f056dd3ad35c5e070f43745631ce65
pull/17/head
James Jackson-South 10 years ago
parent
commit
7d85c2b49a
  1. 1
      src/ImageProcessor/Filters/Alpha.cs
  2. 1
      src/ImageProcessor/Filters/BackgroundColor.cs
  3. 1
      src/ImageProcessor/Filters/Binarization/Threshold.cs
  4. 1
      src/ImageProcessor/Filters/Blend.cs
  5. 1
      src/ImageProcessor/Filters/Brightness.cs
  6. 1
      src/ImageProcessor/Filters/ColorMatrix/ColorMatrixFilter.cs
  7. 1
      src/ImageProcessor/Filters/Contrast.cs
  8. 1
      src/ImageProcessor/Filters/Convolution/Convolution2DFilter.cs
  9. 1
      src/ImageProcessor/Filters/Convolution/Convolution2PassFilter.cs
  10. 1
      src/ImageProcessor/Filters/Convolution/ConvolutionFilter.cs
  11. 1
      src/ImageProcessor/Filters/Glow.cs
  12. 1
      src/ImageProcessor/Filters/Invert.cs
  13. 1
      src/ImageProcessor/Filters/Pixelate.cs
  14. 1
      src/ImageProcessor/Filters/Vignette.cs
  15. 22
      src/ImageProcessor/IImageProcessor.cs
  16. 37
      src/ImageProcessor/ParallelImageProcessor.cs
  17. 1
      src/ImageProcessor/Samplers/Crop.cs
  18. 1
      src/ImageProcessor/Samplers/EntropyCrop.cs
  19. 90
      src/ImageProcessor/Samplers/ImageSampleExtensions.cs
  20. 2
      src/ImageProcessor/Samplers/Resize.cs
  21. 2
      src/ImageProcessor/Samplers/Rotate.cs
  22. 15
      src/ImageProcessor/Samplers/RotateFlip.cs
  23. 7
      tests/ImageProcessor.Tests/Processors/Filters/FilterTests.cs
  24. 19
      tests/ImageProcessor.Tests/Processors/Samplers/SamplerTests.cs

1
src/ImageProcessor/Filters/Alpha.cs

@ -55,6 +55,7 @@ namespace ImageProcessor.Filters
color *= alphaVector;
target[x, y] = Color.FromNonPremultiplied(new Color(color));
}
this.OnRowProcessed();
}
});
}

1
src/ImageProcessor/Filters/BackgroundColor.cs

@ -65,6 +65,7 @@ namespace ImageProcessor.Filters
target[x, y] = color;
}
this.OnRowProcessed();
}
});
}

1
src/ImageProcessor/Filters/Binarization/Threshold.cs

@ -75,6 +75,7 @@ namespace ImageProcessor.Filters
// Any channel will do since it's greyscale.
target[x, y] = color.B >= threshold ? upper : lower;
}
this.OnRowProcessed();
}
});
}

1
src/ImageProcessor/Filters/Blend.cs

@ -69,6 +69,7 @@ namespace ImageProcessor.Filters
target[x, y] = color;
}
this.OnRowProcessed();
}
});
}

1
src/ImageProcessor/Filters/Brightness.cs

@ -57,6 +57,7 @@ namespace ImageProcessor.Filters
target[x, y] = Color.Compress(new Color(vector3, color.A));
}
this.OnRowProcessed();
}
});
}

1
src/ImageProcessor/Filters/ColorMatrix/ColorMatrixFilter.cs

@ -39,6 +39,7 @@ namespace ImageProcessor.Filters
{
target[x, y] = this.ApplyMatrix(source[x, y], matrix);
}
this.OnRowProcessed();
}
});
}

1
src/ImageProcessor/Filters/Contrast.cs

@ -57,6 +57,7 @@ namespace ImageProcessor.Filters
color += shiftVector;
target[x, y] = Color.Compress(new Color(color));
}
this.OnRowProcessed();
}
});
}

1
src/ImageProcessor/Filters/Convolution/Convolution2DFilter.cs

@ -101,6 +101,7 @@ namespace ImageProcessor.Filters
Color targetColor = target[x, y];
target[x, y] = new Color(red, green, blue, targetColor.A);
}
this.OnRowProcessed();
}
});
}

1
src/ImageProcessor/Filters/Convolution/Convolution2PassFilter.cs

@ -104,6 +104,7 @@ namespace ImageProcessor.Filters
target[x, y] = destination;
}
this.OnRowProcessed();
}
});
}

1
src/ImageProcessor/Filters/Convolution/ConvolutionFilter.cs

@ -76,6 +76,7 @@ namespace ImageProcessor.Filters
target[x, y] = new Color(red, green, blue);
}
this.OnRowProcessed();
}
});
}

1
src/ImageProcessor/Filters/Glow.cs

@ -51,6 +51,7 @@ namespace ImageProcessor.Filters
Color sourceColor = target[x, y];
target[x, y] = Color.Lerp(glowColor, sourceColor, .5f * (distance / maxDistance));
}
this.OnRowProcessed();
});
}
}

1
src/ImageProcessor/Filters/Invert.cs

@ -35,6 +35,7 @@ namespace ImageProcessor.Filters
Vector3 vector = inverseVector - color.ToVector3();
target[x, y] = new Color(vector, color.A);
}
this.OnRowProcessed();
}
});
}

1
src/ImageProcessor/Filters/Pixelate.cs

@ -85,6 +85,7 @@ namespace ImageProcessor.Filters
}
}
}
this.OnRowProcessed();
}
});
}

1
src/ImageProcessor/Filters/Vignette.cs

@ -51,6 +51,7 @@ namespace ImageProcessor.Filters
Color sourceColor = target[x, y];
target[x, y] = Color.Lerp(vignetteColor, sourceColor, 1 - .9f * distance / maxDistance);
}
this.OnRowProcessed();
});
}
}

22
src/ImageProcessor/IImageProcessor.cs

@ -5,11 +5,33 @@
namespace ImageProcessor
{
public class ProgressEventArgs : System.EventArgs
{
public int numRowsProcessed;
public int totalRows;
}
/// <summary>
/// A delegate which is called as progress is made processing the image.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public delegate void ProgressEventHandler(object sender, ProgressEventArgs e);
/// <summary>
/// Encapsulates methods to alter the pixels of an image.
/// </summary>
public interface IImageProcessor
{
/// <summary>
/// Event fires when each row of the source image has been processed.
/// </summary>
/// <remarks>
/// This event may be called from threads other than the client thread, and from multiple threads simultaneously.
/// Individual row notifications may arrived out of order.
/// </remarks>
event ProgressEventHandler OnProgress;
/// <summary>
/// Applies the process to the specified portion of the specified <see cref="ImageBase"/>.
/// </summary>

37
src/ImageProcessor/ParallelImageProcessor.cs

@ -6,6 +6,7 @@
namespace ImageProcessor
{
using System;
using System.Threading;
using System.Threading.Tasks;
/// <summary>
@ -13,11 +14,42 @@ namespace ImageProcessor
/// </summary>
public abstract class ParallelImageProcessor : IImageProcessor
{
/// <inheritdoc/>
public event ProgressEventHandler OnProgress;
/// <summary>
/// Gets or sets the count of workers to run the process in parallel.
/// </summary>
public virtual int Parallelism { get; set; } = Environment.ProcessorCount * 2;
/// <summary>
/// The number of rows processed by a derived class.
/// </summary>
private int numRowsProcessed;
/// <summary>
/// The total number of rows that will be processed by a derived class.
/// </summary>
private int totalRows;
/// <summary>
/// Must be called by derived classes after processing a single row.
/// </summary>
protected void OnRowProcessed()
{
if(this.OnProgress != null)
{
int currThreadNumRows = Interlocked.Add(ref this.numRowsProcessed, 1);
// Multi-pass filters process multiple times more rows than totalRows, so update totalRows on the fly
if (currThreadNumRows > this.totalRows)
this.totalRows = currThreadNumRows;
// Report progress. This may be on the client's thread, or on a Task library thread.
this.OnProgress(this, new ProgressEventArgs { numRowsProcessed = currThreadNumRows, totalRows = this.totalRows });
}
}
/// <inheritdoc/>
public void Apply(ImageBase target, ImageBase source, Rectangle sourceRectangle)
{
@ -26,6 +58,9 @@ namespace ImageProcessor
Image temp = frame != null ? new Image(frame) : new Image((Image)source);
this.OnApply(temp, target, target.Bounds, sourceRectangle);
this.numRowsProcessed = 0;
this.totalRows = sourceRectangle.Height;
if (this.Parallelism > 1)
{
int partitionCount = this.Parallelism;
@ -72,6 +107,8 @@ namespace ImageProcessor
this.OnApply(temp, target, target.Bounds, sourceRectangle);
targetRectangle = target.Bounds;
this.numRowsProcessed = 0;
this.totalRows = targetRectangle.Bottom;
if (this.Parallelism > 1)
{

1
src/ImageProcessor/Samplers/Crop.cs

@ -31,6 +31,7 @@ namespace ImageProcessor.Samplers
{
target[x, y] = source[x, y];
}
this.OnRowProcessed();
}
});
}

1
src/ImageProcessor/Samplers/EntropyCrop.cs

@ -89,6 +89,7 @@ namespace ImageProcessor.Samplers
target[x, y] = source[offsetX, offsetY];
}
this.OnRowProcessed();
});
}
}

90
src/ImageProcessor/Samplers/ImageSampleExtensions.cs

@ -16,10 +16,11 @@ namespace ImageProcessor.Samplers
/// <param name="source">The image to resize.</param>
/// <param name="width">The target image width.</param>
/// <param name="height">The target image height.</param>
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
/// <returns>The <see cref="Image"/></returns>
public static Image Crop(this Image source, int width, int height)
public static Image Crop(this Image source, int width, int height, ProgressEventHandler progressHandler = null)
{
return Crop(source, width, height, source.Bounds);
return Crop(source, width, height, source.Bounds, progressHandler);
}
/// <summary>
@ -35,8 +36,9 @@ namespace ImageProcessor.Samplers
/// <param name="sourceRectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to draw.
/// </param>
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
/// <returns>The <see cref="Image"/></returns>
public static Image Crop(this Image source, int width, int height, Rectangle sourceRectangle)
public static Image Crop(this Image source, int width, int height, Rectangle sourceRectangle, ProgressEventHandler progressHandler = null)
{
if (sourceRectangle.Width < width || sourceRectangle.Height < height)
{
@ -45,7 +47,16 @@ namespace ImageProcessor.Samplers
source = source.Resize(sourceRectangle.Width, sourceRectangle.Height);
}
return source.Process(width, height, sourceRectangle, new Rectangle(0, 0, width, height), new Crop());
var processor = new Crop();
processor.OnProgress += progressHandler;
try
{
return source.Process(width, height, sourceRectangle, new Rectangle(0, 0, width, height), processor);
}
finally
{
processor.OnProgress -= progressHandler;
}
}
/// <summary>
@ -53,10 +64,20 @@ namespace ImageProcessor.Samplers
/// </summary>
/// <param name="source">The image to crop.</param>
/// <param name="threshold">The threshold for entropic density.</param>
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
/// <returns>The <see cref="Image"/></returns>
public static Image EntropyCrop(this Image source, float threshold = .5f)
public static Image EntropyCrop(this Image source, float threshold = .5f, ProgressEventHandler progressHandler = null)
{
return source.Process(source.Width, source.Height, source.Bounds, source.Bounds, new EntropyCrop(threshold));
var processor = new EntropyCrop(threshold);
processor.OnProgress += progressHandler;
try
{
return source.Process(source.Width, source.Height, source.Bounds, source.Bounds, processor);
}
finally
{
processor.OnProgress -= progressHandler;
}
}
/// <summary>
@ -65,11 +86,12 @@ namespace ImageProcessor.Samplers
/// <param name="source">The image to resize.</param>
/// <param name="width">The target image width.</param>
/// <param name="height">The target image height.</param>
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
/// <returns>The <see cref="Image"/></returns>
/// <remarks>Passing zero for one of height or width will automatically preserve the aspect ratio of the original image</remarks>
public static Image Resize(this Image source, int width, int height)
public static Image Resize(this Image source, int width, int height, ProgressEventHandler progressHandler = null)
{
return Resize(source, width, height, new BicubicResampler());
return Resize(source, width, height, new BicubicResampler(), progressHandler);
}
/// <summary>
@ -79,11 +101,12 @@ namespace ImageProcessor.Samplers
/// <param name="width">The target image width.</param>
/// <param name="height">The target image height.</param>
/// <param name="sampler">The <see cref="IResampler"/> to perform the resampling.</param>
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
/// <returns>The <see cref="Image"/></returns>
/// <remarks>Passing zero for one of height or width will automatically preserve the aspect ratio of the original image</remarks>
public static Image Resize(this Image source, int width, int height, IResampler sampler)
public static Image Resize(this Image source, int width, int height, IResampler sampler, ProgressEventHandler progressHandler = null)
{
return Resize(source, width, height, sampler, source.Bounds);
return Resize(source, width, height, sampler, source.Bounds, progressHandler);
}
/// <summary>
@ -97,9 +120,10 @@ namespace ImageProcessor.Samplers
/// <param name="sourceRectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to draw.
/// </param>
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
/// <returns>The <see cref="Image"/></returns>
/// <remarks>Passing zero for one of height or width will automatically preserve the aspect ratio of the original image</remarks>
public static Image Resize(this Image source, int width, int height, IResampler sampler, Rectangle sourceRectangle)
public static Image Resize(this Image source, int width, int height, IResampler sampler, Rectangle sourceRectangle, ProgressEventHandler progressHandler = null)
{
if (width == 0 && height > 0)
{
@ -111,7 +135,16 @@ namespace ImageProcessor.Samplers
height = source.Height * width / source.Width;
}
return source.Process(width, height, sourceRectangle, new Rectangle(0, 0, width, height), new Resize(sampler));
var processor = new Resize(sampler);
processor.OnProgress += progressHandler;
try
{
return source.Process(width, height, sourceRectangle, new Rectangle(0, 0, width, height), processor);
}
finally
{
processor.OnProgress -= progressHandler;
}
}
/// <summary>
@ -119,10 +152,11 @@ namespace ImageProcessor.Samplers
/// </summary>
/// <param name="source">The image to resize.</param>
/// <param name="degrees">The angle in degrees to perform the rotation.</param>
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
/// <returns>The <see cref="Image"/></returns>
public static Image Rotate(this Image source, float degrees)
public static Image Rotate(this Image source, float degrees, ProgressEventHandler progressHandler = null)
{
return Rotate(source, degrees, new BicubicResampler());
return Rotate(source, degrees, new BicubicResampler(), progressHandler);
}
/// <summary>
@ -131,10 +165,20 @@ namespace ImageProcessor.Samplers
/// <param name="source">The image to resize.</param>
/// <param name="degrees">The angle in degrees to perform the rotation.</param>
/// <param name="sampler">The <see cref="IResampler"/> to perform the resampling.</param>
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
/// <returns>The <see cref="Image"/></returns>
public static Image Rotate(this Image source, float degrees, IResampler sampler)
public static Image Rotate(this Image source, float degrees, IResampler sampler, ProgressEventHandler progressHandler = null)
{
return source.Process(source.Width, source.Height, source.Bounds, source.Bounds, new Rotate(sampler) { Angle = degrees });
var processor = new Rotate(sampler) { Angle = degrees };
processor.OnProgress += progressHandler;
try
{
return source.Process(source.Width, source.Height, source.Bounds, source.Bounds, processor);
}
finally
{
processor.OnProgress -= progressHandler;
}
}
/// <summary>
@ -143,10 +187,20 @@ namespace ImageProcessor.Samplers
/// <param name="source">The image to resize.</param>
/// <param name="rotateType">The <see cref="RotateType"/> to perform the rotation.</param>
/// <param name="flipType">The <see cref="FlipType"/> to perform the flip.</param>
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
/// <returns>The <see cref="Image"/></returns>
public static Image RotateFlip(this Image source, RotateType rotateType, FlipType flipType)
public static Image RotateFlip(this Image source, RotateType rotateType, FlipType flipType, ProgressEventHandler progressHandler = null)
{
return source.Process(source.Width, source.Height, source.Bounds, source.Bounds, new RotateFlip(rotateType, flipType));
var processor = new RotateFlip(rotateType, flipType);
processor.OnProgress += progressHandler;
try
{
return source.Process(source.Width, source.Height, source.Bounds, source.Bounds, processor);
}
finally
{
processor.OnProgress -= progressHandler;
}
}
}
}

2
src/ImageProcessor/Samplers/Resize.cs

@ -81,6 +81,7 @@ namespace ImageProcessor.Samplers
target[x, y] = source[originX, originY];
}
this.OnRowProcessed();
}
});
@ -141,6 +142,7 @@ namespace ImageProcessor.Samplers
destination = Color.Compress(destination);
target[x, y] = destination;
}
this.OnRowProcessed();
}
});
}

2
src/ImageProcessor/Samplers/Rotate.cs

@ -102,6 +102,7 @@ namespace ImageProcessor.Samplers
target[x, y] = source[rotated.X, rotated.Y];
}
}
this.OnRowProcessed();
}
});
@ -152,6 +153,7 @@ namespace ImageProcessor.Samplers
destination = Color.Compress(destination);
target[x, y] = destination;
}
this.OnRowProcessed();
}
});
}

15
src/ImageProcessor/Samplers/RotateFlip.cs

@ -73,7 +73,7 @@ namespace ImageProcessor.Samplers
/// </summary>
/// <param name="target">The target image.</param>
/// <param name="source">The source image.</param>
private static void Rotate270(ImageBase target, ImageBase source)
private void Rotate270(ImageBase target, ImageBase source)
{
int width = source.Width;
int height = source.Height;
@ -90,6 +90,7 @@ namespace ImageProcessor.Samplers
newY = width - newY - 1;
temp[newX, newY] = source[x, y];
}
this.OnRowProcessed();
});
target.SetPixels(height, width, temp.Pixels);
@ -100,7 +101,7 @@ namespace ImageProcessor.Samplers
/// </summary>
/// <param name="target">The target image.</param>
/// <param name="source">The source image.</param>
private static void Rotate180(ImageBase target, ImageBase source)
private void Rotate180(ImageBase target, ImageBase source)
{
int width = source.Width;
int height = source.Height;
@ -114,6 +115,7 @@ namespace ImageProcessor.Samplers
int newY = height - y - 1;
target[newX, newY] = source[x, y];
}
this.OnRowProcessed();
});
}
@ -122,7 +124,7 @@ namespace ImageProcessor.Samplers
/// </summary>
/// <param name="target">The target image.</param>
/// <param name="source">The source image.</param>
private static void Rotate90(ImageBase target, ImageBase source)
private void Rotate90(ImageBase target, ImageBase source)
{
int width = source.Width;
int height = source.Height;
@ -136,6 +138,7 @@ namespace ImageProcessor.Samplers
int newX = height - y - 1;
temp[newX, x] = source[x, y];
}
this.OnRowProcessed();
});
target.SetPixels(height, width, temp.Pixels);
@ -146,7 +149,7 @@ namespace ImageProcessor.Samplers
/// at half the height of the image.
/// </summary>
/// <param name="target">Target image to apply the process to.</param>
private static void FlipX(ImageBase target)
private void FlipX(ImageBase target)
{
int width = target.Width;
int height = target.Height;
@ -163,6 +166,7 @@ namespace ImageProcessor.Samplers
target[x, y] = temp[x, newY];
target[x, newY] = temp[x, y];
}
this.OnRowProcessed();
});
}
@ -171,7 +175,7 @@ namespace ImageProcessor.Samplers
/// at half of the width of the image.
/// </summary>
/// <param name="target">Target image to apply the process to.</param>
private static void FlipY(ImageBase target)
private void FlipY(ImageBase target)
{
int width = target.Width;
int height = target.Height;
@ -188,6 +192,7 @@ namespace ImageProcessor.Samplers
target[x, y] = temp[newX, y];
target[newX, y] = temp[x, y];
}
this.OnRowProcessed();
});
}
}

7
tests/ImageProcessor.Tests/Processors/Filters/FilterTests.cs

@ -65,12 +65,19 @@ namespace ImageProcessor.Tests
string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file);
using (FileStream output = File.OpenWrite($"TestOutput/Filter/{ Path.GetFileName(filename) }"))
{
processor.OnProgress += ProgressUpdate;
image.Process(processor).Save(output);
processor.OnProgress -= ProgressUpdate;
}
Trace.WriteLine($"{ name }: { watch.ElapsedMilliseconds}ms");
}
}
}
private void ProgressUpdate(object sender, ProgressEventArgs e)
{
Assert.InRange(e.numRowsProcessed, 1, e.totalRows);
}
}
}

19
tests/ImageProcessor.Tests/Processors/Samplers/SamplerTests.cs

@ -57,7 +57,7 @@
string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file);
using (FileStream output = File.OpenWrite($"TestOutput/Resize/{filename}"))
{
image.Resize(image.Width / 2, image.Height / 2, sampler)
image.Resize(image.Width / 2, image.Height / 2, sampler, ProgressUpdate)
.Save(output);
}
@ -85,7 +85,7 @@
string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file);
using (FileStream output = File.OpenWrite($"TestOutput/Resize/{filename}"))
{
image.Resize(image.Width / 3, 0, new TriangleResampler())
image.Resize(image.Width / 3, 0, new TriangleResampler(), ProgressUpdate)
.Save(output);
}
@ -113,7 +113,7 @@
string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file);
using (FileStream output = File.OpenWrite($"TestOutput/Resize/{filename}"))
{
image.Resize(0, image.Height / 3, new TriangleResampler())
image.Resize(0, image.Height / 3, new TriangleResampler(), ProgressUpdate)
.Save(output);
}
@ -140,7 +140,7 @@
string filename = Path.GetFileNameWithoutExtension(file) + "-" + rotateType + flipType + Path.GetExtension(file);
using (FileStream output = File.OpenWrite($"TestOutput/RotateFlip/{filename}"))
{
image.RotateFlip(rotateType, flipType)
image.RotateFlip(rotateType, flipType, ProgressUpdate)
.Save(output);
}
@ -167,7 +167,7 @@
string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file);
using (FileStream output = File.OpenWrite($"TestOutput/Rotate/{filename}"))
{
image.Rotate(45, sampler)
image.Rotate(45, sampler, ProgressUpdate)
//.BackgroundColor(Color.Aqua)
.Save(output);
}
@ -193,7 +193,7 @@
string filename = Path.GetFileNameWithoutExtension(file) + "-EntropyCrop" + Path.GetExtension(file);
using (FileStream output = File.OpenWrite($"TestOutput/EntropyCrop/{filename}"))
{
image.EntropyCrop().Save(output);
image.EntropyCrop(.5f, ProgressUpdate).Save(output);
}
}
}
@ -215,7 +215,7 @@
string filename = Path.GetFileNameWithoutExtension(file) + "-Crop" + Path.GetExtension(file);
using (FileStream output = File.OpenWrite($"TestOutput/Crop/{filename}"))
{
image.Crop(image.Width / 2, image.Height / 2).Save(output);
image.Crop(image.Width / 2, image.Height / 2, ProgressUpdate).Save(output);
}
}
}
@ -235,5 +235,10 @@
Assert.Equal(result, expected);
}
private void ProgressUpdate(object sender, ProgressEventArgs e)
{
Assert.InRange(e.numRowsProcessed, 1, e.totalRows);
}
}
}

Loading…
Cancel
Save