在我自己的一些GUI中,我创建了许多控件(使用uicontrol
),以允许用户配置在后续处理阶段使用的过滤器。
过滤器版本包含一个组合框,用于选择过滤器类型,以及许多根据所选过滤器类型更新的编辑框和许多回调以响应用户输入。
我现在需要在另一个GUI中添加这个过滤器选择,当然,我不想复制粘贴我已经完成的所有逻辑,而是希望创建一些我可以轻松重用的自定义控件为:
filterEditor = uifilter('Parent', gcf);
set(filterEditor, 'FilterDescription', 'Cylinder (r = 45 cm, h = 1 m)');
set(filterEditor, 'Callback', @onFilterEditModified);
是否有标准程序来创建自定义“uicontrol
”对象?我搜索了互联网和matlab的文档,但没有找到任何好的指针......
目前我正在考虑创建源自hgsetget
的自定义类:
classdef uifilter < hgsetget
properties
% Local properties
FilterDescription;
Callback;
end
properties(SetAccess=private, GetAccess=private)
% Internal controls
globalContainer;
comboFilterType;
edit1;
end
methods
function [this] = uifilter(varargin)
% Create a global `uicontainer` to hold my controls
[localPVpairs, genericPVpairs] = separatePVpairs(varargin{:});
this.container = uicontainer(genericPVpairs{:});
% Create my own controls and logic
this.comboFilterType = uicontrol('Parent', this.container, ...);
this.edit1 = ...
end
end
end
为了模仿uicontrol
行为(set
,get
,findobj
等...)但也许有更多标准方法或除{之外的某些基类{3}}从一开始(即一些基类Visible
,Enable
,HitTest
等...已经使用默认实现进行了定义)?
答案 0 :(得分:2)
我认为这是正确的做法。
要正确执行此操作,您可能需要为每个set
属性实施自己的get
和uicontrol
方法。这些set
和get
方法通常只会在底层uicontrol
之间传递值。您可以在没有在初稿中实现一些不太常用的属性(例如FontAngle
)的情况下离开,在必要时添加它们并在此之前使用uicontrol
默认值。
在某些情况下,他们需要做更多事情,当你为set
财产实施Parent
之类的事情时,你需要谨慎一些(可能需要它)销毁原始的uicontrol并为新的父级创建一个新的uicontrol。在为set
和Position
属性实施Units
时,您还需要谨慎行事 - 对于他们以相当复杂的方式进行交互的普通uicontrol
,我认为结果有时可能取决于首先设定的结果。
我还建议,对于内部属性,以及将它们设置为private
,您也可以将它们设置为Hidden
,以防止用户试图干扰它们。
最后一点 - 我认为,从您的其他一些问题来看,您正在使用GUI Layout Toolbox。我没有想太多,但你可能需要提前考虑是否需要采取任何特殊措施来实现这一目标。
答案 1 :(得分:0)
回到这个问题,一个非常简单的方法(相当于定义一个继承自hgsetget
或可能某个uicontrolbase
类的自定义类,以使Enable
具有默认行为, Position
等等......)是在GUI Layout toolbox中创建一个继承自uiextras.Container
的类。
事实上,这个课程完全等同于拥有uicontrolbase
课程的想法。它公开了一个受保护的UIContainer
属性,它是放置所有子元素的面板,因此很容易从中构建可重用的复合组件:
classdef uimyfilter < uiextras.Container
%% --- Exposed properties
% NB: Can be accessed with set/get routines
properties(Dependent, Transient)
FilterDescription;
Callback;
end
methods
{% ... own custom set/get logic for exposed properties ... %}
end
%% --- Lifetime
methods
function [this] = uimyfilter(varargin)
% Consume or init local properties from varargin list
[c, otherPvPairs] = uimyfilter.extractOrInitPvPairs(varargin, { ...
'FilterDescription', @()'Cylinder (r = 10 cm, h = 42 cm)'; ...
'Callback', @()[]; ...
});
% Call superclass with other pv pairs
this@uiextras.Container(otherPvPairs{:});
% Build interface
grid = uiextras.Grid('Parent', this.UIContainer, 'Spacing', 5, 'Padding', 5);
c.handles.cbFilterType = uicontrol('Parent', grid, 'Style', 'Popup', 'String', { 'Cylinder', 'Sphere' }, 'Callback', @(s,e)onFilterTypeChanged(this,s,e));
uiextras.Empty('Parent', grid);
c.handles.cardFilterParams = uiextras.CardPanel('Parent', grid);
uiextras.Empty('Parent', grid);
set(grid, 'ColumnSizes', [90, -1]);
set(grid, 'RowSizes', [23, -1]);
uicontrol('Parent', c.handles.cardFilterParams, 'Style', 'Text', 'String', '... todo: params for cylinder ...', 'BackgroundColor', 'r');
uicontrol('Parent', c.handles.cardFilterParams, 'Style', 'Text', 'String', '... todo: params for sphere ...', 'BackgroundColor', 'r');
% Store local properties and handles for later calls
this.state = c;
% Init Gui
this.refresh();
end
end
%% --- Internal logic
methods(Access=private)
function [] = refresh(this)
set(this.state.handles.cardFilterParams, 'SelectedChild', get(this.state.handles.cbFilterType, 'Value'));
end
function [] = onFilterTypeChanged(this, s, e) %#ok
this.refresh();
if (~isempty(this.state.Callback)),
this.state.Callback(this);
end
end
end
methods(Access = protected)
function [] = redraw(this) %#ok
end
end
properties(GetAccess=private, SetAccess=private)
state;
end
%% --- Helpers
methods(Static, Access=protected)
function [c, otherPvPairs] = extractOrInitPvPairs(pvPairs, consumeDescriptor)
% Check arguments
if (nargin < 2),
error('Not enough input arguments.');
end
if (~isempty(consumeDescriptor) && ...
(~iscell(consumeDescriptor) || ~ismatrix(consumeDescriptor) || ...
~iscellstr(consumeDescriptor(:, 1)) || ~all(cell2mat(cellfun(@(x)isa(x, 'function_handle'), consumeDescriptor(:,2), 'UniformOutput', false)))))
error('Invalid descriptor for properties to consume.');
end
if (~iscell(pvPairs) || (~isvector(pvPairs) && ~isempty(pvPairs)) || (length(pvPairs(1:2:end)) ~= length(pvPairs(2:2:end))) || ~iscellstr(pvPairs(1:2:end)))
error('Invalid list or property names/values pairs.');
end
% Consume local properties
c = struct();
otherNames = pvPairs(1:2:end);
otherValues = pvPairs(2:2:end);
for ki = 1:size(consumeDescriptor, 1),
pname = consumeDescriptor{ki,1};
pinit = consumeDescriptor{ki,2};
idx = strcmpi(otherNames, pname);
if (isempty(idx)),
c.(pname) = pinit();
elseif (isscalar(idx)),
c.(pname) = otherValues{idx};
otherNames(idx) = []; otherValues(idx) = [];
else
error('Property `%s` appears more than once.', pname);
end
end
% Recompose other pv
otherPvPairs = cell(1, 2*length(otherNames));
otherPvPairs(1:2:end) = otherNames(:);
otherPvPairs(2:2:end) = otherValues(:);
end
end
end
暴露属性和内部逻辑当然完全依赖于复合组件,构建界面就像向uicontrol
添加uiextras.(...)
或this.UIContainer
个对象一样简单。
PS:对于R2014b及更高版本,您必须继承GUI Layout toolbox for HG2中的uix.Container
,无论如何,这个想法是相似的。