You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PyQt6标尺刻度方向修改:如何让刻度根部朝向视图内侧

PyQt6 Ruler控件:调整刻度根部位置与动态设置基准方向

以下是针对你的需求修改后的完整代码,同时附上关键修改说明:

from PyQt6.QtWidgets import QWidget, QApplication
from PyQt6.QtGui import QPainter, QPen, QColor, QFont
from PyQt6.QtCore import Qt, QPoint, QPointF

RULER_BREADTH = 20  # 补充定义标尺宽度常量

class Ruler(QWidget):
    def __init__(self, orientation=Qt.Orientation.Horizontal, base_direction=None, parent=None):
        super().__init__(parent)
        self._orientation = orientation
        # 设置默认基准方向
        if base_direction is None:
            self._base_direction = Qt.Edge.BottomEdge if orientation == Qt.Orientation.Horizontal else Qt.Edge.RightEdge
        else:
            self._base_direction = base_direction
        
        self._origin = 0.0
        self._unit = 1.0
        self._zoom = 1.0
        self._mouse_tracking = False
        self._cursor_pos = QPoint()
        self.setMouseTracking(True)
        
        f = QFont("Courier", 9)
        f.setStyleHint(QFont.StyleHint.TypeWriter)
        self.setFont(f)

        # 固定控件尺寸,预留标签空间
        if orientation == Qt.Orientation.Horizontal:
            self.setFixedHeight(RULER_BREADTH + 5)
        else:
            self.setFixedWidth(RULER_BREADTH + 5)

    def minimumSizeHint(self):
        return self.sizeHint()

    def sizeHint(self):
        if self._orientation == Qt.Orientation.Horizontal:
            return super().sizeHint().expandedTo(self.minimumSizeHint()).boundedTo(self.minimumSizeHint())
        return super().sizeHint().expandedTo(self.minimumSizeHint()).boundedTo(self.minimumSizeHint())

    def paintEvent(self, event):
        painter = QPainter(self)
        painter.setRenderHints(
            QPainter.RenderHint.Antialiasing |
            QPainter.RenderHint.TextAntialiasing
        )

        # 填充背景
        painter.fillRect(self.rect(), QColor(236, 233, 216))

        # 绘制不同层级刻度
        self._draw_scale(painter, mm_interval=1, tick_length=RULER_BREADTH / 3, draw_labels=False)
        self._draw_scale(painter, mm_interval=5, tick_length=RULER_BREADTH * 2 / 3, draw_labels=False)
        self._draw_scale(painter, mm_interval=10, tick_length=RULER_BREADTH, draw_labels=True)

        # 绘制鼠标位置指示器
        if self._mouse_tracking:
            painter.setOpacity(0.4)
            self._draw_mouse_tick(painter)
            painter.setOpacity(1.0)

        # 绘制内边框(刻度根部参考线)
        painter.setPen(QPen(Qt.GlobalColor.black, 2))
        if self._orientation == Qt.Orientation.Horizontal:
            line_start = self.rect().topLeft() if self._base_direction == Qt.Edge.TopEdge else self.rect().bottomLeft()
            line_end = self.rect().topRight() if self._base_direction == Qt.Edge.TopEdge else self.rect().bottomRight()
        else:
            line_start = self.rect().topLeft() if self._base_direction == Qt.Edge.LeftEdge else self.rect().topRight()
            line_end = self.rect().bottomLeft() if self._base_direction == Qt.Edge.LeftEdge else self.rect().bottomRight()
        painter.drawLine(line_start, line_end)

    def _draw_scale(self, painter, mm_interval, tick_length, draw_labels=False):
        is_horz = (self._orientation == Qt.Orientation.Horizontal)
        length = self.width() if is_horz else self.height()

        # 计算像素与毫米的转换比例
        screen = QApplication.primaryScreen()
        dpi = screen.logicalDotsPerInchX() if is_horz else screen.logicalDotsPerInchY()
        px_per_mm = dpi / 25.4
        step_pix = mm_interval * px_per_mm * self._zoom

        # 计算可见刻度范围
        first_tick = int(self._origin // step_pix) - 1
        last_tick = int((self._origin + length) // step_pix) + 1

        painter.setPen(QPen(Qt.GlobalColor.red, 1))
        for tick in range(first_tick, last_tick + 1):
            scene_pos = tick * step_pix
            screen_pos = scene_pos - self._origin
            if screen_pos < 0 or screen_pos > length:
                continue

            # 根据基准方向绘制刻度(根部对齐内边框)
            if is_horz:
                if self._base_direction == Qt.Edge.BottomEdge:
                    start_y = self.height()
                    end_y = self.height() - tick_length
                else:
                    start_y = 0
                    end_y = tick_length
                painter.drawLine(QPointF(screen_pos, start_y), QPointF(screen_pos, end_y))

                # 绘制刻度标签
                if draw_labels and (tick % (10 // mm_interval) == 0):
                    label = str(abs(tick * mm_interval))
                    painter.setPen(QPen(Qt.GlobalColor.black))
                    x = int(screen_pos + 2)
                    y = int(end_y - 10) if self._base_direction == Qt.Edge.BottomEdge else int(end_y + 10)
                    painter.drawText(x, y, label)
            else:
                if self._base_direction == Qt.Edge.RightEdge:
                    start_x = self.width()
                    end_x = self.width() - tick_length
                else:
                    start_x = 0
                    end_x = tick_length
                painter.drawLine(QPointF(start_x, screen_pos), QPointF(end_x, screen_pos))

                # 绘制刻度标签
                if draw_labels and (tick % (10 // mm_interval) == 0):
                    label = str(abs(tick * mm_interval))
                    painter.setPen(QPen(Qt.GlobalColor.black))
                    painter.save()
                    if self._base_direction == Qt.Edge.RightEdge:
                        tx, ty = int(end_x - 2), int(screen_pos + 2)
                        painter.translate(tx, ty)
                        painter.rotate(-90)
                    else:
                        tx, ty = int(end_x + 2), int(screen_pos + 2)
                        painter.translate(tx, ty)
                        painter.rotate(90)
                    painter.drawText(0, 0, label)
                    painter.restore()

    def _draw_mouse_tick(self, painter):
        if self._orientation == Qt.Orientation.Horizontal:
            x = self._cursor_pos.x()
            painter.drawLine(QPointF(x, 0), QPointF(x, self.height()))
        else:
            y = self._cursor_pos.y()
            painter.drawLine(QPointF(0, y), QPointF(self.width(), y))

    def mouseMoveEvent(self, event):
        self._cursor_pos = event.position().toPoint()
        self.update()
        super().mouseMoveEvent(event)

    def setOrigin(self, val):
        self._origin = val
        self.update()

    def setUnit(self, val):
        self._unit = val
        self.update()

    def setZoom(self, val):
        self._zoom = val
        self.update()

    def setMouseTrack(self, track: bool):
        self._mouse_tracking = track
        self.update()

    def setBaseDirection(self, direction: Qt.Edge):
        # 校验基准方向与标尺方向的匹配性
        if self._orientation == Qt.Orientation.Horizontal:
            if direction not in (Qt.Edge.TopEdge, Qt.Edge.BottomEdge):
                raise ValueError("水平标尺仅支持TopEdge或BottomEdge作为基准方向")
        else:
            if direction not in (Qt.Edge.LeftEdge, Qt.Edge.RightEdge):
                raise ValueError("垂直标尺仅支持LeftEdge或RightEdge作为基准方向")
        self._base_direction = direction
        self.update()

    @staticmethod
    def toMM(orientation: Qt.Orientation) -> float:
        screen = QApplication.primaryScreen()
        dpi = screen.logicalDotsPerInchX() if orientation == Qt.Orientation.Horizontal else screen.logicalDotsPerInchY()
        return 25.4 / dpi

关键修改说明

1. 实现刻度根部对齐内边框

  • 水平标尺:根据基准方向,从顶部或底部内边框开始,向控件内部绘制刻度(替代原有的从顶部向外延伸逻辑)
  • 垂直标尺:根据基准方向,从左侧或右侧内边框开始,向控件内部绘制刻度(替代原有的从左侧向外延伸逻辑)
  • 同步调整刻度标签的位置,确保标签始终显示在控件内部

2. 动态设置基准方向

  • 新增_base_direction属性,支持水平标尺切换TopEdge/BottomEdge,垂直标尺切换LeftEdge/RightEdge
  • 新增setBaseDirection方法,支持动态修改基准方向,并添加参数合法性校验
  • 内边框的绘制位置跟随基准方向变化,始终作为刻度的根部参考线

使用示例

if __name__ == "__main__":
    app = QApplication([])
    # 创建水平标尺,基准方向为顶部
    ruler_horz_top = Ruler(Qt.Orientation.Horizontal, Qt.Edge.TopEdge)
    ruler_horz_top.setZoom(2)
    ruler_horz_top.show()

    # 创建垂直标尺,基准方向为左侧
    ruler_vert_left = Ruler(Qt.Orientation.Vertical, Qt.Edge.LeftEdge)
    ruler_vert_left.setZoom(2)
    ruler_vert_left.show()

    # 动态切换基准方向
    # ruler_horz_top.setBaseDirection(Qt.Edge.BottomEdge)
    app.exec()

内容的提问来源于stack exchange,提问作者Tom

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 11:14:53