private void buildPreviewTableComponents() {
		tableModel = new EncodedStringsTableModel(program, selectedAddresses);
		tableModel.addTableModelListener(e -> {
			Integer rowNum = rowToSelect.getAndSet(null);
			if (rowNum != null && rowNum < tableModel.getRowCount()) {
				table.selectRow(rowNum);
				table.requestFocusInWindow();
			}
		});
		tableModel.addThreadedTableModelListener(new ThreadedTableModelListener() {

			@Override
			public void loadingStarted() {
				setStatusText("Filtering strings...");
				setCreateButtonInfo(0, 0);
				threadedTablePanel.showEmptyTableOverlay(false);
			}

			@Override
			public void loadingFinished(boolean wasCancelled) {
				EncodedStringsFilterStats stats = tableModel.getStats();
				prevStats = stats.clone();
				int rowCount = tableModel.getRowCount();
				setStatusText("%s strings found, %d strings match, %d excluded%s.".formatted(
					stats.total, rowCount, stats.getTotalOmitted(),
					wasCancelled ? " (partial results)" : ""));
				List<Address> previousAddrs = previouslySelectedRowAddrs.getAndSet(null);
				if (previousAddrs != null) {
					setSelectedAddresses(previousAddrs);
				}
				selectedRowChange();

				codecErrorsCountLabel.setText(getErrorCountString(stats.codecErrors));
				nonStdCtrlCharsErrorsCountLabel.setText(getErrorCountString(stats.nonStdCtrlChars));
				stringModelFailedCountLabel.setText(getErrorCountString(stats.failedStringModel));
				minLenFailedCountLabel.setText(getErrorCountString(stats.stringLength));
				scriptFailedCountLabel.setText(getErrorCountString(stats.requiredScripts));
				latinScriptFailedCountLabel.setText(getErrorCountString(stats.latinScript));
				commonScriptFailedCountLabel.setText(getErrorCountString(stats.commonScript));
				otherScriptsFailedCountLabel.setText(getErrorCountString(stats.otherScripts));
				advancedFailedCountLabel
						.setText(getErrorCountString(stats.getTotalForAdvancedOptions()));

				updateRequiredScriptsList(stats);
				threadedTablePanel.showEmptyTableOverlay(rowCount == 0);
			}

			@Override
			public void loadPending() {
				// ignore
			}
		});

		JPanel emptyTableOverlay = new JPanel(new GridBagLayout());
		emptyTableOverlay.add(new GHtmlLabel("<html>No strings matched filter criteria..."),
			new GridBagConstraints());
		emptyTableOverlay.getAccessibleContext().setAccessibleName("Empty Table Overlay");
		threadedTablePanel =
			new EncodedStringsThreadedTablePanel<>(tableModel, 1000, emptyTableOverlay);
		threadedTablePanel.setBorder(BorderFactory.createTitledBorder("Preview"));
		threadedTablePanel.getAccessibleContext().setAccessibleName("Threaded Table");
		table = threadedTablePanel.getTable();
		table.setName("DataTable");
		table.getAccessibleContext().setAccessibleName("Data Table");
		table.setPreferredScrollableViewportSize(new Dimension(350, 150));
		table.getSelectionModel().addListSelectionListener(e -> selectedRowChange());

		table.installNavigation(tool);
		filterPanel = new GhidraTableFilterPanel<>(table, tableModel);
		filterPanel.getAccessibleContext().setAccessibleName("Filter");
	}
For test/screen shot use

@return button
Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/strings/EncodedStringsDialog.java