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

Сайт управляется системой uCoz