Selectors

Resides 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 Composites

In 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 Class

One 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
- ActOnElementsFilteredBySelectorClass

You may also set a Selector class directly on a Parent or any random FrameworkElement:
myElement.SetSelectorClassOnParent("testThisSelector");

Even Without the Selectors Engine

But 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?

Last edited Mar 15 at 5:49 PM by stagathome0069, version 46

Comments

No comments yet.