This is an automated email from the ASF dual-hosted git repository.

psxjoy pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fesod.git


The following commit(s) were added to refs/heads/main by this push:
     new 49790895 feat: support freeze pane via FreezePane or register 
WriteHandler on write (#916)
49790895 is described below

commit 497908955a979820f5813f31548e4ca7cd9a5cb4
Author: Bengbengbalabalabeng 
<[email protected]>
AuthorDate: Wed Jun 10 21:14:24 2026 +0800

    feat: support freeze pane via FreezePane or register WriteHandler on write 
(#916)
    
    * feat: support freeze pane via @FreezePane or register WriteHandler on 
write
    
    * refactor(code-style): refactor part of the code implementation
    
    - add default value for annotation fields
    - delete redundant references
    
    * feat: add overload constructor for SheetFreezePaneStrategy
    
    * docs: add @FreezePane annotation usage documentation
    
    * refactor: modify test method name
    
    * docs: update the usage documentation for the @FreezePane annotation
---
 .../sheet/annotation/write/style/FreezePane.java   |  55 ++++
 .../metadata/property/SheetFreezePaneProperty.java |  76 ++++++
 .../write/metadata/holder/AbstractWriteHolder.java |  11 +
 .../write/property/ExcelWriteHeadProperty.java     |   4 +
 .../sheet/write/style/SheetFreezePaneStrategy.java |  77 ++++++
 .../property/SheetFreezePanePropertyTest.java      |  87 +++++++
 .../write/freezepane/SheetFreezePaneTest.java      | 279 +++++++++++++++++++++
 .../write/style/SheetFreezePaneStrategyTest.java   | 116 +++++++++
 website/docs/sheet/help/annotation.md              |  11 +
 .../current/sheet/help/annotation.md               |  11 +
 10 files changed, 727 insertions(+)

diff --git 
a/fesod-sheet/src/main/java/org/apache/fesod/sheet/annotation/write/style/FreezePane.java
 
b/fesod-sheet/src/main/java/org/apache/fesod/sheet/annotation/write/style/FreezePane.java
new file mode 100644
index 00000000..225f8d56
--- /dev/null
+++ 
b/fesod-sheet/src/main/java/org/apache/fesod/sheet/annotation/write/style/FreezePane.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.fesod.sheet.annotation.write.style;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * An annotation used to define a freeze pane for an Excel sheet.
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface FreezePane {
+
+    /**
+     * Horizontal position of split.
+     */
+    int colSplit() default 0;
+
+    /**
+     * Vertical position of split.
+     */
+    int rowSplit() default 0;
+
+    /**
+     * Left column visible in right pane. By default, it's equal to colSplit.
+     */
+    int leftmostColumn() default -1;
+
+    /**
+     * Top row visible in bottom pane. By default, it's equal to rowSplit.
+     */
+    int topRow() default -1;
+}
diff --git 
a/fesod-sheet/src/main/java/org/apache/fesod/sheet/metadata/property/SheetFreezePaneProperty.java
 
b/fesod-sheet/src/main/java/org/apache/fesod/sheet/metadata/property/SheetFreezePaneProperty.java
new file mode 100644
index 00000000..4d8490fb
--- /dev/null
+++ 
b/fesod-sheet/src/main/java/org/apache/fesod/sheet/metadata/property/SheetFreezePaneProperty.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.fesod.sheet.metadata.property;
+
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.Setter;
+import org.apache.fesod.sheet.annotation.write.style.FreezePane;
+
+/**
+ * Property configuration for an Excel sheet freeze pane.
+ * <p>
+ * This class holds the configuration details required to create a freeze pane.
+ * It is typically built from the {@link FreezePane} annotation.
+ */
+@Getter
+@Setter
+@EqualsAndHashCode
+public class SheetFreezePaneProperty {
+
+    /**
+     * Horizontal position of split.
+     */
+    private int colSplit;
+
+    /**
+     * Vertical position of split.
+     */
+    private int rowSplit;
+
+    /**
+     * Left column visible in right pane.
+     */
+    private int leftmostColumn;
+
+    /**
+     * Top row visible in bottom pane
+     */
+    private int topRow;
+
+    public static SheetFreezePaneProperty build(FreezePane freezePane) {
+        if (freezePane == null) {
+            return null;
+        }
+        SheetFreezePaneProperty result = new SheetFreezePaneProperty();
+        result.setColSplit(freezePane.colSplit());
+        result.setRowSplit(freezePane.rowSplit());
+        result.setLeftmostColumn(getOrDefault(freezePane.leftmostColumn(), 
freezePane.colSplit()));
+        result.setTopRow(getOrDefault(freezePane.topRow(), 
freezePane.rowSplit()));
+        return result;
+    }
+
+    private static Integer getOrDefault(Integer value, Integer defaultValue) {
+        if (value == -1) {
+            return defaultValue;
+        }
+        return value;
+    }
+}
diff --git 
a/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/metadata/holder/AbstractWriteHolder.java
 
b/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/metadata/holder/AbstractWriteHolder.java
index be35c494..293f18ad 100644
--- 
a/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/metadata/holder/AbstractWriteHolder.java
+++ 
b/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/metadata/holder/AbstractWriteHolder.java
@@ -51,6 +51,7 @@ import 
org.apache.fesod.sheet.metadata.property.ExcelContentProperty;
 import org.apache.fesod.sheet.metadata.property.LoopMergeProperty;
 import org.apache.fesod.sheet.metadata.property.OnceAbsoluteMergeProperty;
 import org.apache.fesod.sheet.metadata.property.RowHeightProperty;
+import org.apache.fesod.sheet.metadata.property.SheetFreezePaneProperty;
 import org.apache.fesod.sheet.write.handler.CellWriteHandler;
 import org.apache.fesod.sheet.write.handler.DefaultWriteHandlerLoader;
 import org.apache.fesod.sheet.write.handler.RowWriteHandler;
@@ -68,6 +69,7 @@ import 
org.apache.fesod.sheet.write.metadata.WriteBasicParameter;
 import org.apache.fesod.sheet.write.metadata.style.WriteCellStyle;
 import org.apache.fesod.sheet.write.property.ExcelWriteHeadProperty;
 import org.apache.fesod.sheet.write.style.AbstractVerticalCellStyleStrategy;
+import org.apache.fesod.sheet.write.style.SheetFreezePaneStrategy;
 import 
org.apache.fesod.sheet.write.style.column.AbstractHeadColumnWidthStyleStrategy;
 import org.apache.fesod.sheet.write.style.row.SimpleRowHeightStyleStrategy;
 
@@ -342,6 +344,7 @@ public abstract class AbstractWriteHolder extends 
AbstractHolder implements Writ
         dealStyle(handlerList);
         dealRowHigh(handlerList);
         dealOnceAbsoluteMerge(handlerList);
+        dealSheetFreezePane(handlerList);
     }
 
     private void dealStyle(List<WriteHandler> handlerList) {
@@ -387,6 +390,14 @@ public abstract class AbstractWriteHolder extends 
AbstractHolder implements Writ
         handlerList.add(new 
OnceAbsoluteMergeStrategy(onceAbsoluteMergeProperty));
     }
 
+    private void dealSheetFreezePane(List<WriteHandler> handlerList) {
+        SheetFreezePaneProperty freezePaneProperty = 
getExcelWriteHeadProperty().getFreezePaneProperty();
+        if (freezePaneProperty == null) {
+            return;
+        }
+        handlerList.add(new SheetFreezePaneStrategy(freezePaneProperty));
+    }
+
     private void dealRowHigh(List<WriteHandler> handlerList) {
         RowHeightProperty headRowHeightProperty = 
getExcelWriteHeadProperty().getHeadRowHeightProperty();
         RowHeightProperty contentRowHeightProperty = 
getExcelWriteHeadProperty().getContentRowHeightProperty();
diff --git 
a/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/property/ExcelWriteHeadProperty.java
 
b/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/property/ExcelWriteHeadProperty.java
index 92cbbea2..fb97d78c 100644
--- 
a/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/property/ExcelWriteHeadProperty.java
+++ 
b/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/property/ExcelWriteHeadProperty.java
@@ -38,6 +38,7 @@ import lombok.Setter;
 import org.apache.fesod.sheet.annotation.write.style.ColumnWidth;
 import org.apache.fesod.sheet.annotation.write.style.ContentLoopMerge;
 import org.apache.fesod.sheet.annotation.write.style.ContentRowHeight;
+import org.apache.fesod.sheet.annotation.write.style.FreezePane;
 import org.apache.fesod.sheet.annotation.write.style.HeadFontStyle;
 import org.apache.fesod.sheet.annotation.write.style.HeadRowHeight;
 import org.apache.fesod.sheet.annotation.write.style.HeadStyle;
@@ -53,6 +54,7 @@ import org.apache.fesod.sheet.metadata.property.FontProperty;
 import org.apache.fesod.sheet.metadata.property.LoopMergeProperty;
 import org.apache.fesod.sheet.metadata.property.OnceAbsoluteMergeProperty;
 import org.apache.fesod.sheet.metadata.property.RowHeightProperty;
+import org.apache.fesod.sheet.metadata.property.SheetFreezePaneProperty;
 import org.apache.fesod.sheet.metadata.property.StyleProperty;
 
 /**
@@ -67,6 +69,7 @@ public class ExcelWriteHeadProperty extends ExcelHeadProperty 
{
     private RowHeightProperty headRowHeightProperty;
     private RowHeightProperty contentRowHeightProperty;
     private OnceAbsoluteMergeProperty onceAbsoluteMergeProperty;
+    private SheetFreezePaneProperty freezePaneProperty;
 
     public ExcelWriteHeadProperty(
             ConfigurationHolder configurationHolder, Class<?> headClazz, 
List<List<String>> head) {
@@ -78,6 +81,7 @@ public class ExcelWriteHeadProperty extends ExcelHeadProperty 
{
         this.contentRowHeightProperty = 
RowHeightProperty.build(headClazz.getAnnotation(ContentRowHeight.class));
         this.onceAbsoluteMergeProperty =
                 
OnceAbsoluteMergeProperty.build(headClazz.getAnnotation(OnceAbsoluteMerge.class));
+        this.freezePaneProperty = 
SheetFreezePaneProperty.build(headClazz.getAnnotation(FreezePane.class));
 
         ColumnWidth parentColumnWidth = 
headClazz.getAnnotation(ColumnWidth.class);
         HeadStyle parentHeadStyle = headClazz.getAnnotation(HeadStyle.class);
diff --git 
a/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/style/SheetFreezePaneStrategy.java
 
b/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/style/SheetFreezePaneStrategy.java
new file mode 100644
index 00000000..f54b1c66
--- /dev/null
+++ 
b/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/style/SheetFreezePaneStrategy.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.fesod.sheet.write.style;
+
+import org.apache.fesod.common.util.ValidateUtils;
+import org.apache.fesod.sheet.metadata.property.SheetFreezePaneProperty;
+import org.apache.fesod.sheet.write.handler.SheetWriteHandler;
+import org.apache.fesod.sheet.write.metadata.holder.WriteSheetHolder;
+import org.apache.fesod.sheet.write.metadata.holder.WriteWorkbookHolder;
+
+/**
+ * A strategy for creating a freeze pane. Any existing freeze pane or split 
pane is overwritten.
+ */
+public class SheetFreezePaneStrategy implements SheetWriteHandler {
+
+    /**
+     * Horizontal position of split.
+     */
+    private final int colSplit;
+
+    /**
+     * Vertical position of split.
+     */
+    private final int rowSplit;
+
+    /**
+     * Left column visible in right pane.
+     */
+    private final int leftmostColumn;
+
+    /**
+     * Top row visible in bottom pane
+     */
+    private final int topRow;
+
+    public SheetFreezePaneStrategy(SheetFreezePaneProperty property) {
+        this(property.getColSplit(), property.getRowSplit(), 
property.getLeftmostColumn(), property.getTopRow());
+    }
+
+    public SheetFreezePaneStrategy(int colSplit, int rowSplit) {
+        this(colSplit, rowSplit, colSplit, rowSplit);
+    }
+
+    public SheetFreezePaneStrategy(int colSplit, int rowSplit, int 
leftmostColumn, int topRow) {
+        ValidateUtils.isTrue(colSplit >= 0, "colSplit must be >= 0");
+        ValidateUtils.isTrue(rowSplit >= 0, "rowSplit must be >= 0");
+        ValidateUtils.isTrue(leftmostColumn >= 0, "leftmostColumn must be >= 
0");
+        ValidateUtils.isTrue(topRow >= 0, "topRow must be >= 0");
+
+        this.colSplit = colSplit;
+        this.rowSplit = rowSplit;
+        this.leftmostColumn = leftmostColumn;
+        this.topRow = topRow;
+    }
+
+    @Override
+    public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, 
WriteSheetHolder writeSheetHolder) {
+        writeSheetHolder.getSheet().createFreezePane(colSplit, rowSplit, 
leftmostColumn, topRow);
+    }
+}
diff --git 
a/fesod-sheet/src/test/java/org/apache/fesod/sheet/metadata/property/SheetFreezePanePropertyTest.java
 
b/fesod-sheet/src/test/java/org/apache/fesod/sheet/metadata/property/SheetFreezePanePropertyTest.java
new file mode 100644
index 00000000..3d13fda2
--- /dev/null
+++ 
b/fesod-sheet/src/test/java/org/apache/fesod/sheet/metadata/property/SheetFreezePanePropertyTest.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.fesod.sheet.metadata.property;
+
+import org.apache.fesod.sheet.annotation.write.style.FreezePane;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+class SheetFreezePanePropertyTest {
+
+    @Test
+    void build_returnsNull_whenAnnotationIsNull() {
+        Assertions.assertNull(SheetFreezePaneProperty.build(null));
+    }
+
+    @Test
+    void build_mapsColSplitAndRowSplit() {
+        FreezePane annotation = 
FreezePaneClass.class.getAnnotation(FreezePane.class);
+        SheetFreezePaneProperty property = 
SheetFreezePaneProperty.build(annotation);
+
+        Assertions.assertNotNull(property);
+        Assertions.assertEquals(2, property.getColSplit());
+        Assertions.assertEquals(3, property.getRowSplit());
+    }
+
+    @Test
+    void build_usesDefaults_whenLeftmostColumnAndTopRowNotSet() {
+        FreezePane annotation = 
DefaultsClass.class.getAnnotation(FreezePane.class);
+        SheetFreezePaneProperty property = 
SheetFreezePaneProperty.build(annotation);
+
+        Assertions.assertNotNull(property);
+        Assertions.assertEquals(4, property.getLeftmostColumn());
+        Assertions.assertEquals(5, property.getTopRow());
+    }
+
+    @Test
+    void build_usesExplicitValues_whenLeftmostColumnAndTopRowSet() {
+        FreezePane annotation = 
ExplicitValuesClass.class.getAnnotation(FreezePane.class);
+        SheetFreezePaneProperty property = 
SheetFreezePaneProperty.build(annotation);
+
+        Assertions.assertNotNull(property);
+        Assertions.assertEquals(10, property.getLeftmostColumn());
+        Assertions.assertEquals(20, property.getTopRow());
+    }
+
+    @Test
+    void build_zeroColSplitAndRowSplit() {
+        FreezePane annotation = 
ZeroSplitClass.class.getAnnotation(FreezePane.class);
+        SheetFreezePaneProperty property = 
SheetFreezePaneProperty.build(annotation);
+
+        Assertions.assertNotNull(property);
+        Assertions.assertEquals(0, property.getColSplit());
+        Assertions.assertEquals(0, property.getRowSplit());
+        // When not specified, defaults should fall back to 0 (the split 
values)
+        Assertions.assertEquals(0, property.getLeftmostColumn());
+        Assertions.assertEquals(0, property.getTopRow());
+    }
+
+    @FreezePane(colSplit = 2, rowSplit = 3)
+    static class FreezePaneClass {}
+
+    @FreezePane(colSplit = 4, rowSplit = 5)
+    static class DefaultsClass {}
+
+    @FreezePane(colSplit = 1, rowSplit = 1, leftmostColumn = 10, topRow = 20)
+    static class ExplicitValuesClass {}
+
+    @FreezePane(colSplit = 0, rowSplit = 0)
+    static class ZeroSplitClass {}
+}
diff --git 
a/fesod-sheet/src/test/java/org/apache/fesod/sheet/write/freezepane/SheetFreezePaneTest.java
 
b/fesod-sheet/src/test/java/org/apache/fesod/sheet/write/freezepane/SheetFreezePaneTest.java
new file mode 100644
index 00000000..c1695e15
--- /dev/null
+++ 
b/fesod-sheet/src/test/java/org/apache/fesod/sheet/write/freezepane/SheetFreezePaneTest.java
@@ -0,0 +1,279 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.fesod.sheet.write.freezepane;
+
+import java.io.File;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import lombok.Getter;
+import lombok.Setter;
+import org.apache.fesod.sheet.ExcelWriter;
+import org.apache.fesod.sheet.FesodSheet;
+import org.apache.fesod.sheet.annotation.ExcelProperty;
+import org.apache.fesod.sheet.annotation.write.style.FreezePane;
+import org.apache.fesod.sheet.write.style.SheetFreezePaneStrategy;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.ss.usermodel.WorkbookFactory;
+import org.apache.poi.ss.util.PaneInformation;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+class SheetFreezePaneTest {
+
+    private File file07;
+    private File file03;
+
+    @BeforeEach
+    void setup(@TempDir Path dir) {
+        this.file07 = createTmpFile(dir, "writeFreezepane07.xlsx");
+        this.file03 = createTmpFile(dir, "writeFreezepane03.xls");
+    }
+
+    private File createTmpFile(Path dir, String filename) {
+        return new File(dir.resolve(filename).toString());
+    }
+
+    interface FreezePaneModel {
+
+        void setStr1(String str1);
+
+        String getStr1();
+
+        void setStr2(String str2);
+
+        String getStr2();
+    }
+
+    @Getter
+    @Setter
+    @FreezePane(colSplit = 2, rowSplit = 1)
+    static class FreezeTwoColsOneRowData implements FreezePaneModel {
+        @ExcelProperty("Str 1")
+        private String str1;
+
+        @ExcelProperty("Str 2")
+        private String str2;
+    }
+
+    @Getter
+    @Setter
+    @FreezePane(rowSplit = 1)
+    static class FreezeFirstRowOnlyData implements FreezePaneModel {
+        @ExcelProperty("Str 1")
+        private String str1;
+
+        @ExcelProperty("Str 2")
+        private String str2;
+    }
+
+    @Getter
+    @Setter
+    @FreezePane(colSplit = 1)
+    static class FreezeFirstColOnlyData implements FreezePaneModel {
+        @ExcelProperty("Str 1")
+        private String str1;
+
+        @ExcelProperty("Str 2")
+        private String str2;
+    }
+
+    @Getter
+    @Setter
+    @FreezePane(colSplit = 2, rowSplit = 3, leftmostColumn = 5, topRow = 10)
+    static class FreezePaneData implements FreezePaneModel {
+        @ExcelProperty("Str 1")
+        private String str1;
+
+        @ExcelProperty("Str 2")
+        private String str2;
+    }
+
+    @Getter
+    @Setter
+    static class NoFreezePaneData implements FreezePaneModel {
+        @ExcelProperty("Str 1")
+        private String str1;
+
+        @ExcelProperty("Str 2")
+        private String str2;
+    }
+
+    private <T extends FreezePaneModel> List<FreezePaneModel> 
buildData(Class<T> clazz, int rows) {
+        try {
+            List<FreezePaneModel> list = new ArrayList<>();
+            for (int i = 0; i < rows; i++) {
+                FreezePaneModel obj = 
clazz.getDeclaredConstructor().newInstance();
+                obj.setStr1("String1-" + i);
+                obj.setStr2("String2-" + i);
+                list.add(obj);
+            }
+            return list;
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private void assertFreezePane(
+            File file, int expectedColSplit, int expectedRowSplit, int 
expectedLeftmost, int expectedTopRow)
+            throws Exception {
+        try (Workbook workbook = WorkbookFactory.create(file)) {
+            Sheet sheet = workbook.getSheetAt(0);
+            PaneInformation pane = sheet.getPaneInformation();
+            if (expectedColSplit == 0 && expectedRowSplit == 0) {
+                Assertions.assertNull(pane, "Expected no freeze pane");
+                return;
+            }
+            Assertions.assertNotNull(pane, "Expected freeze pane to be set");
+            Assertions.assertEquals(
+                    expectedColSplit, pane.getVerticalSplitPosition(), 
"Vertical split (colSplit) mismatch");
+            Assertions.assertEquals(
+                    expectedRowSplit, pane.getHorizontalSplitPosition(), 
"Horizontal split (rowSplit) mismatch");
+            Assertions.assertEquals(expectedLeftmost, 
pane.getVerticalSplitLeftColumn(), "Leftmost column mismatch");
+            Assertions.assertEquals(expectedTopRow, 
pane.getHorizontalSplitTopRow(), "Top row mismatch");
+        }
+    }
+
+    @Test
+    void should_freezeTwoColsOneRow_whenAnnotationColSplit2RowSplit1() throws 
Exception {
+        runFreezeTwoColsOneRow(file07);
+        runFreezeTwoColsOneRow(file03);
+    }
+
+    private void runFreezeTwoColsOneRow(File file) throws Exception {
+        FesodSheet.write(file, FreezeTwoColsOneRowData.class)
+                .sheet()
+                .doWrite(buildData(FreezeTwoColsOneRowData.class, 5));
+        assertFreezePane(file, 2, 1, 2, 1);
+    }
+
+    @Test
+    void should_freezeHeaderRowOnly_whenAnnotationColSplit0RowSplit1() throws 
Exception {
+        runFreezeHeaderRowOnly(file07);
+        runFreezeHeaderRowOnly(file03);
+    }
+
+    private void runFreezeHeaderRowOnly(File file) throws Exception {
+        FesodSheet.write(file, FreezeFirstRowOnlyData.class)
+                .sheet()
+                .doWrite(buildData(FreezeFirstRowOnlyData.class, 5));
+        assertFreezePane(file, 0, 1, 0, 1);
+    }
+
+    @Test
+    void should_freezeFirstColOnly_whenAnnotationColSplit1RowSplit0() throws 
Exception {
+        runFreezeFirstColOnly(file07);
+        runFreezeFirstColOnly(file03);
+    }
+
+    private void runFreezeFirstColOnly(File file) throws Exception {
+        FesodSheet.write(file, FreezeFirstColOnlyData.class)
+                .sheet()
+                .doWrite(buildData(FreezeFirstColOnlyData.class, 5));
+        assertFreezePane(file, 1, 0, 1, 0);
+    }
+
+    @Test
+    void should_useExplicitPanePositions_whenAnnotationSetsLeftmostAndTopRow() 
throws Exception {
+        runUseExplicitPanePositions(file07);
+        runUseExplicitPanePositions(file03);
+    }
+
+    private void runUseExplicitPanePositions(File file) throws Exception {
+        FesodSheet.write(file, 
FreezePaneData.class).sheet().doWrite(buildData(FreezePaneData.class, 5));
+        assertFreezePane(file, 2, 3, 5, 10);
+    }
+
+    @Test
+    void should_notSetFreezePane_whenAnnotationAbsent() throws Exception {
+        runNotSetFreezePane(file07);
+        runNotSetFreezePane(file03);
+    }
+
+    private void runNotSetFreezePane(File file) throws Exception {
+        FesodSheet.write(file, 
NoFreezePaneData.class).sheet().doWrite(buildData(NoFreezePaneData.class, 5));
+        try (Workbook workbook = WorkbookFactory.create(file)) {
+            Sheet sheet = workbook.getSheetAt(0);
+            Assertions.assertNull(sheet.getPaneInformation());
+        }
+    }
+
+    @Test
+    void should_applyFreezePane_whenRegisteredViaWriteHandler() throws 
Exception {
+        runApplyFreezePaneStrategy(file07, false);
+        runApplyFreezePaneStrategy(file03, false);
+        runApplyFreezePaneStrategy(file07, true);
+        runApplyFreezePaneStrategy(file03, true);
+    }
+
+    private void runApplyFreezePaneStrategy(File file, boolean useSimplify) 
throws Exception {
+        SheetFreezePaneStrategy strategy;
+        if (useSimplify) {
+            strategy = new SheetFreezePaneStrategy(1, 1);
+        } else {
+            strategy = new SheetFreezePaneStrategy(1, 1, 1, 1);
+        }
+
+        FesodSheet.write(file, NoFreezePaneData.class)
+                .registerWriteHandler(strategy)
+                .sheet()
+                .doWrite(buildData(NoFreezePaneData.class, 5));
+        assertFreezePane(file, 1, 1, 1, 1);
+    }
+
+    @Test
+    void should_applyDifferentFreezePanes_whenMultipleSheets() throws 
Exception {
+        runApplyDifferentFreezePanes(file07);
+        runApplyDifferentFreezePanes(file03);
+    }
+
+    private void runApplyDifferentFreezePanes(File file) throws Exception {
+        try (ExcelWriter excelWriter = FesodSheet.write(file).build()) {
+            excelWriter.write(
+                    buildData(FreezeTwoColsOneRowData.class, 3),
+                    FesodSheet.writerSheet(0)
+                            .head(FreezeTwoColsOneRowData.class)
+                            .build());
+
+            excelWriter.write(
+                    buildData(FreezeFirstRowOnlyData.class, 3),
+                    
FesodSheet.writerSheet(1).head(FreezeFirstRowOnlyData.class).build());
+        }
+
+        try (Workbook workbook = WorkbookFactory.create(file)) {
+            // Sheet 0: colSplit=2, rowSplit=1
+            Sheet sheet0 = workbook.getSheetAt(0);
+            PaneInformation pane0 = sheet0.getPaneInformation();
+            Assertions.assertNotNull(pane0);
+            Assertions.assertEquals(2, pane0.getVerticalSplitPosition());
+            Assertions.assertEquals(1, pane0.getHorizontalSplitPosition());
+
+            // Sheet 1: colSplit=0, rowSplit=1
+            Sheet sheet1 = workbook.getSheetAt(1);
+            PaneInformation pane1 = sheet1.getPaneInformation();
+            Assertions.assertNotNull(pane1);
+            Assertions.assertEquals(0, pane1.getVerticalSplitPosition());
+            Assertions.assertEquals(1, pane1.getHorizontalSplitPosition());
+        }
+    }
+}
diff --git 
a/fesod-sheet/src/test/java/org/apache/fesod/sheet/write/style/SheetFreezePaneStrategyTest.java
 
b/fesod-sheet/src/test/java/org/apache/fesod/sheet/write/style/SheetFreezePaneStrategyTest.java
new file mode 100644
index 00000000..f89996ca
--- /dev/null
+++ 
b/fesod-sheet/src/test/java/org/apache/fesod/sheet/write/style/SheetFreezePaneStrategyTest.java
@@ -0,0 +1,116 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.fesod.sheet.write.style;
+
+import org.apache.fesod.sheet.metadata.property.SheetFreezePaneProperty;
+import org.apache.fesod.sheet.write.metadata.holder.WriteSheetHolder;
+import org.apache.fesod.sheet.write.metadata.holder.WriteWorkbookHolder;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+class SheetFreezePaneStrategyTest {
+
+    @Test
+    void constructor_throws_whenColSplitNegative() {
+        Assertions.assertThrows(IllegalArgumentException.class, () -> new 
SheetFreezePaneStrategy(-1, 0, 0, 0));
+    }
+
+    @Test
+    void constructor_throws_whenRowSplitNegative() {
+        Assertions.assertThrows(IllegalArgumentException.class, () -> new 
SheetFreezePaneStrategy(0, -1, 0, 0));
+    }
+
+    @Test
+    void constructor_throws_whenLeftmostColumnNegative() {
+        Assertions.assertThrows(IllegalArgumentException.class, () -> new 
SheetFreezePaneStrategy(0, 0, -1, 0));
+    }
+
+    @Test
+    void constructor_throws_whenTopRowNegative() {
+        Assertions.assertThrows(IllegalArgumentException.class, () -> new 
SheetFreezePaneStrategy(0, 0, 0, -1));
+    }
+
+    @Test
+    void constructor_acceptsAllZeros() {
+        Assertions.assertDoesNotThrow(() -> new SheetFreezePaneStrategy(0, 0, 
0, 0));
+        Assertions.assertDoesNotThrow(() -> new SheetFreezePaneStrategy(0, 0));
+    }
+
+    @Test
+    void constructor_acceptsPositiveValues() {
+        Assertions.assertDoesNotThrow(() -> new SheetFreezePaneStrategy(2, 3, 
4, 5));
+    }
+
+    @Test
+    void constructor_fromProperty_shouldNotThrowException() {
+        SheetFreezePaneProperty property = new SheetFreezePaneProperty();
+        property.setColSplit(1);
+        property.setRowSplit(2);
+        property.setLeftmostColumn(3);
+        property.setTopRow(4);
+
+        Assertions.assertDoesNotThrow(() -> new 
SheetFreezePaneStrategy(property));
+    }
+
+    @Test
+    void afterSheetCreate_createsFreezePaneWithCorrectParams() {
+        SheetFreezePaneStrategy strategy = new SheetFreezePaneStrategy(2, 3, 
4, 5);
+
+        WriteWorkbookHolder workbookHolder = 
Mockito.mock(WriteWorkbookHolder.class);
+        WriteSheetHolder sheetHolder = Mockito.mock(WriteSheetHolder.class);
+        Sheet sheet = Mockito.mock(Sheet.class);
+        Mockito.when(sheetHolder.getSheet()).thenReturn(sheet);
+
+        strategy.afterSheetCreate(workbookHolder, sheetHolder);
+
+        Mockito.verify(sheet).createFreezePane(2, 3, 4, 5);
+    }
+
+    @Test
+    void afterSheetCreate_withZeroValues() {
+        SheetFreezePaneStrategy strategy = new SheetFreezePaneStrategy(0, 0);
+
+        WriteWorkbookHolder workbookHolder = 
Mockito.mock(WriteWorkbookHolder.class);
+        WriteSheetHolder sheetHolder = Mockito.mock(WriteSheetHolder.class);
+        Sheet sheet = Mockito.mock(Sheet.class);
+        Mockito.when(sheetHolder.getSheet()).thenReturn(sheet);
+
+        strategy.afterSheetCreate(workbookHolder, sheetHolder);
+
+        Mockito.verify(sheet).createFreezePane(0, 0, 0, 0);
+    }
+
+    @Test
+    void afterSheetCreate_delegatesToSheetNotWorkbook() {
+        SheetFreezePaneStrategy strategy = new SheetFreezePaneStrategy(1, 1, 
1, 1);
+
+        WriteWorkbookHolder workbookHolder = 
Mockito.mock(WriteWorkbookHolder.class);
+        WriteSheetHolder sheetHolder = Mockito.mock(WriteSheetHolder.class);
+        Sheet sheet = Mockito.mock(Sheet.class);
+        Mockito.when(sheetHolder.getSheet()).thenReturn(sheet);
+
+        strategy.afterSheetCreate(workbookHolder, sheetHolder);
+
+        Mockito.verify(sheet).createFreezePane(1, 1, 1, 1);
+        Mockito.verifyNoInteractions(workbookHolder);
+    }
+}
diff --git a/website/docs/sheet/help/annotation.md 
b/website/docs/sheet/help/annotation.md
index 374511e0..19287de6 100644
--- a/website/docs/sheet/help/annotation.md
+++ b/website/docs/sheet/help/annotation.md
@@ -146,3 +146,14 @@ Defines a one-time absolute merge region. The parameters 
are as follows:
 | lastRowIndex     | -1            | The index of the last row to merge.       
   |
 | firstColumnIndex | -1            | The index of the first column to merge.   
   |
 | lastColumnIndex  | -1            | The index of the last column to merge.    
   |
+
+### `@FreezePane`
+
+Define a freeze pane for an Excel sheet. The parameters are as follows:
+
+| Name           | Default Value | Description                                 
                             |
+|----------------|---------------|--------------------------------------------------------------------------|
+| colSplit       | 0             | Horizontal position of freeze pane.         
                             |
+| rowSplit       | 0             | Vertical position of freeze pane.           
                             |
+| leftmostColumn | -1            | Left column visible in right pane. By 
default, it's equal to `colSplit`. |
+| topRow         | -1            | Top row visible in bottom pane. By default, 
it's equal to `rowSplit`.    |
diff --git 
a/website/i18n/zh-cn/docusaurus-plugin-content-docs/current/sheet/help/annotation.md
 
b/website/i18n/zh-cn/docusaurus-plugin-content-docs/current/sheet/help/annotation.md
index 27bc59b2..bcecf7f2 100644
--- 
a/website/i18n/zh-cn/docusaurus-plugin-content-docs/current/sheet/help/annotation.md
+++ 
b/website/i18n/zh-cn/docusaurus-plugin-content-docs/current/sheet/help/annotation.md
@@ -123,3 +123,14 @@ title: '注解'
 | lastRowIndex     | -1  | 合并区域的最后一行索引 |
 | firstColumnIndex | -1  | 合并区域的第一列索引  |
 | lastColumnIndex  | -1  | 合并区域的最后一列索引 |
+
+### `@FreezePane`
+
+为 Excel 工作表定义冻结窗格。具体参数如下:
+
+| 名称             | 默认值 | 描述                                 |
+|----------------|-----|------------------------------------|
+| colSplit       | 0   | 冻结窗格的水平位置(即需要冻结的列数)                |
+| rowSplit       | 0   | 冻结窗格的垂直位置(即需要冻结的行数)                |
+| leftmostColumn | -1  | 右侧窗格中可见的最左侧列。默认情况下,该值等于 `colSplit` |
+| topRow         | -1  | 底部窗格中可见的最顶部行。默认情况下,该值等于 `rowSplit` |


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]


Reply via email to