Create Gui dynamically - resize Gui according to Label Position

5 次查看(过去 30 天)
Hello,
my usecase is as follows:
I want to process a certain amount of data in a table. The data contains a certain id, that should be unique, but isn't yet. So I run a loop over those unique ids (obtained via unique function) and look for all the entries containing this id. Let's assume, id=abc123 has 3 entries in the data table. The user should be able to select which one of those entries is the correct one. The others will be deleted. So in the end, there will be a data table with unique ids.
In order to do that, I want to create a Gui from code dynamically, based on the number of entries. The Gui will display some information about the entries which will help the user to make their decision. All this information is provided via dynamically created labels. At the end, I create a dropdown and a button so that the user can select the entry he wants to continue with.
Creating the Gui and all the components within it works fine. I use the grid method to arrange the components.
My problem, however, is resizing of the Gui after creating it. My last resort would be to give the Gui a fixed width and height, that would work, but I want to resize it dynamically depending on the components' positions. After creating all the compontents, I would take the position of the last label on the left side and the positions of the first label on the top and the button at the bottom and calculate width and height of the Gui. This works perfectly when I'm in debugging mode but once I run the code, the Gui won't resize properly.
I already found the reason. It's because compared to debugging mode, the components don't "get enough time" to update their position properly. It works kind of but still not very reliable when I pause the code for a fraction of a second after creating each label. It works fine when I have only two entries, but not for ten. And it's not very nice code, I guess.
So, what I would need is the possibility to "update" the Gui, make all the components arrange themselves, getting their correct positions and then I could calculate width and height. Is there such a command? Or could you think of a better way?
Here's an example:
data = array2table(["object 1" "abc123" "30" "45";
"object 1a" "abc123" "35" "50";
"object 2" "xyz234" "25" "40";
"object 3" "mno678" "32" "53";
"object 4" "pqr789" "62" "75";
"object 4a" "pqr789" "60" "73";
"object 4b" "pqr789" "64" "76";
"object 5" "pqr789" "30" "80";
"object 6" "abc123" "32" "53";
"object 7" "pqr789" "40" "70";
"object 8" "pqr789" "41" "71";
"object 9" "pqr789" "42" "72"], "VariableNames",{'entry' 'id' 'number' 'weight'});
% get unique ids
dataUq = unique(data.id);
% loop over unique ids
idxKeep = []; % row index of entries that will be kept
for j = 1:length(dataUq)
idx = find(strcmp(dataUq(j),data.id)); % find all entry for unique id
if length(idx)>1 % multiple entries found
% the right entry has to be selected by user
idxKeep = [idxKeep; createGui(data,idx)];
else
idxKeep = [idxKeep; idx]; % only one entry found, everything ok
end
end
dataKeep = data(idxKeep,:); % table with unique entries
function idxKeep = createGui(data,idx)
% Create figure window
screen = get(0,'screensize');
screenWidth = screen(3);
screenHeight = screen(4);
fig = uifigure;
fig.Name = "Please choose entry";
fig.WindowStyle = "alwaysontop";
fig.Position = [100 100 100 100];
% column header and respective table variable name
labels = {'Entry Name:' 'entry'
'Quantity:' 'number'
'Weight in kg:' 'weight'};
% Manage app layout
colmax = size(labels,1)+1;
rowmax = length(idx)+4;
gl = uigridlayout(fig,[rowmax colmax]); % Grid layout
gl.RowHeight = repmat({'fit'},1,rowmax); % All columns and rows as 'fit'
gl.ColumnWidth = repmat({'fit'},1,colmax);
row = 1;
col = 1;
% Create Labels
lblId = createLbl(gl,row,col,"Id:",'bold');
createLbl(gl,row,[col+1 colmax],data.id(idx(1)),'bold');
createLbl(gl,row+1,[col colmax],"Available entries to choose from:",'bold');
dd = uidropdown(gl);
dd.Layout.Column = [col col+1];
dd.Layout.Row = rowmax;
dd.Items = "please select";
% Loop over numbers of entries + 1 (Header row)
for i=1:length(idx)+1
if i==length(idx)+1
%createLbl(gl,row+2,col,"Nr:",'bold'); % Alternative, needs fixed Gui width and height
lbls.(['lbl' num2str(row+2) '_' num2str(col)]) = createLbl(gl,row+2,col,"Nr:",'bold');
for j=1:size(labels,1)
%createLbl(gl,row+2,col+j,labels{j,1},'bold'); % Alternative, needs fixed Gui width and height
lbls.(['lbl' num2str(row+2) '_' num2str(col+j)]) = createLbl(gl,row+2,col+j,labels{j,1},'bold');
end
clear j
else
%createLbl(gl,row+2+i,col,['Entry ' num2str(i)],'bold'); % Alternative, needs fixed Gui width and height
lbls.(['lbl' num2str(row+2+i) '_' num2str(col)]) = createLbl(gl,row+2+i,col,['Entry ' num2str(i)],'bold');
dd.Items = [dd.Items ['Entry ' num2str(i)]];
for j=1:size(labels,1)
%createLbl(gl,row+2+i,col+j,data.(labels{j,2})(idx(i)),'normal'); % Alternative, needs fixed Gui width and height
lbls.(['lbl' num2str(row+2+i) '_' num2str(col+j)]) = createLbl(gl,row+2+i,col+j,data.(labels{j,2})(idx(i)),'normal');
end
clear j
end
end
clear i
pause(1/5)
% Create UI components
button = uibutton(gl, "Text","save entry", "ButtonPushedFcn", @(src,event) objButtonPushed(fig,dd,idx));
button.Layout.Row = rowmax;
button.Layout.Column = [col+2];
% Positioning an resizing Gui
%guiWidth = 400; % always works, last resort
%guiHeight = 200; % always works, last resort
guiWidth = lbls.(['lbl' num2str(rowmax-1) '_' num2str(colmax)]).Position(1) + lbls.(['lbl' num2str(rowmax-1) '_' num2str(colmax)]).Position(3) + 50; % Width = last label's x-Position + last label's width + 50
guiHeight = lblId.Position(2) + lblId.Position(4) - dd.Position(2) + 20; % Height = first label's y-Position + first label's height - dropdown's y-Position + 20
guiX = screenWidth/2 - guiWidth/2;
guiY = screenHeight/2 - guiHeight/2;
fig.Position = [guiX guiY guiWidth guiHeight];
waitfor(fig, 'UserData');
idxKeep = fig.UserData;
close(fig)
end
% Program app behavior
function objButtonPushed(figVeh,dd,idx)
if strcmp("please select",dd.Value)
return
end
id = str2double(regexprep(dd.Value,"Entry ",""));
set(figVeh,'UserData',idx(id));
end
function varargout = createLbl(gui,row,col,text,weight)
lbl = uilabel(gui);
lbl.Layout.Row = row;
lbl.Layout.Column = col;
lbl.Text = text;
lbl.WordWrap = "on";
lbl.FontWeight = weight;
pause(1/4) % pause to make sure, label is created properly
varargout{1} = lbl;
end
I chose 1/4 sec in createLbl so that the difference can be seen.
If you comment out that pause or if the pause is not long enough, it doesn't work at all. It would look like this:
Resizing the Gui manually always works. The labels are positioned perfectly but it doesn't show in their position coordinates. With pause I get the Gui I want, at least in this case. But it depends on the amount of data if it works or not.
I'm currently using Matlab 2022b but would consider upgrading if newer versions had the functionality I need.
Thank you
Laura

采纳的回答

Voss
Voss 2024-3-21
Here's a programmatic GUI using a uitable that may work for you:
data = array2table(["object 1" "abc123" "30" "45";
"object 1a" "abc123" "35" "50";
"object 2" "xyz234" "25" "40";
"object 3" "mno678" "32" "53";
"object 4" "pqr789" "62" "75";
"object 4a" "pqr789" "60" "73";
"object 4b" "pqr789" "64" "76";
"object 5" "pqr789" "30" "80";
"object 6" "abc123" "32" "53";
"object 7" "pqr789" "40" "70";
"object 8" "pqr789" "41" "71";
"object 9" "pqr789" "42" "72"], "VariableNames",{'entry' 'id' 'number' 'weight'});
% get unique ids
dataUq = unique(data.id);
% loop over unique ids
idxKeep = []; % row index of entries that will be kept
for j = 1:length(dataUq)
idx = find(strcmp(dataUq(j),data.id)); % find all entry for unique id
if length(idx)>1 % multiple entries found
% the right entry has to be selected by user
idxKeep = [idxKeep; createGui(data,idx)];
else
idxKeep = [idxKeep; idx]; % only one entry found, everything ok
end
end
dataKeep = data(idxKeep,:); % table with unique entries
function idxKeep = createGui(data,idx)
% Create figure window
fig = uifigure( ...
'Visible','off', ...
'AutoResizeChildren','off', ...
'Name',"Please choose entry", ...
'WindowStyle',"alwaysontop");
% column header and respective table variable name
labels = {'Entry Name:' 'entry'
'Quantity:' 'number'
'Weight in kg:' 'weight'};
% Create UI components
lblId = uilabel(fig, ...
'Text',sprintf("Id: %s",data.id(idx(1))), ...
'FontWeight','bold');
lblInstr = uilabel(fig, ...
'Text',"Available entries to choose from:", ...
'FontWeight','bold');
Nidx = numel(idx);
names = "Entry "+(1:Nidx);
[~,cols] = ismember(labels(:,2),data.Properties.VariableNames);
t = uitable(fig, ...
'ColumnName',labels(:,1), ...
'ColumnWidth',{90 80 90}, ...
'RowName',names, ...
'Data',data(idx,cols));
dd = uidropdown(fig, ...
'Items',["please select" names], ...
'Position',[11 9 125 22]);
uibutton(fig, ...
"Text","save entry", ...
"ButtonPushedFcn", @objButtonPushed, ...
'Position',[146 9 65 22]);
% Positioning and resizing Gui
screen = get(0,'screensize');
guiWH = [332 Nidx*22+129];
guiXY = screen([3 4])/2 - guiWH/2;
fig.Position = [guiXY guiWH];
fig.SizeChangedFcn = @scf_fig;
fig.Visible = 'on';
idxKeep = [];
uiwait(fig);
function scf_fig(~,~)
fpos = fig.Position;
t.Position = [11 40 max(0,fpos([3 4])-[22 100])];
lblId.Position = [11 fpos(4)-30 max(0,fpos(3)-22) 20];
lblInstr.Position = [11 fpos(4)-60 max(0,fpos(3)-22) 20];
end
% Program app behavior
function objButtonPushed(~,~)
if strcmp("please select",dd.Value)
return
end
id = str2double(regexprep(dd.Value,"Entry ",""));
idxKeep = idx(id);
close(fig);
end
end
  4 个评论

请先登录,再进行评论。

更多回答(1 个)

Taylor
Taylor 2024-3-21
Have you considered developing this in App Designer instead of the script based approach you're currently using? With App Designer you can take advantage of auto-reflow to ensure optimal componet layout regardless of how the window has been sized. I also just think working in App Designer is much more intuitive in general.
  2 个评论
Laura V.
Laura V. 2024-3-21
Yes, using App Designer crossed my mind. I didn't know about auto-reflow, though, I'll check it out, thanks!
So would it be possible to create the app in App Designer with placeholders and then load it as uifigure into the code to fill the placeholders dynamically and create further rows for all entries? I could prepare everyting that is static like the column header but I would have to fill the entries dynamically, as well as the Dropdown and Buttons at the end.
Taylor
Taylor 2024-3-21
Apps created in App Designer are UIFigures themselves. Based on the layout of your app currently, you would probably want to utilize a table component (equivalent to uitable) to display your "entries". The table will create a scrollbar when it is filled with data beyond its visual capacity. I've attached a simple app to showcase this behavior.

请先登录,再进行评论。

类别

Help CenterFile Exchange 中查找有关 Develop uifigure-Based Apps 的更多信息

产品


版本

R2022b

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!

Translated by