Сообщений 0    Оценка 40        Оценить  
Система Orphus

DataGridView: сохранение ширины колонок

Автор: Кощеев Сергей Александрович
Источник: RSDN Magazine #4-2009
Опубликовано: 20.06.2010
Исправлено: 10.12.2016
Версия текста: 1.0
Аннотация
Введение
История
Особенности Net.Framework
Требования
Идеи
Решение
Черный ящик DLL
Выводы и перспективы

Исходные тексты

Аннотация

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

Введение

ПРИМЕЧАНИЕ

Грид – программный компонент, обеспечивающий вывод строк результатов выполнения запроса к базе данных в виде таблицы. В данной статье речь идет о конкретном .Net-компоненте DataGridView, который в статье просто называется «грид».

Задача сохранения и восстановления установленных пользователем размеров колонок Grid актуальна с прошлого века. Эта задача является частью реализации корректного масштабирования форм. Размеры и разрешения мониторов меняются быстро и не только в большую сторону. Масштабирование форм в качественной системе должно обеспечивать комфортную работу как на 26-дюймовом мониторе, так и на нетбуках с шириной экрана в 600 пикселей.

История

В прошлом в разработках на Visual Basic 5 и 6 значения размеров и местоположения форм, а также ширина колонок сохранялись в системном реестре. Привязка ширины Grid к размерам формы обеспечивалась обработкой события Form_Resize и свойством Tag управляющих элементов, где можно было задать тэг привязки к краю формы. Вся функциональность обеспечивалась вызовом глобальных функций по событиям Form_Load и Form_Unload.

Вот пример (на VB6) сохранения и восстановления ширины колонок в системном реестре, а также масштабирования форм, извлеченный из архивов прошлого века:

      Public
      Sub FormScale(ByVal f As Form)
Dim c As VB.Control, h AsLong, w AsLongForEach c In f.Controls
    If c.Tag = "C"Then' главный масштабируемый
      c.Tag = "C" & CStr(f.Width - c.Width) & "H" & CStr(f.Height - c.Height)
    ElseIf c.Tag Like"C*"Then
      w = CLng(Mid$(c.Tag, 2, InStr(c.Tag, "H") - 2))
      h = CLng(Mid$(c.Tag, InStr(c.Tag, "H") + 1))
      c.Width = f.Width - w
      c.Height = f.Height - h
    ElseIf c.Tag = "R"Then' правые
      c.Tag = "R" & CStr(f.Width - c.Left)
    ElseIf c.Tag Like"R*"Then
      w = CLng(Mid$(c.Tag, 2))
      c.Left = f.Width - w
    ElseIf c.Tag = "B"Then' нижние
      c.Tag = "B" & CStr(f.Height - c.Top)
    ElseIf c.Tag Like"B*"Then
      h = CLng(Mid$(c.Tag, 2))
      c.Top = f.Height - h
    ElseIf c.Tag = "LIRI"ThenIf f.Width < c.Left + c.Width Then f.Width = c.Left + c.Width
    ElseIf c.Tag = "LIHI"ThenIf f.Height < c.Top + c.Height Then f.Height = c.Top + c.Width
    EndIfNext c
EndSubPublicSub SaveGridCols(g AsObject, f As Form)
Dim co AsObjectOnErrorResumeNextForEach co In g.Columns
    SaveSetting App.EXEName, f.Name, g.Name + "_C" + CStr(co.ColIndex), co.Width
  Next co
EndSubPublicSub RestoreGridCols(g AsObject, f As Form)
Dim co AsObject, w
  OnErrorResumeNextForEach co In g.Columns
    w = GetSetting(App.EXEName, f.Name, g.Name + "_C" + CStr(co.ColIndex), 0)
    If w <> 0 Then co.Width = CDbl(w)
  Next co
EndSub

Особенности Net.Framework

Наступил новый век, а вместе с ним пришла технология Net.Framework. Однако актуальность задачи масштабирования форм и запоминания ширины колонки осталась. Что изменилось?

Требования

Теперь сформулируем общие требования и пожелания к реализации:

Идеи

Главная идея реализации заключается в использовании уже готового класса System.Data.DataSet (нетипизированного) для запоминания и сохранения в настройках. Обоснование идеи:

Несомненно, DataSet является несколько непрофильным инструментом для хранения настроек, более логичным было бы использовать специально созданную XML структуру, а также средства XLINQ для чтения и записи. Однако автор предпочел DataSet, руководствуюясь следующими соображениеми:

Вторая идея заключается в том, что для включения механизма сохранения используется лишь один метод. Он добавляет к форме обработчики событий Load и FormClosing.

Вызов этого метода можно сделать из конструктора каждой формы:

       FormSavingSettings.AllSizeSaving.SavingOn(this);

Решение

Теперь перейдём к рассмотрению кода.

Я буду приводить код частями, давая пояснения перед требующими этого частями.

      using System;
using System.Configuration;
using System.Data;
using System.Windows.Forms;
using System.Drawing;

namespace FormSavingSettings
{
  /// <summary>/// Автор: С.А.Кощеев, ИФК "Алемар", НГУЭУ, Новосибирск/// Метод расширения для класса System.Windows.Forms.Form /// реализующий запоминание местоположения и размеров формы /// и ширины колонок всех гридов на форме./// Значения запоминаются по именам формы, грида и колонки./// Для использования метода расширения во всех формах нужно/// включить using FormSavingSettings./// Ширина запоминается по имени колонки./// Для включения механизма сохранения и восстановления достаточно/// в конструктор формы добавить строку:/// this.SavingOn();/// </summary>publicstaticclass AllSizeSaving
  {

Обычно настройки приложения создаются вручную с помощью страницы свойств проекта Settings (эта страница создает файл с именем Settings.settings). Однако настройки можно задавать и программным путем. Класс AllSizeUserSettings автоматизирует сохранение настроек. Код создан на основе примера из MSDN. Класс расположен внутри основного статического класса, что ограничивает его видимость.

      /// <summary>
      /// Класс-оболочка для пользовательских параметров приложения.
      /// его использование ограничено статическим классом eeSaveCW
      /// </summary>
      private
      class AllSizeUserSettings : ApplicationSettingsBase
    {
      /// <summary>/// Единственное свойство с именем Data типа DataSet/// для хранения настроек всех форм и гридов/// </summary>
      [UserScopedSetting()]
      [DefaultSettingValue(null)]
      public DataSet Data
      {
        get
        {
          return ((DataSet)this["dsAllSizeUserSettings"]);
        }
        set
        {
          this["dsAllSizeUserSettings"] = (DataSet)value;
        }
      }
    }

Инициализация переменной, хранящей настройки, даже не понадобилось создавать конструктор.

      /// <summary>
      /// Хранение настроек в статическом классе
      /// </summary>
      private
      static AllSizeUserSettings _settingsSaver = new AllSizeUserSettings();

Метод ResetALL() пригодится при переходе с 42” монитора на нетбук.

      /// <summary>
      /// Сброс всех сохранённых настроек
      /// </summary>
      static
      public
      void ResetALL()
    {
      _settingsSaver.Data = null;
    }

Метод GridTableName формирует имя DataTable из имен формы и DataGridView. Двойное подчеркивание (__) между ними уменьшает вероятность коллизии.

      /// <summary>
      /// Формируем имя таблицы (DataTable) из имён Формы и Грида
      /// </summary>
      /// <param name="UserForm">Форма</param>
      /// <param name="UserGrid">Грид</param>
      /// <returns></returns>
      private
      static
      string GridTableName(Form UserForm, DataGridView UserGrid)
    {
      return UserForm.Name + "__" + UserGrid.Name;
    }

Метод MakeColWidthTable создаёт таблицу для хранения ширины колонок и задаёт ее структуру. Структура очень проста: имя колонки и её ширина. У результирующей таблицы есть имя, пока что она пустая и никуда не добавлена.

      /// <summary>
      /// Создание таблицы для хранения ширины колонок
      /// </summary>
      /// <param name="TableName">Имя таблицы</param>
      /// <returns>Готовая пустая таблица</returns>
      private
      static DataTable MakeColWidthTable(string TableName)
    {
      DataTable dt = new DataTable(TableName);
      DataColumn column = new DataColumn();
      column.DataType = System.Type.GetType("System.String");
      column.ColumnName = "GridColumnName";
      dt.Columns.Add(column);
      column = new DataColumn();
      column.DataType = System.Type.GetType("System.Int32");
      column.ColumnName = "GridColumnWidth";
      dt.Columns.Add(column);
      return dt;
    }

Метод MakeFormTable создает таблицу, которая будет хранить местоположение, размер, состояние формы, и содержать лишь одну строку:

      /// <summary>
      /// Создание таблицы для хранения размеров и позиции формы
      /// </summary>
      /// <param name="TableName">Имя (формы)</param>
      /// <returns>Готовая пустая таблица</returns>
      private
      static DataTable MakeFormTable(string TableName)
    {
      DataColumn column;

      DataTable dt = new DataTable(TableName);

      column = new DataColumn();
      column.DataType = System.Type.GetType("System.Int32");
      column.ColumnName = "Location_X";
      dt.Columns.Add(column);

      column = new DataColumn();
      column.DataType = System.Type.GetType("System.Int32");
      column.ColumnName = "Location_Y";
      dt.Columns.Add(column);

      column = new DataColumn();
      column.DataType = System.Type.GetType("System.Int32");
      column.ColumnName = "Height";
      dt.Columns.Add(column);

      column = new DataColumn();
      column.DataType = System.Type.GetType("System.Int32");
      column.ColumnName = "Width";
      dt.Columns.Add(column);

      column = new DataColumn();
      column.DataType = System.Type.GetType("System.Int32");
      column.ColumnName = "WindowState";
      dt.Columns.Add(column);

      return dt;
    }

Метод FillGrideTable обходит все колонки DataGridView и добавляет по строчке в DataTable на каждую колонку DataGridView:

      /// <summary>
      /// Заполнение таблицы размерами колонок DataGridView
    /// </summary>/// <param name="dt">таблица</param>/// <param name="grid">грид</param>privatestaticvoid FillGrideTable(DataTable dt, DataGridView grid)
    {
      foreach (DataGridViewColumn c in grid.Columns)
      {
        DataRow r = dt.NewRow();
        r["GridColumnName"] = c.Name;
        r["GridColumnWidth"] = c.Width;
        dt.Rows.Add(r);
      }
    }

Метод FillFormSizeTable заполняет единственную строку таблицы формы. Этот метод будет вызываться только для новых форм, т.е. тех, о которых еще нет информации в настройках приложения. В остальных случаях будет вызываться нижеследующий метод UpdateFormSizeTable, который предотвратит затирание данных о местоположении и размерах тех окон, которые оказались максимизированными или минимизированными на момент закрытия.

      /// <summary>
      /// Заполнение таблицы, содержащей размеры формы
      /// </summary>
      /// <param name="dt">Таблица</param>
      /// <param name="f">Форма</param>
      private
      static
      void FillFormSizeTable(DataTable dt, Form f)
    {
      DataRow r = dt.NewRow();
      r["Location_X"] = f.Location.X;
      r["Location_Y"] = f.Location.Y;
      r["Height"] = f.Size.Height;
      r["Width"] = f.Size.Width;
      r["WindowState"] = f.WindowState;
      dt.Rows.Add(r);
    }
    /// <summary>/// Запись изменений в таблицу формы./// Сохраняет размеры формы, учитывая ее состояние /// (минимизирована/максимизирована/в нормальном состоянии)/// </summary>/// <param name="dt">таблица</param>/// <param name="f">форма</param>privatestaticvoid UpdateFormSizeTable(DataTable dt, Form f)
    {
      DataRow r = dt.Rows[0];

      if (f.WindowState == FormWindowState.Normal)
      {
        r["Location_X"] = f.Location.X;
        r["Location_Y"] = f.Location.Y;
        r["Height"] = f.Size.Height;
        r["Width"] = f.Size.Width;
      }

      r["WindowState"] = f.WindowState;
    }

Восстановление ширины колонок DataGridView. Блоки try-catch обеспечат работоспособность при манипуляциях разработчика с набором колонок DataGridView, связанных с добавлением, удалением и переименованием колонок

      /// <summary>
      /// Восстановление ширины колонок грида
      /// </summary>
      /// <param name="dt">Таблица</param>
      /// <param name="grid">Грид</param>
      private
      static
      void RestoreGridViewColWidths(DataTable dt, DataGridView grid)
    {
      foreach (DataRow r in dt.Rows)
        try
        {
          grid.Columns[(string)r["GridColumnName"]].Width = 
            (int)r["GridColumnWidth"];
        }
        catch (Exception ex)
        {
        }
    }

Метод RestoreForm восстанавливает всё, не глядя на состояния окна. Местоположение и размер будут восстановлены, если пользователь переведёт максимизированное окно в нормальное состояние.

      /// <summary>
      /// Восстановление местоположения и размеры формы
      /// </summary>
      /// <param name="dt">Таблица</param>
      /// <param name="form">Грид</param>
      private
      static
      void RestoreForm(DataTable dt, Form form)
    {
      DataRow rowForm = dt.Rows[0];
      try
      {
        form.Location = new Point((int)rowForm["Location_X"], 
                                  (int)rowForm["Location_Y"]);
        form.Size = new Size((int)rowForm["Width"], (int)rowForm["Height"]);
        form.WindowState = (FormWindowState)rowForm["WindowState"];
      }
      catch (Exception ex)
      {
      }
    }

Рассмотрим метод SaveFormGrid, главная функция сохранения. Сигнатура метода соответствует делегату EventHandler для того, чтобы подключить в качестве обработчика события FormClosing. При первом запуске создаётся чистый пустой DataSet, в который добавляются DataTable по мере открытия и закрытия форм. Если настройки для формы уже есть, они редактируются, если нет – создаются. Для сохранения ширины колонок DataGridView всегда создаётся новый DataTable, старый же удаляется, выполняется обход всех управляющих элементов формы, и, как только обнаруживается DataGridView, формируется имя DataTable и сохраняются настройки грида.

      /// <summary>
      /// Сохранение местоположения и размеры формы и ширина всех колонок всех гридов
      /// </summary>
      private
      static
      void SaveFormGrid(object sender, EventArgs e)
    {
      Form senderForm = (Form)sender;
      DataSet ds;
      if (_settingsSaver.Data != null) ds = _settingsSaver.Data;
      else ds = new DataSet();
      if (ds.Tables.IndexOf(senderForm.Name) == -1)
      {
        DataTable dt = MakeFormTable(senderForm.Name);
        FillFormSizeTable(dt, senderForm);
        ds.Tables.Add(dt);
      }
      else UpdateFormSizeTable(ds.Tables[senderForm.Name], senderForm);
      foreach (Control c in senderForm.Controls)
        if (c is DataGridView)
        {
          DataGridView grid = (DataGridView)c;
          string name = GridTableName(senderForm, grid);
          if (ds.Tables.IndexOf(name) > -1) ds.Tables.Remove(name);
          DataTable dt = MakeColWidthTable(name);
          FillGrideTable(dt, grid);
          ds.Tables.Add(dt);
        }
      _settingsSaver.Data = ds;
      _settingsSaver.Save();
    }

Метод RestoreFormGrid вызывается при загрузке формы по событию Load. После восстановления размера и местоположения окна обходятся все элементы управления в поисках управляющих элементов типа DataGridView. Если для DataGridView найдены сохранённые значения ширина колонки, они будут восстановлены.

      /// <summary>
      /// Восстановление местоположения и размеры формы и ширина всех колонок всех гридов
      /// </summary>
      private
      static
      void RestoreFormGrid(object sender, EventArgs e)
    {
      if (_settingsSaver.Data == null) return;
      Form f = (Form)sender;
      DataSet ds = _settingsSaver.Data;
      if (ds.Tables.IndexOf(f.Name) == -1) return;
      RestoreForm(ds.Tables[f.Name], f);
      foreach (Control c in f.Controls)
        if (c is DataGridView)
        {
          DataGridView grid = (DataGridView)c;
          string name = GridTableName(f, grid);
          if (ds.Tables.IndexOf(name) > -1)
            RestoreGridViewColWidths(ds.Tables[name], grid);
        }
    }

Наконец мы добрались до метода, который включает механизм сохранения и восстановления ширины колонок, а также местоположения и размеров форм. В нем закрытые статические методы класса добавляются в качестве обработчиков событий.

      /// <summary>
      /// Включение механизма запоминания и восстановления.
      /// Добавляет обработку событий Load и FormClosing
      /// </summary>
      /// <param name="f">Форма</param>
      public
      static
      void SavingOn(Form f)
    {
      f.Load += RestoreFormGrid;
      f.FormClosing += SaveFormGrid;
    }
  }
}

Черный ящик DLL

Полученный код можно использовать как в виде исходного текста, так в виде отдельной сборки типа «Class Library».

В ней будет находится наш класс FormSavingSettings.AllSizeSaving

С парой методов:

Использование:

Для включения в проект следует добавить ссылку на библиотеку FormSavingSettings.dll

FormSavingSettings.AllSizeSaving.SavingOn(this);

Файлы FormSavingSettings.dll и FormSavingSettings.XML прилагаются.

Выводы и перспективы

В результате получилась удобная в использовании библиотека, требующая при использовании минимума ручного кодирования. Разработчику достаточно потратить минуту для включения механизма в проект и секунд по 5 на каждую форму, чтобы добиться результата.

По ходу подготовки статьи было высказано интересное предложение сделать визуальный компонент, который достаточно разместить на форме, что бы вовсе не потребовалось кодирования. Это хорошее предложение для новой версии решения.

Структура нетипизированного DataSet достаточно универсальна, чтобы дополнительно сохранять значение фильтров строк DataGridView и текущее положение курсора, а также другие настройки, требующие сохранения во всех формах. Однако реализация универсальности потребует дополнительных ухищрений.

Исходный текст доступен для изучения (студентами), модернизации и развития, вариант dll – для использования в проектах.


Эта статья опубликована в журнале RSDN Magazine #4-2009. Информацию о журнале можно найти здесь
    Сообщений 0    Оценка 40        Оценить