Happy stress free coding

Sep 16, 2008

Building databound components in wpf

The concept of dependency properties

 
 

public static readonly DependencyProperty IsSpinningProperty =

 

DependencyProperty.Register(

 

"IsSpinning", typeof(Boolean),

 

...

  
 

);

 

public bool IsSpinning

 

{

 

get { return (bool)GetValue(IsSpinningProperty); }

 

set { SetValue(IsSpinningProperty, value); }

 

}

 

"The purpose of dependency properties is to provide a way to compute the value of a property based on the value of other inputs."

How to subscribe to a IEnumerable datasource

 
 

ItemsSourceProperty = DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(ItemsControl), new PropertyChangedCallback(ItemsControl.ItemsSourceChanged));

Derive from ItemsControl and override the method below to react to changes in the list:

 
 

OnItemsChanged(NotifyCollectionChangedEventArgs e)

The ItemsControl keep an ItemCollection that is aCollectionView (that can handle sorting and more importantly stays in sync with the ItemSource).

ItemsControl has an event OnItemsChanged that it fires when detecting changes in the datasource. Implement this to do additional stuff, but basically the widgets you keep per item (ie your rows or nodes) are created by the GetContainerForItemOverride method of ItemsControl.

 
 

protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)

 

{

 

switch (e.Action)

 

{

 

case NotifyCollectionChangedAction.Add:

 

case NotifyCollectionChangedAction.Move:

 

break;

  
 

case NotifyCollectionChangedAction.Remove:

 

case NotifyCollectionChangedAction.Reset:

 

if ((this.SelectedItem == null) || this.IsSelectedContainerHookedUp)

 

{

 

break;

 

}

 

this.SelectFirstItem();

 

return;

  
 

case NotifyCollectionChangedAction.Replace:

 

{

 

object selectedItem = this.SelectedItem;

 

if ((selectedItem == null) || !selectedItem.Equals(e.OldItems[0]))

 

{

 

break;

 

}

 

this.ChangeSelection(selectedItem, this._selectedContainer, false);

 

return;

 

}

 

default:

 

throw new NotSupportedException(SR.Get(SRID.UnexpectedCollectionChangeAction, new object[] { e.Action }));

 

}

 

}

The concept of visual templates

 
 

[TemplatePart(Name="Normal State", Type=typeof(Storyboard)),

 

TemplatePart(Name="MouseOver State", Type=typeof(Storyboard)),

 

TemplatePart(Name="RootElement", Type=typeof(FrameworkElement)),

 

TemplatePart(Name="Pressed State", Type=typeof(Storyboard)),

 

TemplatePart(Name="FocusVisualElement", Type=typeof(UIElement)),

 

TemplatePart(Name="Disabled State", Type=typeof(Storyboard))]

 

public class Button : ButtonBase

 

{

 

// ...

 

protected override void OnApplyTemplate();

 

}

You choose yourself what to expose as configurable visual styles. The stuff that you expose is of a known type, like Storyboard. The implementation of this can be replaced by the user.

You follow the pattern of exposing TemplateParts and use them by asking GetTemplateChild.

 
 

protected override void OnApplyTemplate()

 

{

 

base.OnApplyTemplate();

 

object templateChild = base.GetTemplateChild("RootElement");

 

this._elementRoot = templateChild as FrameworkElement;

 

this._elementFocusVisual = base.GetTemplateChild("FocusVisualElement")as UIElement; if (this._elementRoot != null)

 

{

 

this._stateNormal = this._elementRoot.Resources["Normal State"]as Storyboard;

 

this._stateMouseOver = this._elementRoot.Resources["MouseOver State"]as Storyboard;

 

this._stateMouseOver = obj5 as Storyboard;

 

this._statePressed = this._elementRoot.Resources["Pressed State"]as Storyboard;

 

this._stateDisabled = this._elementRoot.Resources["Disabled State"]as Storyboard;

 

}

 

base.UpdateVisualState();

 

}

 

DataTemplates

Once you have a control with a widget that is supposed to display something, you do not simply render the data straight up, instead you fill it with a piece of xaml that the user has provided: a DataTemplate. This DataTemplate has most likely Bindings and Paths that should make sense to your widgets datacontext.

And in the case of being a hierarchy like HierarchicalDataTemplate it also has a binding for the sub items.

 
 

<Window x:Class="WpfApplication1.Window1"

 

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

 

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

 

Title="Window1" Height="702" Width="318"

 

xmlns:src="clr-namespace:WpfApplication1"

 

xmlns:plex="clr-namespace:PlexityHide.W;assembly=WGrid"

 

xmlns:system="clr-namespace:System;assembly=mscorlib"

 

>

 

<Window.Resources>

  
 

<HierarchicalDataTemplate x:Key="EcoApple2" DataType="EcoProject1.Model.Apple">

 

<StackPanel Margin="10">

 

<TextBlock Text="{Binding Path=Attribute1}"/>

 

</StackPanel>

 

</HierarchicalDataTemplate>

  
  
  
  
 

<HierarchicalDataTemplate x:Key="EcoApple" >

 

<StackPanel Margin="10">

 

<TextBlock Text="{Binding Path=Attribute1}"/>

 

</StackPanel>

 

</HierarchicalDataTemplate>

  
  
 

<HierarchicalDataTemplate x:Key="EcoShowTime" DataType="EcoProject1.Model.EcoLocalTime" ItemsSource="{Binding Path=Apples}" ItemTemplate="{StaticResource EcoApple}">

 

<StackPanel Margin="10">

 

<TextBlock Text="{Binding Path=Place}"/>

 

<TextBox Text="{Binding Path=Time}"/>

 

</StackPanel>

 

</HierarchicalDataTemplate>

  
 

<src:MyTemplateSelector x:Key="myDataTemplateSelector"/>

  
  
 

</Window.Resources>

Your DataTemplates can be chosen by name of the DataTemplate:

<HierarchicalDataTemplate x:Key="EcoShowTime" DataType="EcoProject1.Model.EcoLocalTime" ItemsSource="{Binding Path=Apples}" ItemTemplate="{StaticResource EcoApple}">

Or by type of the object that is in your ItemSource:

<HierarchicalDataTemplate DataType="EcoProject1.Model.EcoLocalTime" ItemsSource="{Binding Path=Apples}" ItemTemplate="{StaticResource EcoApple}">

Or implement your own TemplateSelector

<src:MyTemplateSelector x:Key="myDataTemplateSelector"/>

 

 
 

public class MyTemplateSelector : DataTemplateSelector

 

{

 

public override DataTemplate SelectTemplate(object item, DependencyObject container)

 

{

  
 

/* if (item != null && item is MyData)

 

{

 

MyData data = item as MyData;

 

return MakeTemplate(data.MyName % 2);

 

}*/

  
 

return null;

 

}

 

DataTemplate MakeTemplate(int i)

 

{

 

XNamespace _xmlns = @"http://schemas.microsoft.com/winfx/2006/xaml/presentation";

 

XElement templ =

 

new XElement(_xmlns + "DataTemplate", new XAttribute("xmlns", _xmlns),

 

new XElement(_xmlns + "StackPanel", new XAttribute("Orientation", "Horizontal"),

 

new XElement(_xmlns + "TextBlock", new XAttribute("Text", "{Binding Path=MyName, Mode=OneWay}")),

 

new XElement(_xmlns + "TextBlock", new XAttribute("Text", "{Binding Path=[" + i.ToString() + @"], Mode=OneWay}"))));

 

DataTemplate dt = (DataTemplate)XamlReader.Load(templ.CreateReader());

 

return dt;

 

}

 

}

And make your component use it:

<plex:WGrid x:Name="wGrid4" ItemTemplateSelector="{StaticResource myDataTemplateSelector}">

 

When defining visual styles you do not need to re-invent the wheel; you can base styles on existing styles:

  • <Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <
    Page.Resources>
    <
    Style TargetType="{x:Type TextBlock}" x:Key="style">
    <
    Setter Property="Foreground" Value="Red"/>
    <
    Setter Property="FontSize" Value="72"/>
    <
    Setter Property="FontWeight" Value="Bold"/>
    </
    Style>
    </
    Page.Resources>
    <
    ContentPresenter Content="WPF">
    <
    ContentPresenter.Resources>
    <
    Style TargetType="{x:Type TextBlock}" BasedOn="{StaticResource style}"/>
    </
    ContentPresenter.Resources>
    </
    ContentPresenter>
    </
    Page>

 

Found this good walk thru at msdn:

You use the ItemTemplate to specify the visualization of the data objects. If your ItemsControl is bound to a collection object and you do not provide specific display instructions using a DataTemplate, the resulting UI of each item is a string representation of each object in the underlying collection.

When you set an ItemTemplate on an ItemsControl, the UI is generated as follows (using the ListBox as an example):

  1. During content generation, the ItemsPanel initiates a request for the ItemContainerGenerator to create a container for each data item. For ListBox, the container is a ListBoxItem. The generator calls back into the ItemsControl to prepare the container.

  1. Part of the preparation involves the copying of the ItemTemplate of the ListBox to be the ContentTemplate of the ListBoxItem.

  1. Similar to all ContentControl types, the ControlTemplate of a ListBoxItem contains a ContentPresenter. When the template is applied, it creates a ContentPresenter whose ContentTemplate is bound to the ContentTemplate of the ListBoxItem.

  1. Finally, the ContentPresenter applies that ContentTemplate to itself, and that creates the UI.

If you have more than one DataTemplate defined and you want to supply logic to programmatically choose and apply a DataTemplate, use the ItemTemplateSelector property.

The ItemsControl provides great flexibility for visual customization and provides many styling and templating properties. Use the ItemContainerStyle property or the ItemContainerStyleSelector property to set a style to affect the appearance of the elements that contain the data items. For example, for ListBox, the generated containers are ListBoxItem controls; for ComboBox, they are ComboBoxItem controls. To affect the layout of the items, use the ItemsPanel property. If you are using grouping on your control, you can use the GroupStyle or GroupStyleSelector property.

 

 

And this was even better:

Styling and Templating an ItemsControl

Even though the ItemsControl is not the only control type that you can use a DataTemplate with, it is a very common scenario to bind an ItemsControl to a collection. In the What Belongs in a DataTemplate section we discussed that the definition of your DataTemplate should only be concerned with the presentation of data. In order to know when it is not suitable to use a DataTemplate it is important to understand the different style and template properties provided by the ItemsControl. The following example is designed to illustrate the function of each of these properties. The ItemsControl in this example is bound to the same Tasks collection as in the previous example. For demonstration purposes, the styles and templates in this example are all declared inline.

 
 

<ItemsControl Margin="10"

 

ItemsSource="{Binding Source={StaticResource myTodoList}}">

 

<!--The ItemsControl has no default visual appearance.

 

Use the Template property to specify a ControlTemplate to define

 

the appearance of an ItemsControl. The ItemsPresenter uses the specified

 

ItemsPanelTemplate (see below) to layout the items. If an

 

ItemsPanelTemplate is not specified, the default is used. (For ItemsControl,

 

the default is an ItemsPanelTemplate that specifies a StackPanel.-->

 

<ItemsControl.Template>

 

<ControlTemplate TargetType="ItemsControl">

 

<Border BorderBrush="Aqua" BorderThickness="1" CornerRadius="15">

 

<ItemsPresenter/>

 

</Border>

 

</ControlTemplate>

 

</ItemsControl.Template>

 

<!--Use the ItemsPanel property to specify an ItemsPanelTemplate

 

that defines the panel that is used to hold the generated items.

 

In other words, use this property if you want to affect

 

how the items are laid out.-->

 

<ItemsControl.ItemsPanel>

 

<ItemsPanelTemplate>

 

<WrapPanel />

 

</ItemsPanelTemplate>

 

</ItemsControl.ItemsPanel>

 

<!--Use the ItemTemplate to set a DataTemplate to define

 

the visualization of the data objects. This DataTemplate

 

specifies that each data object appears with the Proriity

 

and TaskName on top of a silver ellipse.-->

 

<ItemsControl.ItemTemplate>

 

<DataTemplate>

 

<DataTemplate.Resources>

 

<Style TargetType="TextBlock">

 

<Setter Property="FontSize" Value="18"/>

 

<Setter Property="HorizontalAlignment" Value="Center"/>

 

</Style>

 

</DataTemplate.Resources>

 

<Grid>

 

<Ellipse Fill="Silver"/>

 

<StackPanel>

 

<TextBlock Margin="3,3,3,0"

 

Text="{Binding Path=Priority}"/>

 

<TextBlock Margin="3,0,3,7"

 

Text="{Binding Path=TaskName}"/>

 

</StackPanel>

 

</Grid>

 

</DataTemplate>

 

</ItemsControl.ItemTemplate>

 

<!--Use the ItemContainerStyle property to specify the appearance

 

of the element that contains the data. This ItemContainerStyle

 

gives each item container a margin and a width. There is also

 

a trigger that sets a tooltip that shows the description of

 

the data object when the mouse hovers over the item container.-->

 

<ItemsControl.ItemContainerStyle>

 

<Style>

 

<Setter Property="Control.Width" Value="100"/>

 

<Setter Property="Control.Margin" Value="5"/>

 

<Style.Triggers>

 

<Trigger Property="Control.IsMouseOver" Value="True">

 

<Setter Property="Control.ToolTip"

 

Value="{Binding RelativeSource={x:Static RelativeSource.Self},

 

Path=Content.Description}"/>

 

</Trigger>

 

</Style.Triggers>

 

</Style>

 

</ItemsControl.ItemContainerStyle>

 

</ItemsControl>

 

The following is a screenshot of the example when it is rendered:

Note that instead of using the

ItemTemplate, you can use the ItemTemplateSelector. Refer to the previous section for an example. Similarly, instead of using the ItemContainerStyle, you have the option to use the ItemContainerStyleSelector.

Two other style-related properties of the ItemsControl that are not shown here are GroupStyle and GroupStyleSelector.

0 Comments:

Post a Comment

Subscribe to Post Comments [Atom]



<< Home