我一直在开发gui,这需要创建Mathematica缺少的公共控件(例如spinner,treeview,openerbar等)。一个是多面板,即窗格对象,其被分成两个(或更多个)子窗格,其中分隔符可以由鼠标设置。这是我的双窗格版本。我想听听你的意见和想法,如何扩展它,不仅处理2个,而且处理任意数量的子窗格,以及如何优化它。目前,对于负载较重的子窗格,它严重滞后,不知道为什么。
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}]
答案 0 :(得分: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 = 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}]
无法评论您提到的性能问题。此外,当您开始使用鼠标拖动时,实际光标位置通常相对于分隔符位置非常偏离。这适用于您和我的版本,也许需要一些更精确的缩放。
只想再次强调 - 只有在我进行重构之后才能实现泛化,将分裂逻辑与可视化相关的事物分开。至于优化,我也认为,出于同样的原因,尝试优化此版本要比原始版本容易得多。
修改强>
我有点犹豫要添加这个注释,但必须提到的是,我的上述解决方案在工作时显示了一个被专家UI mma程序员认为是错误的做法。也就是说,它使用Module
- 内部Dynamic
内部生成的变量Module
(特别是上面代码中的split
,以及各种辅助函数)。我使用它的原因是我无法仅使用DynamicModule
- 生成的变量,加上Module
来完成这项工作 - 生成的变量以前总是适用于我。但是,请参阅John Fultz在this MathGroup主题中的帖子,其中他指出应该避免这种做法。
答案 1 :(得分:6)
大力建立Leonid的解决方案,这是我的版本。我已经应用了几个更改,基本上是为了更容易跟踪动态大小更改,因为我只是无法内化Leonid代码的一部分。
所做的更改:
DividerWidth
选项,现在无法由用户设置。这并不重要。maxX
)已被删除,因为它现在是根据用户指定的面板宽度值计算的:w
。w
,主动态变量)明确保存面板的宽度,而不是保存分隔符位置。此外,它被设为一个列表(w[[n]]
)而不是一个函数(因为split[n]
在Leonid的版本中)。ImageMargins
,FrameMargins
,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} /. 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]];
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"}]
垂直和水平窗格合并:
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"]
我想听听您对此解决方案的想法/意见。