DialogClass — модуль для работы с диалогами с стиле ООП

Здесь обсуждается всё, что касается макросов.
Smitis
Posts: 1528
Joined: Fri 18 Mar, 2005 12:51
Location: Питер
Has thanked: 15 times
Been thanked: 27 times
Contact:

DialogClass — модуль для работы с диалогами с стиле ООП

Post by Smitis » Tue 25 Feb, 2014 14:53

Модуль для работы с диалогами в стиле ООП.

- Задание параметров диалога и элементов диалога в более нагладном виде - таблицами с именованными полями.
- Вся работа с диалогом и элементами осуществляется вызовом соответствующих методов без непосредственного вызова far.SendDlgMessage и других функций из таблицы far.
- Не требуется сохранять хендл диалога.
- Нет необходимости считать вручную и где-то сохранять ID элементов. Элементы диалога имеют дополнительные поля, в том числе поле Name, позволяющее обращаться к элементам диалога по именам.
- Специальные функции облегчают работу с созданием диалога "по строкам".
- Дополнительные методы облегчат сохранение всех значений элементов в таблицу. И наоборот, заполнение значений элементов из таблицы.









Некоторые идеи честно стырены из исходников luafar4editor :)

DialogClass 1.11
  1. --[=[ ----------------------------------------------------------------------------------------------
  2. Модуль для работы с диалогами в стиле ООП.
  3. v. 1.11 (C) 2013-2015 Smitis
  4. http://forum.farmanager.com/viewtopic.php?t=8523
  5. --]=] ----------------------------------------------------------------------------------------------
  6.  
  7. local _version = "1.11"
  8. local F = far.Flags
  9.  
  10. local function log2num(b) return tonumber(b) or (b and 1) or 0 end
  11. local function state2num(b) return b==nil and -1 or log2num(b) end
  12.  
  13. -- Преобразование флагов, заданых в различном виде (число, строка, таблица) к единому числовому значению.
  14. local function flags2num(...)
  15.     local flags = 0
  16.     for i=1,select("#",...) do
  17.         local x = select(i,...)
  18.         if type(x)=="table" then -- таблица вида {DIF_CENTERGROUP=1, DIF_DEFAULTBUTTON=1}
  19.             for f,v in pairs(x) do
  20.                 if v~=0 then
  21.                     flags = bor(flags,F[f])
  22.                 end
  23.             end
  24.         elseif type(x)=="string" then -- строки вида "DI_BUTTON"
  25.             flags = bor(flags,F[x])
  26.         else -- числа или nil
  27.             flags = bor(flags,x or 0)
  28.         end
  29.     end
  30.     return flags
  31. end
  32.  
  33. ----------------------------------------------------------------------------------------------------
  34. -- Класс DlgItem (элемент диалога)
  35. ----------------------------------------------------------------------------------------------------
  36.  
  37. local DlgItemClass = {}
  38.  
  39. DlgItemClass.NamedFields = {
  40.     Type=1, X1=2, Y1=3, X2=4, Y2=5, Selected=6, Sel=6, S=6, ListItems=6, LI=6, VBuf=6, History=7, His=7,
  41.     Mask=8, Separator=8, Sep=8, Flags=9, F=9, Data=10, Text=10, T=10, MaxLength=11, Max=11, UserData=12
  42. }
  43.  
  44. function DlgItemClass:__index(key)
  45.     if DlgItemClass.NamedFields[key] then
  46.         return rawget(self,DlgItemClass.NamedFields[key])
  47.     end
  48.     return rawget(self,key) or DlgItemClass[key]
  49. end
  50.  
  51. function DlgItemClass:__newindex(key,val)
  52.     rawset(self, DlgItemClass.NamedFields[key] or key, val)
  53. end
  54.  
  55. -- Отправить сообщение диалогу для данного элемента
  56. function DlgItemClass:Send(Msg,Param)
  57.     return self.Parent:Send(Msg,self.Id,Param)
  58. end
  59. -- Проверить тип элемента. Истина, если совпадает хотя бы с одним
  60. function DlgItemClass:CheckType(...)
  61.     for i=1,select("#",...) do
  62.         local tp = select(i,...)
  63.         if self.Type==(F[tp] or tp) then return true end
  64.     end
  65.     return false
  66. end
  67. -- Проверить флаги элемента. Истина, если установлен хотя бы один флаг
  68. function DlgItemClass:CheckFlags(...)
  69.     return band(self.Flags,flags2num(...))~=0
  70. end
  71. -- Установить флаги элемента
  72. function DlgItemClass:SetFlags(...)
  73.     self.Flags = bor(self.Flags,flags2num(...))
  74.     return self.Flags
  75. end
  76. -- Вкл./выкл элемент диалога
  77. function DlgItemClass:Enable(x)
  78.     return self:Send(F.DM_ENABLE,state2num(x)) == 1
  79. end
  80. -- Установить состояние EDITUNCHANGEDFLAG
  81. function DlgItemClass:Unchanged(x)
  82.     return self:Send(F.DM_EDITUNCHANGEDFLAG,state2num(x)) == 1
  83. end
  84. -- Показать/скрыть элемент диалога
  85. function DlgItemClass:Show(x)
  86.     return self:Send(F.DM_SHOWITEM,state2num(x)) == 1
  87. end
  88. -- Установить фокус на элемент диалога
  89. function DlgItemClass:Focus()
  90.     return self:Send(F.DM_SETFOCUS,0) == 1
  91. end
  92. -- Установить значение CHECKBOX или RADIOBUTTON
  93. function DlgItemClass:SetCheck(x)
  94.     return self:Send(F.DM_SETCHECK,x)
  95. end
  96. -- Получить значение CHECKBOX или RADIOBUTTON
  97. function DlgItemClass:GetCheck()
  98.     return self:Send(F.DM_GETCHECK,0)
  99. end
  100. -- Установить логическое состояние CHECKBOX или RADIOBUTTON
  101. function DlgItemClass:SetCheckState(x)
  102.     return self:SetCheck(log2num(x)) >= 1
  103. end
  104. -- Получить логическое состояние CHECKBOX или RADIOBUTTON
  105. function DlgItemClass:GetCheckState()
  106.     return self:GetCheck() >= 1
  107. end
  108. -- Установить текстовое значение
  109. function DlgItemClass:SetText(text)
  110.     return self:Send(F.DM_SETTEXT,text)
  111. end
  112. -- Получить текстовое значение
  113. function DlgItemClass:GetText()
  114.     return self:Send(F.DM_GETTEXT,0)
  115. end
  116. -- Установить текущую позицию для списка
  117. function DlgItemClass:SetListPos(pos,top)
  118.     return self:Send(F.DM_LISTSETCURPOS,{SelectPos=pos,TopPos=top})
  119. end
  120. -- Получить текущую позицию для списка
  121. function DlgItemClass:GetListPos()
  122.     local FarListPos = self:Send(F.DM_LISTGETCURPOS,0)
  123.     return FarListPos.SelectPos, FarListPos.TopPos
  124. end
  125. -- Установки значения в зависимости от типа элемента диалога
  126. -- [???] Если val таблица, посылать DM_SETDLGITEM?
  127. function DlgItemClass:SetValue(val,top)
  128.     if self:CheckType(F.DI_CHECKBOX,F.DI_RADIOBUTTON) then
  129.         return self:SetCheck(log2num(val))
  130.     elseif self:CheckType(F.DI_LISTBOX) then
  131.         return self:SetListPos(val,top)
  132.     elseif self:CheckType(F.DI_COMBOBOX) then
  133.         if type(val)=="number" then
  134.             return self:SetListPos(val,top)
  135.         else
  136.             return self:SetText(tostring(val))
  137.         end
  138.     else
  139.         return self:SetText(tostring(val))
  140.     end
  141. end
  142. -- Получения значения в зависимости от типа элемента диалога
  143. function DlgItemClass:GetValue()
  144.     if self:CheckType(F.DI_CHECKBOX,F.DI_RADIOBUTTON) then
  145.         return self:GetCheck()
  146.     elseif self:CheckType(F.DI_LISTBOX) then
  147.         return self:GetListPos()
  148.     else
  149.         return self:GetText()
  150.     end
  151. end
  152.  
  153. ----------------------------------------------------------------------------------------------------
  154. -- Класс DlgItems (контейнер для всех dlgitem)
  155. -- Элементы с индексами <=0 являются private полями и нигде, кроме как здесь не должны использоваться.
  156. -- local __named__  =  0 -- Таблица имён элементов
  157. -- local __parent__ = -1 -- Ссылка на родительский диалог
  158. -- local __framed__ = -2 -- Ссылка на элемент рамки. Если nil или false - рамка не добавлялась.
  159. ----------------------------------------------------------------------------------------------------
  160.  
  161. local DlgItemsClass = {}
  162.  
  163. function DlgItemsClass:__index(name)
  164.     --return rawget(self, self[0][name] or name)
  165.     -- Если элемент не существует, вернём функцию, которая этот элемент добавит
  166.     return rawget(self, self[0][name] or name) or
  167.         function(newitem)
  168.             newitem.Name = name
  169.             self(newitem)
  170.         end
  171. end
  172.  
  173. -- Добавление нового элемента диалога
  174. function DlgItemsClass:__newindex(name,item)
  175.     local x
  176.     if type(item)=="table" then
  177.         local id = #self+1
  178.         local parent = self[-1]
  179.         item.Name = name or item.Name or "_"
  180.         item.Id = id
  181.         item.Parent = parent
  182.         self[0][item.Name] = id -- в список именованных элементов
  183.         -- копируем все виртуальные поля и удаляем их
  184.         for f,v in pairs(item) do
  185.             if DlgItemClass.NamedFields[f] then
  186.                 item[DlgItemClass.NamedFields[f]] = v
  187.                 item[f] = nil
  188.             end
  189.         end
  190.  
  191.         setmetatable(item,parent.DlgItemClass or DlgItemClass) -- объект
  192.  
  193.         item.Flags = flags2num(item.Flags) -- Flags [9] в number
  194.         if type(item.Type)=="number" or item.Type:sub(1,3)=="DI_" then
  195.             item.Type = item.Type and flags2num(item.Type) or F.DI_TEXT -- тип элемента в number или тип по умолчанию
  196.         else -- Если тип - строка начинающаяся не с DI_, это сокращённое обозначение типа элемента, параметров, опций
  197.             local typ = item.Type:sub(1,1)
  198.             local opt = item.Type:sub(2)
  199.             if typ == "b" then
  200.                 item.Type = F.DI_BUTTON
  201.                 if opt:find("b") then item:SetFlags(F.DIF_NOBRACKETS) end
  202.                 if opt:find(";") then item:SetFlags(F.DIF_BTNNOCLOSE) end
  203.             elseif typ == "f" then
  204.                 item.Type = opt:find("d") and F.DI_DOUBLEBOX or F.DI_SINGLEBOX
  205.             elseif typ == "t" then
  206.                 item.Type = opt:find("v") and F.DI_VTEXT or F.DI_TEXT
  207.                 -- DIF_WORDWRAP ???
  208.             elseif typ == "e" then
  209.                 item.Type = opt:find("f") and F.DI_FIXEDIT or F.DI_EDIT
  210.                 if opt:find("o") then item:SetFlags(F.DIF_READONLY) end
  211.                 if opt:find("e") then item:SetFlags(F.DIF_EDITOR) end
  212.                 if opt:find("h") then item:SetFlags(F.DIF_HISTORY) end
  213.                 if opt:find("m") then item:SetFlags(F.DIF_MANUALADDHISTORY) end
  214.                 if opt:find("l") then item:SetFlags(F.DIF_USELASTHISTORY) end
  215.             elseif typ == "p" then
  216.                 item.Type = F.DI_PSWEDIT
  217.             elseif typ == "s" then -- DI_TEXT | DI_VTEXT & DIF_SEPARATORUSER | DIF_SEPARATOR | DIF_SEPARATOR2
  218.                 item.Type = opt:find("v") and F.DI_VTEXT or F.DI_TEXT
  219.                 if opt:find("u") or item.Separator then
  220.                     item:SetFlags(F.DIF_SEPARATORUSER)
  221.                 elseif opt:find("d") then
  222.                     item:SetFlags(F.DIF_SEPARATOR2)
  223.                 else
  224.                     item:SetFlags(F.DIF_SEPARATOR)
  225.                 end
  226.             elseif typ == "r" then
  227.                 item.Type = F.DI_RADIOBUTTON
  228.             elseif typ == "c" then
  229.                 item.Type = F.DI_CHECKBOX
  230.                 if opt:find("3") then item:SetFlags(F.DIF_3STATE) end
  231.             elseif typ == "l" then
  232.                 item.Type = F.DI_LISTBOX
  233.             elseif typ == "x" then
  234.                 item.Type = F.DI_COMBOBOX
  235.                 if opt:find("d") then item:SetFlags(F.DIF_DROPDOWNLIST) end
  236.             elseif typ == "u" then
  237.                 item.Type = F.DI_USERCONTROL
  238.             else
  239.                 item.Type = F.DI_TEXT
  240.             end
  241.             if opt:find("|") then item:SetFlags(F.DIF_CENTERTEXT) end
  242.             if opt:find(">") then item:SetFlags(F.DIF_RIGHTTEXT) end
  243.             if opt:find("<") then item:SetFlags(F.DIF_LEFTTEXT) end
  244.             if opt:find("!") then item:SetFlags(F.DIF_DEFAULTBUTTON) end
  245.             if opt:find("[=c]") then item:SetFlags(F.DIF_CENTERGROUP) end
  246.             if opt:find("[:g]") then item:SetFlags(F.DIF_GROUP) end
  247.             if opt:find("[?x]") then item:SetFlags(F.DIF_DISABLE) end
  248.             if opt:find("[*s]") then item:SetFlags(F.DIF_SELECTONENTRY) end
  249.             if opt:find("/") then item:SetFlags(F.DIF_NOFOCUS) end
  250.             if opt:find("@") then item:SetFlags(F.DIF_FOCUS) end
  251.             if opt:find("&") then item:SetFlags(F.DIF_SHOWAMPERSAND) end
  252.             x = opt:gsub("[^+]","") -- подсчитываем "+"
  253.             if x:len()>0 then item[3] = parent:NewLine(x:len()) end
  254.             x = opt:gsub("[^%-]","") -- посчитываем "-"
  255.             if x:len()>0 then item[3] = parent:NewLine(-x:len()) end
  256.         end
  257.  
  258.         if type(item.Selected)=="boolean" then -- item[6]
  259.             item.Selected = state2num(item.Selected)
  260.         else
  261.             item.Selected = item.Selected or 0
  262.         end
  263.         if type(item.History)=="string" and item.History~="" then -- если есть History
  264.             item:SetFlags(F.DIF_HISTORY)
  265.         end
  266.         if type(item.Separator)=="string" and item.Separator~="" then -- если есть Mask/Separator
  267.             if item:CheckType(F.DI_TEXT,F.DI_VTEXT) then
  268.                 item:SetFlags(F.DIF_SEPARATORUSER)
  269.             else
  270.                 item:SetFlags(F.DIF_MASKEDIT)
  271.             end
  272.         end
  273.         if item:CheckType(F.DI_BUTTON,F.DI_CHECKBOX,F.DI_RADIOBUTTON) then
  274.             item.Text = item.Text or item.Name -- если не задана надпись на кнопке, берём имя элемента
  275.         end
  276.  
  277.         -- Координаты
  278.         if item.SL or item.YS or item.SetLine then parent:SetLine(item.SL or item.YS or item.SetLine) end
  279.         if item.NL or item.YN or item.NewLine then parent:NewLine(item.NL or item.YN or item.NewLine) end
  280.         if item:CheckType(F.DI_BUTTON,F.DI_CHECKBOX,F.DI_RADIOBUTTON) then
  281.             item.X2, item.Y2, item.W, item.H = 0, nil, nil, nil
  282.         end
  283.         if item:CheckType(F.DI_EDIT,F.DI_FIXEDIT) then
  284.             item.Y2, item.H = nil, nil
  285.         end
  286.         if item:CheckType(F.DI_TEXT) then
  287.             item.H = nil
  288.             if item:CheckFlags(F.DIF_SEPARATOR2,F.DIF_SEPARATOR) then
  289.                 item.X1, item.X2, item.W = item.X1 or -1, 0, nil
  290.             elseif not item:CheckFlags(F.DIF_SEPARATORUSER,F.DIF_CENTERTEXT) then
  291.                 item.X2 = item.X2 or 0
  292.             end
  293.         end
  294.         if item:CheckType(F.DI_VTEXT) then
  295.             item.W = nil
  296.             if item:CheckFlags(F.DIF_SEPARATOR2,F.DIF_SEPARATOR) then
  297.                 item.Y1, item.Y2, item.H = item.Y1 or -1, 0, nil
  298.             elseif not item:CheckFlags(F.DIF_SEPARATORUSER,F.DIF_CENTERTEXT) then
  299.                 item.Y2 = item.Y2 or 0
  300.             end
  301.         end
  302.         item.X1 = item.X1 or parent.LC
  303.         item.X2 = item.W and item.X1+item.W-1 or item.X2 or parent.RC
  304.         item.Y1 = item.Y1 or parent.Line
  305.         item.Y2 = item.H and item.Y1+item.H-1 or item.Y2 or item.Y1
  306.         parent.LastLine = math.max(parent.LastLine,item.Y2)
  307.  
  308.         rawset(self,id,item) -- Добавляем
  309.     end
  310. end
  311.  
  312. -- Добавление DlgItem вызовом как функции
  313. function DlgItemsClass:__call(...)
  314.     DlgItemsClass.__newindex(self,false,...)
  315. end
  316.  
  317. -- Конструктор DlgItemsClass
  318. setmetatable( DlgItemsClass, {
  319.     __call = function(self,dlg)
  320.         return setmetatable({[-2]=false,[-1]=dlg,[0]={}},self)
  321.     end
  322. })
  323.  
  324. ----------------------------------------------------------------------------------------------------
  325. -- Класс Dialog
  326. ----------------------------------------------------------------------------------------------------
  327.  
  328. local DialogClass = {}
  329. DialogClass.__index = DialogClass
  330.  
  331. -- Подчинённые классы (для удобства наследования)
  332. DialogClass.DlgItemClass = DlgItemClass
  333. DialogClass.DlgItemsClass = DlgItemsClass
  334.  
  335. ----------------------------------------------------------------------------------------------------
  336. -- Добавить элемент диалога
  337. function DialogClass:AddItem(item)
  338.     if type(item)=="table" then
  339.         self.Items[false] = item
  340.         return item
  341.     end
  342. end
  343. -- Добавить элементы диалога из массива
  344. function DialogClass:AddItems(items)
  345.     if type(items)=="table" then
  346.         for n,item in ipairs(items) do
  347.             self:AddItem(item)
  348.         end
  349.     end
  350.     return self
  351. end
  352. -- Проверить наличие элемента, вернуть элемент или nil
  353. -- Такой метод необходим, так как нельзя просто проверить несуществующий элемент в Items (вернётся функция)
  354. function DialogClass:ExistItem(name)
  355.     return rawget(self.Items, self.Items[0][name])
  356. end
  357. -- Заполнения свойств диалога
  358. function DialogClass:SetProperties(Tab,...)
  359.     local Guid,X1,Y1,X2,Y2,HelpTopic,Items,Flags,DlgProc,Frame
  360.     if type(Tab)=="table" then -- свойства переданы таблицей, всё после Tab игнорируется
  361.         for i,v in pairs(Tab) do
  362.             if i == "Items" then -- таблица элементов диалога, потом добавим через AddItems
  363.                 Items = v
  364.             else
  365.                 self[i] = v
  366.             end
  367.         end
  368.     else -- свойства переданы обычным, не табличным, способом
  369.         Guid = Tab
  370.         X1,Y1,X2,Y2,HelpTopic,Items,Flags,DlgProc = ...
  371.     end
  372.     self.Guid = Guid or self.Guid or ""
  373.     if self.Guid:match("%x%x%x%x%x%x%x%x%-%x%x%x%x%-%x%x%x%x%-%x%x%x%x%-%x%x%x%x%x%x%x%x%x%x%x%x") then self.Guid=win.Uuid(self.Guid) end
  374.     self.X1 = X1 or self.X1 or -1
  375.     self.Y1 = Y1 or self.Y1 or -1
  376.     self.X2 = X2 or self.X2 or 0
  377.     self.Y2 = Y2 or self.Y2 or 0
  378.     if self.W then
  379.         self.X2 = self.X1>=0 and self.X1+self.W-1 or self.W
  380.         self.W = nil -- self.X1>=0 and self.X2-self.X1+1 or self.X2
  381.     end
  382.     if self.H then
  383.         self.Y2 = self.Y1>=0 and self.Y1+self.H-1 or self.H
  384.         self.H = nil -- self.Y1>=0 and self.Y2-self.Y1+1 or self.Y2
  385.     end
  386.     self.LC = self.LC or 1
  387.     self.RC = self.RC or ( self.X1>=0 and self.X2-self.X1-1 or self.X2-2 )
  388.     self.Line = self.Line or 1
  389.     self.MaxLine = self.Y1>=0 and self.Y2-self.Y1-1 or self.Y2-2
  390.     self.LastLine = self.LastLine or 0
  391.     self.HelpTopic = HelpTopic or self.HelpTopic
  392.     if (self.Frame or self.Title) and not self.Items[-2] then
  393.         self.Title = self.Title or ""
  394.         self.Frame = self.Frame or 2
  395.         -- добавить элемент, сохраняя LastLine
  396.         self.LastLine, self.Items.__MainFrame = self.LastLine, {self.Frame==1 and F.DI_SINGLEBOX or F.DI_DOUBLEBOX,3,1,(self.X1>=0 and self.X2-self.X1-3 or self.X2-4),(self.Y1>=0 and self.Y2-self.Y1-1 or self.Y2-2),0,0,0,0,self.Title}
  397.         self.Items[-2] = true --self.Items.__MainFrame
  398.         self.Line = 1 -- на уровне рамки (не ниже - для упрощения использования опции +)
  399.         self.LC = 5 -- правее рамки через символ
  400.         self.RC = self.X1>=0 and self.X2-self.X1-5 or self.X2-6 -- левее рамки через символ
  401.     end
  402.     self.WW = self.RC-self.LC+1 -- Width of Work area
  403.     self:AddItems(Items)
  404.     self.Flags = flags2num(Flags or self.Flags)
  405.     self.DlgProc = DlgProc or self.DlgProc
  406.     self.Handle = self.Handle or 0
  407.     self.Refreshed = self.Refreshed or true
  408.     self.Ret = 0
  409.     return self
  410. end
  411. -- Отправка сообщения диалогу
  412. function DialogClass:Send(Msg,Param1,Param2)
  413.     return far.SendDlgMessage(self.Handle,Msg,Param1,Param2)
  414. end
  415. -- Аналог far.DialogInit()
  416. -- В DlgProc будут переданы два дополнительных параметра
  417. function DialogClass:Init(...)
  418.     self:SetProperties(...)
  419.     self.Handle =
  420.         far.DialogInit(
  421.             self.Guid,self.X1,self.Y1,self.X2,self.Y2,self.HelpTopic,self.Items,self.Flags,
  422.             self.DlgProc and function(hDlg,Msg,Param1,Param2) return self.DlgProc(hDlg,Msg,Param1,Param2,self,self.Items) end
  423.         )
  424.     return self.Handle
  425. end
  426. -- Аналог far.DialogRun()
  427. function DialogClass:Run()
  428.     self.Ret = far.DialogRun(self.Handle)
  429.     if self.Refreshed then self:Refresh() end
  430.     return self.Ret
  431. end
  432. -- Аналог far.DialogFree()
  433. function DialogClass:Free()
  434.     far.DialogFree(self.Handle)
  435.     self.Handle = 0
  436. end
  437. -- Аналог far.Dialog()
  438. function DialogClass:Exec(...)
  439.     self:Init(...) self:Run() self:Free()
  440.     return self.Ret
  441. end
  442. -- Функциональный запуск D() - то же, что D:Exec()
  443. function DialogClass:__call(...)
  444.     return self:Exec(...)
  445. end
  446. -- Установить фокус на элемент диалога
  447. function DialogClass:SetFocus(id)
  448.     return self:Send(F.DM_SETFOCUS,id,0)
  449. end
  450. -- Получить id элемента диалога в фокусе
  451. function DialogClass:GetFocus()
  452.     return self:Send(F.DM_GETFOCUS,0,0)
  453. end
  454. -- Вкл./выкл. автоматическую перерисовку
  455. function DialogClass:AutoRedraw(x)
  456.     return self:Send(F.DM_ENABLEREDRAW,log2num(x),0)
  457. end
  458. -- Перерисовать
  459. function DialogClass:Redraw()
  460.     return self:Send(F.DM_REDRAW,0,0)
  461. end
  462. ----------------------------------------------------------------------------------------------------
  463. -- Установить текущую строку.
  464. -- При отрицательном значении отсчёт от D.MaxLine
  465. function DialogClass:SetLine(n)
  466.     if n then self.Line = n<0 and self.MaxLine+1+n or n end
  467.     return self.Line
  468. end
  469. DialogClass.SL = DialogClass.SetLine --синоним
  470. -- Перейти на следующую строку или несколько строк относительно текущей
  471. function DialogClass:NewLine(n)
  472.     self.Line = self.Line+(n or 1)
  473.     return self.Line
  474. end
  475. DialogClass.NL = DialogClass.NewLine --синоним
  476. ----------------------------------------------------------------------------------------------------
  477. -- Сохранить все поля диалога, имена которых не начинаются с символа подчёркивания, в таблицу по условию.
  478. -- Возвращается ссылка на таблицу. Если таблица не задана, создаётся новая.
  479. -- Условие позволяет сохранить только нужные элементы.
  480. function DialogClass:Save(Tab,Condition)
  481.     if self.Handle==0 then return end
  482.     local tab = type(Tab)=="table" and Tab or {}
  483.     for id,item in ipairs(self.Items) do
  484.         -- если имя не начинается с подчёркивания или
  485.         -- если Condition строка, проверяем значение поля элемента,
  486.         -- если Condition функция, проверяем результат,
  487.         -- только эти элементы сохраняем
  488.         if item.Name and (
  489.                 (Condition and ((type(Condition)=="string" and item[Condition]) or (type(Condition)=="function" and Condition(item))))
  490.                 or (item.Name~="" and item.Name:sub(1,1)~="_")
  491.             ) then
  492.             if not item:CheckType(F.DI_BUTTON,F.DI_DOUBLEBOX,F.DI_SINGLEBOX,F.DI_USERCONTROL) then -- не все элементы имеет смысл сохранять
  493.                 tab[item.Name] = item:GetValue()
  494.             end
  495.         end
  496.     end
  497.     return tab
  498. end
  499. -- Скопировать все информационные поля items в таблицу, по аналогии с Save,
  500. -- только копируются значения из таблицы элементов, а не те, которые на экране
  501. -- [==[
  502. function DialogClass:Copy(Tab,Condition)
  503.     local tab = type(Tab)=="table" and Tab or {}
  504.     for id,item in ipairs(self.Items) do
  505.         if item.Name and (
  506.                 (Condition and ((type(Condition)=="string" and item[Condition]) or (type(Condition)=="function" and Condition(item))))
  507.                 or (item.Name~="" and item.Name:sub(1,1)~="_")
  508.             ) then
  509.             if self:CheckType(F.DI_CHECKBOX,F.DI_RADIOBUTTON) then
  510.                 tab[item.Name] = item.Selected
  511.             elseif self:CheckType(F.DI_LISTBOX) then
  512.                 tab[item.Name] = item.Selected.SelectIndex
  513.             elseif not item:CheckType(F.DI_BUTTON,F.DI_DOUBLEBOX,F.DI_SINGLEBOX,F.DI_USERCONTROL) then -- не все элементы имеет смысл сохранять
  514.                 tab[item.Name] = item.Text
  515.             end
  516.         end
  517.     end
  518.     return tab
  519. end
  520. --]==]
  521. -- Обновить все исходные items значениями из диалога
  522. function DialogClass:Refresh()
  523.     for id,item in ipairs(self.Items) do
  524.         local real = far.GetDlgItem(self.Handle,id) -- можно даже сделать setmetatable
  525.         if item:CheckType(F.DI_LISTBOX) then
  526.             item.Selected.SelectIndex = item:GetListPos()
  527.         elseif item:CheckType(F.DI_COMBOBOX) then
  528.             item.Selected.SelectIndex = item:GetListPos()
  529.             item.Text = real[10]
  530.         elseif item:CheckType(F.DI_CHECKBOX,F.DI_RADIOBUTTON) then
  531.             item.Selected = real[6]
  532.         else
  533.             item.Text = real[10]
  534.         end
  535.     end
  536. end
  537. -- Загрузить все поля диалога из таблицы по условию
  538. function DialogClass:Load(Tab,Condition)
  539.     if self.Handle==0 then return end
  540.     for id,item in ipairs(self.Items) do
  541.         if item.Name~="" and item.Name:sub(1,1)~="_" and Tab[item.Name]~=nil then -- если имя не начинается с подчёркивания
  542.             if type(Condition)=="string" then
  543.                 if item[Condition] then
  544.                     item:SetValue(Tab[item.Name])
  545.                 end
  546.             elseif type(Condition)=="function" then
  547.                 if Condition(item) then
  548.                     item:SetValue(Tab[item.Name])
  549.                 end
  550.             else
  551.                 item:SetValue(Tab[item.Name])
  552.             end
  553.         end
  554.     end
  555. end
  556. ----------------------------------------------------------------------------------------------------
  557. -- Конструктор
  558. local function New(...)
  559.     local D = {}
  560.     D.Items = DlgItemsClass(D) -- D.Items = setmetatable({[-2]=false,[-1]=D,[0]={}},DlgItemsClass)
  561.     D.I, D.i = D.Items, D.Items -- синонимы
  562.     setmetatable(D,DialogClass) -- объект
  563.     D:SetProperties(...)
  564.     -- Возвращаемые значения: сам диалог, таблица Items, функция SetLine, функция NewLine
  565.     return D, D.Items, function(n) return D:SetLine(n) end, function(n) return D:NewLine(n) end
  566. end
  567. ----------------------------------------------------------------------------------------------------
  568. return
  569.     {
  570.         New = New,
  571.         DialogClass=DialogClass,
  572.         DlgItemClass=DlgItemClass,
  573.         DlgItemsClass=DlgItemsClass,
  574.         log2num = log2num,
  575.         state2num = state2num,
  576.         flags2num = flags2num,
  577.         _VERSION = _version
  578.     }
  579. ----------------------------------------------------------------------------------------------------
Пример наследования классов
  1. --[=[ ----------------------------------------------------------------------------------------------
  2. Пример наследования классов DialogClass и DlgItemClass.
  3. Сделаны два дополнения:
  4.  
  5. Дополнение 1.
  6. - Новое поле в объектах DlgItemClass Hidden для скрытия элементов диалога при вызове Show.
  7. - Перегруженный метод Show - элемент диалога всегда скрывается, если его свойство Hidden==true, независимо от параметра, переданного в Show.
  8.  
  9. Дополнение 2. Имитируем многостраничный диалог (типа TabControl, но без вкладок).
  10. - Страницы нумеруем от 1 до некоторого максимального значения, определяемого новым свойством диалога MaxPage.
  11. - Номер текущей страницы хранится в поле Page диалога.
  12. - Для переключения страниц добавляем новый метод ShowPage(page).
  13. Кроме всего прочего, метод проверяет переданный номер, на попадаение в диапазон 1...MaxPage и обеспечивает циклическое переключение страниц вызовами, подобными D:ShowPage(D.Page+1) и D:ShowPage(D.Page-1).
  14. Метод устанавливает свойство Page в новое значение.
  15. - Переключение страниц имитируем показом/скрытием элементов диалога. Для этого каждый элемент диалог может иметь свойство Page, задающий номер страницы, на которой элемент показывается, для остальных страниц он скрывается.
  16. Для показа элемента на нескольких страницах, Page задаём массивом номеров страниц или строкой, содержащей номера страниц разделённых нецифровыми символами или таблицей.
  17. Элементы диалога, не имеющие свойство Page при переключении страниц не затрагиваются.
  18. - Начальную страницу можно задать через свойство Page диалога.
  19. - Для каждой страницы сохраняется фокус текущего элемента в свойстве диалога массиве PageFocus.
  20.  
  21. Все перегружаемые методы используют старые методы в качестве базовых.
  22. --]=] ----------------------------------------------------------------------------------------------
  23.  
  24. local dialog = require("DialogClass")
  25. local dialognew = { unpack(dialog) } -- клонируем dialog (достаточно одного уровня)
  26.  
  27. local DlgItemClassNamedFields = dialog.DlgItemClass.NamedFields
  28.  
  29. ----------------------------------------------------------------------------------------------------
  30. local DlgItemClassNew = {}
  31. ----------------------------------------------------------------------------------------------------
  32. -- Приходится повторять __index из базового класса
  33. function DlgItemClassNew:__index(key)
  34.     if DlgItemClassNamedFields[key] then
  35.         return rawget(self,DlgItemClassNamedFields[key])
  36.     end
  37.     return rawget(self,key) or DlgItemClassNew[key]
  38. end
  39.  
  40. ----------------------------------------------------------------------------------------------------
  41. -- Перегрузка метода Show
  42. function DlgItemClassNew:Show(x)
  43.     return dialog.DlgItemClass.Show(self,self.Hidden and 0 or dialog.log2num(x))
  44. end
  45. ----------------------------------------------------------------------------------------------------
  46. DlgItemClassNew.__newindex = dialog.DlgItemClass.__newindex
  47.  
  48. setmetatable(DlgItemClassNew,dialog.DlgItemClass)
  49. ----------------------------------------------------------------------------------------------------
  50.  
  51. local DialogClassNew = {}
  52. DialogClassNew.DlgItemClass = DlgItemClassNew
  53. DialogClassNew.DlgItemsClass = dialog.DlgItemsClass
  54.  
  55. ----------------------------------------------------------------------------------------------------
  56. -- Переключение на заданную страницу.
  57. function DialogClassNew:ShowPage(page)
  58.     local prev = self.Page
  59.     -- Если page не задана, будет повторно обработана текущая страница.
  60.     page = tonumber(page or self.Page)
  61.     if page>self.MaxPage then page=1 elseif page<1 then page=self.MaxPage end
  62.     self:AutoRedraw(false)
  63.     self.PageFocus[self.Page] = self:GetFocus()
  64.     self.Page = page
  65.     page = '%D'..tostring(page)..'%D' -- шаблон проверки
  66.     for id,item in ipairs(self.Items) do
  67.         if item.Page then
  68.             item:Show((";"..(type(item.Page)=="table" and table.concat(item.Page,";") or tostring(item.Page))..";"):find(page)~=nil)
  69.         end
  70.     end
  71.     self:SetFocus(self.PageFocus[self.Page])
  72.     self:AutoRedraw(true)
  73.     return prev
  74. end
  75. ----------------------------------------------------------------------------------------------------
  76. -- Перегрузка Clear
  77. function DialogClassNew:Clear()
  78.     dialog.DialogClass.Clear(self)
  79.     self.Page = 1
  80.     self.MaxPage = 1
  81.     self.PageFocus = {0}
  82.     return self
  83. end
  84. ----------------------------------------------------------------------------------------------------
  85. -- Перегрузка Init
  86. function DialogClassNew:Init(...)
  87.     local ret = dialog.DialogClass.Init(self,...)
  88.     self:ShowPage() -- иницилизация начальной страницы self.Page
  89.     return ret
  90. end
  91. ----------------------------------------------------------------------------------------------------
  92. DialogClassNew.__call = dialog.DialogClass.__call
  93. DialogClassNew.__index = setmetatable(DialogClassNew,dialog.DialogClass)
  94. ----------------------------------------------------------------------------------------------------
  95. -- Новый конструктор
  96. local function New(...)
  97.     -- В качестве базового используем старый конструктор, но вызываем его без параметров, иначе все Items будут старыми DlgItemClass.
  98.     -- Сохраняем всё возвращённое конструктором, не разбирая что там, как таблицу, на случай будущих изменений в базовом конструкторе.
  99.     local DA = { dialog.New() }
  100.     -- Первый элемент - диалог, остальные нас не интересуют
  101.     local D = DA[1]
  102.     -- Новая метатаблица для диалога
  103.     setmetatable(D,DialogClassNew)
  104.     -- Иницилизация новых полей
  105.     D.Page, D.MaxPage = 1, 1
  106.     D.PageFocus = {0}
  107.     -- Вот теперь можно задавать параметры
  108.     D:SetProperties(...)
  109.     -- Возвращаем то, что вернул старый конструктор
  110.     return unpack(DA)
  111. end
  112.  
  113. ----------------------------------------------------------------------------------------------------
  114.  
  115. dialognew.DialogClass = DialogClassNew
  116. dialognew.DlgItemClass = DlgItemClassNew
  117. dialognew.New = New
  118.  
  119. return dialognew
  120.  
  121. ----------------------------------------------------------------------------------------------------

Smitis
Posts: 1528
Joined: Fri 18 Mar, 2005 12:51
Location: Питер
Has thanked: 15 times
Been thanked: 27 times
Contact:

Re: DialogClass — модуль для работы с диалогами с стиле ООП

Post by Smitis » Fri 28 Feb, 2014 17:40

Небольшие доработки, без изменения функционала.
Вызов SendDlgMessage остался только один, все остальные - через методы.

Выявился один непонятный момент.
Получить значения полей диалога, в том числе изменённых, можно двумя способами.
1. Стандартный. Посылаем сообщение с нужным ID. Получаем значение. Работает пока диалог "жив" - до DialogFree. Так работает метод DialogClass:Save.
2. После закрытия диалога посмотреть соответствующие поля элементов диалога, [10] для текста, [6] для чекбоксов, и т.д. Как я понимаю, это уже фишка LuaFar. Но в данном случае (модуле DialogClass) такое не проходит. Значения не изменяются. Основное отличие класса DialogItems, содержащего все эти элементы диалога в метатаблице (это не таблица table). Скорее всего дело именно в этом, но ведь всё остальное работает. В чём тут дело?

Небольшой тестовый скрипт, диалог из макроса InsertMacro.
Создаётся разными способыми (меняется функция в макросе)
Разум когда-нибудь победит.

Shmuel
Posts: 5401
Joined: Thu 23 Mar, 2006 21:36
Location: Israel
Has thanked: 19 times
Been thanked: 169 times

Re: DialogClass — модуль для работы с диалогами с стиле ООП

Post by Shmuel » Fri 28 Feb, 2014 17:51

Smitis wrote: 2. После закрытия диалога посмотреть соответствующие поля элементов диалога, [10] для текста, [6] для чекбоксов, и т.д. Как я понимаю, это уже фишка LuaFar. Но в данном случае (модуле DialogClass) такое не проходит. Значения не изменяются. Основное отличие класса DialogItems, содержащего все эти элементы диалога в метатаблице (это не таблица table). Скорее всего дело именно в этом, но ведь всё остальное работает. В чём тут дело?
В LuaFAR можете посмотреть, как это сделано (service.c:5773 - Lua, встроенный в Си) - значения тоже получаются до вызова DialogFree. Чудес не бывает.

Smitis
Posts: 1528
Joined: Fri 18 Mar, 2005 12:51
Location: Питер
Has thanked: 15 times
Been thanked: 27 times
Contact:

Re: DialogClass — модуль для работы с диалогами с стиле ООП

Post by Smitis » Fri 28 Feb, 2014 18:01

Shmuel wrote:Чудес не бывает.
Значит бывают :)
В первом варианте InsertMacro1 все изменения в диалоге (поле описания, состояние чекбоксов) доступны уже ПОСЛЕ закрытия диалога. Разве ж только far.Dialog() не выполняет far.DialogFree(). Но тогда это уже больше на баг похоже.
Разум когда-нибудь победит.

Shmuel
Posts: 5401
Joined: Thu 23 Mar, 2006 21:36
Location: Israel
Has thanked: 19 times
Been thanked: 169 times

Re: DialogClass — модуль для работы с диалогами с стиле ООП

Post by Shmuel » Fri 28 Feb, 2014 18:03

Ну я же вам указал, где посмотреть. Такое впечатление, что вы этого не сделали, а отвечаете.

Smitis
Posts: 1528
Joined: Fri 18 Mar, 2005 12:51
Location: Питер
Has thanked: 15 times
Been thanked: 27 times
Contact:

Re: DialogClass — модуль для работы с диалогами с стиле ООП

Post by Smitis » Fri 28 Feb, 2014 18:20

Каюсь, сразу не посмотрел.
Разум когда-нибудь победит.

Smitis
Posts: 1528
Joined: Fri 18 Mar, 2005 12:51
Location: Питер
Has thanked: 15 times
Been thanked: 27 times
Contact:

Re: DialogClass — модуль для работы с диалогами с стиле ООП

Post by Smitis » Fri 28 Feb, 2014 21:23

v.0.03
В метод DialogClass:Init добавился первым параметр Opt, как и в конструкторе New. Надеюсь, больше ломки по параметрам не будет. Предполагается, что Opt будет либо строкой, где некоторые опции заданы буквами, либо таблицей параметров. На текущей момент только таблица, параметр один - Refresh, если истина, после DialogRun происходит считывание всех значений элементов обратно в Items вызовом соответственно добавленного метода Refresh. Наверное, стоит его зделать true по умолчанию, тогда вызов метода DialogClass:Open будет работать и как far.Dialog.
Или лучше вместо Refresh назвать Update? Или ещё как?

Также не нравится название метода Open. Open - это открыть. А тут открыть, потом закрыть... Может назвать его Call? В FarLua он far.Dialog. Но DialogClass:Dialog выгладит как-то масло-масляное.

Добавлен метод Copy. Работает, также, как Save, но не вызывая SendDlgMessage, а просто копируя значения из Items. Ну, типа, после Refresh можно вызвать вместо Save - Copy.

З.Ы.
Кто-нибудь делал на Lua наследование классов? Вроде оно понятно как делать... Хотя всё это как-то через одно место...
А то тут у меня основной класс DialogClass и два подчинённых DlgItem и DlgItems и если делать новые классы с новыми методами/перезагрузкой существующих с наследование от них всех... Что-то в мозгах стопорит :(
Разум когда-нибудь победит.

User avatar
John Doe
Бюрократ
Posts: 11714
Joined: Wed 27 Apr, 2005 20:42
Has thanked: 24 times
Been thanked: 123 times

Re: DialogClass — модуль для работы с диалогами с стиле ООП

Post by John Doe » Fri 28 Feb, 2014 23:43

Smitis wrote:Кто-нибудь делал на Lua наследование классов?
http://lua-users.org/wiki/ObjectOrientedProgramming

User avatar
HaRT
Moderator
Posts: 8309
Joined: Tue 30 Aug, 2005 17:21
Location: Санкт-Петербург
Has thanked: 72 times
Been thanked: 121 times

Re: DialogClass — модуль для работы с диалогами с стиле ООП

Post by HaRT » Sat 01 Mar, 2014 19:02

Smitis wrote:Также не нравится название метода Open. Open - это открыть. А тут открыть, потом закрыть... Может назвать его Call? В FarLua он far.Dialog. Но DialogClass:Dialog выгладит как-то масло-масляное.
В некоторых библиотеках такое называется Exec, притом давным-давно.
Фар есть инструмент, а не нянька. © 2009 DrKnS

Smitis
Posts: 1528
Joined: Fri 18 Mar, 2005 12:51
Location: Питер
Has thanked: 15 times
Been thanked: 27 times
Contact:

Re: DialogClass — модуль для работы с диалогами с стиле ООП

Post by Smitis » Sun 02 Mar, 2014 12:36

Если бы было так просто. Если один класс, то да. А если несколько взаимосвязанных? Например, если создам новый DlgItemClassNew, наследник от старого, то и конструктор надо переделывать и DlgItemsClass новый...
Разум когда-нибудь победит.

Smitis
Posts: 1528
Joined: Fri 18 Mar, 2005 12:51
Location: Питер
Has thanked: 15 times
Been thanked: 27 times
Contact:

Re: DialogClass — модуль для работы с диалогами с стиле ООП

Post by Smitis » Sun 02 Mar, 2014 19:55

v.1.00
Open перименован в Exec.
Плюс мелкие исправления.
В целом, всё рабочее.
Разум когда-нибудь победит.

Smitis
Posts: 1528
Joined: Fri 18 Mar, 2005 12:51
Location: Питер
Has thanked: 15 times
Been thanked: 27 times
Contact:

Re: DialogClass — модуль для работы с диалогами с стиле ООП

Post by Smitis » Fri 07 Mar, 2014 10:11

v.1.01
- Вместо таблицы Class, вызов модуля возвращает сами таблицы DialogClass, DlgItemClass и DlgItemsClass.
- Создание элемента диалога перенесено в метод AddItem, который теперь играет роль конструктора.
- AddItem теперь возвращает саму таблицу item.
- Хранение ссылки на DlgItemClass в самом объекте было не самым лучшим решением. Удалено.
Все изменения не изменяют основного функционала и предназначены, в основном, для лучшей возможности организации наследования классов в других модулях (пример в шапке).
Разум когда-нибудь победит.

Smitis
Posts: 1528
Joined: Fri 18 Mar, 2005 12:51
Location: Питер
Has thanked: 15 times
Been thanked: 27 times
Contact:

Re: DialogClass — модуль для работы с диалогами с стиле ООП

Post by Smitis » Fri 07 Mar, 2014 11:30

Нда... Если не хранить ссылку на подчинённый DlgItemClass, то приходится перегружать AddItem. Поэтому потом опять сделаю хранение ссылки, только не с объектом, а в метатаблице. Тогда в производном классе можно будет как AddItem перегрузить, так и просто задать новый подчинённый класс в виде DialogClassNew.DlgItemClass = DlgItemClassNew и не трогать AddItem.
Разум когда-нибудь победит.

Smitis
Posts: 1528
Joined: Fri 18 Mar, 2005 12:51
Location: Питер
Has thanked: 15 times
Been thanked: 27 times
Contact:

Re: DialogClass — модуль для работы с диалогами с стиле ООП

Post by Smitis » Tue 18 Mar, 2014 10:48

v.1.02
В основном, некоторая внутренняя реорганизация без изменения функционала. Например, весь код добавления элемента диалога возвращён обратно в DlgItemsClass ибо незачем с, по сути, приватными полями работать извне класса.
- Объект DialogClass не содержит больше копию таблицы Opt.
- Подкорректирован пример наследования.
Разум когда-нибудь победит.

Smitis
Posts: 1528
Joined: Fri 18 Mar, 2005 12:51
Location: Питер
Has thanked: 15 times
Been thanked: 27 times
Contact:

Re: DialogClass — модуль для работы с диалогами с стиле ООП

Post by Smitis » Thu 08 May, 2014 09:16

v.1.03
- Исправлен I:GetListPos
- Добавлен метод I:Unchanged - управление состоянием "неизменённый" элемента диалога.
- Методы I:Show, I:Enable при отсутствии параметра теперь возвращают текущее состояние, не меняя его.
- I:Focus теперь возвращает логическое значение.
Разум когда-нибудь победит.

Smitis
Posts: 1528
Joined: Fri 18 Mar, 2005 12:51
Location: Питер
Has thanked: 15 times
Been thanked: 27 times
Contact:

Re: DialogClass — модуль для работы с диалогами с стиле ООП

Post by Smitis » Fri 06 Jun, 2014 14:18

v1.05
Если ВДРУГ кто этим пользуется - предыдущее поведение ломается. Может, даже стоило сделать DialogClass2...

Изменения для DlgItemClass и DlgItemsClass:
- Все флаги, заданные строкой или таблицей, преобразуются в number.
- Новый синоним для поля Mask - Separator.
- Возможность задавать элемент диалога таблицей с именованными полями в виде {Type="DI_TEXT",X1=5,Text="QWERTY"}.
- Недостающие поля по умолчанию заполняются значениями Type=far.Flags.DI_TEXT, X1=-1, X2=0, Y1=D.Line (внутренний счётчик строк объекта DialogClass), Y2=Y1, Selected=0, Flags=0.
- Два поля YN и YS (соответствующие синонимы NL, NewLine и SL, SetLine) служат для управления Y1 с использованием внутреннего счётчика строк (D.Line) и имеют тот же смысл, что и методы SetLine и NewLine диалога. Эти поля удаляются после добавления элемента в D.Items.
- Если поле History не пустая строка, автоматически выставляется флаг DIF_HISTORY.
- Если поле Mask (Separator) не пустая строка, в зависимости от типа элемента автоматически выставляется флаг DIF_MASKEDIT или DIF_SEPARATORUSER.
- Если не задан Type элемента диалога, принимается DI_TEXT.
- Добавлено упрощенное задание типа элементов и некоторых флагов - если тип элемента диалога строка не начинающаяся с "DI_", то первый символ рассматривается как тип элемента, а остальные как уточнения типа элемента или флаги. Заданные таким образом флаги объединяюся по OR с остальными флагами, заданными через Flags.

Изменения для DialogClass:
- Изменение синтаксиса New, Init, Exec и SetProperties. Если первый параметр - таблица, остальные параметры игнорируются. Иначе - все параметры обычный список параметров диалога. Таблица используется для задания параметров диалога. Opt больше не используется, получается на один параметр меньше, что ломает предыдущее поведение.
Пример:
D:Init { Guid=DLG_ID, X2=20, Y2=30, Flags="FDLG_WARNING" }.
Недостающие параметры принимаются: X1=-1, Y1=-1.
- Opt.Refresh -> свойство Refreshed диалога.
- AddItem теперь принимает только один параметр - таблицу с параметрами элемента диалога.
- Функциональный вызов диалога D() то же, что и применение метода D:Exec.
- Функциональный вызов D.Items() то же, что и применение метода D:AddItem.

Исправлен пример наследования.
Разум когда-нибудь победит.

Post Reply

Return to “Обсуждение макросов”