开源项目地址

GitHub 开源地址(YtyMark-java)

欢迎提交 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文本编辑器软件开发(已开源

查看往期设计模式文章的:设计模式

超实用的SpringAOP实战之日志记录

2023年下半年软考考试重磅消息

通过软考后却领取不到实体证书?

计算机算法设计与分析(第5版)

Java全栈学习路线、学习资源和面试题一条龙

软考证书=职称证书?

软考中级--软件设计师毫无保留的备考分享

三连支持!!!

开源项目YtyMark文本编辑器--UI界面相关功能(关于设计模式的实战运用)的更多相关文章

  1. Quill – 可以灵活自定义的开源的富文本编辑器

    Quill 的建立是为了解决现有的所见即所得(WYSIWYG)的编辑器本身就是所见即所得(指不能再扩张)的问题.如果编辑器不正是你想要的方式,这是很难或不可能对其进行自定义以满足您的需求. Quill ...

  2. vue项目富文本编辑器vue-quill-editor之自定义图片上传

    使用富文本编辑器的第一步肯定是先安装依赖 npm i vue-quill-editor 1.如果按照官网富文本编辑器中的图片上传是将图片转为base64格式的,如果需要上传图片到自己的服务器,需要修改 ...

  3. KindEditor 开源得富文本编辑器

    正常HTML情况写输入长文本需要textarea 标签 .但textarea 标签局限性很大,切只能输入单一的文本,我们大多情况下看到的新闻类文本信息大多是图文混排得,且有的配有视频和音乐. 我们可以 ...

  4. 【Android】7.8 MyDemos项目的结构和主界面相关代码

    分类:C#.Android.VS2015: 创建日期:2016-02-17 一.简介 上一讲已经说过,系统升级为Win10后,重新创建了一个新的项目:MyDemos,并把前7章合并到了这个项目中,这次 ...

  5. python小项目之文本编辑器

    高考完后这么久才想起这系列教程,实在抱歉,现在该来继续教程了. 本节利用前面所学知识,来完成一个小工具--文本编辑器! tkinter 在实现文本编辑器之前,先来了解下tkinter这个python库 ...

  6. bbs项目富文本编辑器实现上传文件到media目录

    media目录是在project的settings中设置的,static目录是django自己使用的静态文件的上传目录,media目录是用户自定义上传文件的目录 # Django用户上传的文件都放在m ...

  7. springboot中使用kindeditor富文本编辑器实现博客功能

    kindeditor在之前已经用过,现在在springboot项目中使用.并且也在里面使用了图片上传以及回显等功能. 其实主要的功能是图片的处理:kindeditor对输入的内容会作为html标签处理 ...

  8. 【代码导读】Github 开源项目——wysihtml5 富编辑器(Bootstrap 风格)【一】

    如果你经常留迹于各大论坛.博客,肯定对它们的富编辑器稍有印象.纯 Javascript 富编辑器可以说是前台 JS 脚本的巅峰作品.一款完整的编辑器,其复杂的功能,会让你遇到各种头痛的浏览器兼容问题, ...

  9. UI界面相关

    在开发中有些控件或者控件显示的属性需要经常设置,但是又是万变不离其中,经常写着一样的代码会显得冗余,不利于阅读.这里做了简化. 1.UI控件 2.颜色管理 3.图片管理 4.字体选择

  10. 使用 Reactjs + Mobx + React-Router 开发项目时 VSCode 编辑器报警 TS 相关的问题(提示experimentalDecorators )

    vscode 对于 JS support 的支持需要配置,在项目根目录下创建一个 jsconfig.json 文件,把以下内容贴入后保存,重启项目即可生效(去掉提示). { "compile ...

随机推荐

  1. uni-app使用阿里矢量字体图标

    在app.vue下,引入 <style> @font-face { font-family: 'iconfont'; /* project id 1951514 */ src: url(' ...

  2. 虚拟化技术 - CPU虚拟化

    本文分享自天翼云开发者社区<虚拟化技术 - CPU虚拟化>,作者:谢****悦 物理机器是由CPU,内存和I/O设备等一组资源构成的实体.虚拟机也一样,由虚拟CPU,虚拟内存和虚拟I/O设 ...

  3. Django Rest Framework的使用

    Django Rest Framework 一.Rest Framework的基本介绍 程序的客户端有很多:硬件设备,游戏,APP,软件,其他的外部服务端. 1. Web应用模式 在开发Web应用中, ...

  4. 聊聊GRPO算法——从Open R1来看如何训练DeepSeek R1模型

    概述 首发自个人公众号:阿郎小哥的随笔驿站 DeepSeek R1系列建议阅读之前的系列文章: 聊聊DeepSeek R1的一些总结 聊聊DeepSeek R1的开源复现库--Open R1之合成数据 ...

  5. 三种方式从jdbc中获取数据库表字段信息

    一.整体代码 1.method1:执行select语句获取,select * from dims where 1 = 2 2.method2:执行show create table获取,show cr ...

  6. C# 深度学习:对抗生成网络(GAN)训练头像生成模型

    通过生成对抗网络(GAN)训练和生成头像 目录 通过生成对抗网络(GAN)训练和生成头像 说明 简介 什么是 GAN 什么是 DCGAN 参数说明 数据集处理 权重初始化 生成器 判别器 损失函数和优 ...

  7. TVbox蜂蜜影视_v3.1.6:智能电视观影新选择,简洁界面与强大功能兼具

    蜂蜜影视是一款基于猫影视开源项目 CatVodTVJarLoader 开发的智能电视软件,专为追求简洁与高效观影体验的用户设计.该软件从零开始编写,界面清爽,操作流畅,特别适合在智能电视上使用.其最大 ...

  8. HTTP请求中包含账号密码

    如果你需要在HTTP请求中包含账号密码,你可以使用基本的HTTP身份验证.在C#中,你可以通过设置 HttpClient 的 DefaultRequestHeaders 来添加身份验证信息.以下是修改 ...

  9. Web前端入门第 9 问:HTML 块级元素,内联块元素,内联元素三者有什么区别?

    HELLO,这里是大熊学习前端开发的入门笔记. 本系列笔记基于 windows 系统. HTML 中的元素根据其默认的 显示类型 主要分为三类:块级元素.内联元素 和 内联块元素. 它们的核心区别在于 ...

  10. leaflet生成地图封装成jquery插件使用

    公司业务里一直都有使用leaflet地图插件来做地图展示.绘图等操作.公司有个项目已经有好几年了,由于项目原因一直在使用,今年由于google 地图 api过期,导致已经使用的地图无法加载.我作为现在 ...