Visual Tree in Avalon Style...

A control itself is just a behavior and separate from visual presentation. A control normally comes with a default visual style (normally styles from the current desktop theme) and it can be override by the user of the control. They can override with by specifying their own Visual Tree within the Style tag. This will only work for any type that derived from Control class. Some of the types are like Button, ListBox and ComboBox. If you try to set VisualTree in class that is not derived from Control, you will get an exception.

The XAML below is the skeleton to set VisualTree for a Button.

<Style def:Name="MyButtonStyle">
<Button />
<Style.VisualTree>
<!-- your inner element here -->
</Style.VisualTree>
</Style>

For more example of Visual Tree in XAML, please check out Nathan Dunlap’s blog at

https://www.longhornblogs.com/ndunlap/articles/2203.aspx

So why is this feature needed? It is needed because certain developer wants to have a distinct look on their control and cannot accomplish it simply by setting properties on existing control. Take button for example, it comes with default desktop theme and look a button (in WinForm). Well, you might want to reuse the code which handles the Click effect and event handling but you want to use it as an image button in your toolbar. One way to do it is to reset the visual tree for the button and contain an image into the button.

Another benefit of this feature is that you can develop a set of visual style template and design the capability in for the user of your software to switch themes. This is demo in the Login Screen demo in the PDC talk by Henry Hahn.

You can even specify of element tree within the control you are using. Take button as an example again. A Button consists of Shape, Panel and Text in its visual tree. (Please note: in different version of LongHorn build, the default button visual tree might not be the same). To know a control’s visual tree for sure, you can write the following code to scan a control’s visual tree:

 

private void getVisualTree(Media.Visual element, int level)

{

    VisualCollection coll = ((IVisual)element).Children;

  for (int i = 0; i < coll.Count; ++i)

     {

        System.Console.Write(level + ". " + coll[i].ToString());

        this.getVisualTree(coll[i], ++level);

     }

}

 

 

If can also set your visual tree programmatically, to do this you can do something like the following:

 

Style style = new Style(typeof(Button));

FrameworkElementFactory border = new

                 FrameworkElementFactory(typeof(Border));

border.SetValue(BackgroundProperty, Brushes.Red);

style.VisualTree = border;

The code above will override the Button’s default VisualTree with a Border with red background and no content.

If you want to use whatever property from the default, you can use Property Aliasing. This is needed most of the time. The syntax for this is as follow:

<DockPanel xmlns="https://schemas.microsoft.com/2003/xaml/" xmlns:def="Definition">

  <DockPanel.Resources>

    <Style def:Name="MyStyle">

      <Button/>

        <Style.VisualTree>

           <Canvas>

            <Rectangle Width="100%" Height="100%"

                    RadiusX="10" RadiusY="10" Fill="LightBlue"

                    Stroke="CadetBlue" StrokeThickness="2"/>

            <Rectangle Width="50%" Height="50%"

                    RadiusX="10" RadiusY="10" Fill="Green"/>

<!-- Grabbing Content property from default VisualTree -->

            <ContentPresenter ContentControl.Content="*Alias(Target=Content)"

                              Margin="15,13,15,15"/>

          </Canvas>

        </Style.VisualTree>

      </Style>

    </DockPanel.Resources>

    <Button Style="{MyStyle}" Opacity="0.9" DockPanel.Dock="fill">Test 2</Button>

</DockPanel>

In the sample above, we are aliasing the Content property from the default into the new ContentControl.Content property. You can override the property by hardcoding, using property aliasing or you can bind it to some data source.

Let’s get into the last example of using data binding into property within visual tree.

 

<DockPanel xmlns="https://schemas.microsoft.com/2003/xaml/" xmlns:def="Definition">

    <DockPanel.Resources>

      <!—XML Data Source -->

      <XmlDataSource def:Name="BookData" XPath="/Books">

        <Books xmlns="">

          <Book ISBN="0-7356-0562-9" Stock="in">

            <Title>XML in Action</Title>

            <Summary>XML Web Technology</Summary>

      <Foreground>Red</Foreground>

          </Book>

          <Book ISBN="0-7356-1370-2" Stock="in">

            <Title>Programming Microsoft Windows With C#</Title>

            <Summary>C# Programming Using the .NET Framework</Summary>

      <Foreground>Blue</Foreground>

          </Book>

        </Books>

      </XmlDataSource>

      <Style def:Name="BookDataStyle">

        <ContentPresenter/>

        <Style.VisualTree>

          <SimpleText FontSize="Small" Foreground="*Bind(Path=Foreground;BindType=OneWay)">

            <SimpleText.Text>

              <Bind Path="Title"/>

            </SimpleText.Text>

          </SimpleText>

        </Style.VisualTree>

      </Style>

    </DockPanel.Resources>

    <!-- Bind a ListBox to the query-selected books -->

    <ListBox ItemStyle="{BookDataStyle}">

      <ListBox.Items>

        <CollectionContainer>

          <CollectionContainer.Collection>

            <Bind DataSource="{BookData}" Path="Book"/>

          </CollectionContainer.Collection>

        </CollectionContainer>

      </ListBox.Items>

    </ListBox>

</DockPanel>

 

In the above sample, we load in an XML data source which contains data about books and load it into a listbox. In the following line we are setting the datasource to the XML data source defined above.

      <Bind DataSource="{BookData}" Path="Book"/>

This data can be used within the Visual tree to set properties. In our case, we are using it to be the foreground color property and text content.

 

  <SimpleText FontSize="Small"

      Foreground="*Bind(Path=Foreground;BindType=OneWay)">

      <SimpleText.Text>

          <Bind Path="Title"/>

      </SimpleText.Text>

  </SimpleText>

The capability to override VisualTree for a control gives us more possibilities in developing killer apps. One possibility is the ability to skin an application based on the user. Another advantage is that setting VisualTree is sufficient enough to use a control, no longer need to write a new one. A related Avalon feature is PropertyTrigger which is one of the VisualTriggers. PropertyTrigger lets you set a property based on a change trigger in a property of the same target control. For more information about Visual Trigger, check out https://longhorn.msdn.microsoft.com/lhsdk/ref/ns/msavalon.windows/c/style/p/visualtriggers.aspx . Combining VisualTree and VisualTriggers let you create even cooler/richer Avalon application. </>

 

Content is provided "AS IS" with no warranties, and confers no rights.

Comments

  • Anonymous
    February 16, 2004
    Chris Sells blog