Новые средства языка. Индексаторы. Делегат на основе обычного метода класса. Делегаты на основе класса MulticastDelegate, страница 12

Перед тем как создать делегата, надо объявить делегатный тип. Его сигнатура определяется соображениями удобства пользования. Я решил, что при вызове делегатной функции она должна нести информацию: индекс вычисленной точки графика (заданного массивом PointF[]) и координаты текущей точки (имеет тип PointF). В мвязи с этим напрашивается делегатный тип с такой сигнатурой.

public delegate void ProgressHandler (int id, PointF pt);

Новый делегатный тип должен быть объявлен вне класса формы, так как его должны знать два разных класса приложения. В проектах типа Windows Application класс формы должен стоять первым (внутри общего пространства имен). Поэтому подобные объявления нельзя вставлять до объявления самой формы (класса MainForm). Компилятор пропустит, но на этапе выполнения возникнет ошибка. После этого можно приступить к созданию класса, обслуживающего поток, возбуждающий события. В нем надо объявить событие (Move), которое должно иметь тот же самый делегатный тип. Его объявление должно выглядеть так:

private event ProgressHandler Move;    // Event

В данном случае событие — это просто адрес метода (определенного в другом классе). Метод надо вызывать в определенные моменты длительного процесса вычислений. Ключевое слово event можно оставить, а можно и выбросить. Приложение будет работать точно так же, как и с ним!!! В конструкторе класса CalcField следует запомнить адрес метода, реагирующего на событие. Он отождествляется с событием.

public CalcField (Form1 f, ProgressHandler cb)

{

form = f;

Move = cb;

}

Кроме события Move класс CalcField должен иметь метод (назовем его Recalc), который будет вычислять поле в рамках отдельного (рабочего) потока. Внутри метода мы периодически вызываем функцию реакции. Это действие можно назвать возбуждением события.

public void Recalc()

{

// Цикл вычислений, внутри которого изменяется процент выполнения всего задания

{

if (Move != null)  // Если есть потребители уведомлений о событии,

Move (i, p);     // то мы периодически (в цикле) возбуждаем событие

}

}

Адрес метода Recalc, в свою очередь, должен быть задан в качестве параметра при конструировании объекта класса ThreadStart. Главная форма создает и запускает вспомогательный поток таким образом:

private void MainForm_MouseUp (object sender, MouseEventArgs e)

{

//==== Другие действия

CalcField field = new CalcField (this, new ProgressHandler (OnProgress));

thread = new Thread (new ThreadStart (field.Recalc));

thread.Start();

}

Метод OnProgress (он и является делегатом события Move) будет периодически вызываться системой в момент вызова Move (i, p);. Объект Move запомнил адрес этой функции. Мы назвали его событием, но он же является и делегатом события Move. В теле OnProgress мы используем информацию, переданную из другого потока.

public void OnProgress (int id, PointF pt)

{

int pos = (int) Math.Ceiling (100 * (double)id / nodes);

progress.Value = pos;

lblPercent.Text = pos.ToString() + '%';

points[id] = pt;

if (id == nodes - 1)

{

plot.Graph.SetData (points, "Electrostatic field along the line (v/m)", "dist (m)");

plot.Invalidate();

isCalculating = false;

progress.Hide();

lblPercent.Hide();

thread.Abort();

}

}


Приведем полный код приложения.

class CalcField

{

private event ProgressHandler Move;    // Event

private Form1 form;

public CalcField (Form1 f, ProgressHandler cb)

{

form = f;

Move = cb;

}

public void Recalc()

{

Point2D step = (form.pLast - form.pFirst) * (1.0f / (form.nodes - 1));

Point2D pt = form.pFirst;

PointF p = new Point(0,0);

for (int i = 0; i < form.nodes; i++)

{

float

r1 = form.pPos.Dist (pt),

r2 = pt.Dist (form.pNeg),

r11 = r1 * r1,

r22 = r2 * r2,

cs = (pt - form.pPos).Cos (form.pNeg - pt),

d = r11 * r22;

p.Y = d > 0 ?

form.charge * (r11 + r22 - form.charge * (cs + cs)) / d :

p.Y = -1;

if (i > 0)

p.X += !step;

pt += step;

Thread.Sleep(2);

if (Move != null)

Move (i, p);

}

}

}

public class Point2D

{

public float x, y;  // Real coordinates

public Point2D() { x=0; y=0; }