diff --git a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/-6uET5ueEMV8-JHVE8F8gxpEZdgd.xml b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/-6uET5ueEMV8-JHVE8F8gxpEZdgd.xml
new file mode 100644
index 00000000..2cd2598b
--- /dev/null
+++ b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/-6uET5ueEMV8-JHVE8F8gxpEZdgd.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/-6uET5ueEMV8-JHVE8F8gxpEZdgp.xml b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/-6uET5ueEMV8-JHVE8F8gxpEZdgp.xml
new file mode 100644
index 00000000..7f3d7f74
--- /dev/null
+++ b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/-6uET5ueEMV8-JHVE8F8gxpEZdgp.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/GlNzMPyGOM0BrQS1yt1y4cFWeUUd.xml b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/GlNzMPyGOM0BrQS1yt1y4cFWeUUd.xml
index 440028b3..ccd7bbfd 100644
--- a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/GlNzMPyGOM0BrQS1yt1y4cFWeUUd.xml
+++ b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/GlNzMPyGOM0BrQS1yt1y4cFWeUUd.xml
@@ -1,2 +1,2 @@
-
-
\ No newline at end of file
+
+
\ No newline at end of file
diff --git a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/GlNzMPyGOM0BrQS1yt1y4cFWeUUp.xml b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/GlNzMPyGOM0BrQS1yt1y4cFWeUUp.xml
index 72ba7863..8bc49bd6 100644
--- a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/GlNzMPyGOM0BrQS1yt1y4cFWeUUp.xml
+++ b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/GlNzMPyGOM0BrQS1yt1y4cFWeUUp.xml
@@ -1,2 +1,2 @@
-
-
\ No newline at end of file
+
+
\ No newline at end of file
diff --git a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/MCNyQ04ZNyCaVv14MUbnfNGfpPQd.xml b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/MCNyQ04ZNyCaVv14MUbnfNGfpPQd.xml
index b59fde57..bc6b4700 100644
--- a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/MCNyQ04ZNyCaVv14MUbnfNGfpPQd.xml
+++ b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/MCNyQ04ZNyCaVv14MUbnfNGfpPQd.xml
@@ -1,2 +1,2 @@
-
-
\ No newline at end of file
+
+
\ No newline at end of file
diff --git a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/MCNyQ04ZNyCaVv14MUbnfNGfpPQp.xml b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/MCNyQ04ZNyCaVv14MUbnfNGfpPQp.xml
index 86e9ac14..4384f39f 100644
--- a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/MCNyQ04ZNyCaVv14MUbnfNGfpPQp.xml
+++ b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/MCNyQ04ZNyCaVv14MUbnfNGfpPQp.xml
@@ -1,2 +1,2 @@
-
-
\ No newline at end of file
+
+
\ No newline at end of file
diff --git a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/sGQWKvrvt2ruQjk-XJfB0Jza4dUd.xml b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/sGQWKvrvt2ruQjk-XJfB0Jza4dUd.xml
index 8320130b..cccbdb0f 100644
--- a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/sGQWKvrvt2ruQjk-XJfB0Jza4dUd.xml
+++ b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/sGQWKvrvt2ruQjk-XJfB0Jza4dUd.xml
@@ -1,2 +1,2 @@
-
-
\ No newline at end of file
+
+
\ No newline at end of file
diff --git a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/sGQWKvrvt2ruQjk-XJfB0Jza4dUp.xml b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/sGQWKvrvt2ruQjk-XJfB0Jza4dUp.xml
index 75750740..b7d6c319 100644
--- a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/sGQWKvrvt2ruQjk-XJfB0Jza4dUp.xml
+++ b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/sGQWKvrvt2ruQjk-XJfB0Jza4dUp.xml
@@ -1,2 +1,2 @@
-
-
\ No newline at end of file
+
+
\ No newline at end of file
diff --git a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/tCUNwVQSieE2RoENiCkr-MV2apcd.xml b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/tCUNwVQSieE2RoENiCkr-MV2apcd.xml
index 407f4985..1a649464 100644
--- a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/tCUNwVQSieE2RoENiCkr-MV2apcd.xml
+++ b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/tCUNwVQSieE2RoENiCkr-MV2apcd.xml
@@ -1,2 +1,2 @@
-
-
\ No newline at end of file
+
+
\ No newline at end of file
diff --git a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/tCUNwVQSieE2RoENiCkr-MV2apcp.xml b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/tCUNwVQSieE2RoENiCkr-MV2apcp.xml
index e1d45355..d604a5b8 100644
--- a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/tCUNwVQSieE2RoENiCkr-MV2apcp.xml
+++ b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/tCUNwVQSieE2RoENiCkr-MV2apcp.xml
@@ -1,2 +1,2 @@
-
-
\ No newline at end of file
+
+
\ No newline at end of file
diff --git a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/uDx_v6ZvW-Br8iKhWiFaxE8NSJ0d.xml b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/uDx_v6ZvW-Br8iKhWiFaxE8NSJ0d.xml
index 560c6c85..bc4a0b70 100644
--- a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/uDx_v6ZvW-Br8iKhWiFaxE8NSJ0d.xml
+++ b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/uDx_v6ZvW-Br8iKhWiFaxE8NSJ0d.xml
@@ -1,2 +1,2 @@
-
-
\ No newline at end of file
+
+
\ No newline at end of file
diff --git a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/uDx_v6ZvW-Br8iKhWiFaxE8NSJ0p.xml b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/uDx_v6ZvW-Br8iKhWiFaxE8NSJ0p.xml
index 3f7392c0..7ddd1494 100644
--- a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/uDx_v6ZvW-Br8iKhWiFaxE8NSJ0p.xml
+++ b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/uDx_v6ZvW-Br8iKhWiFaxE8NSJ0p.xml
@@ -1,2 +1,2 @@
-
-
\ No newline at end of file
+
+
\ No newline at end of file
diff --git a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/wLsNKNXO4UtoK-s3CTap0Umd4TUd.xml b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/wLsNKNXO4UtoK-s3CTap0Umd4TUd.xml
index bf03fbf8..b735f78e 100644
--- a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/wLsNKNXO4UtoK-s3CTap0Umd4TUd.xml
+++ b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/wLsNKNXO4UtoK-s3CTap0Umd4TUd.xml
@@ -1,2 +1,2 @@
-
-
\ No newline at end of file
+
+
\ No newline at end of file
diff --git a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/wLsNKNXO4UtoK-s3CTap0Umd4TUp.xml b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/wLsNKNXO4UtoK-s3CTap0Umd4TUp.xml
index 663bed51..d29a2560 100644
--- a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/wLsNKNXO4UtoK-s3CTap0Umd4TUp.xml
+++ b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/wLsNKNXO4UtoK-s3CTap0Umd4TUp.xml
@@ -1,2 +1,2 @@
-
-
\ No newline at end of file
+
+
\ No newline at end of file
diff --git a/resources/project/Xc4-tOl6vkwzCeVKSWRahPrXJiQ/51VqhzOOH8Yqqc2oa1CzaYTZP14d.xml b/resources/project/Xc4-tOl6vkwzCeVKSWRahPrXJiQ/51VqhzOOH8Yqqc2oa1CzaYTZP14d.xml
new file mode 100644
index 00000000..378b6137
--- /dev/null
+++ b/resources/project/Xc4-tOl6vkwzCeVKSWRahPrXJiQ/51VqhzOOH8Yqqc2oa1CzaYTZP14d.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/resources/project/Xc4-tOl6vkwzCeVKSWRahPrXJiQ/51VqhzOOH8Yqqc2oa1CzaYTZP14p.xml b/resources/project/Xc4-tOl6vkwzCeVKSWRahPrXJiQ/51VqhzOOH8Yqqc2oa1CzaYTZP14p.xml
new file mode 100644
index 00000000..ff323a3f
--- /dev/null
+++ b/resources/project/Xc4-tOl6vkwzCeVKSWRahPrXJiQ/51VqhzOOH8Yqqc2oa1CzaYTZP14p.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/resources/project/f1CasPtPWEKkULO6lQ_WTjau2Nw/f2kdi_ZH-kkLCJnHzBMki3bnqgod.xml b/resources/project/f1CasPtPWEKkULO6lQ_WTjau2Nw/f2kdi_ZH-kkLCJnHzBMki3bnqgod.xml
new file mode 100644
index 00000000..4356a6ae
--- /dev/null
+++ b/resources/project/f1CasPtPWEKkULO6lQ_WTjau2Nw/f2kdi_ZH-kkLCJnHzBMki3bnqgod.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/resources/project/f1CasPtPWEKkULO6lQ_WTjau2Nw/f2kdi_ZH-kkLCJnHzBMki3bnqgop.xml b/resources/project/f1CasPtPWEKkULO6lQ_WTjau2Nw/f2kdi_ZH-kkLCJnHzBMki3bnqgop.xml
new file mode 100644
index 00000000..511fef92
--- /dev/null
+++ b/resources/project/f1CasPtPWEKkULO6lQ_WTjau2Nw/f2kdi_ZH-kkLCJnHzBMki3bnqgop.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/resources/project/f2kdi_ZH-kkLCJnHzBMki3bnqgo/FVJBsRjCJsxU-SL7Pp8kYCsHV2wd.xml b/resources/project/f2kdi_ZH-kkLCJnHzBMki3bnqgo/FVJBsRjCJsxU-SL7Pp8kYCsHV2wd.xml
new file mode 100644
index 00000000..4356a6ae
--- /dev/null
+++ b/resources/project/f2kdi_ZH-kkLCJnHzBMki3bnqgo/FVJBsRjCJsxU-SL7Pp8kYCsHV2wd.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/resources/project/f2kdi_ZH-kkLCJnHzBMki3bnqgo/FVJBsRjCJsxU-SL7Pp8kYCsHV2wp.xml b/resources/project/f2kdi_ZH-kkLCJnHzBMki3bnqgo/FVJBsRjCJsxU-SL7Pp8kYCsHV2wp.xml
new file mode 100644
index 00000000..01cb34e6
--- /dev/null
+++ b/resources/project/f2kdi_ZH-kkLCJnHzBMki3bnqgo/FVJBsRjCJsxU-SL7Pp8kYCsHV2wp.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/resources/project/f2kdi_ZH-kkLCJnHzBMki3bnqgo/NdScGNwbGcD5YerOttsmPZeexiod.xml b/resources/project/f2kdi_ZH-kkLCJnHzBMki3bnqgo/NdScGNwbGcD5YerOttsmPZeexiod.xml
new file mode 100644
index 00000000..99772b42
--- /dev/null
+++ b/resources/project/f2kdi_ZH-kkLCJnHzBMki3bnqgo/NdScGNwbGcD5YerOttsmPZeexiod.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/resources/project/f2kdi_ZH-kkLCJnHzBMki3bnqgo/NdScGNwbGcD5YerOttsmPZeexiop.xml b/resources/project/f2kdi_ZH-kkLCJnHzBMki3bnqgo/NdScGNwbGcD5YerOttsmPZeexiop.xml
new file mode 100644
index 00000000..21ee7e51
--- /dev/null
+++ b/resources/project/f2kdi_ZH-kkLCJnHzBMki3bnqgo/NdScGNwbGcD5YerOttsmPZeexiop.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/resources/project/f2kdi_ZH-kkLCJnHzBMki3bnqgo/bAXFH3dEyqTJSksdI1ytU-SpEb8d.xml b/resources/project/f2kdi_ZH-kkLCJnHzBMki3bnqgo/bAXFH3dEyqTJSksdI1ytU-SpEb8d.xml
new file mode 100644
index 00000000..7a6326b9
--- /dev/null
+++ b/resources/project/f2kdi_ZH-kkLCJnHzBMki3bnqgo/bAXFH3dEyqTJSksdI1ytU-SpEb8d.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/resources/project/f2kdi_ZH-kkLCJnHzBMki3bnqgo/bAXFH3dEyqTJSksdI1ytU-SpEb8p.xml b/resources/project/f2kdi_ZH-kkLCJnHzBMki3bnqgo/bAXFH3dEyqTJSksdI1ytU-SpEb8p.xml
new file mode 100644
index 00000000..518b9b5d
--- /dev/null
+++ b/resources/project/f2kdi_ZH-kkLCJnHzBMki3bnqgo/bAXFH3dEyqTJSksdI1ytU-SpEb8p.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/resources/project/f2kdi_ZH-kkLCJnHzBMki3bnqgo/zj4G2uES496vbFcI_EghE2iPm2Ed.xml b/resources/project/f2kdi_ZH-kkLCJnHzBMki3bnqgo/zj4G2uES496vbFcI_EghE2iPm2Ed.xml
new file mode 100644
index 00000000..7a6326b9
--- /dev/null
+++ b/resources/project/f2kdi_ZH-kkLCJnHzBMki3bnqgo/zj4G2uES496vbFcI_EghE2iPm2Ed.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/resources/project/f2kdi_ZH-kkLCJnHzBMki3bnqgo/zj4G2uES496vbFcI_EghE2iPm2Ep.xml b/resources/project/f2kdi_ZH-kkLCJnHzBMki3bnqgo/zj4G2uES496vbFcI_EghE2iPm2Ep.xml
new file mode 100644
index 00000000..1e84b0d5
--- /dev/null
+++ b/resources/project/f2kdi_ZH-kkLCJnHzBMki3bnqgo/zj4G2uES496vbFcI_EghE2iPm2Ep.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/test/+wt/+test/TemplateApps.m b/test/+wt/+test/TemplateApps.m
new file mode 100644
index 00000000..3f0e1c78
--- /dev/null
+++ b/test/+wt/+test/TemplateApps.m
@@ -0,0 +1,119 @@
+classdef TemplateApps < matlab.uitest.TestCase
+ % Implements unit tests for the app templates
+
+ % Copyright 2025 The MathWorks Inc.
+
+ %% Properties
+ properties
+ App
+ Figure matlab.ui.Figure
+ % TempFolder
+ end
+
+
+ %% Test Method Setup
+ methods (TestMethodSetup)
+
+ % function prepareTempFolder(testCase)
+ %
+ % % Use a temporary folder for sessions
+ % import matlab.unittest.fixtures.TemporaryFolderFixture
+ % fixture = testCase.applyFixture(TemporaryFolderFixture);
+ % testCase.TempFolder = fixture.Folder;
+ %
+ % end %function
+
+ end %methods
+
+
+ %% Test Method Teardown
+ methods (TestMethodTeardown)
+ % Teardown for each test
+
+ function deleteApp(testCase)
+
+ if isscalar(testCase.App) && isvalid(testCase.App)
+ if isscalar(testCase.App.Figure)
+ delete(testCase.App.Figure)
+ end
+ delete(testCase.App)
+ end
+
+ end %function
+
+ end %methods
+
+ %% Test methods
+ methods (Test)
+
+ function testTemplateBaseApp(testCase)
+ % Verifies behavior of the app template
+
+ % Launch the app
+ diag = "Expected app to launch without warnings.";
+ fcn = @()TemplateBaseApp();
+ testCase.App = testCase.verifyWarningFree(fcn, diag);
+
+ % Verify app and component creation
+ diag = "Expected app figure to be populated.";
+ testCase.verifyNotEmpty(testCase.App.Figure, diag)
+
+ % Verify app closes
+ diag = "Expected app to close without warnings.";
+ fcn = @()close(testCase.App);
+ testCase.verifyWarningFree(fcn, diag);
+
+ diag = "Expected app to be deleted.";
+ testCase.verifyFalse(isvalid(testCase.App), diag)
+
+ end %function
+
+
+ function testTemplateBaseSingleSessionApp(testCase)
+ % Verifies behavior of the app template
+
+ % Launch the app
+ diag = "Expected app to launch without warnings.";
+ fcn = @()TemplateBaseSingleSessionApp();
+ testCase.App = testCase.verifyWarningFree(fcn, diag);
+
+ % Verify app and component creation
+ diag = "Expected app figure to be populated.";
+ testCase.verifyNotEmpty(testCase.App.Figure, diag)
+
+ % Verify app closes
+ diag = "Expected app to close without warnings.";
+ fcn = @()close(testCase.App);
+ testCase.verifyWarningFree(fcn, diag);
+
+ diag = "Expected app to be deleted.";
+ testCase.verifyFalse(isvalid(testCase.App), diag)
+
+ end %function
+
+
+ function testTemplateBaseMultiSessionApp(testCase)
+ % Verifies behavior of the app template
+
+ % Launch the app
+ diag = "Expected app to launch without warnings.";
+ fcn = @()TemplateBaseMultiSessionApp();
+ testCase.App = testCase.verifyWarningFree(fcn, diag);
+
+ % Verify app and component creation
+ diag = "Expected app figure to be populated.";
+ testCase.verifyNotEmpty(testCase.App.Figure, diag)
+
+ % Verify app closes
+ diag = "Expected app to close without warnings.";
+ fcn = @()close(testCase.App);
+ testCase.verifyWarningFree(fcn, diag);
+
+ diag = "Expected app to be deleted.";
+ testCase.verifyFalse(isvalid(testCase.App), diag)
+
+ end %function
+
+ end %methods
+
+end %classdef
\ No newline at end of file
diff --git a/widgets/+wt/+apps/AbstractSessionApp.m b/widgets/+wt/+apps/AbstractSessionApp.m
index 6689af2c..50376bac 100644
--- a/widgets/+wt/+apps/AbstractSessionApp.m
+++ b/widgets/+wt/+apps/AbstractSessionApp.m
@@ -2,7 +2,7 @@
?wt.apps.BaseMultiSessionApp}) AbstractSessionApp < wt.apps.BaseApp
% Abstract base class for Widgets Toolbox app with 1+ sessions
-% Copyright 2024-2025 The MathWorks Inc.
+ % Copyright 2024-2025 The MathWorks Inc.
%% Abstract Public Properties
@@ -87,10 +87,6 @@
% Call superclass constructor
app@wt.apps.BaseApp(varargin{:});
- % Attach listeners to Session being set
- app.SessionSetListener = listener(app,"Session","PostSet",...
- @(~,~)app.onSessionSet_Private());
-
end %function
end %methods
@@ -99,6 +95,19 @@
%% Protected Methods
methods (Access = protected)
+ function setup_internal(app)
+ % Preform internal pre-setup necessary
+
+ % Show output if Debug is on
+ app.displayDebugText();
+
+ % Attach listeners to Session being set
+ app.SessionSetListener = listener(app,"Session","PostSet",...
+ @(~,~)app.onSessionSet_Private());
+
+ end %function
+
+
function onSessionSet(app)
% This method is called when the Session property has changed,
% such as a session being added or removed from the app. The
@@ -248,11 +257,12 @@ function saveSession_Internal(app, useSaveAs, session)
cleanupObj = onCleanup(@()delete(dlg));
% Save the session
+ % This will trigger update if path updates and/or session
+ % is no longer dirty
session.save(sessionPath);
- % Update the app in case filepath changed
+ % Update the title
app.updateTitle()
- app.update()
end %if strlength(sessionPath)
@@ -359,7 +369,7 @@ function saveSession_Internal(app, useSaveAs, session)
% Define arguments
arguments
app (1,1) wt.apps.BaseApp
- session (1,1) wt.model.BaseSession = app.Session
+ session wt.model.BaseSession = app.Session
end
% Show output if Debug is on
@@ -369,7 +379,7 @@ function saveSession_Internal(app, useSaveAs, session)
isCancelled = false;
% Don't save and return now if session is invalid or clean
- if ~isvalid(session) || ~session.Dirty
+ if ~isscalar(session) || ~isvalid(session) || ~session.Dirty
return
end
diff --git a/widgets/+wt/+apps/BaseMultiSessionApp.m b/widgets/+wt/+apps/BaseMultiSessionApp.m
index 99f4c885..a8783071 100644
--- a/widgets/+wt/+apps/BaseMultiSessionApp.m
+++ b/widgets/+wt/+apps/BaseMultiSessionApp.m
@@ -28,6 +28,12 @@
% Number of sessions loaded
NumSessions (1,1) double
+ % Names of sessions loaded
+ SessionNames (:,1) string
+
+ % Names of sessions for display, with * for unsaved session
+ SessionDisplayNames (:,1) string
+
% Currently selected session
SelectedSession wt.model.BaseSession
@@ -57,6 +63,27 @@
value = numel(app.Session);
end
+ function value = get.SessionNames(app)
+ numSessions = app.NumSessions;
+ value = repmat("",numSessions,1');
+ if numSessions > 0
+ isValidSession = isvalid(app.Session);
+ value(isValidSession) = [app.Session(isValidSession).Name];
+ end
+ end
+
+ function value = get.SessionDisplayNames(app)
+ numSessions = app.NumSessions;
+ value = repmat("",numSessions,1');
+ if numSessions > 0
+ isValidSession = isvalid(app.Session);
+ value(isValidSession) = [app.Session(isValidSession).Name];
+ isDirty = false(numSessions, 1);
+ isDirty(isValidSession) = [app.Session(isValidSession).Dirty];
+ value(isDirty) = value(isDirty) + " *";
+ end
+ end
+
function value = get.SelectedSession(app)
value = app.Session( app.SelectedSessionIndex );
end
@@ -126,11 +153,22 @@ function close(app)
% Store the session
% This also triggers app.update(), app.updateTitle()
if isempty(app.Session)
+
app.Session = session;
+
else
+
+ % Ensure a unique name
+ thisName = matlab.lang.makeUniqueStrings(...
+ session.Name, app.SessionNames);
+ session.FilePath = thisName;
+ session.Dirty = false;
+
+ % Append the session
app.Session(end+1) = session;
- % Don't select by default, but concrete app may do this
- end
+ % Don't select it by default, but concrete app may do this
+
+ end %if
end %function
@@ -271,6 +309,9 @@ function selectSession(app, session)
function setup_internal(app)
% Preform internal pre-setup necessary
+ % Call superclass method
+ app.setup_internal@wt.apps.AbstractSessionApp();
+
% Show output if Debug is on
app.displayDebugText();
diff --git a/widgets/+wt/+apps/BaseSingleSessionApp.m b/widgets/+wt/+apps/BaseSingleSessionApp.m
index acdb8075..ffdaa530 100644
--- a/widgets/+wt/+apps/BaseSingleSessionApp.m
+++ b/widgets/+wt/+apps/BaseSingleSessionApp.m
@@ -124,9 +124,11 @@ function close(app)
% Call superclass internal load method
session = app.loadSession_Internal(sessionPath);
- % Store the session
+ % If successful, store the session
% This also triggers app.update(), app.updateTitle()
- app.Session = session;
+ if isscalar(session) && isvalid(session)
+ app.Session = session;
+ end
end %function
@@ -139,6 +141,9 @@ function close(app)
function setup_internal(app)
% Preform internal pre-setup necessary
+ % Call superclass method
+ app.setup_internal@wt.apps.AbstractSessionApp();
+
% Show output if Debug is on
app.displayDebugText();
diff --git a/widgets/+wt/+model/BaseSession.m b/widgets/+wt/+model/BaseSession.m
index a59d95dd..9167cdd5 100644
--- a/widgets/+wt/+model/BaseSession.m
+++ b/widgets/+wt/+model/BaseSession.m
@@ -65,6 +65,7 @@
end
end
end
+
%% Public methods (subclass may override these)
@@ -138,28 +139,6 @@ function save(session, filePath)
end %methods
- % %% Hidden methods
- % methods (Hidden)
- %
- % function setFilePathSilently(obj, filePath)
- % % Set FilePath without triggering change notifications
- %
- % % Define arguments
- % arguments
- % obj (1,1) wt.model.BaseModel
- % filePath (1,1) string
- % end
- %
- % oldValue = obj.EnableChangeListeners;
- % obj.EnableChangeListeners = false;
- % obj.FilePath = filePath;
- % obj.EnableChangeListeners = oldValue;
- %
- % end %function
- %
- % end %methods
-
-
%% Protected methods (subclass may override these)
methods (Access = protected)
@@ -200,7 +179,7 @@ function onModelChanged(obj,evt)
end %function
end %methods
-
+
%% Accessors
methods
diff --git a/widgets/examples/templates/TemplateBaseApp.m b/widgets/examples/templates/TemplateBaseApp.m
new file mode 100644
index 00000000..8944df06
--- /dev/null
+++ b/widgets/examples/templates/TemplateBaseApp.m
@@ -0,0 +1,158 @@
+classdef TemplateBaseApp < wt.apps.BaseApp
+ % Implements a template for a BaseApp
+
+ % Copyright 2022-2025 The MathWorks Inc.
+
+
+ %% Internal Components
+ % Create properties here for each control, layout, or view component
+ % that will be placed directly into the main app window.
+ properties ( Transient, NonCopyable, SetAccess = protected )
+
+ % Once the app is created and debugged, consider also protecting
+ % get access to these properties. Leave them accessible to unit
+ % tests, however. Use this example:
+ % GetAccess = {?matlab.uitest.TestCase, ?wt.apps.BaseApp})
+
+ % Grid Layouts
+ % (BaseApp already brings "Grid", the main Grid layout in the window)
+ Tab1Grid matlab.ui.container.GridLayout
+ Tab2Grid matlab.ui.container.GridLayout
+ Panel1Grid matlab.ui.container.GridLayout
+ Panel2Grid matlab.ui.container.GridLayout
+
+ % Panels
+ Panel1 matlab.ui.container.Panel
+ Panel2 matlab.ui.container.Panel
+
+ % Tabs
+ TabGroup matlab.ui.container.TabGroup
+ Tab1 matlab.ui.container.Tab
+ Tab2 matlab.ui.container.Tab
+
+ % View Components
+ %View1 namespace.ClassName
+ %View2 namespace.ClassName
+
+ % Temporary label components
+ Panel1Text matlab.ui.control.Label
+ Panel2Text matlab.ui.control.Label
+ Tab1Text matlab.ui.control.Label
+ Tab2Text matlab.ui.control.Label
+
+ end %properties
+
+
+
+ %% Setup and Configuration of the App
+ methods ( Access = protected )
+
+ function setup(app)
+ % Runs once on instantiation of the app
+
+ % Set the name
+ app.Name = "My App";
+
+ % Configure the main grid
+ app.Grid.ColumnWidth = {300,'1x'};
+ app.Grid.RowHeight = {'1x',150};
+ app.Grid.Padding = 5;
+
+ % Create a panel
+ app.Panel1 = uipanel(app.Grid);
+ app.Panel1.Title = "Panel 1";
+ app.Panel1.Layout.Row = [1 2];
+ app.Panel1.Layout.Column = 1;
+ % Create a panel
+ app.Panel2 = uipanel(app.Grid);
+ app.Panel2.Title = "Panel 2";
+ app.Panel2.Layout.Row = 2;
+ app.Panel2.Layout.Column = 2;
+
+ % Create a tab group
+ app.TabGroup = uitabgroup(app.Grid);
+ app.TabGroup.Layout.Row = 1;
+ app.TabGroup.Layout.Column = 2;
+ app.TabGroup.SelectionChangedFcn = @(h,e)onTabChanged(app,e);
+
+ % Create Tabs, each with a grid
+ app.Tab1 = uitab(app.TabGroup);
+ app.Tab1.Title = 'Tab 1';
+
+ app.Tab2 = uitab(app.TabGroup);
+ app.Tab2.Title = 'Tab 2';
+
+ % Create grid layouts to position content inside each container
+ app.Panel1Grid = uigridlayout(app.Panel1, [1,1],"Padding", 0);
+ app.Panel2Grid = uigridlayout(app.Panel2, [1,1],"Padding", 0);
+ app.Tab1Grid = uigridlayout(app.Tab1, [1,1],"Padding", 0);
+ app.Tab2Grid = uigridlayout(app.Tab2, [1,1],"Padding", 0);
+
+ % Place some temporary content in each container
+ app.Panel1Text = uilabel(app.Panel1Grid);
+ app.Panel1Text.Text = "Panel 1 Contents";
+ app.Panel1Text.HorizontalAlignment = "center";
+ app.Panel1Text.FontSize = 30;
+ app.Panel1Text.Layout.Row = 1;
+ app.Panel1Text.Layout.Column = 1;
+
+ app.Panel2Text = uilabel(app.Panel2Grid);
+ app.Panel2Text.Text = "Panel 2 Contents";
+ app.Panel2Text.HorizontalAlignment = "center";
+ app.Panel2Text.FontSize = 30;
+ app.Panel2Text.Layout.Row = 1;
+ app.Panel2Text.Layout.Column = 1;
+
+ app.Tab1Text = uilabel(app.Tab1Grid);
+ app.Tab1Text.Text = "Tab 1 Contents";
+ app.Tab1Text.HorizontalAlignment = "center";
+ app.Tab1Text.FontSize = 30;
+ app.Tab1Text.Layout.Row = 1;
+ app.Tab1Text.Layout.Column = 1;
+
+ app.Tab2Text = uilabel(app.Tab2Grid);
+ app.Tab2Text.Text = "Tab 2 Contents";
+ app.Tab2Text.HorizontalAlignment = "center";
+ app.Tab2Text.FontSize = 30;
+ app.Tab2Text.Layout.Row = 1;
+ app.Tab2Text.Layout.Column = 1;
+
+ % Additional examples:
+ % (add other views, layouts, and components here as needed)
+ %app.View1 = namespace.ClassName( app.Tab1Grid );
+ %app.View2 = namespace.ClassName( app.Tab2Grid );
+
+ end %function
+
+ end %methods
+
+ %% Update
+ methods ( Access = protected )
+
+ function update(app)
+ % Update the display of the app
+ % For the main app, app.update() must be called explicitly
+ % during callbacks or other changes that require contents to
+ % refresh.
+
+ end %function
+
+ end %methods
+
+
+ %% Callbacks
+ methods
+
+ function onTabChanged(app,evt)
+ % Triggered on changing tab
+
+ % (this method is optional if using tabs)
+
+ newTab = evt.NewValue;
+ disp("Selected Tab: " + newTab.Title);
+
+ end %function
+
+ end %methods
+
+end %classdef
\ No newline at end of file
diff --git a/widgets/examples/templates/TemplateBaseMultiSessionApp.m b/widgets/examples/templates/TemplateBaseMultiSessionApp.m
new file mode 100644
index 00000000..73fda5d6
--- /dev/null
+++ b/widgets/examples/templates/TemplateBaseMultiSessionApp.m
@@ -0,0 +1,376 @@
+classdef TemplateBaseMultiSessionApp < wt.apps.BaseMultiSessionApp
+ % Implements a template for a BaseSingleSessionApp
+
+ % Copyright 2022-2025 The MathWorks Inc.
+
+ %% Internal Components
+ % Create properties here for each control, layout, or view component
+ % that will be placed directly into the main app window.
+ properties ( Transient, NonCopyable, SetAccess = protected )
+
+ % Once the app is created and debugged, consider also protecting
+ % get access to these properties. Leave them accessible to unit
+ % tests, however. Use this example:
+ % GetAccess = {?matlab.uitest.TestCase, ?wt.apps.BaseApp})
+
+ % Grid Layouts
+ % (BaseApp already brings "Grid", the main Grid layout in the window)
+ Tab1Grid matlab.ui.container.GridLayout
+ Tab2Grid matlab.ui.container.GridLayout
+ SessionListPanelGrid matlab.ui.container.GridLayout
+ SessionDescriptionPanelGrid matlab.ui.container.GridLayout
+ Panel2Grid matlab.ui.container.GridLayout
+
+ % Panels
+ SessionListPanel matlab.ui.container.Panel
+ SessionDescriptionPanel matlab.ui.container.Panel
+ Panel2 matlab.ui.container.Panel
+
+ % Tabs
+ TabGroup matlab.ui.container.TabGroup
+ Tab1 matlab.ui.container.Tab
+ Tab2 matlab.ui.container.Tab
+
+ % Toolbar and sections
+ Toolbar wt.Toolbar
+ FileSection wt.toolbar.HorizontalSection
+ HelpSection wt.toolbar.HorizontalSection
+
+ % Toolbar buttons
+ NewButton matlab.ui.control.Button
+ OpenButton matlab.ui.control.Button
+ SaveButton matlab.ui.control.Button
+ SaveAsButton matlab.ui.control.Button
+ CloseButton matlab.ui.control.Button
+ HelpButton matlab.ui.control.Button
+
+ % View Components
+ %View1 namespace.ClassName
+ %View2 namespace.ClassName
+
+ % Session information components
+ SessionList matlab.ui.control.ListBox
+ SessionDescription matlab.ui.control.TextArea
+
+ % Temporary label components
+ Panel2Text matlab.ui.control.Label
+ Tab1Text matlab.ui.control.Label
+ Tab2Text matlab.ui.control.Label
+
+ end %properties
+
+
+
+ %% Setup and Configuration of the App
+ methods ( Access = protected )
+
+ function setup(app)
+ % Runs once on instantiation of the app
+
+ % Set the name
+ app.Name = "My App";
+
+ % Configure the main grid
+ app.Grid.ColumnWidth = {300,'1x'};
+ app.Grid.RowHeight = {100,'1x',150};
+ app.Grid.Padding = 5;
+
+ % Create toolbar (split out for brevity)
+ app.createToolbar()
+
+ % Create a panel for session list
+ app.SessionListPanel = uipanel(app.Grid);
+ app.SessionListPanel.Title = "Sessions";
+ app.SessionListPanel.Layout.Row = 2;
+ app.SessionListPanel.Layout.Column = 1;
+
+ % Create a panel for session description
+ app.SessionDescriptionPanel = uipanel(app.Grid);
+ app.SessionDescriptionPanel.Title = "Session Description:";
+ app.SessionDescriptionPanel.Layout.Row = 3;
+ app.SessionDescriptionPanel.Layout.Column = 1;
+
+ % Create a panel
+ app.Panel2 = uipanel(app.Grid);
+ app.Panel2.Title = "Panel 2";
+ app.Panel2.Layout.Row = 3;
+ app.Panel2.Layout.Column = 2;
+
+ % Create a tab group
+ app.TabGroup = uitabgroup(app.Grid);
+ app.TabGroup.Layout.Row = 2;
+ app.TabGroup.Layout.Column = 2;
+ app.TabGroup.SelectionChangedFcn = @(h,e)onTabChanged(app,e);
+
+ % Create Tabs, each with a grid
+ app.Tab1 = uitab(app.TabGroup);
+ app.Tab1.Title = 'Tab 1';
+
+ app.Tab2 = uitab(app.TabGroup);
+ app.Tab2.Title = 'Tab 2';
+
+ % Create grid layouts to position content inside each container
+ app.SessionListPanelGrid = uigridlayout(...
+ app.SessionListPanel, [1,1],"Padding", 0);
+ app.SessionDescriptionPanelGrid = uigridlayout(...
+ app.SessionDescriptionPanel, [1,1],"Padding", 0);
+ app.Panel2Grid = uigridlayout(app.Panel2, [1,1],"Padding", 0);
+ app.Tab1Grid = uigridlayout(app.Tab1, [1,1],"Padding", 0);
+ app.Tab2Grid = uigridlayout(app.Tab2, [1,1],"Padding", 0);
+
+ % Put a listbox of the sessions
+ app.SessionList = uilistbox(app.SessionListPanelGrid);
+ app.SessionList.FontSize = 16;
+ app.SessionList.ValueChangedFcn = @(~,evt)onSessionListChanged(app,evt);
+
+ % Put a description for the selected session
+ app.SessionDescription = uitextarea(app.SessionDescriptionPanelGrid);
+ app.SessionDescription.ValueChangedFcn = ...
+ @(~,evt)onSessionDescriptionChanged(app,evt);
+
+ % Add contents to other panes
+ app.Panel2Text = uilabel(app.Panel2Grid);
+ app.Panel2Text.Text = "Panel 2 Contents";
+ app.Panel2Text.HorizontalAlignment = "center";
+ app.Panel2Text.FontSize = 30;
+ app.Panel2Text.Layout.Row = 1;
+ app.Panel2Text.Layout.Column = 1;
+
+ app.Tab1Text = uilabel(app.Tab1Grid);
+ app.Tab1Text.Text = "Tab 1 Contents";
+ app.Tab1Text.HorizontalAlignment = "center";
+ app.Tab1Text.FontSize = 30;
+ app.Tab1Text.Layout.Row = 1;
+ app.Tab1Text.Layout.Column = 1;
+
+ app.Tab2Text = uilabel(app.Tab2Grid);
+ app.Tab2Text.Text = "Tab 2 Contents";
+ app.Tab2Text.HorizontalAlignment = "center";
+ app.Tab2Text.FontSize = 30;
+ app.Tab2Text.Layout.Row = 1;
+ app.Tab2Text.Layout.Column = 1;
+
+ % Additional examples:
+ % (add other views, layouts, and components here as needed)
+ %app.View1 = namespace.ClassName( app.Tab1Grid );
+ %app.View2 = namespace.ClassName( app.Tab2Grid );
+
+ end %function
+
+
+ function createToolbar(app)
+ % Create the toolbar contents
+
+ % (This method is optional if using a toolbar. It's split out
+ % into the separate method here to keep the setup method
+ % shorter)
+
+ % Create the toolbar container
+ app.Toolbar = wt.Toolbar(app.Grid);
+ app.Toolbar.Layout.Row = 1;
+ app.Toolbar.Layout.Column = [1 2];
+
+ % File Section
+ app.FileSection = wt.toolbar.HorizontalSection();
+ app.FileSection.Title = "FILE";
+ app.FileSection.ButtonPushedFcn = @(~,evt)onFileToolbarButtonPushed(app,evt);
+
+ app.NewButton = app.FileSection.addButton('add_24.png','New Session');
+ app.OpenButton = app.FileSection.addButton('folder_24.png','Open Session');
+ app.SaveButton = app.FileSection.addButton('save_24.png','Save Session');
+ app.SaveAsButton = app.FileSection.addButton("saveClean_24.png","Save As");
+ app.CloseButton = app.FileSection.addButton("close_24.png","Close Session");
+
+ app.FileSection.ComponentWidth(:) = 55;
+
+ % Help Section
+ app.HelpSection = wt.toolbar.HorizontalSection();
+ app.HelpSection.Title = "HELP";
+ app.HelpButton = app.HelpSection.addButton('help_24.png','Help');
+ app.HelpButton.ButtonPushedFcn = @(h,e)onHelpButton(app);
+
+ % Add all toolbar sections to the toolbar
+ % This is done last for performance reasons
+ app.Toolbar.Section = [
+ app.FileSection
+ app.HelpSection
+ ];
+
+ end %function
+
+
+ function sessionObj = createNewSession(~)
+ % Create and return a new session object for this app
+
+ % The session should be a class that inherits wt.model.BaseSession
+ %sessionObj = namespace.SessionClassName;
+
+ % For example purposes, using this one:
+ sessionObj = wt.model.BaseSession;
+
+ end %function
+
+ end %methods
+
+ %% Update
+ methods ( Access = protected )
+
+ function update(app)
+ % Update the display of the app
+ % For the main app, app.update() must be called explicitly
+ % during callbacks or other changes that require contents to
+ % refresh.
+
+ % Get selected session
+ selSession = app.SelectedSession;
+
+ % Update the session list
+ app.SessionList.Items = app.SessionDisplayNames;
+ app.SessionList.ItemsData = app.Session;
+ if isempty(selSession) || ~ismember(selSession, app.Session)
+ app.SessionList.Value = {};
+ else
+ app.SessionList.Value = selSession;
+ end
+
+ % Update the session description
+ if isempty(selSession)
+ app.SessionDescription.Value = "";
+ app.SessionDescription.Enable = false;
+ app.SessionDescription.UserData = [];
+ else
+ app.SessionDescription.Value = selSession.Description;
+ app.SessionDescription.Enable = true;
+ app.SessionDescription.UserData = selSession;
+ end
+
+ % Update toolbar button enables
+ app.updateToolbarEnables()
+
+ % Examples:
+ %app.View1.Model = sessionObj;
+ %app.View2.Model = sessionObj;
+
+ end %function
+
+
+ function updateToolbarEnables(app)
+
+ % Get the session states
+ selSession = app.SelectedSession;
+ hasSelectedSession = isscalar(selSession);
+ hasDirtySession = hasSelectedSession && selSession.Dirty;
+
+ % File toolbar enables
+ app.SaveButton.Enable = hasDirtySession;
+ app.SaveAsButton.Enable = hasSelectedSession;
+ app.CloseButton.Enable = hasSelectedSession;
+
+ end %function
+
+ end %methods
+
+
+ %% Callbacks
+ methods
+
+ function onTabChanged(app,evt)
+ % Triggered on changing tab
+
+ % (this method is optional if using tabs)
+
+ newTab = evt.NewValue;
+ disp("Selected Tab: " + newTab.Title);
+
+ end %function
+
+
+ function onSessionListChanged(app,evt)
+ % Triggered on selecting from the session list
+
+ % (this method is optional if using tabs)
+
+ % What was selected?
+ newValue = evt.Value;
+
+ % Select the session
+ app.selectSession(newValue);
+
+ % Update the app
+ app.update();
+
+ end %function
+
+
+ function onSessionDescriptionChanged(app,evt)
+ % Triggered on editing a session description
+
+ % Get the new value
+ newValue = join(string(evt.Value), newline);
+
+ % Get the session tied to the field
+ % Otherwise, if you click off the field immediately to select
+ % another session, it edits the wrong session!
+ session = app.SessionDescription.UserData;
+
+ % Update the session description
+ session.Description = newValue;
+
+ end %function
+
+
+ function onFileToolbarButtonPushed(app,evt)
+
+ % Which button was pressed?
+ switch evt.Button
+
+ case app.NewButton
+
+ % Add a new session
+ session = app.newSession();
+
+ % Select the new session
+ if ~isempty(session)
+ app.selectSession(session);
+ end
+
+ case app.OpenButton
+
+ % Prompt and load a session
+ session = app.loadSession();
+
+ % Select the new session
+ if ~isempty(session)
+ app.selectSession(session);
+ end
+
+ case app.SaveButton
+
+ % Save the selected session
+ app.saveSession(false);
+
+ case app.SaveAsButton
+
+ % Save the selected session as different file
+ app.saveSession(true);
+
+ case app.CloseButton
+
+ % Close the currently selected session
+ app.closeSession();
+
+ end %switch
+
+ end %function
+
+
+ function onHelpButton(app)
+ % Triggered when the toolbar button is pressed
+
+ disp("Help Button Pushed!");
+
+ end %function
+
+ end %methods
+
+end %classdef
\ No newline at end of file
diff --git a/widgets/examples/templates/TemplateBaseSingleSessionApp.m b/widgets/examples/templates/TemplateBaseSingleSessionApp.m
new file mode 100644
index 00000000..6b10fc0f
--- /dev/null
+++ b/widgets/examples/templates/TemplateBaseSingleSessionApp.m
@@ -0,0 +1,301 @@
+classdef TemplateBaseSingleSessionApp < wt.apps.BaseSingleSessionApp
+ % Implements a template for a BaseSingleSessionApp
+
+ % Copyright 2022-2025 The MathWorks Inc.
+
+
+ %% Internal Components
+ % Create properties here for each control, layout, or view component
+ % that will be placed directly into the main app window.
+ properties ( Transient, NonCopyable, SetAccess = protected )
+
+ % Once the app is created and debugged, consider also protecting
+ % get access to these properties. Leave them accessible to unit
+ % tests, however. Use this example:
+ % GetAccess = {?matlab.uitest.TestCase, ?wt.apps.BaseApp})
+
+ % Grid Layouts
+ % (BaseApp already brings "Grid", the main Grid layout in the window)
+ Tab1Grid matlab.ui.container.GridLayout
+ Tab2Grid matlab.ui.container.GridLayout
+ SessionDescriptionPanelGrid matlab.ui.container.GridLayout
+ Panel2Grid matlab.ui.container.GridLayout
+
+ % Panels
+ SessionDescriptionPanel matlab.ui.container.Panel
+ Panel2 matlab.ui.container.Panel
+
+ % Tabs
+ TabGroup matlab.ui.container.TabGroup
+ Tab1 matlab.ui.container.Tab
+ Tab2 matlab.ui.container.Tab
+
+ % Toolbar and sections
+ Toolbar wt.Toolbar
+ FileSection wt.toolbar.HorizontalSection
+ HelpSection wt.toolbar.HorizontalSection
+
+ % Toolbar buttons
+ NewButton matlab.ui.control.Button
+ OpenButton matlab.ui.control.Button
+ SaveButton matlab.ui.control.Button
+ SaveAsButton matlab.ui.control.Button
+ ExportGTButton matlab.ui.control.Button
+ HelpButton matlab.ui.control.Button
+
+ % View Components
+ %View1 namespace.ClassName
+ %View2 namespace.ClassName
+
+ % Session information components
+ SessionDescription matlab.ui.control.TextArea
+
+ % Temporary label components
+ Panel1Text matlab.ui.control.Label
+ Panel2Text matlab.ui.control.Label
+ Tab1Text matlab.ui.control.Label
+ Tab2Text matlab.ui.control.Label
+
+ end %properties
+
+
+
+ %% Setup and Configuration of the App
+ methods ( Access = protected )
+
+ function setup(app)
+ % Runs once on instantiation of the app
+
+ % Set the name
+ app.Name = "My App";
+
+ % Configure the main grid
+ app.Grid.ColumnWidth = {300,'1x'};
+ app.Grid.RowHeight = {100,'1x',150};
+ app.Grid.Padding = 5;
+
+ % Create toolbar (split out for brevity)
+ app.createToolbar()
+
+ % Create a panel for session description
+ app.SessionDescriptionPanel = uipanel(app.Grid);
+ app.SessionDescriptionPanel.Title = "Session Description:";
+ app.SessionDescriptionPanel.Layout.Row = [2 3];
+ app.SessionDescriptionPanel.Layout.Column = 1;
+
+ % Create a panel
+ app.Panel2 = uipanel(app.Grid);
+ app.Panel2.Title = "Panel 2";
+ app.Panel2.Layout.Row = 3;
+ app.Panel2.Layout.Column = 2;
+
+ % Create a tab group
+ app.TabGroup = uitabgroup(app.Grid);
+ app.TabGroup.Layout.Row = 2;
+ app.TabGroup.Layout.Column = 2;
+ app.TabGroup.SelectionChangedFcn = @(h,e)onTabChanged(app,e);
+
+ % Create Tabs, each with a grid
+ app.Tab1 = uitab(app.TabGroup);
+ app.Tab1.Title = 'Tab 1';
+
+ app.Tab2 = uitab(app.TabGroup);
+ app.Tab2.Title = 'Tab 2';
+
+ % Create grid layouts to position content inside each container
+ app.SessionDescriptionPanelGrid = uigridlayout(...
+ app.SessionDescriptionPanel, [1,1],"Padding", 0);
+ app.Panel2Grid = uigridlayout(app.Panel2, [1,1],"Padding", 0);
+ app.Tab1Grid = uigridlayout(app.Tab1, [1,1],"Padding", 0);
+ app.Tab2Grid = uigridlayout(app.Tab2, [1,1],"Padding", 0);
+
+ % Put a description for the selected session
+ app.SessionDescription = uitextarea(app.SessionDescriptionPanelGrid);
+ app.SessionDescription.ValueChangedFcn = ...
+ @(~,evt)onSessionDescriptionChanged(app,evt);
+
+ % Place some temporary content in each container
+ app.Panel2Text = uilabel(app.Panel2Grid);
+ app.Panel2Text.Text = "Panel 2 Contents";
+ app.Panel2Text.HorizontalAlignment = "center";
+ app.Panel2Text.FontSize = 30;
+ app.Panel2Text.Layout.Row = 1;
+ app.Panel2Text.Layout.Column = 1;
+
+ app.Tab1Text = uilabel(app.Tab1Grid);
+ app.Tab1Text.Text = "Tab 1 Contents";
+ app.Tab1Text.HorizontalAlignment = "center";
+ app.Tab1Text.FontSize = 30;
+ app.Tab1Text.Layout.Row = 1;
+ app.Tab1Text.Layout.Column = 1;
+
+ app.Tab2Text = uilabel(app.Tab2Grid);
+ app.Tab2Text.Text = "Tab 2 Contents";
+ app.Tab2Text.HorizontalAlignment = "center";
+ app.Tab2Text.FontSize = 30;
+ app.Tab2Text.Layout.Row = 1;
+ app.Tab2Text.Layout.Column = 1;
+
+ % Additional examples:
+ % (add other views, layouts, and components here as needed)
+ %app.View1 = namespace.ClassName( app.Tab1Grid );
+ %app.View2 = namespace.ClassName( app.Tab2Grid );
+
+ end %function
+
+
+ function createToolbar(app)
+ % Create the toolbar contents
+
+ % (This method is optional if using a toolbar. It's split out
+ % into the separate method here to keep the setup method
+ % shorter)
+
+ % Create the toolbar container
+ app.Toolbar = wt.Toolbar(app.Grid);
+ app.Toolbar.Layout.Row = 1;
+ app.Toolbar.Layout.Column = [1 2];
+
+ % File Section
+ app.FileSection = wt.toolbar.HorizontalSection();
+ app.FileSection.Title = "FILE";
+ app.FileSection.ButtonPushedFcn = @(~,evt)onFileToolbarButtonPushed(app,evt);
+
+ app.NewButton = app.FileSection.addButton('add_24.png','New Session');
+ app.OpenButton = app.FileSection.addButton('folder_24.png','Open Session');
+ app.SaveButton = app.FileSection.addButton('save_24.png','Save Session');
+ app.SaveAsButton = app.FileSection.addButton("saveClean_24.png","Save As");
+ app.FileSection.ComponentWidth(:) = 55;
+
+ % Help Section
+ app.HelpSection = wt.toolbar.HorizontalSection();
+ app.HelpSection.Title = "HELP";
+ app.HelpButton = app.HelpSection.addButton('help_24.png','Help');
+ app.HelpButton.ButtonPushedFcn = @(h,e)onHelpButton(app);
+
+ % Add all toolbar sections to the toolbar
+ % This is done last for performance reasons
+ app.Toolbar.Section = [
+ app.FileSection
+ app.HelpSection
+ ];
+
+ end %function
+
+
+ function sessionObj = createNewSession(~)
+ % Create and return a new session object for this app
+
+ % The session should be a class that inherits wt.model.BaseSession
+ %sessionObj = namespace.SessionClassName;
+
+ % For example purposes, using this one:
+ sessionObj = wt.model.BaseSession;
+
+ end %function
+
+ end %methods
+
+ %% Update
+ methods ( Access = protected )
+
+ function update(app)
+ % Update the display of the app
+ % For the main app, app.update() must be called explicitly
+ % during callbacks or other changes that require contents to
+ % refresh.
+
+ % Update the session description text
+ app.SessionDescription.Value = app.Session.Description;
+
+ % Update toolbar button enables
+ app.updateToolbarEnables()
+
+ % Examples:
+ %app.View1.Model = sessionObj;
+ %app.View2.Model = sessionObj;
+
+ end %function
+
+
+ function updateToolbarEnables(app)
+
+ % Get the session states
+ hasDirtySession = isscalar(app.Session) && app.Session.Dirty;
+
+ % File toolbar enables
+ app.SaveButton.Enable = hasDirtySession;
+
+ end %function
+
+ end %methods
+
+
+ %% Callbacks
+ methods
+
+ function onTabChanged(app,evt)
+ % Triggered on changing tab
+
+ % (this method is optional if using tabs)
+
+ newTab = evt.NewValue;
+ disp("Selected Tab: " + newTab.Title);
+
+ end %function
+
+
+ function onSessionDescriptionChanged(app,evt)
+ % Triggered on editing a session description
+
+ % Get the new value
+ newValue = join(string(evt.Value), newline);
+
+ % Update the session description
+ app.Session.Description = newValue;
+
+ end %function
+
+
+ function onFileToolbarButtonPushed(app,evt)
+
+ % Which button was pressed?
+ switch evt.Button
+
+ case app.NewButton
+
+ % Add a new session
+ app.newSession();
+
+ case app.OpenButton
+
+ % Prompt and load a session
+ app.loadSession();
+
+ case app.SaveButton
+
+ % Save the selected session
+ app.saveSession(false);
+
+ case app.SaveAsButton
+
+ % Save the selected session as different file
+ app.saveSession(true);
+
+ end %switch
+
+ end %function
+
+
+ function onHelpButton(app)
+ % Triggered when the toolbar button is pressed
+
+ % (this method is optional if using the toolbar)
+
+ disp("Help Button Pushed!");
+
+ end %function
+
+ end %methods
+
+end %classdef
\ No newline at end of file