10 How-To Examples

List of Examples
1. How To Change Skin of an Application
2. How To Layer Two Elements in the Same Grid Cell
3. How To Add a Setting Leveraging a Base Type for K
4. How To Nest IParents Within BeginComposite
5. How To Vary the Settings and/or Contents of a Composite (i.e. Mimmick a DataTemplateSelector)
6. How To Set a Style
7. How To Set a Trigger
8. How To Modify an Element at X-Y Within the BeginComposite . . . EndComposite Chain
9. How to Enable Accelerator Shortcut Keys (e.g. Alt-E to Edit)
10. How to Set Tab Order and to Set Focus to First Field on a Form

1. How To Change Skin of an Application

changeskinpic.png

One of the purported key benefits of WPF is the ability to both theme and skin an application easily. In WPF Composites, I force the theme to always be Aero in order to provide consistency and control. I find it is too difficult and time-consuming to support a range of themes. However, skinning seems to be a much simpler prospect. I have added several ChangeSkin overloaded methods to BrushExt to support skinning an app easily. This is done by changing the skin of controls based on Type, starting at a top-level control and recursing down (e.g. in the code below, I pass in the MainWindow as the top-level starting point.)

Just about every aspect of every supported control is available for skinning. There may be a few exceptions such as file menu items, OnMouseOver highlight colors, and a few pieces of the Extended WPF Toolkit™ controls which I may not have had time to look at. The key point in this regard is just to plan ahead if you wish to support skinning so that you can make any inaccessible items a neutral color that will work well with the colors of all your different skins.

Here is the skinning example from the Demo App:

//TEST SKINNING BY TYPES
//Set the skins for StackPanel, TextBlock, and Border tying a change skin action to each of these
BrushExt.ChangeSkin<StackPanel, TextBlock, Border>(App.Current.MainWindow, Brushes.Red, (fe, b) => { ((StackPanel)fe).Background = b; }, Brushes.Red, (fe, b) => { ((TextBlock)fe).Background = b; }, Brushes.Red, (fe, b) => { ((Border)fe).Background = b; });

//Set the skins for ListBox and DataGrid
BrushExt.ChangeSkin<ListBox, DataGrid>(App.Current.MainWindow, Brushes.Red, (fe, b) =>
{
     ((ListBox)fe).SetVerticalScrollThumbColors<ListBox>("Red", "Silver", "Gray", "Red", "0", "0");
     ((ListBox)fe).Background = b;
}, 
Brushes.Red, (fe, b) => {
     ((DataGrid)fe).SetVerticalScrollThumbColors<DataGrid>("Red", "Silver", "Gray", "Red", "0", "0");
     ((DataGrid)fe).Background = b;
     ((DataGrid)fe).SetColumnHeaderForeground(Brushes.White);
     ((DataGrid)fe).SetColumnHeaderColor(Brushes.Red );
});

//Set the skins for Label, ComboBox, and Menu
BrushExt.ChangeSkin<Label, ComboBox, Menu>(App.Current.MainWindow, Brushes.Red, (fe, b) => { ((Label)fe).Background = b; }, Brushes.Red, (fe, b) => { ((ComboBox)fe).Background = b; }, Brushes.Red, (fe, b) => { ((Menu)fe).Background = b; });

//Set the skins for all Composite Containers of Type VerticalPanel (StackPanel)
BrushExt.ChangeSkinOnContainers<StackPanel>(App.Current.MainWindow, ContainerType.VerticalPanel, Brushes.Red, (fe, b) => { ((StackPanel)fe).Background = b; });

//Set the skins for all Composite Containers of Type HorizontalPanel (StackPanel)
BrushExt.ChangeSkinOnContainers<StackPanel>(App.Current.MainWindow, ContainerType.HorizontalPanel, Brushes.Red, (fe, b) => { ((StackPanel)fe).Background = b; });

//Set the skins for all Composite Containers of Type Grid
BrushExt.ChangeSkinOnContainers<Grid>(App.Current.MainWindow, ContainerType.Grid, Brushes.Red, (fe, b) => { ((Grid)fe).Background = b; });

//Set the skins for Rectangle, TextBox, and TabControl
BrushExt.ChangeSkin<Rectangle, TextBox, TabControl>(App.Current.MainWindow, Brushes.Red, (fe, b) => { ((Rectangle)fe).Fill = b; }, Brushes.Red, (fe, b) => { ((TextBox)fe).Background = b; }, Brushes.Red, (fe, b) =>
{
     ((TabControl)fe).Background = b;
});

//Set the skins for HeaderedContent such as Headers of TabControlItems and/or GroupBoxes
BrushExt.ChangeSkinOnHeaderedContent(App.Current.MainWindow, Brushes.Red, (fe, b) =>
{
     if (fe is ContentControl && ((fe as ContentControl).Content is Border))
     {
             ((fe as ContentControl).Content as Border).Background = Brushes.Red;
      }

      if (fe is TextBlock)
       {
             (fe as TextBlock).Background = Brushes.Red;
        }

        if (fe is Label)
        {
             (fe as Label).Background = Brushes.Red;
         }
});

//Set the skins for controls based on Selector Class Name
BrushExt.ChangeSkinByClass(App.Current.MainWindow, "decimalControl", Brushes.Purple, (fe, b) => { ((wpfToolkit.DecimalUpDown)fe).Background = b; }, fe => { return true; });
                 
})
.EndMenuComposite();


2. How To Layer Two Elements in the Same Grid Cell

OverlayTextExample.png

For the purpose of layering text in the same grid cell, you may want to add a TextBlock at the same Row and Column position in a Grid. This causes a problem since elements must be added with a unique row/column, aka X-Y coordinate.

A workaround that may address this is adding the extra TextBlock with a unique X-Y coordinate first, then changing its Row and Column position in the Grid manually afterwards:
//Get a handle to the TextBlock at 15-0 that has already been added
TextBlock testOverlayingText = myGrid.GetChildViaLookupDictionary<TextBlock, Grid>(gridguid1, 15, 0);
testOverlayingText.Foreground = Brushes.White;

//Change its Row and Column position in the Grid to 0-0
Grid.SetRow(testOverlayingText, 0);
Grid.SetColumn(testOverlayingText, 0);
The main caveat to this is to avoid attempting to Get the Child by grid row and column after the fact. It is okay to GetChildViaLookupDictionary since that will use the X-Y coordinate that was stored, i.e. 15-0 in the above example. However, the GetElement<K, T>(this ContentControl chain, int row, int column) method from CommonExt would not work since this method leverages IComposite's Get method which in turn uses the GridComposite's Get method which retrieves by the actual grid row and column position. This grid row position will now be 0-0 not 15-0.

Alternatively, you could also merely layer another composite in the same location. The above approach involved creating another Child and moving it into the same cell in the same GridComposite. This alternate approach is to use two composites placed into the same cell in the same Parent myGrid:
myGrid.BeginComposite<Grid>(gridguid1)
        .AddText<Grid>(0, 0, "Add a New Vacation")
        .EndComposite<Grid, GridArgs>(new GridArgs(0, 0));

myGrid.BeginComposite<Grid>(gridguid2)
        .AddText<Grid>(0, 0, "Overlay Text")
        .EndComposite<Grid, GridArgs>(new GridArgs(0, 0));
This is allowed because each composite is stored by a unique Guid key rather than by X-Y coordinate. It is the GridArgs only that determines the X-Y placement of Composites. This may in fact be the better solution.


3. How To Add a Setting Leveraging a Base Type for K

During a BeginSettings . . . EndSettings call, you typically call Set<K, T>( property, value) several times. K will typically represent whatever child Type you want to eventually set the property value on. However, what if the property you want to set resides on a Base Type further up the child's class hierarchy?

As of the latest release, this will now be supported when calling Set on a row-column. You can leverage a Base Type as K (up to 8 levels deep), e.g. FrameworkElement:

//SUPPORTED
.Set<FrameworkElement, ContentControl3D>(1,0,"Height", 130D)
.SetToRow<FrameworkElement, ContentControl3D>(1, 0, 3, "Height", 30D)
However, calling Set by Type only (instead of on a row-column) currently still will not support Base Types:
//NOT SUPPORTED
.Set<FrameworkElement, ContentControl3D>("Height", 130D)
This functionality may, perhaps, be added at some point in the future.


4. How To Nest IParents Within BeginComposite

While you are "in-the-flow" of adding items to a Composite with the BeginComposite . . . EndComposite syntax, you may occasionally wish to add and nest a new IParent right there. The new .AddAnyIParent<K, T> method now allows you to do this. See the code example below:
myStackPanel.BeginComposite<StackPanel>(myStackPanelGuid)
                .AddLabel<StackPanel>(0, 1, "XYZ Travel Application", Brushes.Transparent)

 . . . add a bunch more stuff here . . .

                .AddText(0, 4, "example")
                //nest a new IParent ContentControl with a UniformGridComposite, instantiating it on-the-fly . . .
                .AddAnyIParent<ContentControl, StackPanel>(0, 5, myIParent =>
                {
                   myIParent.Initialize(ContainerType.UniformGrid, Brushes.LightGray, new Thickness(3));
                   myIParent.SetCompositeUniformGridSettings(1, 2, 300, 100);
                   myIParent.BeginSettings()
                       .SetFontOnLabel(0, 0, "Arial", 12, FontWeights.DemiBold, FontStyles.Italic)
                       .EndSettings();

                   myIParent.BeginComposite()
                       .AddLabel(0, 0, "This is a:", Brushes.LightSteelBlue)
                       .AddLabel(0, 1, "UniformGrid", Brushes.LightGray)
                       .EndComposite<ContentControl, ContentControlArgs>(null);
                })
               .EndComposite<StackPanel, StackPanelArgs>(null);
This allows for better flow and flexibility. I just recognized how often I would start a BeginComposite chain only to have to stop and scroll up to instantiate a new IParent. This new method should help fix this problem, allowing you to nest new, small IParent code blocks as needed right there inline.

It is also worth noting that if you have already initialized an IParent that is to house just a single Composite or two, you may want to nest it with its BeginComposite call(s). You may do so by using BeginComposite in combination with GetParentFromComposite<T> at the end, like so:
myStackPanel.BeginComposite<StackPanel>(myStackPanelGuid)
                .AddLabel<StackPanel>(0, 1, "XYZ Travel Application", Brushes.Transparent)

 . . . add a bunch more stuff here . . .

                .AddText(0, 4, "example")
                .AddExisting<ScrollViewer, StackPanel>(0, 5, 
                       scvw.BeginComposite<ScrollViewer>(scguid2)
                            .AddExisting<GroupBox, ScrollViewer>(0, 0, 
                                    gpb.BeginComposite<GroupBox>(gxguid1)
                                            .AddAnything<TextBox, GroupBox>(1, 1)
                                            .AddAnything<TextBox, GroupBox>(2, 1)
                                            .AddText<GroupBox>(1, 0, "Attraction:")
                                            .AddText<GroupBox>(2, 0, "Location:")
                                            .AddText<GroupBox>(3, 0, "this is text at row 3 column 0")
                                            .AddText<GroupBox>(0, 0, "Arboretum")
                                            .AddImage<GroupBox>(0, 1, @"pack://siteoforigin:,,,/Images/woods.bmp", UriKind.Absolute, Double.NaN, 1, 1)
                                           .SetMouseOverColorOnContainer<Grid, GroupBox>(Brushes.Silver)
                                   .EndComposite<GroupBox, GroupBoxArgs>(null)
                                   .GetParentFromComposite<GroupBox>()))
                      .EndComposite<ScrollViewer, ScrollViewerArgs>(null)
                      .GetParentFromComposite<ScrollViewer>())
.EndComposite<StackPanel, StackPanelArgs>(null);
Even though this may make for a very long single line of code, it does more cleanly show the hierarchy of nested IParents and children?


5. How To Vary the Settings and/or Contents of a Composite (i.e. Mimmick a DataTemplateSelector)

changesettingexample.png
What if you create settings to be applied to an IParent's Composites, but then realize that you need the settings to vary for a subset of these Composites based on a data value? Or, you realize that you need to vary the contents of the Composite?

There are two different ways to handle this. First, you can use the new ChangeSettings method when you are building your Composites, interrogating the value as you build. For instance, in this example, I check the Flight property value of my business entity (POCO) and call ChangeSettings to vary the Foreground of a TextBlock:
if (trav.Flight == "SEA")
{
         masterDetail.ChangeSetting<TextBlock, DataGrid>(0, 1, "Foreground", Brushes.Green);

         //Column 1
         masterDetail.BeginComposite<DataGrid>(trav.ID + "1")
                                .AddText(0, 0, trav.Counter.ToString())
                                .AddText(0, 1, "Flight: " + trav.Flight)
                                .AddText(0, 2, "Hotel: " + trav.Hotel)
                                .AddText(0, 3, "Attraction: " + " " + trav.Attraction)
                                .AddLabel(0, 4, "Vary for SEA", Brushes.WhiteSmoke)
                                .SetMouseOverColorOnContainer<StackPanel, DataGrid>(Brushes.Silver)
                                .EndComposite<DataGrid, DataGridArgs>(new DataGridArgs(trav.ID, trav.Counter.Value, 0, columnHeaderList));
         
        masterDetail.ChangeSetting<TextBlock, DataGrid>(0, 1, "Foreground", Brushes.Black);
}
else
{
. . .  handle regular case here . . .
}
Notice that I revert the setting back to its original value by calling ChangeSetting after the Composite is created. Also, notice that I vary the Contents of this Composite as well by adding an extra label with the text "Vary for SEA" and with the Brush WhiteSmoke applied as the Background color of the Label.

The second way that you can handle varying a setting is via the UpdateByKey method:
if (trav.Flight == "SEA")
{
         //Column 1
         var borderToVary = masterDetail.BeginComposite<DataGrid>(trav.ID + "1")
                                .AddText(0, 0, trav.Counter.ToString())
                                .AddText(0, 1, "Flight: " + trav.Flight)
                                .AddText(0, 2, "Hotel: " + trav.Hotel)
                                .AddText(0, 3, "Attraction: " + " " + trav.Attraction)
                                .AddLabel(0, 4, "Vary for SEA", Brushes.WhiteSmoke)
                                .SetMouseOverColorOnContainer<StackPanel, DataGrid>(Brushes.Silver)
                                .EndComposite<DataGrid, DataGridArgs>(new DataGridArgs(trav.ID, trav.Counter.Value, 0, columnHeaderList));

        masterDetail.UpdateByKey<TextBlock, DataGrid>(borderToVary.GetKey(), 0, 1, txt => { txt.Foreground = Brushes.Green; });
}
else
{
. . .  handle regular case here . . .
}
If you wanted to add the extra label after the Composite was already created as well, then you could likely add an internal IParent into the Composite on the first pass. This would allow you to later get a handle to the IParent child and remove/add extra atomic Composites at will, such as a Composite that wraps a label. This is in lieu of any eventual ReplaceItemByKeyAtXY method being created in a future release . . .

Of course, you could also just add the label to all Composites and merely vary the Visibility setting? There are typically multiple ways to approach these scenarios.


6. How To Set a Style

Let's say, for example, that you are working with the DataGrid and you want to override the default border on the DataGridCell from a thickness of 1 to a thickness of 0. The XAML approach would likely be something like adding this Style tag to the Resources of the DataGrid:
<Style TargetType="DataGridCell">
<Setter Property="BorderThickness" Value="0"/>
</Style>
However, you can replicate this in code-behind in WPF Composites by leveraging the StyleExt.SetStyle static method:
myDataGrid.SetStyle(typeof(DataGridCell), BorderThicknessProperty, new Thickness(0));


7. How To Set a Trigger

The static class StyleExt contains a SetTrigger method that you can use to set a Trigger. In the example below, myLabel's foreground color will be changed to Yellow whenever the IsMouseOverProperty is true (i.e. whenever the mouse is over the label.)
myLabel.SetTrigger(typeof(Label), ForegroundProperty, Brushes.Yellow, Control.IsMouseOverProperty, true);


8. How To Modify an Element at X-Y Within the BeginComposite . . . EndComposite Chain

Typically, the properties of Child elements within an IParent are set via the BeginSettings . . . EndSettings chain. However, for convenience and flexibility, there may be times when you want to change the value of a Child element at position X-Y directly within a BeginComposite . . . EndComposite Chain. This is made possible via the ModifyAtXY method.
For instance, in the example below, first a TextBlock with the text "Color: " is added to the IParent myDataGrid. Then, this TextBlock is accessed and modified via the ModifyAtXY method, passing in an Action that has a TextBlock as its parameter. You could modify the TextBlock as you see fit inside this Action. In the example below, I merely change its background color.
myDataGrid.BeginComposite<DataGrid>(trav.ParkingGarages[0].ID + "1")
            .AddText(0, 0, "Color: " )
            .ModifyAtXY<TextBlock, DataGrid>(0, 0, txtBlock => { txtBlock.Background = Brushes.Transparent; })
            .EndComposite<DataGrid, DataGridArgs>(new DataGridArgs(trav.ParkingGarages[0].ID, trav.Counter.Value, 1));
Here's another example. In this example, I first add a NumericTextBox and then tag it with the Selector Class "decimalControl" via the ModifyAtXY method:
            .AddNumericTextBox<ContentControl3D>(6, 1, 139D, 23D, 299.99M, 999.99M, 0.00M, 1.00M, "C2", true)
            .ModifyAtXY<wpfToolkit.DecimalUpDown, ContentControl3D>(6, 1, numTxtBx => { numTxtBx.SetSelectorClassOnParent("decimalControl"); })


9. How to Enable Accelerator Shortcut Keys (e.g. Alt-E to Edit)

For accelerator keys, if adding an underscore "_" to the content of the control fails to work, such as:
FasterWPF.Factory.BeginMenuComposite(145D, 20D, "_Edit Alt-E" . . .
There is now a backup, alternative approach available via the new method:
GetKeyPressedInCombinatinWithAltKey
This method returns a System.Windows.Input.Key that you can check against, for example:
if GetKeyPressedInCombinatinWithAltKey(eventArgs1)==Key.E


10. How to Set Tab Order and to Set Focus to First Field on a Form

To set Tab Order of textboxes on a Form use the TabIndex property:
//EXAMPLE CODE IN F#
let myTextBox = borderOfCompositeContainingTextBox.GetChildFromComposite<TextBox, StackPanel>(0,0)
myTextBox.TabIndex<-tabIndex

To set Focus, I ended up using a combination of method calls. I call both Keybboard.Focus and Focus on the last textbox in the Form and then leverage the Windows Forms SendKeys.SendWait to Tab from the last field into the first field on the Form . . . the SendWait likely occurs asynchronously (which likely is why it works despite being out-of-order with regards to the Focus method calls?):
//EXAMPLE CODE IN F#
let theTextBox = FasterWPF.Selectors.SelectFrameworkElementsByType<TextBox>(brd1).Last()
System.Windows.Forms.SendKeys.SendWait("{Tab}"); 
Keyboard.Focus(theTextBox)|>ignore 
theTextBox.Focus()|>ignore

Last edited Mar 24, 2014 at 5:04 AM by stagathome0069, version 87

Comments

No comments yet.