2011-10-08 4 views
14

私はずっとguiを開発していましたが、Mathematicaにはない一般的なコントロールオブジェクト(スピナー、ツリービュー、オープナーバーなど)を作成する必要があります。 1つはマルチパネルであり、2つ(またはそれ以上)のサブペインに分割されたペインオブジェクトであり、分割器はマウスによって設定することができる。ここに私のデュアルペインのバージョンです。あなたの意見や考え方を聞いて、2つだけではなく、いくつのサブペインにも対応できるように拡張する方法と、それを最適化する方法についても聞きたいと思います。現在、大量にロードされているサブペインの場合、それはひどく遅れています。理由はわかりません。分割ペインのGUIオブジェクト

Options[SplitPane] = {Direction -> "Vertical", 
    DividerWidth -> Automatic, Paneled -> {True, True}}; 
SplitPane[opts___?OptionQ] := 
    Module[{dummy}, SplitPane[Dynamic[dummy], opts]]; 
SplitPane[val_, opts___?OptionQ] := SplitPane[val, {"", ""}, opts]; 
SplitPane[val_, content_, opts___?OptionQ] := 
    SplitPane[val, content, {100, 50}, opts]; 
SplitPane[Dynamic[split_, arg___], {expr1_, expr2_}, {maxX_, maxY_}, 
    opts___?OptionQ] := 
    DynamicModule[{temp, dir, d, panel, coord, max, fix, val}, 
    {dir, d, panel} = {Direction, DividerWidth, Paneled} /. {opts} /. 
    Options[SplitPane]; 
    dir = dir /. {Bottom | Top | "Vertical" -> "Vertical", _ -> 
     "Horizontal"}; 
    d = d /. Automatic -> 2; 
    split = If[NumberQ[split], split, max/2]; 
    val = Clip[split /. {_?NumberQ -> split, _ -> maxX/2}, {0, maxX}]; 
    {coord, max, fix} = 
    Switch[dir, "Vertical", {First, maxX, maxY}, 
    "Horizontal", {(max - Last[#]) &, maxY, maxX}]; 
    panel = (# /. {None | False -> 
      Identity, _ -> (Panel[#, ImageMargins -> 0, 
      FrameMargins -> -1] &)}) & /@ panel; 

    Grid[If[dir === "Vertical", 
    {{ 
     Dynamic[ 
     panel[[1]]@ 
     Pane[expr1, ImageSize -> {split - d, fix}, 
      ImageSizeAction -> "Scrollable", Scrollbars -> Automatic, 
      AppearanceElements -> None], TrackedSymbols :> {split}], 
     [email protected][ 
     MouseAppearance[ 
      Pane[Null, ImageSize -> {d*2, fix}, ImageMargins -> -1, 
      FrameMargins -> -1], "FrameLRResize"], 
     "MouseDown" :> (temp = 
      [email protected]@"CellContentsAbsolute"; 
      split = 
      If[Abs[temp - split] <= d \[And] 0 <= temp <= max, temp, 
      split]), 
     "MouseDragged" :> (temp = 
      [email protected]@"CellContentsAbsolute"; 
      split = If[0 <= temp <= max, temp, split])], 
     [email protected] 
     panel[[2]]@ 
     Pane[expr2, ImageSizeAction -> "Scrollable", 
      Scrollbars -> Automatic, AppearanceElements -> None, 
      ImageSize -> {max - split - d, fix}] 
     }}, 
    { 
     [email protected] 
     Dynamic[panel[[1]]@ 
     Pane[expr1, ImageSize -> {fix, split - d}, 
      ImageSizeAction -> "Scrollable", Scrollbars -> Automatic, 
      AppearanceElements -> None], TrackedSymbols :> {split}], 
     [email protected]@EventHandler[ 
     MouseAppearance[ 
      Pane[Null, ImageSize -> {fix, d*2}, ImageMargins -> -1, 
      FrameMargins -> -1], "FrameTBResize"], 
     "MouseDown" :> (temp = 
      [email protected]@"CellContentsAbsolute"; 
      split = 
      If[Abs[temp - split] <= d \[And] 0 <= temp <= max, temp, 
      split]), 
     "MouseDragged" :> (temp = 
      [email protected]@"CellContentsAbsolute"; 
      split = If[0 <= temp <= max, temp, split])], 
     [email protected] 
     Dynamic[panel[[2]]@ 
     Pane[expr2, ImageSizeAction -> "Scrollable", 
      Scrollbars -> Automatic, 
      ImageSize -> {fix, max - split - d}, 
      AppearanceElements -> None], TrackedSymbols :> {split}] 
     } 
    ], Spacings -> {0, -.1}] 
    ]; 
SplitPane[val_, arg___] /; NumberQ[val] := 
    Module[{x = val}, SplitPane[Dynamic[x], arg]]; 

pos = 300; 
SplitPane[ 
Dynamic[pos], {Manipulate[ 
    Plot[Sin[x (1 + a x)], {x, 0, 6}], {a, 0, 2}], 
    Factorial[123]}, {500, 300}] 

SplitPane output

+1

非常に印象的!最適化について:ControlActiveおよび/またはSynchronousUpdatingオプションの使用はどうですか? –

+0

非常にうまくいっていますが、あなたの「質問」の形式はサイトには適していないことに注意してください。ここのMmaコミュニティは一般的にそれを容認していますが、投票を終了(および削除)しないように、サイトの一般的なルールに従って質問を策定してください。 –

答えて

14

いくつかのパネルに一般化するための鍵は、あなたのコードをリファクタリングすることでした。現在の形では、非常に素晴らしいですが、視覚化/ UIプリミティブとオプションを分割ロジックにミックスしていて、重複したコードがたくさんありました。これは一般化を困難にした。ここでリファクタリングバージョンである:ここでは

ClearAll[SplitPane]; 
Options[SplitPane] = { 
    Direction -> "Vertical", DividerWidth -> Automatic, Paneled -> True 
}; 
SplitPane[opts___?OptionQ] := Module[{dummy}, SplitPane[Dynamic[dummy], opts]]; 
SplitPane[val_, opts___?OptionQ] := SplitPane[val, {"", ""}, opts]; 
SplitPane[val_, content_, opts___?OptionQ] := 
    SplitPane[val, content, {100, 50}, opts]; 
SplitPane[sp_List, {cont__}, {maxX_, maxY_}, opts___?OptionQ] /; 
     Length[sp] == Length[Hold[cont]] - 1 := 
    Module[{scrollablePane, dividerPane, onMouseDownCode, onMouseDraggedCode, dynPane, 
     gridArg, split, divider, panel}, 
    With[{paneled = Paneled /. {opts} /. Options[SplitPane],len = Length[Hold[cont]]}, 
     Which[ 
      TrueQ[paneled ], 
      panel = Table[True, {len}], 
      MatchQ[paneled, {Repeated[(True | False), {len}]}], 
      panel = paneled, 
      True, 
      Message[SplitPane::badopt]; Return[$Failed, Module] 
     ] 
    ]; 

    DynamicModule[{temp, dir, d, coord, max, fix, val}, 
     {dir, d} = {Direction, DividerWidth}/.{opts}/.Options[SplitPane]; 
     dir = dir /. { 
     Bottom | Top | "Vertical" -> "Vertical", _ -> "Horizontal" 
     }; 
     d = d /. Automatic -> 2; 
     val = Clip[sp /. {_?NumberQ -> sp, _ -> maxX/2}, {0, maxX}]; 
     {coord, max, fix} = 
     Switch[dir, 
      "Vertical", 
      {First, maxX, maxY}, 
      "Horizontal", 
      {(max - Last[#]) &, maxY, maxX} 
     ]; 
     Do[split[i] = sp[[i]], {i, 1, Length[sp]}]; 
     split[Length[sp] + 1] = max - Total[sp] - 2*d*Length[sp]; 
     panel = 
      (# /. { 
      None | False -> Identity, 
      _ -> (Panel[#, ImageMargins -> 0,FrameMargins -> -1] &) 
      }) & /@ panel; 
     scrollablePane[args___] := 
      Pane[args, ImageSizeAction -> "Scrollable", 
       Scrollbars -> Automatic, AppearanceElements -> None]; 
     dividerPane[size : {_, _}] := 
      Pane[Null, ImageSize -> size, ImageMargins -> -1,FrameMargins -> -1]; 

     onMouseDownCode[n_] := 
     Module[{old}, 
      temp = [email protected]@"CellContentsAbsolute"; 
      If[Abs[temp - split[n]] <= d \[And] 0 <= temp <= max, 
      old = split[n]; 
      split[n] = temp-Sum[split[i], {i, n - 1}]; 
      split[n + 1] += old - split[n];  
     ]]; 

     onMouseDraggedCode[n_] := 
     Module[{old}, 
      temp = [email protected]@"CellContentsAbsolute"; 
      If[0 <= temp <= max, 
       old = split[n]; 
       split[n] = temp -Sum[split[i], {i, n - 1}]; 
       split[n + 1] += old - split[n]; 
      ] ; 
     ]; 

     SetAttributes[dynPane, HoldFirst]; 
     dynPane[expr_, n_, size_] := 
      panel[[n]]@scrollablePane[expr, ImageSize -> size]; 

     divider[n_, sizediv_, resizeType_] := 
     [email protected][ 
      MouseAppearance[dividerPane[sizediv], resizeType], 
      "MouseDown" :> onMouseDownCode[n], 
      "MouseDragged" :> onMouseDraggedCode[n] 
     ]; 

     SetAttributes[gridArg, HoldAll]; 
     gridArg[{content__}, sizediv_, resizeType_, sizeF_] := 
     Module[{myHold, len = Length[Hold[content]] }, 
      SetAttributes[myHold, HoldAll]; 
      List @@ Map[ 
      Dynamic, 
      Apply[Hold, 
       MapThread[Compose, 
        { 
         Range[len] /. { 
         len :>    
          Function[ 
          exp, 
          myHold[dynPane[exp, len, sizeF[len]]], 
          HoldAll 
          ], 
         n_Integer :> 
          Function[exp, 
          myHold[dynPane[exp, n, sizeF[n]], 
           divider[n, sizediv, resizeType] 
          ], 
          HoldAll] 
         }, 
         Unevaluated /@ Unevaluated[{content}] 
        }] (* MapThread *) 
       ] /. myHold[x__] :> x 
      ] (* Map *) 
     ]; (* Module *) 
     (* Output *) 
     Grid[ 
     If[dir === "Vertical", 
      [email protected] gridArg[{cont}, {d*2, fix},"FrameLRResize",{split[#] - d, fix} &], 
      (* else *) 
      List /@ gridArg[{cont}, {fix, d*2},"FrameTBResize", {fix, split[#] - d} &] 
     ], 
     Spacings -> {0, -.1}]]]; 

SplitPane[val_, arg___] /; NumberQ[val] := 
    Module[{x = val}, SplitPane[Dynamic[x], arg]]; 

は、それが見えるかもしれ方法です。

SplitPane[{300, 300}, 
{ 
    Manipulate[Plot[Sin[x (1 + a x)], {x, 0, 6}], {a, 0, 2}], 
    Factorial[123], 
    CompleteGraph[5] 
}, {900, 300}] 

enter image description here

あなたが言及したパフォーマンスの問題をコメントすることはできません。また、マウスでドラッグを開始すると、実際のカーソルの位置は、多くの場合、ディバイダの位置に対してかなりオフになります。これはあなたのバージョンと私のバージョンの両方であり、より正確なスケーリングが必要になるかもしれません。

もう一度強調したいのですが、リファクタリングを行った後にのみ一般化が可能になり、分割ロジックを視覚関連のものから分離することができました。最適化に関しては、同じ理由で、このバージョンを元のバージョンよりも最適化しようとするほうがはるかに簡単だと思います。

EDIT私はこのノートを追加するために少しためらったが、仕事をしながら上記の私のソリューションは、専門家のUIのMMAのプログラマーによって悪いを考えられている1回の練習を示していることを言及しなければなりません。つまり、Module - Moduleの内側にDynamicの内部で生成された変数を使用します(特に上記のコードではsplit、さまざまな補助関数も使用します)。私が使用した理由は、DynamicModule - 生成された変数だけでこの作業を行うことができず、Module - 生成された変数が以前私のために働いていたからです。しかし、this MathGroupスレッドのJohn Fultzの投稿を参照してください。ここで、この練習は避けるべきです。

+0

+1かなり時間がかかっているはずです... –

+0

@Sjoerdしました。しかし、私はその話題に興味があります。また、私はUIの人ではないので、時々私はもう少しそれを取得しようとします。 –

+1

'未評価/未評価@ {コンテンツ}]' :) –

6

Leonidのソリューションを大きくビルドしました。ここは私のバージョンです。私はいくつかの変更点を適用しました。基本的に動的なサイズ変更を簡単に追跡できるようにするため、またLeonidのコードの一部を内部化することができなかったためです。作っ

変更:

  • は今、それがユーザーによって設定することができない、DividerWidthオプションを削除しました。それはそれほど重要ではありません。
  • ユーザが指定したパネル幅の値(w)から最大水平サイズ(上記の記事ではmaxX)を削除しました。
  • 最初の引数(w、主な動的変数)は、除算器の位置を保存する代わりに明示的にパネルの幅を保存します。また、関数の代わりにリスト(w[[n]])になりました(split[n]はLeonidのバージョン)。
  • ディバイダに最小化/復元ボタンを追加しました。
  • 制限デバイダ移動:分周器は、それ以上の移動が可能ではない、彼らの右neigbourへの左から移動させることができます。
  • 微調整分周幅、ImageMarginsFrameMarginsSpacingsはゼロサイズのウィンドウを可能にします。まだ対処する

問題:

  • 仕切りを最大化/最小化したとき、彼らは一番右/左のものと重複しなければなりません。ディバイダのLIFOスタックは、ディバイダを最大に設定する問題を解決し、ボタンを使用して他のディバイダを変更しようとします。これにより、以前の状態に戻ってしまうため、問題が発生する可能性があります。ディバイダを積み重ねる際の問題は、グリッドでは解決できないか、非常に特別に調整された負のSpacingsでしか解決できないということです。私はそれが対処する価値はないと思う。
  • パネルを幅/高さをゼロに縮小すると、マイナーアライメントの問題が発生します。これは私と一緒に暮らすことができる。

ClearAll[SplitPane]; 
Options[SplitPane] = {Direction -> "Vertical", Paneled -> True}; 
SplitPane[opts___?OptionQ] := 
    Module[{dummy = {200, 200}}, SplitPane[Dynamic[dummy], opts]]; 
SplitPane[val_, opts___?OptionQ] := SplitPane[val, {"", ""}, opts]; 
SplitPane[val_, content_, opts___?OptionQ] := 
    SplitPane[val, content, Automatic, opts]; 
SplitPane[Dynamic[w_], cont_, s_, opts___?OptionQ] := 
    DynamicModule[{ 
    scrollPane, divPane, onMouseDownCode, onMouseDraggedCode, grid, 
    dir, panel, bg, coord, mouse, icon, sizeD, sizeB, 
    num, old, pos, origo, temp, max, prev, state, fix}, 

    {dir, panel} = {Direction, Paneled} /. {opts} /. [email protected]; 
    dir = dir /. {Bottom | Top | "Vertical" -> "Vertical", _ -> 
     "Horizontal"}; 
    bg = panel /. {None | False -> [email protected], _ -> None}; 
    panel = 
    panel /. {None | False -> 
     None, _ -> {RGBColor[0.70588, 0.70588, 0.70588]}}; (* 
    Simulate Panel-like colors on the frame. *) 
    fix = s /. {Automatic -> If[dir === "Vertical", 300, 800]}; 

    (* {coordinate function, mouse cursor, button icon, divider size, 
    button size} *) 
    {coord, mouse, icon, sizeD, sizeB} = Switch[dir, 
    "Vertical", {First, 
     "FrameLRResize", {"\[RightPointer]", "\[LeftPointer]"}, {5, 
     fix}, {5, 60}}, 
    "Horizontal", {(max - [email protected]#) &, 
     "FrameTBResize", {"\[DownPointer]", "\[UpPointer]"}, {fix, 
     7}, {60, 7}} 
    ]; 

    SetAttributes[{scrollPane, grid}, HoldAll]; 
    (* Framed is required below becase otherwise the horizontal \ 
version of scrollPane cannot be set to zero height. *) 
    scrollPane[expr_, size_] := 
    Framed[Pane[expr, Scrollbars -> Automatic, 
     AppearanceElements -> None, ImageSizeAction -> "Scrollable", 
     ImageMargins -> 0, FrameMargins -> 0, ImageSize -> size], 
    FrameStyle -> panel, ImageMargins -> 0, FrameMargins -> 0, 
    ImageSize -> size]; 
    divPane[n_] := 
    [email protected][MouseAppearance[Framed[ 
     Item[Button[[email protected][state[[n]], [email protected], [email protected]], 

      If[state[[n]], prev[[n]] = w; 
      w[[n]] = max - Sum[w[[i]], {i, n - 1}]; 
      Do[w[[i]] = 0, {i, n + 1, num}]; state[[n]] = False;, 
      w = prev[[n]]; state[[n]] = True;] 
      , ContentPadding -> False, ImageSize -> sizeB, 
      FrameMargins -> 0, ImageMargins -> -1, 
      Appearance -> "Palette"], Alignment -> {Center, Center}] 
     , ImageSize -> sizeD, FrameStyle -> None, ImageMargins -> 0, 
     FrameMargins -> 0, Background -> bg], mouse], 
     "MouseDown" :> [email protected], 
     "MouseDragged" :> [email protected], PassEventsDown -> True]; 
    onMouseDownCode[n_] := (
    old = {w[[n]], w[[n + 1]]}; 
    origo = [email protected]@"CellContentsAbsolute"; 
    ); 
    onMouseDraggedCode[n_] := (
    temp = [email protected]@"CellContentsAbsolute" - origo; 
    w[[n]] = Min[Max[0, [email protected] + temp], [email protected]]; 
    w[[n + 1]] = [email protected] - w[[n]]; 
    ); 
    (* Framed is required below because it gives the expression \ 
margins. Otherwise, 
    if the scrollPane is set with larger than 0 FrameMargins, 
    they cannot be shrinked to zero width. *) 
    grid[content_, size_] := 
    Riffle[MapThread[ 
     Dynamic[scrollPane[Framed[#1, FrameStyle -> None], [email protected]#2], 
     TrackedSymbols :> {w}] &, {content, [email protected]@w}], 
    Dynamic[[email protected]#, TrackedSymbols :> {w}] & /@ 
     [email protected](([email protected]) - 1)]; 

    [email protected][If[dir === "Vertical", 
     [email protected][cont, {w[[#]], fix} &], 
     List /@ grid[cont, {fix, w[[#]]} &] 
     ], Spacings -> {0, -.1}, 
    ItemSize -> {{Table[0, {[email protected]}]}, {Table[0, {[email protected]}]}}], 

    Initialization :> (
    (* w = width data list for all panels *) 
    (* m = number of panels *) 
    (* state = button states *) 
    (* prev = previous state of w *) 
    (* max = total width of all panels *) 
    num = [email protected]; state = True & /@ [email protected]; 
    prev = w & /@ [email protected]; max = [email protected];) 
    ]; 
SplitPane[val_, 
    arg___] /; ([email protected] === List \[And] And @@ (NumberQ /@ val)) := 
    Module[{x = val}, SplitPane[[email protected], arg]]; 

のは、縦方向に分割さペインを試してみましょう:

SplitPane[{50, 50, 50, 
    50}, {Manipulate[Plot[Sin[x (1 + a x)], {x, 0, 6}], {a, 0, 2}, 
    ContentSize -> 300], Null, CompleteGraph[5], "121234"}, 
Direction -> "Horizontal"] 

:ここ

w = {200, 50, 100, 300}; 
SplitPane[ 
[email protected], {Manipulate[Plot[Sin[x (1 + a x)], {x, 0, 6}], {a, 0, 2}], 
    Null, CompleteGraph[5], "121234"}] 

vertical example

は、水平方向に分割さペインです組み合わせの

垂直方向と水平方向のペイン:

xpane = {200, 300}; 
ypane = {200, 50}; 
SplitPane[[email protected], { 
    Manipulate[Plot[Sin[x (1 + a x)], {x, 0, 6}], {a, 0, 2}], 
    Dynamic[ 
    SplitPane[[email protected], {CompleteGraph[5], "status"}, [email protected], 
    Paneled -> False, Direction -> "Horizontal"], 
    TrackedSymbols :> {xpane}] 
    }, 300, Direction -> "Vertical"] 

combined example

私は、このソリューションであなたのアイデア/コメントを聞きたいです。

関連する問題