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 | < Назад | Оглавление | Далее > |