Сплит панель объекта графического интерфейса - PullRequest
14 голосов
/ 08 октября 2011

Я некоторое время разрабатывал графический интерфейс, который требует создания общих объектов управления, которых не хватает в Mathematica (например, спиннер, древовидная структура, панель открывания и т. Д.).Одним из них является мультипанель, то есть объект панели, который разделен на две (или более) подпанели, где разделитель может быть установлен мышью.Вот моя версия двойной панели.Я хотел бы услышать ваше мнение и идеи о том, как расширить его для обработки не только двух, но любого количества подпанелей, а также о том, как его оптимизировать.В настоящее время для сильно загруженных субпанелей это ужасно отстает, понятия не имею, почему.

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}],
       Deploy@EventHandler[
         MouseAppearance[
          Pane[Null, ImageSize -> {d*2, fix}, ImageMargins -> -1, 
           FrameMargins -> -1], "FrameLRResize"],
         "MouseDown" :> (temp = 
            coord@MousePosition@"CellContentsAbsolute"; 
           split = 
            If[Abs[temp - split] <= d \[And] 0 <= temp <= max, temp, 
             split]), 
         "MouseDragged" :> (temp = 
            coord@MousePosition@"CellContentsAbsolute"; 
           split = If[0 <= temp <= max, temp, split])],
       Dynamic@
        panel[[2]]@
         Pane[expr2, ImageSizeAction -> "Scrollable", 
          Scrollbars -> Automatic, AppearanceElements -> None, 
          ImageSize -> {max - split - d, fix}]
       }},
     {
      List@
       Dynamic[panel[[1]]@
         Pane[expr1, ImageSize -> {fix, split - d}, 
          ImageSizeAction -> "Scrollable", Scrollbars -> Automatic, 
          AppearanceElements -> None], TrackedSymbols :> {split}],
      List@Deploy@EventHandler[
         MouseAppearance[
          Pane[Null, ImageSize -> {fix, d*2}, ImageMargins -> -1, 
           FrameMargins -> -1], "FrameTBResize"],
         "MouseDown" :> (temp = 
            coord@MousePosition@"CellContentsAbsolute"; 
           split = 
            If[Abs[temp - split] <= d \[And] 0 <= temp <= max, temp, 
             split]), 
         "MouseDragged" :> (temp = 
            coord@MousePosition@"CellContentsAbsolute"; 
           split = If[0 <= temp <= max, temp, split])],
      List@
       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

Ответы [ 2 ]

14 голосов
/ 09 октября 2011

Ключом к обобщению нескольких панелей было рефакторинг вашего кода.В своем нынешнем виде, хотя он и был очень хорош, он смешивал примитивы визуализации / пользовательского интерфейса и опции с логикой разделения и содержал много дублирующегося кода.Это затруднило обобщение.Вот измененная версия:

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 = coord@MousePosition@"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 = coord@MousePosition@"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_] :=
         Deploy@EventHandler[
            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",
           List@ 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

Не можете прокомментировать упомянутые вами проблемы с производительностью.Кроме того, когда вы начинаете перетаскивать мышью, реальная позиция курсора часто совершенно не совпадает с позицией делителя.Это как для вашей, так и для моей версии, возможно, требуется более точное масштабирование.

Просто хочу еще раз подчеркнуть - обобщение стало возможным только после того, как я произвел рефакторинг, чтобы отделить логику расщепления от вещей, связанных с визуализацией.Что касается оптимизации, я также думаю, что по той же причине попытаться оптимизировать эту версию будет гораздо проще, чем оригинальная.

РЕДАКТИРОВАТЬ

Я колебалсянемного, чтобы добавить это примечание, но следует отметить, что мое решение, приведенное выше, хотя и работает, показывает одну практику, которая считается плохой опытными программистами UMA mma.А именно, он использует Module - сгенерированные переменные внутри Dynamic внутри этого Module (в частности, split в приведенном выше коде, также различные вспомогательные функции).Я использовал его по той причине, что мне не удалось заставить эту работу работать только с DynamicModule - сгенерированными переменными, плюс Module - сгенерированные переменные всегда работали для меня раньше.Однако, пожалуйста, смотрите пост Джона Фульца в этой ветке MathGroup, где он утверждает, что этой практики следует избегать.

6 голосов
/ 17 октября 2011

Тяжело опираясь на решение Леонида, вот моя версия.Я применил несколько изменений, в основном, чтобы было легче отслеживать динамические изменения размера, и потому что мне просто не удалось усвоить часть кода Леонида.

Изменения сделаны:

  • Удалено *Опция 1006 *, теперь она не может быть установлена ​​пользователем.Это не так важно.
  • Максимальный горизонтальный размер (maxX в постах выше) был отброшен, поскольку теперь он рассчитывается на основе заданных пользователем значений ширины панели: w.
  • Первый аргумент (w, основная динамическая переменная) явно сохраняет ширину панелей вместо сохранения позиций делителя.Кроме того, он был создан как список (w[[n]]) вместо функции (как split[n] было в версии Леонида).
  • Добавлены кнопки минимизации / восстановления в разделители.
  • Ограниченоперемещение разделителя: разделители можно перемещать только слева направо, их дальнейшее перемещение невозможно.
  • Точно настроенная ширина разделителя, ImageMargins, FrameMargins, Spacings, чтобы разрешить панели нулевого размера.

Проблемы, которые еще предстоит решить:

  • При минимизации / максимизации делителей они должны перекрывать левый / правый.Стек разделителей LIFO решит проблему установки максимального значения разделителя, а затем попытается изменить другие разделители с помощью их кнопок.Это может вызвать некоторые проблемы, поскольку они возвращаются к предыдущим состояниям.Проблема со стековыми делителями состоит в том, что это не может быть решено в Grid, или может быть решено только с помощью специально настроенных отрицательных интервалов.Я думаю, что это не стоит иметь дело.
  • Незначительные проблемы с выравниванием при сжатии панели до нулевой ширины / высоты.С этим я могу жить.

</p> <pre><code>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} /. Options@SplitPane; dir = dir /. {Bottom | Top | "Vertical" -> "Vertical", _ -> "Horizontal"}; bg = panel /. {None | False -> GrayLevel@.9, _ -> 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 - Last@#) &, "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_] := Deploy@EventHandler[MouseAppearance[Framed[ Item[Button[Dynamic@If[state[[n]], First@icon, Last@icon], 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" :> onMouseDownCode@n, "MouseDragged" :> onMouseDraggedCode@n, PassEventsDown -> True]; onMouseDownCode[n_] := ( old = {w[[n]], w[[n + 1]]}; origo = coord@MousePosition@"CellContentsAbsolute"; ); onMouseDraggedCode[n_] := ( temp = coord@MousePosition@"CellContentsAbsolute" - origo; w[[n]] = Min[Max[0, First@old + temp], Total@old]; w[[n + 1]] = Total@old - 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], size@#2], TrackedSymbols :> {w}] &, {content, Range@Length@w}], Dynamic[divPane@#, TrackedSymbols :> {w}] & /@ Range@((Length@w) - 1)]; Deploy@Grid[If[dir === "Vertical", List@grid[cont, {w[[#]], fix} &], List /@ grid[cont, {fix, w[[#]]} &] ], Spacings -> {0, -.1}, ItemSize -> {{Table[0, {Length@w}]}, {Table[0, {Length@w}]}}], 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 = Length@w; state = True & /@ Range@num; prev = w & /@ Range@num; max = Total@w;) ]; SplitPane[val_, arg___] /; (Head@val === List \[And] And @@ (NumberQ /@ val)) := Module[{x = val}, SplitPane[Dynamic@x, arg]];

Давайте попробуем вертикально разделить панель:

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

vertical example

Вот горизонтально разделенная панель:

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

Вертикальные и горизонтальные панели вместе:

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

combined example

Я хотел бы услышать ваши идеи / комментарии по этому решению.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...