Конференция "Основная" » Как дисэйблить кнопки если операция не поддерживается… ?
 
  • Kolan © (25.01.08 15:32) [0]
    Здравствуйте,
     Допустим у нас есть два вида объектов А и Б.

    С ними можно выполнять похожие по смыслы(интерфейсу) операции, только реализованы они будут по разному.

    Понятно, что удобно узать стратегии. Тогда надо определить общий интерфейс, и сделать его конкретный реализации для А и Б.

    Пример:
    IItemsOperationsController = interface
       ['{3574BBB8-1413-4508-AD48-F4BEFFC88A28}']
       procedure AddItem;
       procedure DeleteCurrentItem;
       procedure SetCurrentItem(AnObject: TObject);
       procedure PerformOpertionWithCurretnItem(ADeviceOperationPerformer:
         IDeviceOperationPerformer);
     end;

     TCustomItemsOperationsController = class(TInterfacedObject, IItemsOperationsController)
     public
       procedure AddItem; virtual; abstract;
       procedure DeleteCurrentItem; virtual; abstract;
       procedure SetCurrentItem(AnObject: TObject); virtual; abstract;
       procedure PerformOpertionWithCurretnItem(ADeviceOperationPerformer:
         IDeviceOperationPerformer); virtual; abstract;
     end;

     TNetItemsOperationsController = class(TCustomItemsOperationsController)
     protected
       function QueryNetName(var AName: string): Boolean;
     public
       procedure AddItem; override;
       procedure DeleteCurrentItem; override;
       procedure SetCurrentItem(AnObject: TObject); override;
       procedure PerformOpertionWithCurretnItem(ADeviceOperationPerformer:
         IDeviceOperationPerformer); override;
     end;

     TDeviceItemsOperationsController = class(TCustomItemsOperationsController)
     protected
       function QueryDeviceAddress(var AnAddress: Byte): Boolean;
     public
       procedure AddItem; override;
       procedure DeleteCurrentItem; override;
       procedure SetCurrentItem(AnObject: TObject); override;
       procedure PerformOpertionWithCurretnItem(ADeviceOperationPerformer:
         IDeviceOperationPerformer); override;
     end;



    Тогда вызов будет такой(это код экшена):
     procedure TMainForm.DeleteItemExecute(Sender: TObject);
    begin
     GetConfigurationUtilityController.DeleteCurrentItem;
    end;



    Где
    GetConfigurationUtilityController

    — это синглетон, который делегирует вызов нужному контроллеру.
    procedure TConfigurationUtilityController.DeleteCurrentItem;
    begin
     GetItemsOperationsController.DeleteCurrentItem;
    end;



    Но вот проблемма, объекты типа Б не поддерживают операцию удаления(например). Для них хорошо бы кнопочку(экшен) удалить сделать недоступной.

    Вопрос: Как?
  • Palladin © (25.01.08 15:38) [1]
    определяем базовый интерфейс
    IItemsOperationsController = interface
      procedure AddItem;
      procedure DeleteCurrentItem;
      procedure SetCurrentItem(AnObject: TObject);
      procedure PerformOpertionWithCurretnItem(ADeviceOperationPerformer:
        IDeviceOperationPerformer);
    end;
    определяем интерфейс наследник с поддержкой удаления
    IItemsOperationsControllerWithDeleteSupport = interface(IItemsOperationsController)
      procedure DeleteCurrentItem;
    end;

    и фсе... теперь можно определять поддержку удаления поддержкой интерфейса
  • Palladin © (25.01.08 15:39) [2]
    ой, в базе обшибся, там Delete не нужен
  • Kolan © (25.01.08 15:47) [3]
    > и фсе… теперь можно определять поддержку удаления поддержкой
    > интерфейса

    Я примерно понял. Только ты, плз пример напиши…
    Нопонятно кто должен проверять поддержку и дисейблить…
  • clickmaker © (25.01.08 15:53) [4]
    IsSupported(operationName): boolean; virtual
    а вызывать в Action.OnUpdate
    так, что-ли?
  • Kolan © (25.01.08 15:57) [5]
    > IsSupported(operationName): boolean; virtual

    Ну у кого это операция будет реализована?

    В данном примере в Синглетоне (GetConfigurationUtilityController — это синглетон, который делегирует вызов нужному контроллеру.)?

    Что-то вроде:

    procedure TMainForm.DeleteItemUpdate(Sender: TObject);
    begin
     (Sender as TAction).Enabled := GetConfigurationUtilityController.GetItemsOperationsController.GetInterface(IIte msOperationsControllerWithDeleteSupport, Int);    
    end;



    ?
  • clickmaker © (25.01.08 16:05) [6]

    > [5] Kolan ©   (25.01.08 15:57)

    что-то у тебя мудренно как-то всё...
    наверно, в IItemsOperationsController и объявить
    а переопределять ниже по иерархии
  • Kolan © (25.01.08 16:16) [7]
    > а переопределять ниже по иерархии

    Непонял.

    Как в [5] по идее не надо будет ничего перопределять…

    ЗЫ
     Я же поэтому и прошу примерчик, а то непонятно…
  • clickmaker © (25.01.08 16:19) [8]

    > [7] Kolan ©   (25.01.08 16:16)

    ну я тогда не понял твою задумку...
    мне как-то старый добрый полиморфизм ближе в этом отношении
  • Palladin © (25.01.08 16:22) [9]

    > procedure TMainForm.DeleteItemUpdate(Sender: TObject);
    > begin
    >  (Sender as TAction).Enabled := GetConfigurationUtilityController.GetItemsOperationsController.GetInterface(IIte  
    > msOperationsControllerWithDeleteSupport, Int);    
    > end;

    да, именно так
  • offff (25.01.08 16:27) [10]

    > Но вот проблемма, объекты типа Б не поддерживают операцию
    > удаления(например). Для них хорошо бы кнопочку(экшен) удалить
    > сделать недоступной.


    Замечания к реализации. IMHO

    Тогда и интерфейс IItemsOperationsController не должен ее содержать.

    Добиться можно выделив операцию в отдельную сущность.
    А именно либо отдельный интерфейс, либо класс на каждую операцию.
    Аля command согласно GOF.
  • offff (25.01.08 16:27) [11]
    Удалено модератором
    Примечание: дубль
  • offff (25.01.08 16:34) [12]
    Либо сделать посетителя операций. и сделать посетителя Menu Manager.

    IItemsOperationsController = interface
    function OpsVisitor(...)  <- сюда твоего Menu Manager
      {

      }
     

    end
  • offff (25.01.08 16:34) [13]
    Удалено модератором
    Примечание: дубль
  • Kolan © (25.01.08 16:34) [14]
    > А именно либо отдельный интерфейс

    Паладин так и предлагает.


    > Аля command согласно GOF

    Возится с каждой командой долго выходит…
  • Kolan © (25.01.08 16:37) [15]
    > function OpsVisitor(…)  <— сюда твоего Menu Manager

    Ну и что я буду говорить этому менеджеру? Задисейбли кнопку «Удалить». А если кнопок много? Что же их все пересчислять?

    Паладин действительно прав. Получается я контракт нарущил. Говорю что оддерживаю удаление, а на самом деле нет… Осталось грамотно связать…
  • offff (25.01.08 16:42) [16]

    > Kolan ©   (25.01.08 16:37) [15]


    Menu Manager - отключает все кнопки.

    MenuManager=class(CommandVisitor)
    procedure OpAdd
    procedure OpRemove
    Procedure OpCurrent
    end;

    Реализатор IItemsOperationsController пробегает по всем его коммандам и делает вызов

    AbstractCommand.Accept(visitor:CommandVisitor)  

    procedure MenuManager.OpAdd
    begin
    ADDITEM.VISIBLE:=TRUE;
    end;

    procedure MenuManager.OpRemove
    begin
    RemoveITEM.VISIBLE:=TRUE;
    end;

    Procedure MenuManager.OpCurrent
    begin
    ....
    end;
  • offff (25.01.08 16:42) [17]
    Удалено модератором
    Примечание: да что'ж ты дублями то :)
  • oxffff © (25.01.08 16:47) [18]
    Что то задублилось у меня. Sorry.

    Что касаемо реализации то я уже тут предлагал улучшенную реализацию GOF паттерна VISITOR без привязки к VMT.
    Кстати напишу в своем блоге об этом.

    :)
  • oxffff © (25.01.08 16:49) [19]

    > offff   (25.01.08 16:42) [17]
    > Удалено модератором
    > Примечание: да что'ж ты дублями то :)


    Sorry снова. Сам не знаю что за дела. Под oxffff пользователем порядок.
    :)
  • Kolan © (25.01.08 19:36) [20]
    > [16] offff   (25.01.08 16:42)

    Не, не понял… Как устроен MenuManager непонял.


    > Реализатор IItemsOperationsController пробегает по всем
    > его коммандам и делает вызов

    Что за команды?


    > ADDITEM.VISIBLE:=TRUE;

    Какое-то жесткое присвоение…

    Нихрена не понял короче…
  • oxffff © (26.01.08 01:24) [21]

    > Нихрена не понял короче…


    AddCommand=class;
    DeleteCommand=class;
    CurrentCommand=class;

    AbstractCommandVisitor=class
    procedure VAdd(Command:AddCommand);virtual;abstract;
    procedure VCurrent(Command:CurrentCommand);virtual;abstract;
    procedure VDelete(Command:DeleteCommand);virtual;abstract;
    end;

    MenuBarCommandVisitor=class(AbstractCommandVisitor)
    procedure VAdd(Command:AddCommand);override;
    procedure VDelete(Command:DeleteCommand);override;
    procedure VCurrent(Command:CurrentCommand);override;
    end;

    AbstractCommand=class
    function Exec(const param):integer;virtual;
    procedure Accept(Visitor:AbstractCommandVisitor);virtual;
    end;

    TAbstractCommandClass=Class of AbstractCommand;

    AddCommand=class(AbstractCommand)
    function Exec(const param):integer;override;
    procedure Accept(Visitor:AbstractCommandVisitor);override;
    end;

    DeleteCommand=class(AbstractCommand)
    function Exec(const param):integer;override;
    procedure Accept(Visitor:AbstractCommandVisitor);override;
    end;

    CurrentCommand=class(AbstractCommand)
    function Exec(const param):integer;override;
    procedure Accept(Visitor:AbstractCommandVisitor);override;
    end;

    DynaCommands=class
    Commands:Tlist;
    constructor create;overload;
    constructor create(Commands:array of AbstractCommand);overload;
    destructor destroy;override;
    procedure Accept(Visitor:AbstractCommandVisitor);
    function TryPerfomOp(CommandClass:TAbstractCommandClass;const param):boolean;
    end;

    var
     Form1: TForm1;

    implementation

    {$R *.dfm}

    { DynaCommands }

    constructor DynaCommands.create;
    begin
    Commands:=TList.Create;
    end;

    procedure DynaCommands.Accept(Visitor: AbstractCommandVisitor);
    var i:integer;
    begin
    for i:=0 to commands.Count-1 do AbstractCommand(commands.Items[i]).Accept(Visitor);
    end;

    constructor DynaCommands.create(Commands: array of AbstractCommand);
    var i:integer;
    begin
    create;
    for i:=0 to length(commands)-1 do self.Commands.Add(commands[i]);
    end;

    destructor DynaCommands.destroy;
    var i:integer;
    begin
    for i:=0 to commands.Count-1 do Tobject(commands.Items[i]).Free;
    Commands.free;
    inherited;
    end;

    function DynaCommands.TryPerfomOp(CommandClass:TAbstractCommandClass;const param):boolean;
    var i:integer;
    begin
    for i:=0 to commands.count-1 do
      if (AbstractCommand(commands.Items[i]) is CommandClass) then
             begin
             AbstractCommand(commands.Items[i]).Exec(param);
             result:=true;
             exit;
             end;
    result:=false;
    end;

    { AbstractCommand }

    procedure AbstractCommand.Accept(Visitor: AbstractCommandVisitor);
    begin
    //
    end;

    function AbstractCommand.Exec(const param): integer;
    begin
    //
    end;

    { AddCommand }

    procedure AddCommand.Accept(Visitor: AbstractCommandVisitor);
    begin
    Visitor.VAdd(self);
    end;

    function AddCommand.Exec(const param): integer;
    begin
    //
    end;

    { DeleteCommand }

    procedure DeleteCommand.Accept(Visitor: AbstractCommandVisitor);
    begin
    Visitor.VDelete(self);
    end;

    function DeleteCommand.Exec(const param): integer;
    begin
    //
    end;

    { MenuBarCommandVisitor }

    procedure MenuBarCommandVisitor.VAdd(Command: AddCommand);
    begin
    Showmessage('Enable ADD Command');
    end;

    procedure MenuBarCommandVisitor.VCurrent(Command: CurrentCommand);
    begin
    Showmessage('Enable Current Command');
    end;

    procedure MenuBarCommandVisitor.VDelete(Command: DeleteCommand);
    begin
    Showmessage('Enable Delete Command');
    end;

    { CurrentCommand }

    procedure CurrentCommand.Accept(Visitor: AbstractCommandVisitor);
    begin
    Visitor.VCurrent(self);
    end;

    function CurrentCommand.Exec(const param): integer;
    begin
    //
    end;

    procedure TForm1.Button1Click(Sender: TObject);
    var DynaCmds:DynaCommands;
       MenuBar:MenuBarCommandVisitor;
    begin
    try
    DynaCmds:=DynaCommands.create([AddCommand.Create,CurrentCommand.Create]);
     try
    //Disable menu item
         MenuBar:=MenuBarCommandVisitor.Create;
         try
    //Menu init
         DynaCmds.Accept(MenuBar);
         if not DynaCmds.TryPerfomOp(DeleteCommand,self) then showmessage('Not such command DeleteCommand');
         if DynaCmds.TryPerfomOp(CurrentCommand,self) then showmessage('Not such command CurrentCommand')
         finally
         MenuBar.free;
         end;
     finally
     DynaCmds.free;
     end;
    except
    end;
    end;
  • oxffff © (26.01.08 01:26) [22]

    >      if DynaCmds.TryPerfomOp(CurrentCommand,self) then showmessage('Not
    > such command CurrentCommand')


    Есть такая команда. :)
  • Kolan © (26.01.08 09:21) [23]
    > [21] oxffff ©   (26.01.08 01:24)

    Смотрю…
  • Kolan © (26.01.08 12:05) [24]
    Понял, таки это решение с командами. А хотелось бы от них избавится.

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

    :)
  • Игорь Шевченко © (26.01.08 22:44) [25]

    > Как дисэйблить кнопки если операция не поддерживается… ?
    >  


    Button.Enabled := false;

    Чего гадать-то ?

    В свое время тоже маялся подобной заумью, получилось следующее:

    Есть MDI-форма с тулбаром, на тулбаре кнопки, на кнопках Actions, в зависимости от допустимости операций с текущим активным MDI-дитем, кнопки должны быть открыты или закрыты.

    type
     TCommandType = (ctFind, ctGoto);
     TCommandTypes = set of TCommandType;

     IToolbarCommands = interface
     ['{D97A7812-091F-4E2F-84DA-08A394646BC2}']
       function SupportedCommands : TCommandTypes;
       procedure FindCommand;
       procedure GotoCommand;
     end;

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

    procedure TfrmMain.ActionList1Update(Action: TBasicAction;
     var Handled: Boolean);
    var
     Supported: TCommandTypes;
     IC: IToolbarCommands;
    begin
     if Assigned(ActiveMDIChild) and
        ActiveMDIChild.GetInterface(IToolbarCommands, IC) then
       Supported := IC.SupportedCommands
     else
       Supported := [];
     actFind.Enabled := ctFind in Supported;
     actGoto.Enabled := ctFind in Supported;
    end;

    Теперь бы делал иначе.


    > Понятно, что удобно узать стратегии.


    Keep It Simple, Stupid
  • Kolan © (27.01.08 09:52) [26]
    > получилось следующее:

    Просто и понятно.


    > Теперь бы делал иначе.

    А как? если не секрет? Оч. интересно…
    Хотелось бы, чтобы дабавлять команды было просто, дисэбл делался автоматически и код был простым…


    > Keep It Simple, Stupid

    В смысле Short and Simple ? :)
  • Kolan © (27.01.08 09:53) [27]
    > actFind.Enabled := ctFind in Supported;
    > actGoto.Enabled := ctFind in Supported;

    Вот это только плохой кусок, тут придется добавлять вручную…
  • oxffff © (27.01.08 10:50) [28]

    > Kolan ©   (27.01.08 09:53) [27]
    > > actFind.Enabled := ctFind in Supported;
    > > actGoto.Enabled := ctFind in Supported;
    >
    > Вот это только плохой кусок, тут придется добавлять вручную…


    Рассмотри другой способ.
    Команда сама предоставляет callback процедуру и/или иконку.
    Твой Menu опрашивает команды на наличие этой функциональности.
    И в случае отсутствия ставит произвольную иконку.
    Так ты сможешь избавиться  от статической привязки построителя меню к набору обрабатываемых им конанд.
    + обеспечишь подрузку внешних комманд.
  • Kolan © (27.01.08 11:04) [29]
    Надо подумать…
  • Игорь Шевченко © (27.01.08 12:44) [30]
    Kolan ©   (27.01.08 09:52) [26]


    > А как? если не секрет? Оч. интересно…


    Унаследовал бы класс формы от предка, в котором были бы методы CanFind, CanGoto (это в моем случае) и честно бы их опрашивал.

    Код получился бы короче и яснее.


    > В смысле Short and Simple ? :)


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

    Задай себе вопрос - что тебе чаще придется делать - читать код или дополнять его функциональность.


    > Вот это только плохой кусок, тут придется добавлять вручную…


    В твоем случае, как бы ты не сделал, тоже придется добавлять вручную.

    Напоследок немножно помедитируй над цитатой:

    "ОО-языки упрощают абстракцию, возможно, даже слишком ее упрощают. Они поддерживают создание структур с большим количеством связующего кода и сложными уровнями.
    Это может оказаться полезным в случае, если предметная область является
    действительно сложной и требует множества абстракций, и вместе с тем такой подход может обернуться неприятностями, если программисты реализуют простые вещи сложными способами, просто потому что им известны эти способы и они умеют  ими пользоваться.
    Все ОО-языки несколько сколнны "втягивать" программистов в ловушку избыточной иерархии. Чрезмерное количество уровней разрушает прозрачность: крайне затрудняется их просмотр и анализ ментальной модели, которую по существу реализует код. Всецело нарушаются правила простоты, ясности и прозрачности, а в результате код наполняется скрытыми ошибкми и создает постоянные проблемы при сопровождении.
    Данная тенденция, вероятно, усугубляется тем, что множество курсов по
    программированию преподают громоздкую иерархию как способ удовлетворения правила представления. С этой точки зрения множество классов приравнивается к внедрению знаний в данные. Проблема данного подхода заключается в том, что слишком часто "развитые данные" в связующих уровнях фактически не относятся у какому-либо естественному объекту в области действия программы - они предназначены только для связующего уровня.
    Одной из причин того, что ОО-языки преуспели в большинстве характерных для них предметных областей (GUI-интерфейсы, моделирование, графические средства), возможно, является то, что в этих областях относительно трудно неправильно определить онтологию типов.
    Например, в GUI-интерфейсах и графических средствах присутствует довольно естественное соотвествие между манипулируемыми
    визуальными объектами и классами. Если выясняется, что создается большое
    количество классов, которые не имеют очевидного соответствия с тем, что
    происходит на экране, то, соотвественно, легко заметить, что связующий уровень стал слишком большим."

    (с) Эрик Рэймонд, Искусство программирования для Unix
  • Kolan © (27.01.08 12:56) [31]
    > честно бы их опрашивал

    Что это значит? То есть кнопки бы всегда были активны?


    > Напоследок немножно помедитируй над цитатой:

    Медитирую…
  • Игорь Шевченко © (27.01.08 13:27) [32]
    Kolan ©   (27.01.08 12:56) [31]


    > Что это значит? То есть кнопки бы всегда были активны?


    Это значит, что в обработчике события ActionListUpdate я бы смотрел, является ли текущее окно экземпляром класса, поддерживающего эти операции и если да, то опрашивал бы эти методы.

    actFind.Enabled := (ActiveMDIChild is TMyClass) and TMyClass(ActiveMDIChild).CanFind

    как-то так.
  • Kolan © (27.01.08 13:33) [33]
    > Это значит,

    Ааа, не увидел что тут CanFind.

    Понял.

    Тогда на каждую операцию еще надо добавить Can операцию. Довольно просто.

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

    Будет так:
    actFind.Enabled := ActiveMDIChild.GetInterface(IFind, TheInt);



    Вообщем понятно. Много вариантов получил. Благодарю за обсуждение.
  • Игорь Шевченко © (27.01.08 13:45) [34]

    > Будет так:
    > actFind.Enabled := ActiveMDIChild.GetInterface(IFind, TheInt);
    >


    Тогда уж:

    actFind.Enabled := Supports(ActiveMDIChild, IFind);

    Небольшая разница, но код становится яснее.
  • Kolan © (27.01.08 13:51) [35]
    > Небольшая разница, но код становится яснее.

    Согласен.

    Имхо ниче так решение особенно в сочетании со стратегиями. Можно сделать мелкие интерфейсы (IFind, IGoto), а стратегии(или MDI Children) будут реализовывать их комбинации.

    Благодарю. :)
 
Конференция "Основная" » Как дисэйблить кнопки если операция не поддерживается… ?
Есть новые Нет новых   [134482   +35][b:0][p:0.002]