| netlib.narod.ru | < Назад | Оглавление | Далее > |
Хочу обратить ваше внимание на небольшой структурный изъян программы СарtureLoss. В идеале класс CaptureLossWindow можно было бы задействовать в других программах, но это невозможно, так как имя класса, использующего CaptureLossWindow, жестко прописано в определении поля CaptureLossWindow:
public CaptureLoss form;
Но почему имя этого класса должно быть здесь жестко запрограммировано? Потому, что если записать его в виде
public Form form;
все просто перестанет работать. Причина в том, что позже класс вызывает
form.OnLostCapture();
что генерирует ошибку периода компиляции, поскольку form определен как объект типа Form, а метод OnLostCapture определен не в классе Form, а в классе CaptureLoss.
Есть ли выход из этой затруднительной ситуации? Хорошее решение — использовать интерфейс С#. Интерфейс похож на определение класса. Он содержит методы, свойства и индексатор. Но интерфейс содержит лишь сигнатуры (signatures) этих членов, но не их тела.
Как известно, класс может быть наследником другого класса, а класс, который не выглядит ничьим наследником, на самом деле является наследником Object. Класс также может быть наследником одного или нескольких интерфейсов. Если класс наследует интерфейсу, в нем должны быть реализованы все методы и свойства, определенные в интерфейсе. (Интерфейсы также могут наследовать другим интерфейсам; тогда в классе, производном от такого интерфейса-наследника, должны быть также реализованы все методы и свойства, определенные в базовом интерфейсе.) Интерфейсы в С# теснее всего соприкасаются с понятием множественного наследования, но от интерфейсов наследуются только имена методов и свойств, но не их код.
Интерфейсы помогают повышать степень абстрактности классов, так как для определения переменной вместо имени класса или структуры может быть использовано имя соответствующего интерфейса, после чего класс может вызывать методы и свойства, определенные в этом интерфейсе.
Небольшая программа CaptureLossNotifyWindow содержит наряду с одноименным классом интерфейс ICaptureLossNotify. Согласно правилам именования имена интерфейсов начинаются с заглавной буквы I.
CaptureLossNotifyWindow.cs
//--------------------------------------------------------
// CaptureLossNotifyWindow.cs (C) 2001 by Charles Petzold
//--------------------------------------------------------
using System;
using System.Drawing;
using System.Windows.Forms;
interface ICaptureLossNotify
{
void OnLostCapture();
}
class CaptureLossNotifyWindow: NativeWindow
{
public ICaptureLossNotify control;
protected override void WndProc(ref Message message)
{
if (message.Msg == 533) // WM_CAPTURECHANGED
control.OnLostCapture();
base.WndProc(ref message);
}
}
Интерфейс определяет сигнатуру только для метода OnLostCapture. Заметьте: поле control в классе CaptureLossNotifyWindow определено как объект типа ICaptureLossNotify:
public ICaptureLossNotify control;
Этот факт говорит компилятору, что на переменной control может быть вызван метод OnLostCapture, что и делает метод WndProc.
Чтобы задействовать преимущества класса CaptureLossNotifyWindow, форма должна указать, что наряду с Form ее предком был интерфейс ICaptureLossNotify. В свою очередь это свидетельство того, что в классе реализован метод OnLostCapture. Вот программа, где реализован код рисования залитых прямоугольников при помощи наследования от ICaptureLossNotify.
EvenBetterBlockOut.cs
//---------------------------------------------------
// EvenBetterBlockOut.cs (C) 2001 by Charles Petzold
//---------------------------------------------------
using System;
using System.Drawing;
using System.Windows.Forms;
class EvenBetterBlockOut: Form, ICaptureLossNotify
{
bool bBlocking, bValidBox;
Point ptBeg, ptEnd;
Rectangle rectBox;
public static void Main()
{
Application.Run(new EvenBetterBlockOut());
}
public EvenBetterBlockOut()
{
Text = "Even Better Blockout";
BackColor = SystemColors.Window;
ForeColor = SystemColors.WindowText;
// Подключаем системный объект окна
CaptureLossNotifyWindow win = new CaptureLossNotifyWindow();
win.control = this;
win.AssignHandle(Handle);
}
protected override void OnMouseDown(MouseEventArgs mea)
{
if (mea.Button == MouseButtons.Left)
{
ptBeg = ptEnd = new Point(mea.X, mea.Y);
Graphics grfx = CreateGraphics();
grfx.DrawRectangle(new Pen(ForeColor), Rect(ptBeg, ptEnd));
grfx.Dispose();
bBlocking = true;
}
}
protected override void OnMouseMove(MouseEventArgs mea)
{
if (bBlocking)
{
Graphics grfx = CreateGraphics();
grfx.DrawRectangle(new Pen(BackColor), Rect(ptBeg, ptEnd));
ptEnd = new Point(mea.X, mea.Y);
grfx.DrawRectangle(new Pen(ForeColor), Rect(ptBeg, ptEnd));
grfx.Dispose();
Invalidate();
}
}
protected override void OnMouseUp(MouseEventArgs mea)
{
if (bBlocking)
{
Graphics grfx = CreateGraphics();
rectBox = Rect(ptBeg, new Point(mea.X, mea.Y));
grfx.DrawRectangle(new Pen(BackColor), rectBox);
grfx.Dispose();
bBlocking = false;
bValidBox = true;
Invalidate();
}
}
protected override void OnKeyPress(KeyPressEventArgs kpea)
{
if (kpea.KeyChar == '\x001B')
Capture = false;
}
public void OnLostCapture()
{
if (bBlocking)
{
Graphics grfx = CreateGraphics();
grfx.DrawRectangle(new Pen(BackColor), Rect(ptBeg, ptEnd));
grfx.Dispose();
bBlocking = false;
Invalidate();
}
}
protected override void OnPaint(PaintEventArgs pea)
{
Graphics grfx = pea.Graphics;
if (bValidBox)
grfx.FillRectangle(new SolidBrush(ForeColor), rectBox);
if (bBlocking)
grfx.DrawRectangle(new Pen(ForeColor), Rect(ptBeg, ptEnd));
}
Rectangle Rect(Point ptBeg, Point ptEnd)
{
return new Rectangle(Math.Min(ptBeg.X, ptEnd.X),
Math.Min(ptBeg.Y, ptEnd.Y),
Math.Abs(ptEnd.X - ptBeg.X),
Math.Abs(ptEnd.Y - ptBeg.Y));
}
}
Получив вызов OnMouseUp, программа завершает операцию с отслеживанием мыши нормально, а если получен вызов OnLostCapture, перед которым не было вызова OnMouseUp, она прерывает такую операцию.
Наконец-то мы получили в лице EvenBetterBlockOut программу, включающую обработку всех вариантов потери захвата мыши.
| netlib.narod.ru | < Назад | Оглавление | Далее > |