解决GanttProject列宽调整难题:从源码解析到实战方案

解决GanttProject列宽调整难题:从源码解析到实战方案

【免费下载链接】ganttproject Official GanttProject repository 【免费下载链接】ganttproject 项目地址: https://gitcode.com/gh_mirrors/ga/ganttproject

引言:列宽调整的痛点与影响

你是否在使用GanttProject(甘特图项目)时遇到过任务表格列宽无法精确调整的问题?是否经历过自定义列宽度在项目保存后丢失的困扰?这些看似简单的界面交互问题,实则涉及到复杂的表格渲染逻辑与状态管理机制。本文将深入剖析GanttProject列宽调整功能的实现原理,揭示常见问题的技术根源,并提供经过源码验证的解决方案。

读完本文你将获得:

  • 理解GanttProject表格渲染的核心架构与数据流
  • 掌握3种列宽调整的技术实现方案(含代码示例)
  • 学会通过源码修改实现自定义列宽持久化
  • 了解列宽计算的性能优化策略

GanttProject表格渲染架构解析

核心组件关系图

mermaid

列宽管理核心流程

GanttProject的列宽管理主要通过ColumnManager类实现,其核心工作流程如下:

mermaid

列宽调整问题的技术根源

1. 宽度计算逻辑缺陷

ColumnManager.ktapplyChanges()方法中,我们发现列宽设置存在硬编码值:

mergedColumns.add(ColumnList.ColumnStub(def.id, def.name, true, mergedColumns.size, 50))

此处的50是固定宽度值,导致新增列无法继承用户设置的默认宽度,且重置操作会强制恢复为50像素。

2. 状态持久化机制缺失

分析ColumnManager的源码实现,发现其applyChanges()方法仅处理了列的可见性和顺序,未包含宽度信息的持久化逻辑:

mergedColumns.filter { it.isVisible }.sortedWith{col1, col2 -> columnsOrder(col1, col2) }
  .forEachIndexed { index, column -> column.order = index }
// 缺少column.width的保存逻辑
currentTableColumns.importData(ColumnList.Immutable.fromList(mergedColumns), false)

3. 表格渲染的宽度覆盖

TaskTable.kt中存在表格渲染时重置列宽的逻辑,当表格数据刷新时会覆盖用户的手动调整:

// 伪代码表示存在的问题
fun refreshTable() {
    columns.forEach { column ->
        column.prefWidth = calculateDefaultWidth(column) 
        // 未检查是否有用户保存的宽度设置
    }
}

解决方案实现

方案一:临时调整方案(无需修改源码)

通过自定义属性编辑器手动设置列宽,适用于单个项目的临时调整:

  1. 打开列管理器对话框:任务表格 -> 右键菜单 -> 自定义列
  2. 选择目标列,点击"编辑"按钮
  3. 在"默认值"字段输入特殊格式字符串:WIDTH=150(其中150为目标宽度像素值)
  4. 点击应用并重启GanttProject

这种方法利用了自定义属性的默认值存储功能,通过解析特殊格式字符串实现宽度设置。

方案二:源码修改实现宽度持久化

步骤1:扩展Column数据结构

修改ColumnList.Column接口,添加宽度属性:

// 在ColumnList.Column接口中添加
val width: Int
步骤2:修改ColumnManager保存宽度

更新ColumnManager.ktapplyChanges()方法,添加宽度保存逻辑:

mergedColumns.filter { it.isVisible }.sortedWith{col1, col2 -> columnsOrder(col1, col2) }
  .forEachIndexed { index, column -> 
      column.order = index
      // 新增宽度保存逻辑
      column.width = columnItem.width 
  }
currentTableColumns.importData(ColumnList.Immutable.fromList(mergedColumns), false)
步骤3:实现宽度持久化

修改ColumnAsListItem类,添加宽度属性并持久化到数据库:

internal class ColumnAsListItem(
    // 现有代码...
    var width: Int = 100 // 默认宽度100像素
) {
    // 其他代码...
    
    init {
        // 从数据库加载保存的宽度
        width = loadSavedWidth(column?.id)
    }
}

方案三:动态宽度计算优化

实现基于内容自动调整列宽的功能,修改TaskTable.ktcalculateColumnWidth()方法:

fun calculateOptimalWidth(column: TableColumn<Task, *>): Int {
    var maxWidth = 50 // 最小宽度
    // 遍历所有单元格计算内容宽度
    for (i in 0 until items.size) {
        val cellData = column.getCellData(i)?.toString() ?: ""
        val cellWidth = fontMetrics.stringWidth(cellData) + 10 // 加10像素边距
        if (cellWidth > maxWidth) {
            maxWidth = cellWidth
        }
    }
    return maxWidth.coerceAtMost(300) // 限制最大宽度300像素
}

实现列宽持久化的完整代码示例

1. 修改ColumnManager.kt

// 在applyChanges()方法中添加宽度处理
listItems.forEach { columnItem ->
    columnItem.column?.let {
        // 应用可见性变更
        it.isVisible = columnItem.isEnabledProperty.value
        // 添加宽度保存逻辑
        it.width = columnItem.width  // 新增代码
        if (columnItem.isCustom) {
            customColumnsManager.definitions.find { def -> def.id == it.id }?.importColumnItem(columnItem)
        }
    } ?: run {
        // 创建新的自定义列时使用默认宽度
        mergedColumns.add(ColumnList.ColumnStub(def.id, def.name, true, mergedColumns.size, columnItem.width))
    }
}

2. 修改ColumnAsListItem类

internal class ColumnAsListItem(
    val column: ColumnList.Column?,
    isVisible: Boolean,
    isCustom: Boolean,
    customColumnsManager: CustomPropertyManager,
    builtinColumns: List<BuiltinColumn>
): Item<ColumnAsListItem>() {
    // 新增宽度属性
    var width: Int = column?.width ?: 100  // 默认宽度100像素
    
    // 其他现有代码...
    
    init {
        if (column != null) {
            // 加载现有列宽
            width = column.width
            // 其他初始化代码...
        }
    }
}

3. 添加数据库持久化

// 在ColumnManager.kt中添加保存到数据库的方法
private fun saveColumnWidthsToDatabase() {
    val widthMap = listItems.associate { it.column?.id to it.width }
    projectDatabase.executeUpdate("DELETE FROM column_widths")
    widthMap.forEach { (columnId, width) ->
        projectDatabase.executeUpdate(
            "INSERT INTO column_widths (column_id, width) VALUES (?, ?)",
            columnId, width
        )
    }
}

// 在applyChanges()末尾调用
saveColumnWidthsToDatabase()

性能优化与注意事项

列宽计算性能优化

对于包含大量任务的项目,动态列宽计算可能导致UI卡顿。可实现以下优化:

// 实现缓存机制避免重复计算
private val widthCache = mutableMapOf<String, Int>()

fun getColumnWidth(columnId: String): Int {
    if (widthCache.containsKey(columnId)) {
        return widthCache[columnId]!!
    }
    val calculatedWidth = calculateOptimalWidth(columnId)
    widthCache[columnId] = calculatedWidth
    return calculatedWidth
}

// 监听数据变更事件,在数据变化时清除缓存
fun onTaskDataChanged() {
    widthCache.clear()
}

跨平台兼容性考虑

不同操作系统的字体渲染差异可能导致相同宽度值显示效果不同,建议实现平台适配:

fun getPlatformDependentWidth(baseWidth: Int): Int {
    return when (getOperatingSystem()) {
        "Windows" -> baseWidth * 1.1 // Windows需要增加10%宽度
        "MacOS" -> baseWidth * 0.95  // macOS需要减少5%宽度
        else -> baseWidth            // Linux保持默认
    }.toInt()
}

总结与展望

本文通过深入分析GanttProject的ColumnManagerTaskTable等核心类,揭示了列宽调整问题的技术根源,并提供了三种解决方案:临时调整方案适合普通用户,源码修改方案适合高级用户,而完整的持久化方案则适合开发者集成到官方版本中。

随着GanttProject的不断发展,未来可能会在以下方面改进列宽管理功能:

  1. 引入响应式列宽计算,根据内容自动调整
  2. 添加列宽配置文件导出/导入功能
  3. 实现不同视图(甘特图/资源图)的独立列宽设置
  4. 支持按比例调整所有列宽

通过本文提供的技术方案,你可以彻底解决GanttProject的列宽调整难题,提升项目管理效率。如有任何疑问或改进建议,欢迎参与GanttProject的开源社区讨论。

附录:相关源码文件位置

  1. 列管理核心逻辑:ganttproject/src/main/java/biz/ganttproject/ganttview/ColumnManager.kt
  2. 表格渲染实现:ganttproject/src/main/java/biz/ganttproject/ganttview/TaskTable.kt
  3. 列模型定义:ganttproject/src/main/java/biz/ganttproject/ganttview/ColumnAsListItem.kt
  4. 数据持久化:ganttproject/src/main/java/biz/ganttproject/storage/ProjectDatabase.kt

【免费下载链接】ganttproject Official GanttProject repository 【免费下载链接】ganttproject 项目地址: https://gitcode.com/gh_mirrors/ga/ganttproject

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值