вторник, 23 июня 2009 г.

Путь к каталогу от куда запущено приложение в WPF

Каждый раз не могу вспомнить как же его получать. Запишу сюда, вдруг запомню:
Environment.CurrentDirectory

Патерн "Фабрика объектов" на примере "Фабрики форм"

При работе над проектом "Электронный университет" давно используем "Фабрику форм", но там задачи спецефические, да и времени небыло, поэтому документация в достаточно разрозненном виде, да и отягчена работой с AzMan-ом, Web-сервисами и прочими вещами. Поэтому в данной статье попробую расказать основные идеи данного патерна. Итак, начнем.
Задача:
Необходимо реализвать приложение загружающее формы в соответствии с правами пользователя. Или, говоря простым языком, каждый пользователь после аутентификации должен получать главное меню ориентированное под его задачи. Ингода формы связанные с пунктами меню должны быть сразу загружены.
Средства достижения:
1. Reflection - используется для динамического создания объектов
2. Интерфейc IFactoryForm - для того чтобы мы спросили у созданной формы, нужен ли ей пункт меню, а также получили возможность передавать форме команды из главного приложения, а также получать команды из формы, для передачи в другие формы (во завернул) .
3. Enum содержащий все команды - для организации взаимодействия форм между собой в процессе работы.
4. Интерфейс IParam для передачи параметров команды. Кстати этот интерфейс ничего не содержит и создан только для того, чтобы при передаче параметорв команды разработчик работающий с фабрикой понимал, что он передает не просто объект, а объект параметр команды.
5. Ну и собственно сама фабрика - часть кода выполняющая работу с загружаемымми формами.

Давайте начнем в таком порядке и обсуждать.
1. Reflection - написано очень много всего хорошего, поэтому здесь я останавливаться не буду. Кто с Reflection не работал может посмотреть в msdn.
2. Как я уже сказал интерфейс используется для взаимодействия фабрики с формой. Для ростоты использования реализовывать лучше в отдельной dll. В самом простом случае должен иметь вид:

Код интерфейса будет иметь вид:
  public interface IFactoryForm
  {
    event CommandHandler NewCommand;

    /// <summary>
    /// Возвращает с каким пунктом меню необходимо связать форму.
    /// </summary>
    /// <returns>
    /// Путь до пункта меню добавляется форма. Путь имеет вид:
    /// Item-&gt;Item-&gt;Item
    /// Если строка пустая, то пункт меню создавать не надо и форма из пункта меню не вызывается
    /// </returns>
    string GetMenuItem();

    /// <summary>
    /// Вызывается при клике пользователя на пункте меню связанном с формой
    /// </summary>
    void Activate(object sender, EventArgs e);

    /// <summary>
    /// Вызывается главной формой приложения для передачи команд в форму
    /// </summary>
    void DoCommand(Commands p_command, IParam p_param);
  }


* This source code was highlighted with Source Code Highlighter.


3. Перечисление Commands (см. предыдущий рисунок) будет содержать команды которыми обмениваются формы в процессе работы приложения, для начала это всего одна команда которая нам нужна для показа тестовой формы в нашем приложении.
4. Опять же, как видно из рисунка IParam ничего не содержит ;)
5. Ну а теперь перейдем к фабрике.
Фабрика будет реализованна в виде пары методов и вспомогательного события.
Итак первый метод отвечает за создание форм. В нашем проекте идентификация пользователей идет при помощи штрих-кодов с бейджиков. Поэтому главная форма приложения после запуска имеет вид:

Для получения списка сборок и форм для загрузки используется типизированный DataSet содержащий табличку вида:

Код метода:
/// <summary>
    /// Список форм созданных при помощи фабрики
    /// </summary>
    List<IFactoryForm> formsFromFactory;

    private void CreateForms()
    {
      // Показываем форму идентификации
      FrmLogin login = new FrmLogin();
      login.StartPosition = FormStartPosition.CenterParent;
      if (login.ShowDialog() == DialogResult.OK)
      {
        // если штрих-корд считан, то получаем перечень форм для загрузки.
        // Реализация метода может быть любая. Мы, например, берем из базы данных
        FactoryFormData.FormsDataTable forms = GetFormsForPerson(new Guid(login.tbPersonId.Text));
        // Строка для сбора сообщений об ошибках работы
        StringBuilder errors = new StringBuilder();
        // Пробегаем по всем формам и загружаем их в приложение
        foreach (FactoryFormData.FormsRow item in forms)
        {
          // Получаем и загружаем сборку
          Assembly dll = null;
          try
          {
            dll = Assembly.LoadFile(Application.StartupPath + '\\' + item.Assembly);
          }
          catch{}
          if (dll != null)
          {
            // Сборка загружена. загружаем форму
            Type currentFormClass = dll.GetType(item.FormFullName);
            if (currentFormClass != null)
            {
              // Создаем объект и приводим его к IFactoryForm
              ConstructorInfo ci = currentFormClass.GetConstructor(new Type[] { });
              IFactoryForm currentForm = ci.Invoke(new object[] { }) as IFactoryForm;
              formsFromFactory.Add(currentForm);
              ((Form)currentForm).MdiParent = this;
              if (currentForm != null)
              {
                // Собственно форма в памяти, осталось только создать меню и привязать форму к нему
                string menuItemText = currentForm.GetMenuItem();
                switch (menuItemText)
                {
                  case "":
                    // Собственно делать ничего не нужно
                    break;
                  case "_show_":
                    // Форме пункт меню не нужен, форма требует немедленного показа
                    ((Form)currentForm).Show();
                    break;
                  default:
                    // Создаем пункт меню
                    ToolStripMenuItem mi = null;
                    string[] path = menuItemText.Split(new string[] { "->" }, StringSplitOptions.RemoveEmptyEntries);
                    foreach (string currentMenuItemText in path)
                    {
                      ToolStripItemCollection itemsForCheck = null;
                      if (mi == null)
                      {
                        // если мы на вершине иерархии, то ищем пункт меню в главном меню
                        itemsForCheck = msMain.Items;
                      }
                      else
                      {
                        // а если нет, то в подпунктах текущего пункта
                        itemsForCheck = mi.DropDownItems;
                      }
                      ToolStripMenuItem childItem = itemsForCheck.Cast<ToolStripMenuItem>().FirstOrDefault(x => x.Text == currentMenuItemText);
                      if (childItem == null)
                      {
                        // элемент с таким имененм не найден, содаем
                        childItem = new ToolStripMenuItem(currentMenuItemText);
                        if (mi == null)
                        {
                          msMain.Items.Insert(1, childItem);
                        }
                        else
                        {
                          mi.DropDownItems.Add(childItem);
                        }
                        mi = childItem;
                      }
                      mi = childItem;
                    }
                    // Итак сейчас в mi должна быть ссылка на пункт меню к которому привязана форма
                    // Подписываем форму на клик по этому пункту меню
                    mi.Click += currentForm.Activate;
                    break;
                }
              }
              else
              {
                errors.Append(string.Format("Ошибка загрузки формы: {0}. Форма не поддерживает IFactoryForm.\n", item.FormFullName));
              }
            }
            else
            {
              errors.Append(string.Format("Ошибка загрузки Формы: {0}. Форма не найдена в сборке.\n", item.FormFullName));
            }
          }
          else
          {
            errors.Append(string.Format("Ошибка загрузки бибилотеки: {0}. Бибилотека не найдена.\n", item.Assembly));
          }
          if (errors.Length != 0)
          {
            MessageBox.Show(errors.ToString());
          }
        }
      }
    }


* This source code was highlighted with Source Code Highlighter.


Реализация интерфейса у тестовой формы (конечно размещеной в другой сборке, иначе фабрика теряет смысл):
  public partial class TestForm : Form, IFactoryForm
  {
    public TestForm()
    {
      InitializeComponent();
    }

    #region Члены IFactoryForm

    public event CommandHandler NewCommand;

    public string GetMenuItem()
    {
      return "Тестовые формы->Первая тестовая";
    }

    public void Activate(object sender, EventArgs e)
    {
      DoCommand(Commands.ShowFirstForm, null);
    }

    public void DoCommand(Commands p_command, IParam p_param)
    {
      switch (p_command)
      {
        case Commands.ShowFirstForm:
          this.Show();
          break;
      }
    }

    #endregion
  }


* This source code was highlighted with Source Code Highlighter.


Вуаля, все заработало!
Теперь задание на дом, придумать как при помощи имеющегося события NewCommand, метода DoCommand и списка List formsFromFactory реализовать взаимодействие порожденных фабрикой форм между собой, если известно что ссылок друг на друга они не имеют.

четверг, 4 июня 2009 г.

Мысль в слух

Какая же замечательная штука LINQ и расширяющие методы.
Ну вот, например, выбор в ComboBox данных из таблицы с сортировкой:

cbItemOfExpenses.ItemsSource = _budget.ItemOfExpenses.Where(dr => dr.IsReceipts == _isReceipts).OrderBy(dr => dr.Name);

Или вот, формирование агрегатного запроса:
var items = from item in first
group item by item.IdItemOfExpenses into gr              
select
new
{
  Name = _budget.ItemOfExpenses.First(dr => dr.IdItemOfExpenses == gr.Key).Name,                
  Receipts = gr.Sum(dr => dr.IsReceipts ? (double?)dr.Money : 0d),
  Expense = gr.Sum(dr => dr.IsReceipts ? 0d : (double?)dr.Money)
};


* This source code was highlighted with Source Code Highlighter.

Ну разве не замечательно?