動態

詳情 返回 返回

開源項目YtyMark文本編輯器--UI界面相關功能(關於設計模式的實戰運用) - 動態 詳情

🙌開源項目地址

🌍 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. 界面截圖預覽

白天模式的截圖:

image

夜間模式的截圖:

image

✏️4. 總結

YtyMark 編輯器界面UI相關功能,將多種設計模式融入到實際應用中,從實踐中積累程序設計經驗。

更多詳細內容可以前往筆者微信公眾號回覆:設計模式,來獲取,後續有關設計模式的新資料都可以從這個入口獲取到。

  • 秘籍1設計模式手冊:《掌握設計模式:23種經典模式實踐、選擇、價值與思想》

  • 秘籍2練手項目:設計模式實戰項目--markdown文本編輯器軟件開發(已開源

image

查看往期設計模式文章的:設計模式

超實用的SpringAOP實戰之日誌記錄

2023年下半年軟考考試重磅消息

通過軟考後卻領取不到實體證書?

計算機算法設計與分析(第5版)

Java全棧學習路線、學習資源和麪試題一條龍

軟考證書=職稱證書?

軟考中級--軟件設計師毫無保留的備考分享

三連支持!!!

user avatar shanliangdeshou_ccwzfd 頭像
點贊 1 用戶, 點贊了這篇動態!
點贊

Add a new 評論

Some HTML is okay.