netlib.narod.ru< Назад | Оглавление | Далее >

Заставляем работать дочерние объекты

Что вам напоминают крестики, которые рисует программа Checker? Может, сильно увеличенные флажки вроде тех, что встречаются в диалоговых окнах Windows? Помимо улучшения структуры программ и организации их в модули, что показано на примере MouseCursorsProperty, элементы управления помогают определять позицию курсора.

Выше я показал ряд простых способов применения элементов управления Panel и Label, но нашу задачу можно решить и проще. Вовсе не обязательно использовать готовые элементы управления, когда можно сделать свои, создав подклассы класса Control. Control — это основа всех предопределенных элементов управления в Windows Forms. Однако при создании собственных элементов управления рекомендуется использовать в качестве родительского класса UserControl, который является наследником Control через ScrollableControl и ContainerControl.

Вот класс CheckerChild, производный от UserControl. У него единственное поле типа bool, значение которого меняется в ответ на вызов OnClick. При исполнении метода OnPaint программа рисует контур прямоугольника и, если значение логической переменной равно true, рисует в прямоугольнике крестик.

CheckerChild.cs

  //---------------------------------------------
  // CheckerChild.cs (C) 2001 by Charles Petzold
  //---------------------------------------------
  using System;
  using System.Drawing;
  using System.Windows.Forms;

  class CheckerChild: UserControl
  {
      bool bChecked = false;

      public CheckerChild()
      {
          ResizeRedraw = true;
      }
      protected override void OnClick(EventArgs ea)
      {
          base.OnClick(ea);

          bChecked = !bChecked;
          Invalidate();
      }
      protected override void OnKeyDown(KeyEventArgs kea)
      {
          switch(kea.KeyCode)
          {
          case Keys.Enter:
          case Keys.Space:
               OnClick(new EventArgs());
               break;
          }
      }
      protected override void OnPaint(PaintEventArgs pea)
      {
          Graphics grfx = pea.Graphics;
          Pen      pen  = new Pen(ForeColor);

          grfx.DrawRectangle(pen, ClientRectangle);

          if (bChecked)
          {
              grfx.DrawLine(pen, 0, 0, ClientSize.Width, ClientSize.Height);
              grfx.DrawLine(pen, 0, ClientSize.Height, ClientSize.Width, 0);
          }
      }
  }

Этот класс также реагирует на нажатие клавиш Enter и пробел, воспроизводя эффект метода OnClick.

Следующая программа создает 20 таких элементов управления и размещает их в клиентской области. Таким образом, она очень похожа на показанную выше MouseCursorsProperty, кроме замены Label пользовательским элементом управления. Сложнее всего здесь вызов метода OnResize, во время которого форма должна изменить значения свойств Location и Size каждого элемента управления.

CheckerWithChildren.cs

  //----------------------------------------------------
  // CheckerWithChildren.cs (C) 2001 by Charles Petzold
  //----------------------------------------------------
  using System;
  using System.Drawing;
  using System.Windows.Forms;

  class CheckerWithChildren: Form
  {
      protected const int       xNum = 5;
      protected const int       yNum = 4;
      protected CheckerChild[,] acntlChild;

      public static void Main()
      {
          Application.Run(new CheckerWithChildren());
      }
      public CheckerWithChildren()
      {
          Text = "Checker with Children";
          BackColor = SystemColors.Window;
          ForeColor = SystemColors.WindowText;
          CreateChildren();

          OnResize(EventArgs.Empty);
      }
      protected virtual void CreateChildren()
      {
          acntlChild = new CheckerChild[yNum, xNum];

          for (int y = 0; y < yNum; y++)
          for (int x = 0; x < xNum; x++)
          {
              acntlChild[y, x] = new CheckerChild();
              acntlChild[y, x].Parent = this;
          }
      }
      protected override void OnResize(EventArgs ea)
      {
          int cxBlock = ClientSize.Width / xNum;
          int cyBlock = ClientSize.Height / yNum;

          for (int y = 0; y < yNum; y++)
          for (int x = 0; x < xNum; x++)
          {
              acntlChild[y, x].Location = new Point(x*cxBlock, y*cyBlock);
              acntlChild[y, x].Size     = new Size(cxBlock, cyBlock);
          }
     }
}

Самое приятное в этой программе то, что она не определяет позицию курсора! Дочернему элементу управления абсолютно не важно, где его щелкают. Получив вызов , он только рисует или стирает крестик, a Windows сама определяет позицию курсора, чтобы узнать, какому из элементов управления адресован щелчок мыши.

В данном конкретном случае родительской форме не интересно, когда щелкают по ее дочернему объекту. Но она без труда может установить обработчик события Click для этого дочернего объекта. (Метод OnClick в классе CheckerChild вызывает base.OnClick, чтобы гарантировать вызов обработчиков события Click). Форме потребуется метод, определенный так:

  void ChildOnClick(object obj, EventArgs ea)

Обработчики событий будут установлены в цикле во время создания элементов управления:

  acntlChild[y, x].Click += new EventHandler(ChildOnClick);

Поле bChecked класса CheckerChild можно сделать открытым (public}, чтобы родительский объект смог найти сведения о состоянии любого из своих дочерних объектов. Можно также реализовать в CheckerChild свойство, дающее доступ к этим значениям.

А есть ли у CheckerWithChildren интерфейс, использующий клавиатуру? Вопрос интересный... Если запустить программу и нажимать Enter или пробел, программа будет рисовать и стирать крестик в левом верхнем углу. Теперь 7 раз нажмите клавишу Tab (или стрелку вправо, или стрелку вниз, или любую комбинацию этих трех клавиш, но тоже 7 раз). А сейчас нажмите Enter или пробел. В итоге будет нарисован (или стерт) крестик в центральном прямоугольнике во втором ряду.

Без всяких усилий со стороны программиста форма отвечает на клавишу Tab и стрелки, передавая фокус ввода от одного из 20 элементов управления к другому. Эти 20 элементов управления создаются в определенном порядке, начиная с левого верхнего угла и далее, ряд за рядом. Клавиша Tab, стрелка вправо и стрелка вниз передают фокус ввода следующему элементу управления, придерживаясь этого порядка. Клавиши стрелка влево и стрелка вверх, а также комбинация Shift+Tab возвращают фокус ввода предыдущему элементу управления. Этот интерфейс реализован в классе ContainerControl, одном из предков Form. Также можно передать фокус ввода любому элементу управления, щелкнув по нему. На нажатие Enter или пробела реагирует сам элемент управления.

Но в этой программе есть одно неудобство: элемент управления, обладающий фокусом ввода, не отличается от других. Наверное, более широкой границы было бы достаточно, чтобы отличить элемент с фокусом ввода от других.

В следующем классе — подклассе исходного CheckerChild — эта функция реализована.

CheckerChildWithFocus.cs

  //------------------------------------------------------
  // CheckerChildWithFocus.cs (C) 2001 by Charles Petzold
  //------------------------------------------------------
  using System;
  using System.Drawing;
  using System.Drawing.Drawing2D;
  using System.Windows.Forms;

  class CheckerChildWithFocus: CheckerChild
  {
      protected override void OnGotFocus(EventArgs ea)
      {
          Invalidate();
      }
      protected override void OnLostFocus(EventArgs ea)
      {
          Invalidate();
      }
      protected override void OnPaint(PaintEventArgs pea)
      {
          base.OnPaint(pea);

          if (Focused)
          {
              Graphics grfx = pea.Graphics;
              grfx.DrawRectangle(new Pen(ForeColor, 5), ClientRectangle);
          }
      }
  }

При получении и потере фокуса новый элемент управления делает себя недействительным, что позволяет методу OnPaint перерисовать его границу в зависимости от значения свойства Focused.

Форма, использующая новые элементы управления, в основном идентична CheckerWithChildren, но вместо CheckerChild для нее нужны дочерние объекты типа CheckerChildWithFocus. Поэтому новая форма переопределяет метод CreateChildren класса CheckerWithChildren.

CheckerWithChildrenAndFocus.cs

  //------------------------------------------------------------
  // CheckerWithChildrenAndFocus.cs (C) 2001 by Charles Petzold
  //------------------------------------------------------------
  using System;
  using System.Drawing;
  using System.Windows.Forms;

  class CheckerWithChildrenAndFocus: CheckerWithChildren
  {
      public new static void Main()
      {
          Application.Run(new CheckerWithChildrenAndFocus());
      }
      public CheckerWithChildrenAndFocus()
      {
          Text = "Checker with Children and Focus";
      }
      protected override void CreateChildren()
      {
          acntlChild = new CheckerChildWithFocus[yNum, xNum];

          for (int y = 0; y < yNum; y++)
          for (int x = 0; x < xNum; x++)
          {
              acntlChild[y, x] = new CheckerChildWithFocus();
              acntlChild[y, x].Parent = this;
          }
      }
  }

netlib.narod.ru< Назад | Оглавление | Далее >

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