开源项目YtyMark文本编辑器--UI界面相关功能(关于设计模式的实战运用)
开源项目地址
欢迎提交 PR、Issue、Star ️!
1. 简述
YtyMark-java项目分为两大模块:
UI界面(ytyedit-mark)
markdown文本解析和渲染(ytymark)
本文主要内容为UI界面相关功能。
关于markdown文本解析器UI界面的实现。在这整个流程中,如果通过设计模式实现高内聚低耦合,可重用,易于阅读,易于扩展,易于维护等。
YtyMark-java
├── ytyedit-mark/
│ ├── src/
│ │ ├── main/
│ │ │ ├── java/
│ │ │ │ ├── editor/ # JavaFX UI 界面
│ │ │ │ ├── enums/ # Icon图标等
│ │ │ │ ├── utils/ # 资源读取等
│ │ │ │ ├── window/ # 自定义窗口(主窗口、弹框)
│ │ │ │ ├── RenderMarkdown # 解析和渲染
│ │ │ │ ├── YtyEditApplication # 主程序入口
│ │ │ └── resources/
│ │ │ └── css/ # 主题样式(CSS)
│ │ │ └── fonts/ # 字体集
│ │ │ └── images/ # 图片
│ ├── README.md
│ └── pom.xml
2. JavaFX 用户界面
目标:为用户提供可视化的文本输入、实时预览、编辑、保存、导出(PDF/HTML)和主题切换等功能。
使用到的设计模式:
工厂模式:样式的创建通过工厂模式来完成。
策略模式:动态选择工具界面样式,完成UI界面的样式切换。
观察者模式:识别到主题发生变化时执行重新渲染操作;样式切换后,渲染的文字样式也需要同步调整,再结合监听器(观察者模式)来实现主题变化后重新渲染文本内容,除此之外JavaFX使用了大量的监听器。
单例模式:主题样式管理器统一管理全局样式,并提供统一访问入口。
装饰模式:对自定义基础弹框做定制化的扩展,实现不同场景所需的弹框。
命令模式:封装工具界面中的功能点及快捷键命令。
备忘录模式:负责实现撤销和恢复功能,实现精细到单字符的撤销/恢复机制。
2.1. 工厂模式
通过工厂创建样式,将默认的样式创建好,统一放入Map中保存,需要时直接从工厂中获取。
public class StyleFactory {
private static final Map<Key, Style> STYLE_MAP = new HashMap<>();
// 预先注册默认样式
static {
registerStyle(WindowType.MAIN_WINDOW, ThemeType.LIGHT, new MainWindowLightStyle());
registerStyle(WindowType.MAIN_WINDOW, ThemeType.DARK, new MainWindowDarkStyle());
registerStyle(WindowType.DIALOG_WINDOW, ThemeType.LIGHT, new DialogWindowLightStyle());
registerStyle(WindowType.DIALOG_WINDOW, ThemeType.DARK, new DialogWindowDarkStyle());
}
public static void registerStyle(WindowType type, ThemeType theme, Style style) {
STYLE_MAP.put(new Key(type, theme), style);
}
public static Style getStyle(WindowType type, ThemeType theme) {
Style style = STYLE_MAP.get(new Key(type, theme));
if (style == null) {
throw new RuntimeException("窗口类型或主题类型不支持: " + type + " - " + theme);
}
return style;
}
...
}
2.2. 策略模式
动态选择工具界面样式,完成UI界面的样式切换。并支持对已有窗口样式的自由组合,比如深色的主窗口+浅色的弹框。
使用setTheme(ThemeType)方法可以快速切换已经搭配好的主题;使用setStyle可以灵活指定不同窗口的样式。
public class ThemeContext {
...
// 设置样式
public void setTheme(ThemeType theme) {
WindowType[] values = WindowType.values();
for (WindowType windowType : values) {
this.themeManager.setStyle(windowType, theme);
}
}
// 自定义设置不同窗体不同样式
public void setStyle(WindowType type, ThemeType theme) {
this.themeManager.setStyle(type, theme);
}
// 主题切换
public void switchTheme() {
// 清空之前的样式
scene.getStylesheets().clear();
this.themeManager.applyStyle(WindowType.MAIN_WINDOW, scene);
}
}
2.3. 观察者模式和单例模式
识别到主题发生变化时执行重新渲染操作。样式切换后,渲染的文字样式也需要同步调整,结合监听器(观察者模式)来实现主题变化后重新渲染文本内容,除此之外JavaFX使用了大量的监听器。
自定义的主题监听器接口:
public interface ThemeChangeListener {
void onThemeChanged();
}
主题监听器将有主题管理类来统一管理,并结合单例模式,使得主题管理器全局唯一,并提供统一访问入口ThemeManager.getInstance()。
public class ThemeManager {
private static ThemeManager instance;
private final Map<WindowType, Style> currentStyles = new HashMap<>();
private final List<ThemeChangeListener> listeners = new ArrayList<>();
private ThemeManager() {
// 默认主窗口白天模式,弹窗白色模式
currentStyles.put(WindowType.MAIN_WINDOW, new MainWindowLightStyle());
currentStyles.put(WindowType.DIALOG_WINDOW, new DialogWindowLightStyle());
}
public static ThemeManager getInstance() {
if (instance == null) {
instance = new ThemeManager();
}
return instance;
}
public void setStyle(WindowType type, ThemeType theme) {
currentStyles.put(type, StyleFactory.getStyle(type, theme));
// 通知所有观察者主题已变更
this.notifyThemeChanged();
}
...
// 注册观察者
public void addThemeChangeListener(ThemeChangeListener listener) {
listeners.add(listener);
}
// 注销观察者
public void removeThemeChangeListener(ThemeChangeListener listener) {
listeners.remove(listener);
}
// 通知所有观察者
private void notifyThemeChanged() {
for (ThemeChangeListener listener : listeners) {
listener.onThemeChanged();
}
}
}
通用弹框和渲染处理类实现了监听器接口,在创建时注册到监听器中
public class GenericDialog implements ThemeChangeListener {
public GenericDialog(Stage owner) {
...
this.themeManager.addThemeChangeListener(this);
...
}
/**
* 订阅主题样式变更,主题变更后自动切换弹窗样式
*/
@Override
public void onThemeChanged() {
// 清空之前的样式
dialogScene.getStylesheets().clear();
themeManager.applyStyle(WindowType.DIALOG_WINDOW,dialogScene);
}
}
渲染处理类监听相关源码
public class RenderMarkdown implements ThemeChangeListener {
public RenderMarkdown(Tab tab) {
...
// 注册自己为 ThemeContext 的监听者
ThemeManager.getInstance().addThemeChangeListener(this);
...
}
/**
* 订阅主题样式变更,主题变更后自动重新渲染内容
*/
@Override
public void onThemeChanged() {
// 主题变更后自动渲染
renderMarkdown(null);
}
}
2.4. 装饰模式
对自定义基础弹框做定制化的扩展,实现不同场景所需的弹框。弹框装饰类通过组合的方式,对通用弹框做定制化设置
public abstract class DialogDecorator {
private GenericDialog dialog;
public DialogDecorator(GenericDialog dialog) {
this.dialog = dialog;
}
public void removeLogo(boolean flag){
dialog.removeLogo(flag);
}
// 委托设置标题
public void setTitle(String title) {
dialog.setTitle(title);
}
// 委托设置主体内容
public void setContent(Node content) {
dialog.setContent(content);
}
// 委托添加内容
public void addContent(Node node) {
dialog.addContent(node);
}
// 委托设置底部按钮区域
public void setFooter(Node node) {
dialog.setFooter(node);
}
// 委托添加底部按钮
public void addFooter(Node node) {
dialog.addFooter(node);
}
// 显示并阻塞,返回用户操作结果
public void showAndWait() {
dialog.showAndWait();
}
}
2.5. 命令模式和备忘录模式
封装工具界面中的功能点及快捷键命令。备忘录模式负责实现撤销和恢复功能,实现精细到单字符的撤销/恢复机制,主要涉及的类:管理文本状态TextEditorOriginator、管理撤销和恢复的栈UndoRedoCaretaker。
public class UndoRedoCaretaker {
private Deque<TextMemento> undoStack = new ArrayDeque<>();
private Deque<TextMemento> redoStack = new ArrayDeque<>();
private TextEditorOriginator originator;
private boolean isUndoRedo = false;
private static final int MAX_HISTORY = 1000; // 最多保存 1000 步
public UndoRedoCaretaker(TextEditorOriginator originator) {
this.originator = originator;
// 存入初始状态,确保撤销可用
undoStack.push(originator.save());
}
// 保存
public void saveState(String text, int caretPosition) {
...
}
// 撤销
public void undo() {
...
}
// 恢复
public void redo() {
...
}
public boolean isUndoRedo() {
return isUndoRedo;
}
public void clear() {
undoStack.clear();
redoStack.clear();
}
}
3. 界面截图预览
白天模式的截图:

夜间模式的截图:

️4. 总结
YtyMark 编辑器界面UI相关功能,将多种设计模式融入到实际应用中,从实践中积累程序设计经验。
更多详细内容可以前往笔者微信公众号回复:设计模式,来获取,后续有关设计模式的新资料都可以从这个入口获取到。
秘籍1设计模式手册:《掌握设计模式:23种经典模式实践、选择、价值与思想》
秘籍2练手项目:设计模式实战项目--markdown文本编辑器软件开发(已开源)

查看往期设计模式文章的:设计模式
三连支持!!!
开源项目YtyMark文本编辑器--UI界面相关功能(关于设计模式的实战运用)的更多相关文章
- Quill – 可以灵活自定义的开源的富文本编辑器
Quill 的建立是为了解决现有的所见即所得(WYSIWYG)的编辑器本身就是所见即所得(指不能再扩张)的问题.如果编辑器不正是你想要的方式,这是很难或不可能对其进行自定义以满足您的需求. Quill ...
- vue项目富文本编辑器vue-quill-editor之自定义图片上传
使用富文本编辑器的第一步肯定是先安装依赖 npm i vue-quill-editor 1.如果按照官网富文本编辑器中的图片上传是将图片转为base64格式的,如果需要上传图片到自己的服务器,需要修改 ...
- KindEditor 开源得富文本编辑器
正常HTML情况写输入长文本需要textarea 标签 .但textarea 标签局限性很大,切只能输入单一的文本,我们大多情况下看到的新闻类文本信息大多是图文混排得,且有的配有视频和音乐. 我们可以 ...
- 【Android】7.8 MyDemos项目的结构和主界面相关代码
分类:C#.Android.VS2015: 创建日期:2016-02-17 一.简介 上一讲已经说过,系统升级为Win10后,重新创建了一个新的项目:MyDemos,并把前7章合并到了这个项目中,这次 ...
- python小项目之文本编辑器
高考完后这么久才想起这系列教程,实在抱歉,现在该来继续教程了. 本节利用前面所学知识,来完成一个小工具--文本编辑器! tkinter 在实现文本编辑器之前,先来了解下tkinter这个python库 ...
- bbs项目富文本编辑器实现上传文件到media目录
media目录是在project的settings中设置的,static目录是django自己使用的静态文件的上传目录,media目录是用户自定义上传文件的目录 # Django用户上传的文件都放在m ...
- springboot中使用kindeditor富文本编辑器实现博客功能
kindeditor在之前已经用过,现在在springboot项目中使用.并且也在里面使用了图片上传以及回显等功能. 其实主要的功能是图片的处理:kindeditor对输入的内容会作为html标签处理 ...
- 【代码导读】Github 开源项目——wysihtml5 富编辑器(Bootstrap 风格)【一】
如果你经常留迹于各大论坛.博客,肯定对它们的富编辑器稍有印象.纯 Javascript 富编辑器可以说是前台 JS 脚本的巅峰作品.一款完整的编辑器,其复杂的功能,会让你遇到各种头痛的浏览器兼容问题, ...
- UI界面相关
在开发中有些控件或者控件显示的属性需要经常设置,但是又是万变不离其中,经常写着一样的代码会显得冗余,不利于阅读.这里做了简化. 1.UI控件 2.颜色管理 3.图片管理 4.字体选择
- 使用 Reactjs + Mobx + React-Router 开发项目时 VSCode 编辑器报警 TS 相关的问题(提示experimentalDecorators )
vscode 对于 JS support 的支持需要配置,在项目根目录下创建一个 jsconfig.json 文件,把以下内容贴入后保存,重启项目即可生效(去掉提示). { "compile ...
随机推荐
- uni-app使用阿里矢量字体图标
在app.vue下,引入 <style> @font-face { font-family: 'iconfont'; /* project id 1951514 */ src: url(' ...
- 虚拟化技术 - CPU虚拟化
本文分享自天翼云开发者社区<虚拟化技术 - CPU虚拟化>,作者:谢****悦 物理机器是由CPU,内存和I/O设备等一组资源构成的实体.虚拟机也一样,由虚拟CPU,虚拟内存和虚拟I/O设 ...
- Django Rest Framework的使用
Django Rest Framework 一.Rest Framework的基本介绍 程序的客户端有很多:硬件设备,游戏,APP,软件,其他的外部服务端. 1. Web应用模式 在开发Web应用中, ...
- 聊聊GRPO算法——从Open R1来看如何训练DeepSeek R1模型
概述 首发自个人公众号:阿郎小哥的随笔驿站 DeepSeek R1系列建议阅读之前的系列文章: 聊聊DeepSeek R1的一些总结 聊聊DeepSeek R1的开源复现库--Open R1之合成数据 ...
- 三种方式从jdbc中获取数据库表字段信息
一.整体代码 1.method1:执行select语句获取,select * from dims where 1 = 2 2.method2:执行show create table获取,show cr ...
- C# 深度学习:对抗生成网络(GAN)训练头像生成模型
通过生成对抗网络(GAN)训练和生成头像 目录 通过生成对抗网络(GAN)训练和生成头像 说明 简介 什么是 GAN 什么是 DCGAN 参数说明 数据集处理 权重初始化 生成器 判别器 损失函数和优 ...
- TVbox蜂蜜影视_v3.1.6:智能电视观影新选择,简洁界面与强大功能兼具
蜂蜜影视是一款基于猫影视开源项目 CatVodTVJarLoader 开发的智能电视软件,专为追求简洁与高效观影体验的用户设计.该软件从零开始编写,界面清爽,操作流畅,特别适合在智能电视上使用.其最大 ...
- HTTP请求中包含账号密码
如果你需要在HTTP请求中包含账号密码,你可以使用基本的HTTP身份验证.在C#中,你可以通过设置 HttpClient 的 DefaultRequestHeaders 来添加身份验证信息.以下是修改 ...
- Web前端入门第 9 问:HTML 块级元素,内联块元素,内联元素三者有什么区别?
HELLO,这里是大熊学习前端开发的入门笔记. 本系列笔记基于 windows 系统. HTML 中的元素根据其默认的 显示类型 主要分为三类:块级元素.内联元素 和 内联块元素. 它们的核心区别在于 ...
- leaflet生成地图封装成jquery插件使用
公司业务里一直都有使用leaflet地图插件来做地图展示.绘图等操作.公司有个项目已经有好几年了,由于项目原因一直在使用,今年由于google 地图 api过期,导致已经使用的地图无法加载.我作为现在 ...