Select additional items upon selection

When instances of a certain subclass of GoObjects get selected, either by (ctrl+)clicking or drawing a selection rect, I would like to add additional items to the selection.



An Object.OnGetSelection override does not appear to let me do this; the documentation states that it should not change which objects are selected.



Should I use a GoView.OnObjectSelected override ?

Or should I subclass GoToolSelecting ?

Both Object.OnGotSelection and GoView.OnObjectSelected are documented as “should not modify the selection”.

Let me go study this a bit...

ok… I decided to take the path of implementing a new GoToolSelecting.

Now, I didn't know:
- how you want to define what gets selected... so I picked a trivial example of selecting all the GoBasicNodes with the same first character.
- how you wanted to handle Shift or Ctrl click
- if SubGraphs were important
[code]
[Serializable]
public class GoToolSelecting2 : GoToolSelecting {
public GoToolSelecting2(GoView v) : base(v) {}
public override void DoSelect(GoInputEventArgs evt) {
// GoView.PickObject and GoDocument.PickObject will enforce selectability
this.CurrentObject = this.View.PickObject(true, false, evt.DocPoint, true); // only selectable document objects
//NB: can't access "internal" CurrentObjectWasSelected .. this will prevent GoText.EditableWhenSelected from working.
//this.CurrentObjectWasSelected = this.View.Selection.Contains(this.CurrentObject);
if (this.CurrentObject != null) {
if (evt.Control) {
this.Selection.Toggle(this.CurrentObject);
}
else if (evt.Shift) {
this.Selection.Add(this.CurrentObject);
}
else {
//this.Selection.Select(this.CurrentObject); ... this is what DoSelect did before.
GoCollection coll = new GoCollection();
GoBasicNode bn = this.CurrentObject as GoBasicNode;
// if the just picked item is a GoBasicNode, select all the nodes in doc that .Text starts with same char
if (bn != null) {
coll.Add(bn); // first
string first = bn.Text.Substring(0, 1); // select all nodes that start with same first char
foreach (GoObject o in this.View.Document) {
if (o != bn && o is GoBasicNode) {
GoBasicNode n = o as GoBasicNode;
if (n.Text.StartsWith(first)) coll.Add(n);
}
}
}

if (coll.Count > 1) {
this.Selection.Clear();
this.Selection.AddRange(coll); // will do RaiseSelectionStarting, add, add, add..., RaiseSelectionFinished
}
else
this.Selection.Select(this.CurrentObject); // nope, just the one
}
}
else if (!evt.Control && !evt.Shift) {
this.Selection.Clear();
}
}
}
[/code]
this doesn't handle drag-box selection, that's a separate tool.
making CurrentObjectWasSelected "internal" was probably a mistake. not being about to set this flag breaks the EditableWhenSelected behavior. (If that's important to you, I can show you how to work around that with your own GoText class.)

Thanks, that provides a start…



-Basically, objects with this custom selection behavior will calculate a collection of additional items that need to get selected (or deselected).



-It needs to work for all user initiated selection/deselection operations.



-Subclasses are not important (for now at least).



To handle drag-box selection I guess this means I need to subclass GoToolRubberBanding ?

I should say a bit more about why I chose to do this with a Tool as opposed to creating a new GoSelection class and overriding Add.

GoToolRubberBanding.DoRubberBand calls GoView.SelectInRectangle, which builds a collection of all the selectable items and calls GoSelection.AddRange.
GoSelection.AddRange raises the SelectionStarting event, then calls GoSelection.Add for each object in the collection, then raises the SelectionFinished event.
so... the calls to GoSelection.Add from a drag-box selection at this point can't be distinguished from a single click. And I didn't know if your rules about selection cared about that. (example: if you click on A1 and the selects A1 and A2 in your app, but clicking on A2 selects A3 and A4... then figuring out whether A2 is being "Added" because it was clicked on or because A1 was is a bit sticky at this lower level.)

Here’s a quick equivalent for the GoToolRubberBanding



  // Setup by:
  //  goView1.ReplaceMouseTool(typeof(GoToolRubberBanding), new RubberBandTool2(goView1));
  [Serializable]
  public class RubberBandTool2 : GoToolRubberBanding {
    public RubberBandTool2(GoView view)
      : base(view) {
      this.AutoScrolling = true;
    }

     /// <summary>
    /// This method is called as part of the mouse up event, normally to select
    /// the objects within the <paramref name="box"/>.
    /// </summary>
    /// <param name="box">a <c>Rectangle</c> describing what the user outlined, in view coordinates</param>
    /// <remarks>
    /// By default this will call <see cref="GoView.SelectInRectangle"/>, after converting
    /// the <paramref name="box"/> into document coordinates.
    /// If the box is too small in width and height, this acts like a normal mouse click instead.
    /// </remarks>
    public override void DoRubberBand(Rectangle box) {
      if (!IsBeyondDragSize()) {
        DoSelect(this.LastInput);
        // whether or not we found and/or selected anything, call DoClick
        DoClick(this.LastInput);
      }
      else {
        RectangleF docRect = this.View.ConvertViewToDoc(box);
        SelectInRectangle(docRect); // not this.View.
      }
    }

    // copied and modified from GoView...
    public virtual void SelectInRectangle(RectangleF rect) {
      // keep everything in a separate collection, in case adding to the
      // selection changes the selection...
      ArrayList coll = new ArrayList();
      foreach (GoLayer layer in this.View.Layers) {
        if (!layer.IsInDocument) continue;
        if (!layer.CanViewObjects()) continue;
        if (!layer.CanSelectObjects()) continue;
        foreach (GoObject obj in layer) {
          selectObjectInRectangle(obj, rect, coll);
        }
      }
      foreach (GoObject obj in coll) {
        this.Selection.Add(obj);
      }
    }

    private void selectObjectInRectangle(GoObject obj, RectangleF rect, ArrayList coll) {
      if (!obj.CanView())
        return;
      if (obj.CanSelect() &&
          ((obj.SelectionObject != null) ?
           obj.SelectionObject.ContainedByRectangle(rect) :
           obj.ContainedByRectangle(rect))) {
        coll.Add(obj);

        AddObjectsRelatedTo(obj, coll);
      }
      else if (obj is GoGroup) {
        this.Selection.Remove(obj);
        GoGroup g = (GoGroup)obj;
        foreach (GoObject o in g.GetEnumerator()) {
          selectObjectInRectangle(o, rect, coll);
        }
      }
      else {
        this.Selection.Remove(obj);
      }
    }

    private void AddObjectsRelatedTo(GoObject obj, ArrayList coll) {
      GoBasicNode bn = obj as GoBasicNode;

      // if the just picked item is a GoBasicNode, select all the nodes in doc that .Text starts with same char
      if (bn != null) {
        //coll.Add(bn); 
        string first = bn.Text.Substring(0, 1); // select all nodes that start with same first char

        foreach (GoObject o in this.View.Document) {
          if (o != bn && o is GoBasicNode) {
            GoBasicNode n = o as GoBasicNode;
            if (n.Text.StartsWith(first) && (coll.IndexOf(n) == -1) ) coll.Add(n);
          }
        }
      }

    }
  }

Thanks, I got it working.

I decided I only needed the GoToolSelecting subclass.