- PR -

直線を描いたり消したり

1
投稿者投稿内容
CameField
会議室デビュー日: 2004/09/20
投稿数: 11
投稿日時: 2006-11-03 23:49
現在、Javaを用いたプログラミングを行なっているのですが、
どうしたらよいのかと行き詰ってしまったため、質問させていただきます。

JFrameを用いたアプリケーションを作っています。
Frameいっぱいに、画像を貼り付けたJLabelを配置しています。
さらにその上に、マウスでドラッグ移動できるように改造したJButtonをいくつか配置しています。

やりたいことは、いくつか配置してあるJButon同士を直線で結ぶということです。
また、JButtonを移動させても、結ばれた線もくっついてくる、ということをしたいのです。
単純に

Container cont = getContentPane();
Graphics g = cont.getGraphics();
g.drawLine(・・・・);

とすると、一応線は引けるのですが、再描画などがうまくいかなかったり、
描いた線を自由に消したりできないという問題があります。

そこで、たとえばJLabelのようなコンポーネント上に線を引いて、消したいときにはそのコンポーネント自体を消してしまう、みたいなことはできるのでしょうか。
山本 裕介
ぬし
会議室デビュー日: 2003/05/22
投稿数: 2415
お住まい・勤務地: 恵比寿
投稿日時: 2006-11-04 02:50
まずは paint メソッド内で描画するようにしましょう。
再描画や線が消えない問題なども解消されると思います。
unibon
ぬし
会議室デビュー日: 2002/08/22
投稿数: 1532
お住まい・勤務地: 美人谷        良回答(20pt)
投稿日時: 2006-11-04 10:55
引用:

CameFieldさんの書き込み (2006-11-03 23:49) より:
とすると、一応線は引けるのですが、再描画などがうまくいかなかったり、
描いた線を自由に消したりできないという問題があります。


現在、グラフィックス一般において、描いた線だけを消してまた描く、ということは普通はやりません。線を少しでも動かしたい時は、その画面全部をすべて0から描き直し、になります。
線単位で消したり描いたり、ということは何十年前にコンピューターの性能が低かった時にやっていたこともありますが、現在では逆にそのほうが手間がかかってしまうので、全部描き直しのほうが効率的です。
全部描き直しの処理は、インギさんのご指摘のように、paint(や paintComponent)内でおこなうことになります。

引用:

CameFieldさんの書き込み (2006-11-03 23:49) より:
そこで、たとえばJLabelのようなコンポーネント上に線を引いて、消したいときにはそのコンポーネント自体を消してしまう、みたいなことはできるのでしょうか。


線もコンポーネントとして扱えるとたしかに楽にはなると思います。VB はそんな感じです。
しかし、Java には既存のコンポーネントとしてそういうものがないので難しいでしょう。特に斜線を扱う際にコンポーネントを作るとなると線以外の部分を透過にするなど難しくて、結局は自分で draw するのと大差ないです。
ちなみに、JDK のインストール時にサンプルも含めてインストールしてあれば、JDK のフォルダーの中に、
demo\applets\GraphLayout
というフォルダーがあります。これはコンポーネントを使わずに自前で paint 内で draw している例です。

--
unibon {B73D0144-CD2A-11DA-8E06-0050DA15BC86}
CameField
会議室デビュー日: 2004/09/20
投稿数: 11
投稿日時: 2006-11-06 00:08
インギさん、unibonさん、ありがとうございます。
描画の部分はpaint()内に記述し、描くべき直線の始点と終点をすべて保持しておき、
すべての線を毎回描きなおすという方法でできました。

また何かわからないことがあったらよろしくお願いします。
nekoyama
ベテラン
会議室デビュー日: 2005/03/12
投稿数: 71
投稿日時: 2006-11-09 16:38
解決したようですが。

Component#paint(),JComponent#paintComponent()をオーバーライドして実装するのは、
インギさん、unibonさんの仰っているとおりです。

ただ、"画面全部をすべて0から描き直す"実装について、ほかにもあるかもしれませんが、私ならば以下の場合に、そのように実装します。

1.描画するオブジェクトが少なく、描画処理が大したオーバーヘッドにならない。
または、オーバーヘッドは無視しても構わない。
2.自分用のプログラム、または拡張性に乏しくても構わない。処理の実装が面倒くさい。
3.1つの描画対象(今回の場合、ボタン?)が移動したら、それに連動して他の描画対象も再描画する必要がある(たとえば、JDKフォルダのdemo\applets\GraphLayout)。

すくなくとも、描画する必要がないものまで、すべて0から描画しなおす実装は、
褒められる実装とは思えません。
100x100のExcelのようなセルがあるとして、1つのセルの描画内容が変更されたからといって、クリップせずに、残りの9999のセルを再描画することを想像してみて下さい。

処理の実装は苦にしないというのであれば、これに対する実装として、Rendererを自分で実装して、そのRendererを実装しているコンポーネントのみを描画する方法があります。
詳細な実装方法は、javax.swingパッケージ内のJList,JTable,JTree,JComboBoxのソースを見てください。
Rendererを実装した場合、非常に拡張性に富んだものが作れます。
また、再描画時はこのRendererコンポーネントのみを描画するため、
描画範囲を局所化し、描画のオーバーヘッドを極力低減させられます。

また、"線もコンポーネントとして扱えると楽"とunibonさんが仰っていますが、
線、矩形、楕円、任意の形状、すべてjava.awt.Shapeとして扱えます。
これを上記Rendererコンポーネント内でGraphics2Dで描画するのです。

"何十年前にコンピューターの性能が低かった時にやっていたこともありますが、現在では逆にそのほうが手間がかかってしまうので、全部描き直しのほうが効率的です。"
についてですが、unibonさんが説明を省略しただけかもしれませんが、
実装の手間や、実行環境や実装要件によって異なるので、一概に効率的とはいえないのではないでしょうか。

コンピューターの性能が低かった時は、そのように実装"せざる"をえなかった。
現在は、コンピューターの性能向上のおかげで、そのように実装してもよいし、
しなくてもよいという選択肢が増えただけではないでしょうか。

すくなくとも、全部再描画する実装の場合、描画のオーバーヘッドが洒落にならなくなる可能性がある。
しかも、実行環境で他のアプリケーションがCPUを高負荷で使用していたり、
メモリ不足によるスワップを発生させている場合、描画さえまともにできなくなる可能性があります。
そして、おそらくUIの先?裏?にあるビジネスロジックさえ、描画処理の負荷により
まともに動かなくなる可能性があることを認識して実装する必要があります。

どんな場合でも、全部再描画なんてしてると、実装が要件に適切でないにも関わらず、Swingは遅いなんて通説が今後もまかり通ることになりそうな。
unibon
ぬし
会議室デビュー日: 2002/08/22
投稿数: 1532
お住まい・勤務地: 美人谷        良回答(20pt)
投稿日時: 2006-11-09 18:51
引用:

未記入さんの書き込み (2006-11-09 16:38) より:
すくなくとも、描画する必要がないものまで、すべて0から描画しなおす実装は、
褒められる実装とは思えません。
100x100のExcelのようなセルがあるとして、1つのセルの描画内容が変更されたからといって、クリップせずに、残りの9999のセルを再描画することを想像してみて下さい。


アプリケーションのウィンドウが小さかったり、描画される巨大なコンポーネントが小さい除き窓の JScrollPane の中にあったりすれば、たしかにおっしゃるように、クリッピングエリアを考慮して最小限の描画をしたほうが良いのかもしれません。しかし、アプリケーションのウィンドウが最大化されたり、別のウィンドウに覆い被さられていて隠されていたアプリケーションのウィンドウが最前面に呼び出されたり、スクロール範囲が全部見えるまでウィンドウが大きくなったときは、結局はすべて描き直しになります。
私は、アプリケーションはこういうときでもユーザーがストレスを感じないようにすみやかに描画を完了するように作られるべきだと考えます。だとすれば、特殊な場合のみクリッピングエリアだけを描画して高速化したとしても、たしかに軽くはなりますが、あまり恩恵はないと感じます。

引用:

未記入さんの書き込み (2006-11-09 16:38) より:
また、"線もコンポーネントとして扱えると楽"とunibonさんが仰っていますが、
線、矩形、楕円、任意の形状、すべてjava.awt.Shapeとして扱えます。
これを上記Rendererコンポーネント内でGraphics2Dで描画するのです。


あいまいに書きましたが、「コンポーネント」というのは JComponent という意味でした。元のご質問から、ラベルやボタンと並列に扱えるようなものだと便利かなと思いましたので。

引用:

未記入さんの書き込み (2006-11-09 16:38) より:
コンピューターの性能が低かった時は、そのように実装"せざる"をえなかった。
現在は、コンピューターの性能向上のおかげで、そのように実装してもよいし、
しなくてもよいという選択肢が増えただけではないでしょうか。


たしかにシステムが API として低レベルの描画もきちんとサポートしてくれていて、アプリケーションが高速化を目論んでそういう一部のみの描画をおこなうのは悪くないと思います。しかし、極めて上位層のアプリケーションがそこまでカリカリにチューニングしても、苦労の割に得られるものはそれほど多くないと思います。

もっとも、ご指摘は分かります。レスポンスタイムが短くて、CPU 使用率が低いに越したことはないです。私としては、最後には、富豪的プログラミングをどこまでおこなうか、という議論になると思っています。

--
unibon {B73D0144-CD2A-11DA-8E06-0050DA15BC86}
nekoyama
ベテラン
会議室デビュー日: 2005/03/12
投稿数: 71
投稿日時: 2006-11-11 16:04
引用:

あいまいに書きましたが、「コンポーネント」というのは JComponent という意味でした。元のご質問から、ラベルやボタンと並列に扱えるようなものだと便利かなと思いましたので。


一度、書いておくと便利ですよ、AWTとSwingの描画を理解していれば、数十分もせずに実装できるでしょうし。

引用:

たしかにシステムが API として低レベルの描画もきちんとサポートしてくれていて、アプリケーションが高速化を目論んでそういう一部のみの描画をおこなうのは悪くないと思います。しかし、極めて上位層のアプリケーションがそこまでカリカリにチューニングしても、苦労の割に得られるものはそれほど多くないと思います。

もっとも、ご指摘は分かります。レスポンスタイムが短くて、CPU 使用率が低いに越したことはないです。私としては、最後には、富豪的プログラミングをどこまでおこなうか、という議論になると思っています。



unibonさんの仰っていることは分かります。

私は、フレームワークやライブラリの実装に携わる機会が多かったため、
そこにunibonさんと認識の違いがあるのかもしれません。

"一般的"や"普通"が主観的な言葉なので、なんなんですが、
"現在、グラフィックス一般において、描いた線だけを消してまた描く、ということは普通はやりません。"の"一般的"や"普通"は、どうもなぁ・・・と思ったのです。

GUIのフレームワークや、ライブラリは、上位層のアプリケーションのなかで、
下層層にあたるので、その位置でわざわざ処理が重くなる可能性がある処理を実装して
欲しくないのです。フレームワークやライブラリは元がグダグダだと、使い物にならないので。

富豪的、ハードウェア依存、ゴリラ的な処理の実装や、チューニングは、フレームワークやライブラリを使用する実装者が、要件、利益、費用等の因子を勘案して判断すべきことだと思うのです。

私も、趣味であれば、実装がめんどくさいかどうかが心理的に最大の負担なので、
全部描画するアプローチをとるでしょう。
まぁ、趣味の場合、往々にして機能を追加したくなったりして、
拡張性をもたせるために、書き直すはめになったりしますが。。

以上です。
1

スキルアップ/キャリアアップ(JOB@IT)