SelectorsResides in the namespace
FasterWPF.Selectors in the Select static class.
One of the things about WPF that always struck me as odd was the many different types of children:
ContentControl.Content
ItemsControl.Items
Decorator.Child
Grid.Children
ToolBarTray.ToolBars
RichTextBox.Document
I always thought this made things confusing and elements difficult to traverse.
Then I heard about the LogicalTree and the VisualTree and I was ready to accept this until I read that when traversing there are times when you will have to switch back from one tree to the other to traverse down. This seemed too confusing to me: I wanted one consistent, reliable way to traverse all elements on the screen.
Compare this to
CSS 3 Selectors and
jQuery where it is relatively easy to get a handle to any item on the screeen? (WPF Composites now supports something similar to CSS classes: see further below.) Or consider how excellent LINQ-to-Objects is and how great it would be to have a LINQ-to-WPF Elements?
Selectors in WPF CompositesIn WPF Composites, I have effectively built a facade layer over WPF via extension methods to hopefully smooth this all out? Every parent contains a dictionary of Composites internally as well as a separate dictionary of all Children. So getting all children means performing a foreach on FrameworkElements within a parent's ChildLookupDictionary:
myParent.GetChildLookupDictionary<FrameworkElement>();
Getting all composites means performing a foreach on Borders within a parent's CompositeLookupDictionary:
myParent.GetCompositeLookupDictionary()
These methods have enabled me to build the new Selectors Engine. Currently, I only support FrameworkElements NOT FrameworkContentElements. Yet, the Selectors Engine can already do a lot: select all FrameworkElements, all FrameworkElements by Type, all Parents, all Children, all Composites, find a Composite by Key, call an Action against all Parents, or call DisposeEvents recursively down all Parents.
Selectors Engine Examples
//All of these examples recurse down from any root, initialized IParent, e.g. you may pass in a Window, or a UserControl, or a Grid or . . . to these extension methods.
//Select all FrameworkElements
List<FrameworkElement> felist1 = this.SelectAllFrameworkElements(false);
//Select all FrameworkElements by Type
List<TextBox> textBoxChildren = this.SelectFrameworkElementsByType<TextBox>();
textBoxChildren.ForEach(txb => { txb.SetBackgroundColor(Brushes.Goldenrod); });
//Select all Parents Only
List<FrameworkElement> felist2 = this.SelectParentFrameworkElementsOnly(false);
//Select all Children Only
List<FrameworkElement> felist3 = this.SelectChildFrameworkElementsOnly(false);
//Select all Grids from Any GridComposites
List<Grid> felist4 = this.SelectGridsFromAllGridComposites(false);
//Select all Composites Only
List<Border> felist5 = this.SelectCompositesOnly(false);
//Select a Composite By Key
Border userControlComposite = this.SelectCompositeByKey(userCtlGuid);
//Dispose Events down thru any children that may themselves be Parents. Note that I am unable to start recursing down from a Composite. First, get a handle to a Parent, and then you may recurse down from there.
var myUserControl = userControlComposite.GetParentFromComposite<UserControl>();
myUserControl.DisposeEventsOnParentFrameworkElements();
You may also
recurse up from any Framework Element a certain number of elements, stopping when a specific Type is reached, or an element with a specific Name, or an element with a specific Selector Class Name:
FrameworkElement topElement = Selectors.RecurseUpParentFrameworkElements(false, 100, mainGrid);
FrameworkElement elementOfType = Selectors.RecurseUpParentFrameworkElementsUntilType(false, 100, mainGrid, "Border");
FrameworkElement elementWithName = Selectors.RecurseUpParentFrameworkElementsUntilName(false, 100, mainGrid, "mainGrid");
FrameworkElement elementWithClass = Selectors.RecurseUpParentFrameworkElementsUntilClass(false, 100, mainGrid, "testThisSelector");
//There are also overloads that allow storing all parents collected along the upwardly recursed path in a List
List<FrameworkElement> allParentsInPath = new List<FrameworkElement>();
FrameworkElement topElement = Selectors.RecurseUpParentFrameworkElements(false, 100, allParentsInPath, mainGrid);
Selector ClassOne new feature that has recently been added is the ability to stamp Framework Elements with a selector class name (similar to CSS classes for the web) and then retrieve by this class name later using the Selectors engine. Up to three classes may be assigned to a single Framework Element, e.g. red box tab3.
To use this feature, first call
SetSelectorClass<T> in the BeginSettings . . . EndSettings chain in order to assign up to three classes:
this.BeginSettings<UserControl>()
.SetSelectorClass<UserControl>(1, 0, "tabLabel")
.SetSelectorClass<UserControl>(1, 1, "bold", "tabItem3")
.SetSelectorClass<UserControl>(1, 2, "italic", "bold", "tabItem3")
.SetItemBorderColorAndThickness<UserControl>(Brushes.Gray, new Thickness(3)).EndSettings();
Then, Framework Elements added to a Composite at those X-Y coordinates will be assigned those classes.
Afterwards, you may search and retrieve these Framework Elements by those class names using the method:
-
SelectAllFrameworkElementsByClass.
There are also these additional related methods:
-
SetSelectorClassToCol-
SetSelectorClassToRow-
IsInSelectorClassProperties-
FilterElementsBySelectorClass-
ActOnElementsFilteredBySelectorClassYou may also set a Selector class directly on a Parent or any random FrameworkElement:
myElement.SetSelectorClassOnParent("testThisSelector");
Even Without the Selectors EngineBut even without the Selectors Engine, WPF Composites by default allows fine-grained access over elements on the screen by GUID/ID and by X-Y Coordinate (row-column).
For instance, if you have a handle to the parent you can readily get a Child by GUID and X-Y Coordinate:
Label lbl1= myGrid.GetChildFromParent<Label, Grid>(gridguid1, 0, 0);
Or, if you have a handle to the Composite you can readily get a Child by X-Y coordinate:
thisComposite.GetChildFromComposite<TextBox, Grid>(5, 1).Text="hi!";
Or, if you have a handle to the parent you can readily get ALL Children by GUID:
List<object> childList = myGrid.GetChildrenFromComposite<Grid>(gridguid1);
Or, if you have a handle to the parent you can readily get the Composite's container by GUID; for instance, if a GridComposite, you could get the Grid from inside the Composite:
Grid compGrid = myGrid.GetContainerFromComposite<Grid, Grid>(gridguid1);
Also, from the handle of the parent, you can also Remove the entire Composite by Guid/ID:
cnvs.RemoveByKey<Canvas>(cnvsguid0);
Finally, you can Update all children in the parent with a specific X-Y coordinate or a child at an X-Y in a single Composite by key (Guid/ID):
//Update any property on child by row col via an Action
dg.Update<TextBlock, DataGrid>(2, 0, txb1 =>
{
txb1.Background = Brushes.Red;
});
//Update any property on a single child located by key AND by row col . . . via an Action
dg.UpdateByKey<Rectangle, DataGrid>(cellGuid3, 1, 0, txb1 =>
{
txb1.ClearValue(Rectangle.FillProperty);
});
Moreover, the BeginComposite . . . EndComposite syntax itself allows you to Add a Child at a key-x-y coordinate, and the BeginSettings . . . EndSettings allows you to set properties on elements based on x-y.
chain.Set<TextBlock, T>(row, column, "FontSize", fontSize);
Thus, a lot of ground is covered . . . and hopefully a great deal of flexibility is achieved. I believe the only two methods that may be missing yet are RemoveChildAtKeyXY and/or ReplaceChildAtKeyXY . . . these may be implemented in a later release?