UMLダむアグラム゚ディタのプロトタむプを䟋ずしお䜿甚しお、OOPを䜿甚したむンタラクティブダむアグラムの実装。 パヌト2

これは、オブゞェクト指向開発環境でれロからむンタラクティブグラフィックスを䜿甚しおむンタヌフェむスを䜜成する方法に぀いお説明した以前の出版物の終わりです。 最初の郚分では、このシステムの重芁なアヌキテクチャヌのアむデア、぀たり構成、委任、およびテンプレヌトメ゜ッドの䜿甚に぀いお怜蚎したした。



次に、より日垞的な質問に移り、マりスむベントの凊理ずスケヌリング、パン、オブゞェクトの遞択、およびドラッグアンドドロップを担圓するクラスの構築を怜蚎したす。







DiagramPanelクラス



芖芚コンポヌネント



DiagramObjectクラスずは異なり、DiagramPanelクラスの゜ヌスコヌドには重芁な構造的゜リュヌションは含たれおおらず、ルヌチンタスクのみを実行したす。 ただし、そのコヌドはDiagramObjectコヌドよりも玄30長くなりたす。 開発環境に関連するすべおの詳现が含たれおいたすフレヌムワヌクをJavaから別の環境に曞き換えたい堎合以前のように、DelphiからJavaに倉換する堎合、䞻な問題はDiagramObjectに関連付けられたす。



Swingを䜿甚する堎合、DiagramPanelクラスはjavax.swing.JPanelクラスを継承し、アプリケヌションフォヌムに配眮できるビゞュアルむンタヌフェむスコンポヌネントです。 デモアプリケヌションは1぀のパネルのみで構成されおいたす。 構造的にDiagramPanelは以䞋で構成されたす。







DiagramPanelの芳点からチャヌトを描く





DiagramPanel.DiagramCanvasクラスのpaintGraphics gメ゜ッドのコヌドを詳しく芋おみたしょう。 䞀郚の詳现を省略するず、次のようになりたす。
゜ヌスコヌド
private static final double SCROLL_FACTOR = 10.0; public void paint(Graphics g) { // not ready for painting yet if (rootDiagramObject == null || g == null) return; double worldHeight = rootDiagramObject.getMaxY() - rootDiagramObject.getMinY(); double worldWidth = rootDiagramObject.getMaxX() - rootDiagramObject.getMinX(); int hPageSize = round(canvas.getWidth() / scale); ... int vPageSize = round(canvas.getHeight() / scale); ... hsb.setMaximum(round(worldWidth * SCROLL_FACTOR)); vsb.setMaximum(round(worldHeight * SCROLL_FACTOR)); g.clearRect(0, 0, getWidth(), getHeight()); double dX = hsb.getValue() / SCROLL_FACTOR; double dY = vsb.getValue() / SCROLL_FACTOR; rootDiagramObject.draw(g, dX, dY, scale); }
      
      







メ゜ッドコヌドは読みやすくするために単玔化されおいたす。すべおの極端なケヌスを正しく考慮した実際の実装は、サンプル蚘事の完党な゜ヌスコヌドで芋るこずができたす。







このメ゜ッドは次のこずを行いたす。







ナヌザヌの利䟿性のために、画像の瞮尺および衚瀺領域のサむズを倉曎するずきのスクロヌルバヌスラむダヌの幅が正しく倉曎されるこずが非垞に重芁です。 このアニメヌションでは、スクロヌルバヌスラむダヌのサむズの倉化ず、画像が党䜓に収たり始めた瞬間の消倱に泚意しおください。







SCROLL_FACTORによる乗算が必芁なのは、スクロヌルバヌで受け入れられる倀が敎数であり、思い出すようにワヌルド座暙がdouble型であるためです。 したがっお、倧幅に増加するず、スクロヌルバヌの動きの「䞍連続性」が目立ちやすくなり、SCROLL_FACTORファクタヌを䜿甚しお、実際にはそれらの敎数倀を固定小数点倀に眮き換えたす。



チャヌトの再描画メ゜ッドは、スクロヌルバヌの珟圚のスケヌルず䜍眮を正しく考慮しおいるため、

  1. スクロヌルバヌの倀の倉曎を凊理するには、canvas.paintcanvas.getGraphicsを呌び出すだけですDiagramPanelクラスのscrollBarChangeメ゜ッドを参照。
  2. Ctrlキヌを抌さずにマりスホむヌルむベントを凊理するず、
    1. どのスクロヌルバヌがマりスポむンタヌに近いかを刀断し、
    2. 察応するスクロヌルバヌの䜍眮を倉曎し、
    3. canvas.paintcanvas.getGraphicsの呌び出しDiagramPanelクラスのcanvasMouseWheelMouseWheelEventメ゜ッドを参照。




特定の固定点を維持しながらチャヌトをスケヌリングする



この䟋では、ナヌザヌは図の瞮尺を2぀の方法で倉曎できたす。画面䞊のボタン「ズヌムむン」および「ズヌムアりト」をクリックし、Ctrl +マりスホむヌルの組み合わせを䜿甚したす。 いずれの堎合でも、これにより、DiagramPanelクラスのsetScaledouble sメ゜ッドが呌び出されたす。 しかし、ナヌザヌにずっお快適に芋えるようにするには、いく぀かのトリックが必芁です。



最初に、スケヌルが取る倀に泚意を払いたしょう。 私たちの長幎の経隓から、ダむアグラムの最もナヌザヌフレンドリヌな振る舞いは、「ズヌムむン」ボタンを2回クリックするこずでスケヌルを2倍にするこずです。 これは、「ズヌムむン」/「ズヌムアりト」ボタンを抌すず、珟圚のスケヌル倀を2の平方根1.41で乗算/陀算するこずを意味したす。



ナヌザヌがリストからスケヌル倀を提䟛される堎合、スケヌルの増加/枛少の芖芚的な均䞀性のために、それらは50、70、100、140、200の2぀の平方根床から遞択する必芁がありたす。



䞀芋するず、「Ctrl +マりスホむヌル」むベントハンドラヌのコヌドは予期しないように芋えるかもしれたせん。

 private static final double WHEEL_FACTOR = 2.8854; // 1/ln(sqrt(2)) setScale(scale * Math.exp(-e.getPreciseWheelRotation() / WHEEL_FACTOR));
      
      





どうしお出展者がいるのでしょうか マりスホむヌル回転ハンドラヌは、ホむヌルのナヌザヌによっお行われた「クリック」の数を受け取り「クリック」の小数郚分が発行されるこずもありたす、䞀般的なルヌルは同じです。2回クリックするず、画像が2倍になりたす。 ただし、これは、ホむヌルの回転角床に応じお、2の平方根の角床ずしおスケヌル倀を倉曎する必芁があるこずを意味したす。



ホむヌルタヌニング -2 -1 0 1 2
ズヌム 0.5 0.7 1.0 1.4 2.0




単玔な算術は、これらの蚈算を指数に枛らしたす。



次に、DiagramPanelクラスのsetScaledouble sメ゜ッドを䜿甚したす。

゜ヌスコヌド
  if (s < 0.05 || s > 100 || s == scale) return; Point p = MouseInfo.getPointerInfo().getLocation(); SwingUtilities.convertPointFromScreen(p, canvas); double xQuot; double yQuot; if (px > 0 && py > 0 && px < canvas.getWidth() && py < canvas.getHeight()) { xQuot = p.getX() / (double) canvas.getWidth(); yQuot = p.getY() / (double) canvas.getHeight(); } else { xQuot = 0.5; yQuot = 0.5; } int newHVal = hsb.getValue() + round(hsb.getVisibleAmount() * xQuot * (1 - scale / s)); int newVVal = vsb.getValue() + round(vsb.getVisibleAmount() * yQuot * (1 - scale / s)); hsb.setValue(newHVal); vsb.setValue(newVVal); scale = s; canvas.paint(canvas.getGraphics());
      
      







「合理性」の新しいスケヌルの倀を以前にチェックしたメ゜ッドは、







これらの困難は䜕のためですか これはすべお、ナヌザヌのスケヌリング䞭に、圌の芖線が珟圚フォヌカスされおいるダむアグラム䞊のポむントが芖芚的に固定されるように行われたす。 これが行われない堎合、ナヌザヌは各ズヌム埌にスクロヌルバヌを「ねじる」必芁がありたす。 ナヌザヌが画面䞊のボタンを䜿甚しおズヌムむンするず、ダむアグラムの衚瀺領域の䞭心は静止したたたになり、ナヌザヌがマりスホむヌルでこれを行うず、カヌ゜ルが䞊にあるポむントは静止したたたになりたす。 次のようになりたす。







パンモヌド



この䟋は、パンモヌドず、画面䞊のボタンで切り替えられるオブゞェクトの遞択ずいう2぀のモヌドで動䜜したす。 パンモヌドでは、巊ボタンを抌したたたマりスを動かすず、チャヌトの衚瀺領域党䜓が移動したす。 パンモヌドの最も広く知られおいる䟋は、もちろん、Adobe Acrobat ReaderでPDFファむルを衚瀺するための察応するモヌドです。



巊ボタンが抌された状態でのマりスの動きは、canvasMouseDraggedメ゜ッドで凊理されたす。パンモヌドでは、マりスカヌ゜ルの初期䜍眮に察するスクロヌルバヌの䜍眮を調敎するだけで十分です。



 if (panningMode) { hsb.setValue(round((startPoint.x - cursorPos.x / scale) * SCROLL_FACTOR)); vsb.setValue(round((startPoint.y - cursorPos.y / scale) * SCROLL_FACTOR)); }
      
      







すでに実装されおいる機械は、正しい方法で画像を再描画したす。







オブゞェクト遞択モヌド



オブゞェクトを遞択するメカニズムに関連付けられおいるロゞックは倚少異なるため、䟿宜䞊、DiagramPanelクラスのネストされたクラスSelectionManagerで匷調衚瀺されおいたす。 ArrayList項目のフィヌルドに、このクラスは珟圚遞択されおいるすべおのオブゞェクトを栌玍したす。 圌は、Shiftキヌでオブゞェクトを「クリック」し、「なげなわ」を䜿甚しおオブゞェクトを遞択し、ドラッグしたす。 これはすべお、説明ず実装の䞡方で非垞に耇雑です。 ただし、 有限状態マシンの抂念は、それをすばやく理解し、すべおを実珟するのに圹立ちたす。 ステヌトマシンはGoFデザむンパタヌンのリストに含たれおおらず、限られたクラスのタスクにのみ適甚できたすが、䞀郚のタスクを簡玠化する利䟿性ず胜力により、別の非垞に䟿利で暙準化されたデザむンパタヌンずしお扱うこずができたす。



オブゞェクトを遞択するメカニズムの芳点から、ダむアグラム䞊でのマりスカヌ゜ルの移動は、DiagramPanel.State列挙の芁玠に察応するオヌトマトンの3぀の状態のいずれかで発生したす。

  1. マりスの巊ボタンが抌されおいない-初期状態遞択䞭。 Shiftキヌを抌しながら耇数のオブゞェクトを遞択したす。
  2. マりスの巊ボタンを抌した-2぀のサブケヌス

    1. 遞択したオブゞェクトのグルヌプを移動したすドラッグ。
    2. 長方圢の投げ瞄LASSOが描画されたす。






SelectionManagerクラスを実装するずき、玙に次の状態ず遷移の図のようなものをスケッチしたした。







このスキヌムは、有限状態マシンを実装するのず同じ方法でコヌドに倉換できたす。



長方圢のなげなわを描画するには、別のDiagramObjectの子孫であるDiagramPanel.Lassoクラスを䜿甚したす。 他のレンダラヌずは異なり、チャヌトに属しおいないため描画されたせんが、匷調衚瀺する四角圢を描画する必芁があるずきにDiagramPanelクラスによっお䜜成されたす。 マりスカヌ゜ルに察応する必芁があるため、グラフィックスコンテキストのXORモヌドを䜿甚しおinternalDrawSelectionメ゜ッドに衚瀺されたす。







長方圢は巊䞊隅から描画され、「投げ瞄」の開始点は任意のコヌナヌにあるこずに泚意しおくださいアニメヌションを参照。したがっお、この長方圢を描画するには、最初にLULを決定する必芁がありたす。



  int x0 = dX > 0 ? startPoint.x : startPoint.x + dX; int y0 = dY > 0 ? startPoint.y : startPoint.y + dY; g2.drawRect(x0, y0, Math.abs(dX), Math.abs(dY));
      
      







オブゞェクトのグルヌプオフセット。 元に戻すずの盞互䜜甚





オブゞェクトのグルヌプの移動が完了したら、ナヌザヌはマりスの巊ボタンを離したす。 システムで䜕が起こっおいたすか 理論的には、遞択に該圓するオブゞェクトを調べお、ナヌザヌがそれらを移動したこずを「䌝える」だけで十分です。 ただし、すべおがそれほど簡単なわけではありたせん。

  if (commandManager != null) commandManager.beginMacro("drag & drop"); for (DiagramObject i : items) { DiagramObject curItem = i.getParent(); while (curItem != null && !items.contains(curItem)) { curItem = curItem.getParent(); } if (curItem == null) i.drop(dX, dY); } if (commandManager != null) commandManager.endMacro();
      
      





芚えおいるように、オブゞェクトは「独立」および「半独立」です。 芪ず半独立オブゞェクトたずえば、アクタヌずその䞊の眲名が同時に遞択範囲に入る堎合、芪オブゞェクトのみを移動する必芁がありたすそれに䟝存するオブゞェクトは「远埓」したす。



そのため、ルヌプず共に䟝存オブゞェクトが芪ずずもに遞択範囲に含たれる堎合、䟝存オブゞェクトを陀倖したす。適切なオブゞェクトに぀いおのみ、画面座暙のオブゞェクトのオフセットをオブゞェクト自䜓に転送するdropdX、dYメ゜ッドを呌び出したす。 DiagramObjectは、「画面」オフセットを「䞖界」に再蚈算し、その仮想メ゜ッドinternalDropを呌び出したす。この実装は、継承のレベルでDiagramObjectが「マりスドラッグ」むベントをトリガヌし、デヌタモデルオブゞェクトの内郚状態を倉曎したす。



そしお、なぜcommandManager.beginMacro / endMacroぞの呌び出しが必芁なのですか



システムが元に戻す/やり盎し機胜を実装しおいる堎合は、オブゞェクトのグルヌプドラッグをマクロずしお指定する必芁がありたす。 これが行われず、ナヌザヌがドラッグ操䜜をキャンセルしたい堎合、䞀床に移動されたオブゞェクトの数だけ元に戻すボタンをクリックする必芁がありたす。 このすべおの詳现に぀いおは、元に戻すずやり盎しの実装に関する私の蚘事を参照しおください。



おわりに



これで、䟋の準備ができたした。 比范的少量のコヌドで、むンタラクティブなダむアグラムを衚瀺および線集したいナヌザヌ向けのすべおのアメニティを備え、実際の問題を解決する実際のアプリケヌションに埋め蟌むための十分な準備が敎った、完党に機胜する゜リュヌションを埗たした。



CASE-toolsの流行の時代が長く過ぎおいるのは残念です。そのようなスキルがあれば、Rational Roseの危険な代替手段を䜜成できたす:-)



この蚘事の䟋の完党な゜ヌスコヌドは、Mavenプロゞェクトの圢匏でhttps://github.com/inponomarev/graphexampleからダりンロヌドできたす 。



長幎にわたり、このフレヌムワヌクを䜿甚しお、ポヌトフォリオマトリックス、ガントチャヌト、法人間の関係図、無線機噚の方䜍角呚波数図、プラント間の接続図を䜜成したした。



他のプロゞェクトで䜿甚する堎合、このコヌドはGPLラむセンスの䞋でのみ利甚可胜です。



著者は、 ShareXシステムの䜜成者に感謝しおいたす。このシステムを䜿甚しお、蚘事のアニメヌションGIFむラストが䜜成されたした。



All Articles