На главную Наши проекты:
Журнал   ·   Discuz!ML   ·   Wiki   ·   DRKB   ·   Помощь проекту
ПРАВИЛА FAQ Помощь Участники Календарь Избранное RSS
msm.ru
Модераторы: maxim84_
  
> Команды в WPF, как быть и что с ними делать.
    Команды в WPF, как быть и что с ними делать.

    После празников немного тяжко думается, но всетаки вот решил сесть за написание статьи.
    Mаршрутизированные события и маршрутизированные команды это одни из самых интересных нововведения в WPF, которые требуют особого внимания и освоения. Как извесно в WPF и C# 3.0 реализованны многие паттерны проектирования, но к сожалению (сугубо по моему личному мнению) есть много неудобных моментов и сложностей в применении и освоении. Так в проекте в котором я участвую было принято решение широко использовать маршрутизированные события и маршрутизированные команды, но сразу же были замечены сложности и неудобные моменты усложняющие поддержку, а в часности сильно увеличивающие код. Более подробно не буду на этом останавливаться, а лучше перейду к описанию решения к которому я пришол для упрощения работы с маршрутизированными командами.
    Идея заключается примерно в следующем, исключить появления множества команд в разных фреймах приложения и ценрализовать все команды в одном контейнере и автоматизировать создание команд для фреймов ( сам не понял что написал, но как-то так :) ). Теперь непосредственно перейдем к реализации. В проекте используется паттерн DM-V-VM о котором уже много написанно на просторах рунета, так что в примере который я приведу он так же будет использован, так же использован MS EnterpriseLibs выпуст октябрь 2008, упрощающий применение паттерна DM-V-VM.
    Первым делом опишу шаблон будующего примера, т.е. скажем так, мы имеем некоторое главное окно Window1, которое представляет собой контейнер для наших загружаемых фреймов, и непосредственно подопытный SampleFrame. Для SampleFrame создаем презентера SampleFrameVM, ниже привожу полный код для обоих объектов нашего приложения.

    XAML код SampleFrame:

    ExpandedWrap disabled
       <UserControl x:Class="CommandAttributeExp.SampleFrame"
          xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
          xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
          xmlns:Commands="clr-namespace:CommandAttributeExp.Commands"
          MinHeight="200" MinWidth="300" Focusable="True">
          <UserControl.InputBindings>
              <KeyBinding Key="F5" Command="Commands:CustomCommands.Refresh"/>
              <KeyBinding Key="F6" Command="Commands:CustomCommands.Refresh" CommandParameter="Привет"/>
              <KeyBinding Key="F7" Command="Commands:CustomCommands.Refresh" CommandParameter="12354"/>
          </UserControl.InputBindings>
          <Grid>
              <Grid.RowDefinitions>
                  <RowDefinition Height="Auto"/>
                  <RowDefinition Height="Auto"/>
                  <RowDefinition/>
              </Grid.RowDefinitions>
              <DockPanel>
                  <ToolBar>
                      <Button Width="60" Height="20" ToolTip="Создать что то. Insert" Command="Commands:CustomCommands.NewDoc">NewDoc</Button>
                  </ToolBar>
              </DockPanel>
              
              <StackPanel Grid.Row="1">
                  <TextBlock FontSize="14">Button Events</TextBlock>
                  <DockPanel HorizontalAlignment="Left">
                      <Button Command="ApplicationCommands.Save" Width="100">SaveCommand</Button>    
                  </DockPanel>
                  <TextBlock Margin="5">По F5 Refresh</TextBlock>
                  <TextBlock Margin="5">По F6 Refresh c передачей строкового параметра</TextBlock>
                  <TextBlock Margin="5,5,5,0">По F7 Refresh должен был по идее передаваться с числовым параметром</TextBlock>
                  <TextBlock Margin="5,0,5,5"> но проблема в том что параметр всегда в строковом виде передается</TextBlock>
       
              </StackPanel>
              <ListView Grid.Row="2" ItemsSource="{Binding Path=Messages}" SnapsToDevicePixels="True">
              </ListView>
          </Grid>
          </UserControl>


    Код презентера сразу приведу в готовом виде, как выглядит реализация команд в интерпритации Microsoft я думаю все итак не раз видели.

    ExpandedWrap disabled
      using System;
      using System.Collections.ObjectModel;
      using System.ComponentModel;
      using CommandAttributeExp.Commands;
       
      namespace CommandAttributeExp
      {
          public class SampleFrameVM : INotifyPropertyChanged
          {
              private ObservableCollection<string> messages;
              public ObservableCollection<string> Messages
              {
                  get { return messages; }
                  set
                  {
                      messages = value;
                      OnPropertyChanged("Message");
                  }
              }
       
              public SampleFrameVM()
              {
                  Messages = new ObservableCollection<string>();
              }
       
              [Command("Save")]
              public void DoSaveAction()
              {
                  Messages.Add(string.Format("Обработана команда Save {0}", DateTime.Now.TimeOfDay));
              }
       
              [Command("Refresh")]
              public void Refresh()
              {
                  Messages.Add(string.Format("Обработана команда Refresh {0}", DateTime.Now.TimeOfDay));
              }
       
              [Command("Refresh", typeof(string))]
              public void Refresh(string paramString)
              {
                  Messages.Add(string.Format("Обработана команда Refresh со строковым параметром {0}", DateTime.Now.TimeOfDay));
              }
       
              [Command("Refresh", typeof(int))]
              public void Refresh(int paramInt)
              {
                  Messages.Add(string.Format("Обработана команда Refresh с числовым параметром {0}", DateTime.Now.TimeOfDay));
              }
       
              [Command("NewDoc")]
              public void CreateDoc()
              {
                  Messages.Add(string.Format("Обработана команда CreateDoc {0}", DateTime.Now.TimeOfDay));
              }
       
              [MultiCommand(new[] { "NewDoc", "Save" })]
              public void CreateDocAndSave()
              {
                  Messages.Add(string.Format("Этот метод реагирует на две команды Save и NewDoc {0}", DateTime.Now.TimeOfDay));
              }
       
              #region INotifyPropertyChanged Members
       
              public event PropertyChangedEventHandler PropertyChanged;
              private void OnPropertyChanged(string propName)
              {
                  if (PropertyChanged != null)
                      PropertyChanged(this, new PropertyChangedEventArgs(propName));
              }
              #endregion
          }
      }



    Так вот, что нам надо чтобы наши аттрибуты заработали, конечно-же клас описывающий эти самые аттрибуты, вот его код:

    ExpandedWrap disabled
       public class CommandAction
          {
              public static string Save = "Save";
              public static string Open = "Open";
              public static string EditDoc = "EditDoc";
              public static string Refresh = "Refresh";
              public static string NewDoc = "NewDoc";
          }
       
          [AttributeUsage(AttributeTargets.Method, Inherited = false)]
          public class CommandAttribute : Attribute
          {
              public CommandAttribute(string action)
              {
                  HasParam = false;
                  Action = action;
              }
       
              public CommandAttribute(string action, Type paramType)
              {
                  Action = action;
                  HasParam = true;
                  ParamType = paramType;
              }
       
              public Type ParamType { get; set; }
              public bool HasParam { get; private set; }
              public string Action { get; set; }
          }


    Немного подробнее о нем, класс CommandAction это перечисление команд и их строковые метки которые будут использованны в нашем тестовом проекте. Что позволяет нам сделать наш аттрибут, во первых пометить наш метод так что какойто класс реализующий логику команд будет знать что наш метод ждет вызова определенной команды и так же может ли он принять параметр передаваемый командой или нет. Тут есть одно но, все параметры передаваемые комадами строковые (в свете теплых чуств MS к object мне это по меньшей мере показалось странным) что немного нас сковывает в возможностях.
    Что далее, аттрибут мы имеем, так же имеем методы ожидающие что их позавут и попросят что либо выполнить, но кто же должен их звать? Для этих целей служит клас названный мной CommandsCollector, вот его код:

    ExpandedWrap disabled
      using System;
      using System.Reflection;
      using System.Windows;
      using System.Windows.Input;
       
      namespace CommandAttributeExp.Commands
      {
          public class CommandsCollector
          {
              public void CommandExecuted(object target, ExecutedRoutedEventArgs e)
              {
                  var sender = e.Source;
                  var x = ((FrameworkElement) sender).DataContext.GetType();
                  var comName = ((RoutedUICommand) e.Command).Text;
                  foreach (MethodInfo mInfo in x.GetMethods())
                      foreach (Attribute attr in Attribute.GetCustomAttributes(mInfo))
                      {
                          if (attr.GetType() == typeof(CommandAttribute) )
                          {
                              var at = (CommandAttribute)attr;
                              if (at.Action == comName && !at.HasParam && e.Parameter == null)
                                  mInfo.Invoke(((FrameworkElement)sender).DataContext, new object[] { });
       
                              if (at.Action == comName && at.HasParam && e.Parameter != null)
                              {
                                  if (e.Parameter.GetType() == at.ParamType)
                                      mInfo.Invoke(((FrameworkElement)sender).DataContext, new[] { e.Parameter });
                              }  
                          }
                          if(attr.GetType() == typeof(MultiCommandAttribute))
                          {
                              var mat = (MultiCommandAttribute)attr;
                              foreach (var act in mat.Actions)
                              {
                                  if (act == comName && !mat.HasParam)
                                  {
                                      mInfo.Invoke(((FrameworkElement)sender).DataContext, new object[] { });
                                  }
                              }
                          }
                      }
              }
       
       
              public void CommandCanExecute(object sender, CanExecuteRoutedEventArgs e)
              {
                  var s = e.Source;
                  var x = ((FrameworkElement)s).DataContext.GetType();
                  var comName = ((RoutedUICommand)e.Command).Text;
                  foreach (MethodInfo mInfo in x.GetMethods())
                      foreach (Attribute attr in Attribute.GetCustomAttributes(mInfo))
                          if (attr.GetType() == typeof(CommandAttribute) &&
                              ((CommandAttribute)attr).Action == comName)
                              e.CanExecute = true;
              }
       
          }
          
      }



    Этот клас просто через Reflection смотрит класс и выдергивает из него методы помеченные нашим аттрибутом и если совпадает Action выполняет этот метод. Остался последний вопрос, давайте вернемся к коду SampleFrame, что мы видим там интересного, есть InputBindings, но нет привязки к выполняемым по команде методам, а теперь они не нужны, все привязки команд обявляются и хрянятся в контейнере созданном при запуске приложения и привязываются каждому вновь создаваемому фрейму при его загрузке по наличию атрибутов в презентере, вот так все хитро.

    ExpandedWrap disabled
       private void Application_Startup(object sender, StartupEventArgs e)
              {
                  var mw = new Window1();
                  var coll = new CommandsCollector();
                  IUnityContainer Cfg = new UnityContainer();
       
                  Cfg.RegisterInstance(Cfg).
                      RegisterInstance<IWorkplace>(mw).
                      RegisterInstance(coll).
                      RegisterInstance<IDictionary<string, CommandBinding>>(new Dictionary<string, CommandBinding>
                                           {
                                               { CommandAction.Save, new CommandBinding(ApplicationCommands.Save, coll.CommandExecuted, coll.CommandCanExecute) },
                                               { CommandAction.Refresh, new CommandBinding(CustomCommands.Refresh, coll.CommandExecuted) },
                                               { CommandAction.NewDoc, new CommandBinding(CustomCommands.NewDoc, coll.CommandExecuted) }
                                           }).
                      RegisterType<IView<SampleFrameVM>, SampleFrame>();
       
                  Cfg.BuildUp(mw);
       
                  mw.Show(Cfg.Resolve<SampleFrameVM>());
       
                  mw.Show();
              }



    P.S.: писатель из меня никудышный согласен, но описал как мог, думаю всем будет приятней почитать код чем тот бред который я здесь изложил :), в этом случае код в аттаче........
    Прикреплённый файлПрикреплённый файлCommandAttribute.zip (210.15 Кбайт, скачиваний: 970)
    0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
    0 пользователей:


    Рейтинг@Mail.ru
    [ Script execution time: 0,0230 ]   [ 15 queries used ]   [ Generated: 29.03.24, 15:00 GMT ]