Selection order

Hi,

When I select the objects in the view, the order or index# of the objects copied to the view.selection is mapped to the same order as the objects added into the views document.
I am looking for some kind of solution, where if user starts selecting the objects by dragging from the top left corner to the bottom right corner, the objects that will be copied in to the selection handle should be based on the position of each node in the view.
Please let me know if the above solution is possible?

Sure – that’s easy to implement by customizing the GoToolRubberBanding tool. Instead of just calling GoView.SelectInRectangle, we sort the objects to be selected before adding them to the GoView.Selection.

[Serializable]
public class SortedRubberBandTool : GoToolRubberBanding {
public SortedRubberBandTool(GoView view) : base(view) { }

public override void DoRubberBand(Rectangle box) {
if (!IsBeyondDragSize()) { // not really a drag, but a click
// handle any selection side-effects due to this "click"
DoSelect(this.LastInput);
// whether or not we found and/or selected anything, call DoClick
DoClick(this.LastInput);
} else {
RectangleF docRect = this.View.ConvertViewToDoc(box);
// instead of calling this.View.SelectInRectangle(docRect),
// call our own version of SelectInRectangle that sorts the objects by position
SelectInRectangle(docRect);
}
}

public virtual void SelectInRectangle(RectangleF rect) {
GoView view = this.View;
if (!view.CanSelectObjects()) return;
// keep everything in a separate collection, in case adding to the
// selection changes the selection...
GoCollection coll = new GoCollection();
foreach (GoLayer layer in view.Layers) {
if (!layer.IsInDocument) continue;
layer.PickObjectsInRectangle(rect, view.SelectInRectangleStyle, coll, 999999);
}
// now everything's in COLL -- sort by position
GoObject[] arr = coll.CopyArray();
Array.Sort(arr, new PositionComparer());
// and then add them all to the selection, in order
GoSelection sel = view.Selection;
view.RaiseSelectionStarting();
for (int i = 0; i < arr.Length; i++) sel.Add(arr[i]);
view.RaiseSelectionFinished();
}
[Serializable]
public class PositionComparer : System.Collections.IComparer {
public PositionComparer() { }
public int Compare(Object x, Object y) {
GoObject a = (GoObject)x;
GoObject b = (GoObject)y;
if (a.Left < b.Left) return -1;
else if (a.Left > b.Left) return 1;
else return 0;
}
}
}
Install in your GoView with:
goView1.ReplaceMouseTool(typeof(GoToolRubberBanding), new SortedRubberBandTool(goView1));

Walter,

I am having trouble implementing PickObject, PickObjects and PickObjectsInRectangle.
It seems like I am not getting the objects I should be.
I am trying to test whether or not a rectangle (given specific X/Y coordinates and width/height) contains any GoNodes when the user is performing an "Add New Node" action.
The user right-clicks on a node and selects "Add Sibling". At that point I use recursion to call a method called IsOccupied() (in it I use the GoDocument.isUnoccupied() method to perform the actual test) passing in a referenece to the existing GoNode that was right-clicked, a RectangleF and a reference to the new sibling that was just added to the document in the same location as the original node that was right-clicked.
Obviously the check for isOccupied will return true the first time around since I have placed the new GoNode directly on top of the existing GoNode. I then proceed to increase the X coordinate of the RectangleF by that was passed in as an argument to my isOccupied method by 32.
The definition for my method looks like this:
private bool IsOccupied(PersonNode pExisting, PersonNode pShifted, ref RectangleF rect, int LocationX) { if (!this.pDoc.IsUnoccupied(rect, null) && pExisting != null) { //In this scenario we continue to shift the newly placed node. if ((pExisting.Person.FatherID == pShifted.Person.FatherID || pExisting.Person.FatherID != 0) && pExisting.Person.NodeID != pShifted.Person.NodeID) { rect.X += 32; pExisting = pDoc.PickObject(rect.Location, true) as PersonNode; //Get a reference to the node at the new location.

pShifted.Person.LocationX = (int)rect.X;
pShifted.Person.LocationY = (int)rect.Y;
pShifted.Position = new PointF(pShifted.Person.LocationX, pShifted.Person.LocationY);
PersonHash[pShifted.Person.NodeID].LocationX = pShifted.Person.LocationX;
DataRow[] row = this.persons.Select(“Family_Member_CD=” + pShifted.Person.NodeID);
PersonCollection.PersonsRow prow = (PersonCollection.PersonsRow)row[0];
prow.LocationX = pShifted.Person.LocationX;
prow.LocationY = pShifted.Person.LocationY;
IsOccupied(pExisting, pShifted, ref rect, pShifted.Person.LocationX);
}
//In a scenario where we hit a node that is not biologically related to the newly placed node the existing node gets shifted.
else
{
pShifted = pExisting;
rect.X += 32;
pExisting = pDoc.PickObject(rect.Location, true) as PersonNode; //Get a reference to the node at the new location.
pShifted.Person.LocationX = (int)rect.X;
pShifted.Person.LocationY = (int)rect.Y;
pShifted.Position = new PointF(pShifted.Person.LocationX, pShifted.Person.LocationY);
PersonHash[pShifted.Person.NodeID].LocationX = pShifted.Person.LocationX;
DataRow[] row = this.persons.Select(“Family_Member_CD=” + pShifted.Person.NodeID);
PersonCollection.PersonsRow prow = (PersonCollection.PersonsRow)row[0];
prow.LocationX = pShifted.Person.LocationX;
prow.LocationY = pShifted.Person.LocationY;
IsOccupied(pExisting, pShifted, ref rect, pShifted.Person.LocationX);
}
}
return false;
}

I then set
pExisting = pDoc.PickObject(rect.Location, true) as PersonNode
to get a reference to a PersonNode at the new location (I later test to make sure that it's not null).
The purpose of this method is to not only make sure nodes don't overlap but also to insure that the correct nodes get moved when they do. For example, the reason why I set pShifted = pExisting in my "else" statement is b/c once I have discovered that the node at the existing location is not a biological sibling of the new node that was added I then want to stop shifting over the newly added node and start shifting the existing "non-biologically-related" node instead.
Obviously this whole process is VERY, VERY cumbersome. I still have to deal with creating trees where you add both sets of inlaws and how to handle the criss-crossing of the husband's parents with the wife's parents.
I tried a different approach by modding the FamilyTree example that you guys provided. That's actually been my guide from day 1. One of the major flaws with it however is that after I modify it to use Orthogonal links for marriages and children things at first look OK.
I actually got pretty excited at first b/c I thought this was the answer. Then I realized that there was something very important that the current FamilyTree sample doesn't address: Extended Families.
The FamilyTree sample only shows geneology from a single bloodline. It doesn't include the family trees of the people that married into a family. It just shows a node for the 1 person that married in. In my trees I have all of these nodes from people that "married into" families so things can get mighty hairy. Instead of only having a pyramid you end up having either an upside-down pyramid, or something that looks like a bunch of W's and V's all over the place.
Unless I am totally missing something, is there any way to get the layout I need where nodes and links avoid one another -but only when necessary AND the tree still looks normal (i.e. husbands and wives are at the same "generation level" (i.e. Y coordinate) and you don't run into issues where someone's sibling or cousin is placed on top of their marriage link?
Thanks!!!
UPDATE: I think I have the PickObjectsInRectangle() solution working. I believe the issue was that I wasn't testing for null when it didn't return an object. The result of not checking caused an infinite loop that keept passing in a reference to the same PersonNode at the same location in my grid. Oops!
I am still open for suggestions about making this whole layout thing a little easier though.