Sunday, August 14, 2005
Creating custom controls, the real way
   I know the question has been dodged, and I myself didn't have a firm grasp--but I am back to report on "the way" of creating a custom control.
   First of all, be aware that a custom control should be created only as a last option. For the sake of all the work that has gone into Avalon to make controls lookless, you should always evaluate the available controls before contunuing. I believe the true circumstance for a custom control is when you want to package certain ideas together with data. The key word being data. That is really the only reason you create a custom control--you want a common ancestor object without having to rewire the data hook-up everytime. A custom control is not for look-ability. Because, in that sense, you are destroying the lookless metaphor--which is undesirable. You always want that style-ability inherent in Avalon.
 
   Now you may recall the first step in creating an Avalon application with a custom control is to set the 'UICulture' property in the project settings. This is a temporary work-around for satisfying Avalon's taste for localized apps. To do this, navigate to the project settings file (It will be in the project directory, and called something like "<ProjectName>.csproj"). Open it up in Notepad and it should look like below. Add the bolded line.
   <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">        <PropertyGroup>             <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>             <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>            ...            <UICulture>en-US</UICulture>        </PropertyGroup>        ...  </Project>
   
   The next step is to create class file (Widget.cs). The only difference between a regular class and this one is the 'partial' decorator. The 'partial' tells .NET that there is another part of this class in a separate fill.
      namespace ApplicationNamespace       {            public partial class Widget : ContentControl            {            }      }
   Although you could inherit from a number of classes, ContentControl best satisfies the fact that we want a control with lookless behavior. In light of that, ContentControl is just a container. Essentially, this allows us the flexibility of the control to hold anything.
   Since XAML is the easiest language to describe Avalon elements, it would be a shame to stay in C#. So, go ahead and create a XAML file (Widget.XAML).
      <ContentControl x:Class="ApplicationNamespace.Widget"            xmlns="http://schemas.microsoft.com/winfx/avalon/2005"            xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005"            >      </ContentControl>
   The above is the second half of the class declared in C#, which now enables us to fully work in XAML.
   Continuing on, let's add the custom control to the main Window (the stuff bolded is the important lines that hook it up):
      <?Mapping XmlNamespace="controls" ClrNamespace="ApplicationNamespace" ?>      <Window x:Class="ApplicationNamespace.Window1"            xmlns="http://schemas.microsoft.com/winfx/avalon/2005"            xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005"
            xmlns:cc="controls"             >            <Grid>                  <cc:Widget />             </Grid>      
      </Window>
  
   Now, go ahead and set some properties on the custom control:
      <ContentControl x:Class="ApplicationNamespace.Widget"            xmlns="http://schemas.microsoft.com/winfx/avalon/2005"            xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005"                   >
       <ContentControl.Style>
              <Style>
                     <Setter Property="ContentControl.Width" Value="200" />
                     <Setter Property="ContentControl.Height" Value="200" />
                     <Setter Property="ContentControl.Background" Value="Red" />
              </Style>
       </ContentControl.Style>      </ContentControl>
   Run the application...You may notice that nothing is visible. The reason is that we did set the properties and we did create the custom control partial class in XAML, but it really isn't being connected with our real class in C#. The reason is that XAML really isn't a language and it must actually be converted into C#. To do this, we need to call InitializeComponent from the constructor of the Widget class:
      namespace ApplicationNamespace       {            public partial class Widget : ContentControl            {
                  public Widget()
                  {
InitializeComponent();
                  }            }      }
 
Even now if you run the application the control won’t show up. The reason is that we may have specified dimensions and a background, but the control cannot set those on content that doesn’t exist, yet. To add a look, we should provide a default template—which is a way of adding a look to our lookless control. The template can be ripped out or styled differently at any time by the application.
 
<ContentControl x:Class="ApplicationNamespace.Widget"
    xmlns="http://schemas.microsoft.com/winfx/avalon/2005"
    xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005"
    Width="200" Height="200" Background="Red"
    >
    <ContentControl.Style>
            <Style>
                    <Setter Property="ContentControl.Width" Value="200" />
                    <Setter Property="ContentControl.Height" Value="200" />
                    <Setter Property="ContentControl.Background" Value="Red" />
                 <Setter Property="ContentControl.Template">
                        <Setter.Value>
                               <ControlTemplate>
                                      <Grid Background="{TemplateBinding Property=Background}">
                                      </Grid>
                               </ControlTemplate>
                        </Setter.Value>
                 </Setter>
            </Style>
    </ContentControl.Style>
</ContentControl>
                  
Going back to the class, let’s add some data through a property WidgetData—which returns a simple string “Red”. This is the data we are going to expose with our custom control (the essence of it):
   public partial class Setter Property="ContentControl.Template">
                            <Setter.Value>
                                    <ControlTemplate>
                                            <Grid Background="{TemplateBinding Property=Background}">
                                             <TextBlock TextContent="{Binding Path=WidgetData,RelativeSource=/TemplatedParent}" />
                                            </Grid>
                                    </ControlTemplate>
                            </Setter.Value>
                    </Setter>
I am binding the value to the TextBlock.TextContent property. The reason I have specified RelativeSource as ‘/TemplatedParent’ is that the Binding statement will not have any data context without RelativeSource. There are a couple of other values which can be automatically determined by Avalon at runtime. They all start with a forward slash ‘/’ and refer to common paths. ‘TemplatedParent’ gets us the main element that the ControlTemplate is modifying—which is the Widget class.
                  
    xmlns="http://schemas.microsoft.com/winfx/avalon/2005"
    xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005"
    xmlns:cc="controls"
    >
    <Window.Resources>
            <Style x:Key="WidgetStyle">
                    <Setter Property="Control.Template">
                            <Setter.Value>
                                    <ControlTemplate>
                                            <Grid Background="{Binding Path=WidgetData,RelativeSource=/TemplatedParent}" />
                                    </ControlTemplate>
                            </Setter.Value>
                    </Setter>
            </Style>
    </Window.Resources>
    <Grid>
            <cc:Widget Style="{StaticResource WidgetStyle}" />
           
    </Grid>
</Window>
                      more...
http://www.sebura.com
Originally Posted on 8/14/2005 4:13:36 PMContent source: http://longhornblogs.com/rdawson/archive/2005/06/14/14180.aspx
   First of all, be aware that a custom control should be created only as a last option. For the sake of all the work that has gone into Avalon to make controls lookless, you should always evaluate the available controls before contunuing. I believe the true circumstance for a custom control is when you want to package certain ideas together with data. The key word being data. That is really the only reason you create a custom control--you want a common ancestor object without having to rewire the data hook-up everytime. A custom control is not for look-ability. Because, in that sense, you are destroying the lookless metaphor--which is undesirable. You always want that style-ability inherent in Avalon.
 
   Now you may recall the first step in creating an Avalon application with a custom control is to set the 'UICulture' property in the project settings. This is a temporary work-around for satisfying Avalon's taste for localized apps. To do this, navigate to the project settings file (It will be in the project directory, and called something like "<ProjectName>.csproj"). Open it up in Notepad and it should look like below. Add the bolded line.
   <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">        <PropertyGroup>             <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>             <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>            ...            <UICulture>en-US</UICulture>        </PropertyGroup>        ...  </Project>
   
   The next step is to create class file (Widget.cs). The only difference between a regular class and this one is the 'partial' decorator. The 'partial' tells .NET that there is another part of this class in a separate fill.
      namespace ApplicationNamespace       {            public partial class Widget : ContentControl            {            }      }
   Although you could inherit from a number of classes, ContentControl best satisfies the fact that we want a control with lookless behavior. In light of that, ContentControl is just a container. Essentially, this allows us the flexibility of the control to hold anything.
   Since XAML is the easiest language to describe Avalon elements, it would be a shame to stay in C#. So, go ahead and create a XAML file (Widget.XAML).
      <ContentControl x:Class="ApplicationNamespace.Widget"            xmlns="http://schemas.microsoft.com/winfx/avalon/2005"            xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005"            >      </ContentControl>
   The above is the second half of the class declared in C#, which now enables us to fully work in XAML.
   Continuing on, let's add the custom control to the main Window (the stuff bolded is the important lines that hook it up):
      <?Mapping XmlNamespace="controls" ClrNamespace="ApplicationNamespace" ?>      <Window x:Class="ApplicationNamespace.Window1"            xmlns="http://schemas.microsoft.com/winfx/avalon/2005"            xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005"
            xmlns:cc="controls"             >            <Grid>                  <cc:Widget />             </Grid>      
      </Window>
  
   Now, go ahead and set some properties on the custom control:
      <ContentControl x:Class="ApplicationNamespace.Widget"            xmlns="http://schemas.microsoft.com/winfx/avalon/2005"            xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005"                   >
       <ContentControl.Style>
              <Style>
                     <Setter Property="ContentControl.Width" Value="200" />
                     <Setter Property="ContentControl.Height" Value="200" />
                     <Setter Property="ContentControl.Background" Value="Red" />
              </Style>
       </ContentControl.Style>      </ContentControl>
   Run the application...You may notice that nothing is visible. The reason is that we did set the properties and we did create the custom control partial class in XAML, but it really isn't being connected with our real class in C#. The reason is that XAML really isn't a language and it must actually be converted into C#. To do this, we need to call InitializeComponent from the constructor of the Widget class:
      namespace ApplicationNamespace       {            public partial class Widget : ContentControl            {
                  public Widget()
                  {
InitializeComponent();
                  }            }      }
 
Even now if you run the application the control won’t show up. The reason is that we may have specified dimensions and a background, but the control cannot set those on content that doesn’t exist, yet. To add a look, we should provide a default template—which is a way of adding a look to our lookless control. The template can be ripped out or styled differently at any time by the application.
 
<ContentControl x:Class="ApplicationNamespace.Widget"
    xmlns="http://schemas.microsoft.com/winfx/avalon/2005"
    xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005"
    Width="200" Height="200" Background="Red"
    >
    <ContentControl.Style>
            <Style>
                    <Setter Property="ContentControl.Width" Value="200" />
                    <Setter Property="ContentControl.Height" Value="200" />
                    <Setter Property="ContentControl.Background" Value="Red" />
                 <Setter Property="ContentControl.Template">
                        <Setter.Value>
                               <ControlTemplate>
                                      <Grid Background="{TemplateBinding Property=Background}">
                                      </Grid>
                               </ControlTemplate>
                        </Setter.Value>
                 </Setter>
            </Style>
    </ContentControl.Style>
</ContentControl>
                  
Going back to the class, let’s add some data through a property WidgetData—which returns a simple string “Red”. This is the data we are going to expose with our custom control (the essence of it):
   public partial class Setter Property="ContentControl.Template">
                            <Setter.Value>
                                    <ControlTemplate>
                                            <Grid Background="{TemplateBinding Property=Background}">
                                             <TextBlock TextContent="{Binding Path=WidgetData,RelativeSource=/TemplatedParent}" />
                                            </Grid>
                                    </ControlTemplate>
                            </Setter.Value>
                    </Setter>
I am binding the value to the TextBlock.TextContent property. The reason I have specified RelativeSource as ‘/TemplatedParent’ is that the Binding statement will not have any data context without RelativeSource. There are a couple of other values which can be automatically determined by Avalon at runtime. They all start with a forward slash ‘/’ and refer to common paths. ‘TemplatedParent’ gets us the main element that the ControlTemplate is modifying—which is the Widget class.
                  
    xmlns="http://schemas.microsoft.com/winfx/avalon/2005"
    xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005"
    xmlns:cc="controls"
    >
    <Window.Resources>
            <Style x:Key="WidgetStyle">
                    <Setter Property="Control.Template">
                            <Setter.Value>
                                    <ControlTemplate>
                                            <Grid Background="{Binding Path=WidgetData,RelativeSource=/TemplatedParent}" />
                                    </ControlTemplate>
                            </Setter.Value>
                    </Setter>
            </Style>
    </Window.Resources>
    <Grid>
            <cc:Widget Style="{StaticResource WidgetStyle}" />
           
    </Grid>
</Window>
                      more...
http://www.sebura.com
Originally Posted on 8/14/2005 4:13:36 PMContent source: http://longhornblogs.com/rdawson/archive/2005/06/14/14180.aspx