5
$\begingroup$

I have an InputField that searches a predefined list for matching strings and displays them in a TextGrid. But when the list is large, the front end hangs:

enter image description here

Using Cases is obviously slow, but I don't want to use the fourth argument to limit the number of matches, so can't see any simple way to speed it up:

Clear @ SearchStrings
SearchStrings[input_:""] := DynamicModule[{$input=input, matches = {}},
	Panel @ Column @ {
		Row[{InputField[Dynamic[$input], String,
			FieldHint->"Enter company", FieldHintStyle->{Gray},
			BaseStyle->{"ControlStyle"},ContinuousAction->True,
			FieldSize->13], Dynamic @ Text @ Style[" (" <> ToString @ Length[matches]<> " rows)", 11, FontFamily->"Verdana",
				FontWeight->"Thin", FontColor->Gray]}, "   "],
		Pane[
			Dynamic[
				TextGrid[
					Map[{Button[Text @ Style[#, 13, FontFamily->"Verdana",
								FontWeight->"Thin", FontColor->Black], 
								CopyToClipboard[#], Appearance->"Frameless"]}&,
						matches = Cases[$sites, s_/;StringContainsQ[s,$input], {-1}]
					],
					ItemSize->{40,Automatic}, Dividers->All, Spacings->2, FrameStyle -> Directive[Thickness[Tiny], LightGray]
				, Selectable -> False],
			TrackedSymbols :> {$input}],
        Scrollbars->{False,True}, ImageSize->{230,200}]}
]

$sites = ToString /@ Range[10000];

SearchStrings[""]
$\endgroup$
5
  • $\begingroup$ Side note, there is FieldCompletionFunction option for InputField now. $\endgroup$
    – Kuba
    Commented Jun 5, 2018 at 18:01
  • $\begingroup$ related autocompletion topics: 102470, 28529 related grid interface topic: 21117 $\endgroup$
    – Kuba
    Commented Jun 5, 2018 at 18:13
  • 1
    $\begingroup$ p.s. Cases is not slow. Typesetting and rendering of the long list of matched entries is. That is the problem in the 21117 question too. $\endgroup$
    – Kuba
    Commented Jun 5, 2018 at 18:32
  • $\begingroup$ Thanks, I did know about FieldCompletionFunction - but wanted to implement more permanent view of the results. Do you think something like UITableView that supports streaming data into a scrolling view is possible to write ourselves? $\endgroup$
    – M.R.
    Commented Jun 5, 2018 at 20:44
  • $\begingroup$ Yes, but at a cost of items size flexibility and using custom scrollbars which will not render as nicely as OS versions. Plus it needs to be created too. $\endgroup$
    – Kuba
    Commented Jun 6, 2018 at 7:55

1 Answer 1

4
$\begingroup$

The bottleneck is the fact that Pane 'cares' about full content even when only the fraction is needed. We need Pane that fits our needs better.

The solution is based on a beta version of VerticalScrollbar and ListPane, if you find it hard to customize, let me know about options it should take, syntax etc.

It was only tested on Win7.

Let me know what changes you;d like to see in a polished version.

enter image description here

VerticalScrollbar // ClearAll ;

VerticalScrollbar // Options = {
  "PageSize" -> .2
, ImageSize -> Automatic  
};

VerticalScrollbar[Dynamic[scrollPosition_], OptionsPattern[] ]:=With[
  { pageSize = OptionValue["PageSize"]
  , MPY     := MousePosition[{"GraphicsScaled", Graphics}, {0,0}][[2]]
  , thumbCol = GrayLevel @ .9
  , thumbColActive = GrayLevel @ .8
  , scrollHeight = .2
  , imageSize  = OptionValue[ImageSize] /. Automatic -> {15, Full}

  }
, DynamicModule[
    { 
      showScrollbar,            
      scrollRawPosition = 1 - (scrollPosition + scrollHeight),
      mousePositionStart,
      scrollRawPositionOffset = 0,
      clipSRPos = Function[pos, Clip[pos, {0 , 1 - scrollHeight }]],
      col = GrayLevel[.9]
    }
  , 
   Module[
      {pane, scrollbar}

    , scrollbar = Graphics[
        DynamicNamespace @ { EdgeForm@Black, Dynamic@If[CurrentValue["MouseOver"], thumbColActive, thumbCol]
        , Rectangle[{0, Dynamic[scrollRawPosition]}, {1, Dynamic[scrollRawPosition + scrollHeight]}, BoxID -> "thumb"]      
        , Black, Inset["=", DynamicLocation["thumb", None, Center]]
        }      
      , ImageSize        -> imageSize
      , PlotRange        -> {{0,1}, {0,1}}
      , PlotRangePadding -> {2. / 15, 2./200}
      , ImageMargins->0
      , PlotRangeClipping->False
      , AspectRatio->Full


      , ImagePadding->None
      , Background->[email protected]
      ]

    ; scrollbar = EventHandler[
        scrollbar
      , {
          "MouseDown" :> (
            mousePositionStart = MPY
          ; Which[            (*inside, maybe better vie MouseEntered?*)
              mousePositionStart < scrollRawPosition
            , scrollRawPosition = clipSRPos[scrollRawPosition - pageSize]

            , mousePositionStart > scrollRawPosition + scrollHeight  
            , scrollRawPosition = clipSRPos[scrollRawPosition + pageSize]

            , True, scrollRawPositionOffset = MPY - scrollRawPosition 
            ]
          )
        , "MouseDragged" :> (
            scrollRawPosition = clipSRPos[MPY - scrollRawPositionOffset]
          )

        }  
      ]  
        (* visibility *)
    ; scrollbar = PaneSelector[
        { True -> scrollbar, False-> Spacer[0]}
      , Dynamic[showScrollbar]
      ]
        (* listener*)
    ; scrollbar = DynamicWrapper[scrollbar    
      , scrollPosition = 1 - Rescale[scrollRawPosition, {0 , 1-scrollHeight}, {0, 1}]
      , TrackedSymbols :> {scrollRawPosition}
      ]


    ; scrollbar  
    ]
  ]
];

ListPane // ClearAll;

ListPane // Options = {
   ImageSize -> {All, 200},
   "ItemWrapper" -> (Pane[##, ImageMargins->0]&)
};

ListPane[Dynamic[list_], OptionsPattern[]]:=With[
  {
    paneHeight = OptionValue[ImageSize][[2]],
    itemHeight = 25,
    totalLength = Length[list],
    imageSize = OptionValue[ImageSize],
    itemWrapper = OptionValue["ItemWrapper"]
  }
, DynamicModule[
    {
      visibleLength = Ceiling[paneHeight/itemHeight],
      visiblePart,
      position = 1,
      scrollPosition = 0

    }
  , visibleLength = Ceiling[paneHeight/itemHeight]
  ; visiblePart = Take[list,UpTo[visibleLength]]


  ; Module[
      {pane}

    , pane = Dynamic[ (*TODO: preburn*)
        Column[
          itemWrapper[#, ImageSize->{Full, itemHeight},ImageMargins->0, Alignment->{Left,Center}]&/@visiblePart
        , Spacings->0
        ]
      ]
    ; Panel[
        Grid[{{
          pane
        , DynamicWrapper[
            VerticalScrollbar[Dynamic@scrollPosition, ImageSize -> {15, paneHeight}]
          , position = Round @ Rescale[scrollPosition, {0,1}, {1, totalLength}]
          ; visiblePart = Take[list, {position, UpTo[position + visibleLength]}]
          , TrackedSymbols:>{scrollPosition}
          ]

        }}, Spacings->{1,0},Alignment->{Left,Top}]
      , ImageSize->{imageSize[[1]], paneHeight+visibleLength-1}
      , FrameMargins->0
      , Alignment->{Right,Top}
      , BaseStyle->CacheGraphics->False
      ] 
    ]
  ]
];

And your updated code

Clear@SearchStrings
SearchStrings[input_: ""] := 
 DynamicModule[{$input = input, matches = {}},
  Panel[
   Column@{Row[{
       InputField[Dynamic[$input], String, 
        FieldHint -> "Enter company", FieldHintStyle -> {Gray}, 
        BaseStyle -> {"ControlStyle"}, ContinuousAction -> True, 
        FieldSize -> 13],
       Dynamic@
        Text@Style[" (" <> ToString@Length[matches] <> " rows)", 11, 
          FontFamily -> "Verdana", FontWeight -> "Thin", 
          FontColor -> Gray]
       }, "   "
      ],
     DynamicWrapper[
      Dynamic[
       ListPane[
        Dynamic[matches], ImageSize -> {Full, 200}, 
        "ItemWrapper" -> (Button[
            Text@Style[#, 13, FontFamily -> "Verdana", 
              FontWeight -> "Thin", FontColor -> Black], 
            CopyToClipboard[#], Appearance -> "Frameless", ##2] &)
        ],
       TrackedSymbols :> {matches}
       ],
      matches = Cases[$sites, s_ /; StringContainsQ[s, $input], {-1}],
      TrackedSymbols :> {$input}
      ]

     }
   ,
   ImageSize -> {300, All}]
  ]

$sites = ToString /@ Range[10000];

Deploy@SearchStrings[""]
$\endgroup$
1
  • $\begingroup$ What does preburn mean in your todo? $\endgroup$
    – M.R.
    Commented Jan 19, 2020 at 23:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.