创建自定义和可重用的用户界面控件

时间:2015-05-06 14:50:46

标签: matlab user-controls

在我自己的一些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行为(setgetfindobj等...)但也许有更多标准方法或除{之外的某些基类{3}}从一开始(即一些基类VisibleEnableHitTest等...已经使用默认实现进行了定义)?

2 个答案:

答案 0 :(得分:2)

我认为这是正确的做法。

要正确执行此操作,您可能需要为每个set属性实施自己的getuicontrol方法。这些setget方法通常只会在底层uicontrol之间传递值。您可以在没有在初稿中实现一些不太常用的属性(例如FontAngle)的情况下离开,在必要时添加它们并在此之前使用uicontrol默认值。

在某些情况下,他们需要做更多事情,当你为set财产实施Parent之类的事情时,你需要谨慎一些(可能需要它)销毁原始的uicontrol并为新的父级创建一个新的uicontrol。在为setPosition属性实施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,无论如何,这个想法是相似的。