开源项目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 ...
随机推荐
- DeepSeek部署本地知识库
技术背景 在前面的两篇文章中,分别介绍过Ubuntu上关于DeepSeek的部署以及Windows平台关于DeepSeek的部署.其中内容包含了Ollama的下载安装和基本使用.DeepSeek模型文 ...
- yum repo和rpm,添加阿里repos
RPMRPM(Red-hat Package Manager),是一个由红帽最早开发出来的包管理器,目前已经是大多数Linux发行的默认包管理器.RPM管理的包都是以.rpm结尾,其中存储了该软件的安 ...
- C盘扩展卷碰到的那些事-->不是同一块物理磁盘操作扩展卷是有坑的
自己电脑上面用过win10系统资源管理器扩展卷的功能,用过几次都成功扩容了磁盘空间,简单说一下原理: 就是将剩余未分配的磁盘空间划给要扩展的磁盘. 这天公司的电脑C盘老是红色提示空间不足,那就扩充容量 ...
- nginx 简单实践:正向代理、反向代理【nginx 实践系列之二】
〇.前言 本文为 nginx 简单实践系列文章之二,主要简单实践了两个内容:正向代理.反向代理,仅供参考. 关于 Nginx 基础,以及安装和配置详解,可以参考博主过往文章: https://www. ...
- github上文件过大无法推送问题
GitHub 对文件大小有限制,超过 100 MB 的文件无法直接推送到仓库中. 解决思路: 使用 Git Large File Storage (Git LFS) 来管理大文件 不上传对应的大文件 ...
- css快速入门系列 —— 移动开发闲谈
移动开发闲谈(Flex和css 库) 背景 目前在做移动小程序开发,效果必须和设计稿一模一样,一个像素都不能有差异. 虽然公司也提供了图生文的工具,但是有时生成的代码可读性不太好,二次修改也比较费劲, ...
- oh-my-bash在git大仓库下的卡顿问题解决方案
使用oh-my-bash的同学都知道,在cd进入一些git大仓库的时候,oh-my-bash会贴心的帮你扫描一遍 然后你就卡那(nei)了... (风中凌乱.jpg) 本文告诉大家一种关闭git扫描的 ...
- DW - 问题
数据库三范式 1NF(First Normal Form):一个关系模式符合 1NF 的定义,则该关系模式是简单的.简单的意思就是不存在从属或重复的属性,即每个属性都是原子性的. 2NF(Second ...
- 1Panel 专业版评测:全面超越宝塔的运维面板新标杆
一. UX体验与移动端适配:更直观的跨平台交互 1Panel 专业版在用户体验上实现了对宝塔的全面超越.其界面采用现代化设计语言,以黑金主题为代表的可定制化主题系统支持一键切换,视觉风格更符合技术审美 ...
- python基础-函数(函数参数、返回值、执行、传参、作用域、函数名)
前言 !!!注意:本系列所写的文章全部是学习笔记,来自于观看视频的笔记记录,防止丢失.观看的视频笔记来自于:哔哩哔哩武沛齐老师的视频:2022 Python的web开发(完整版) 入门全套教程,零基础 ...