2011-10-08 50 views
14

He estado desarrollando una interfaz gráfica de usuario durante algún tiempo, lo que requiere la creación de objetos de control comunes que Mathematica carece (por ejemplo, spinner, treeview, openerbar, etc.). Uno es el multipanel, es decir, un objeto de panel que se divide en dos (o más) subpanes, donde el divisor se puede establecer con el mouse. Aquí está mi versión de un panel doble. Me gustaría escuchar su opinión e ideas sobre cómo expandirla para manejar no solo 2 sino también cualquier cantidad de subpanes, y también cómo optimizarlos. En la actualidad, para los subpanes con mucha carga, está muy rezagado, ni idea de por qué.Objeto gui de panel dividido

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

¡Bastante impresionante! En cuanto a la optimización: ¿qué hay de usar ControlActive y/o la opción SynchronousUpdating? –

+0

Muy buen trabajo, pero tenga en cuenta que el formato de su "pregunta" no es una buena opción para el sitio. La comunidad Mma aquí es generalmente permisiva al respecto, pero trate de formular sus preguntas de acuerdo con las reglas generales del sitio para evitar cerrar (y eliminar) los votos. –

Respuesta

14

La clave para generalizar a varios paneles fue refactorizar su código. En su forma actual, aunque era muy agradable, estaba mezclando primitivas y opciones de visualización/IU con la lógica dividida, y tenía muchos códigos duplicados. Esto hizo la generalización difícil. Aquí está la versión refactorizado:

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]]; 

Aquí es como puede ser observada:

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

no puedo opinar de los problemas de rendimiento que usted ha mencionado. Además, cuando comienza a arrastrar con el mouse, la posición real del cursor a menudo está bastante desajustada con respecto a la posición del divisor. Esto es tanto para su versión como para la mía, quizás se necesite una escala más precisa.

Solo quiero recalcar una vez más: la generalización solo fue posible después de que realicé la refactorización, para separar la lógica de división de las cosas relacionadas con la visualización. En cuanto a la optimización, también creo que será mucho más fácil intentar optimizar esta versión que la original, por las mismas razones.

EDITAR

Dudé un poco para agregar esta nota, pero hay que mencionar que mi solución anterior, durante el trabajo, muestra una práctica que se considera mala por los programadores expertos interfaz de usuario de MMA. A saber, usa Module - variables generadas dentro de Dynamic interna a esa Module (en particular, split en el código anterior, también varias funciones auxiliares). Las razones por las que lo usé son que no pude hacer que esto funcione con solo DynamicModule - variables generadas, más Module - las variables generadas siempre me funcionaron antes. Sin embargo, consulte la publicación de John Fultz en this subproceso MathGroup, donde indica que esta práctica debe evitarse.

+0

+1 Eso debe haber llevado bastante tiempo ... –

+0

@Sjoerd Lo hizo. Pero estoy interesado en el tema. Además, no soy una persona UI, por lo que a veces trato de obtener un poco más de eso. –

+1

'No evaluado/@ No evaluado [{content}]' :) –

6

Basada en la solución de Leonid, aquí está mi versión. He aplicado varios cambios, básicamente para facilitar el seguimiento de los cambios de tamaño dinámicos y porque simplemente no pude internalizar parte del código de Leonid.

Los cambios realizados:

  • eliminado DividerWidth opción, ya que no se pueden configurar por el usuario. No es tan importante.
  • El tamaño horizontal máximo (maxX en las publicaciones anteriores) se eliminó ya que ahora se calcula a partir de los valores de ancho de panel especificados por el usuario: w.
  • El primer argumento (w, la variable dinámica principal) guarda explícitamente el ancho de los paneles en lugar de guardar las posiciones del divisor.Además, se hizo para ser una lista (w[[n]]) en lugar de una función (como split[n] estaba en la versión de Leonid).
  • Se agregaron botones de minimizar/restaurar a los divisores.
  • Movimiento de divisor restringido: los divisores solo se pueden mover de su lado izquierdo a su derecho, no se pueden realizar más movimientos.
  • ancho del divisor de ajuste fino, ImageMargins, FrameMargins, Spacings para permitir paneles de tamaño cero.

problemas todavía para hacer frente a: los

  • Cuando minimizar/maximizar divisores, que debe superponerse a la izquierda/derecha. Una pila de divisores LIFO resolvería el problema de establecer un divisor al máximo y de intentar cambiar otros divisores a través de sus botones. Esto podría causar algún problema, ya que regresan a los estados anteriores. El problema con el apilamiento de divisores es que no se puede resolver en cuadrícula, o solo se puede resolver con espacios negativos muy ajustados. Creo que no vale la pena tratar con eso.
  • Problema de alineación menor al reducir un panel a ancho/alto cero. Esto puedo vivir con.

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]]; 

Probemos un panel dividido verticalmente:

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

Aquí es un panel horizontal dividido:

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"] 

horizontal example

paneles verticales y horizontales combinadas:

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

me gustaría escuchar sus ideas/comentarios sobre esta solución.