Welcome to MSDN Blogs Sign in | Join | Help

The blog is close down

We will no longer keep this blog.

For information about WPF, you can find more in Kevin Moore's blog: http://blogs.msdn.com/okoboji/ 

Thanks,

ATC Avalon Team

 

A Fancy StatusBar

Download and try it! 

When moving Mouse over an item, the item jumps up until Mouse leaves. The key steps to achieve it are as follows. 

1.      Subclass StatusBarItem and add new property ImagePath. Therefore, we can pass image to StatusBarItem easily.

 

2.      Subclass StatusBar and override MouseLeave and MouseMove event. In MouseMove event handler, find current item according to mouse position, and then change each item’s position by changing Canvas.Top property. When Mouse Leave, restore each item to original position.

Declaimer: This posting is provided "AS IS" with no warranties, and confers no rights.

Posted by ATC Avalon Team | (Comments Off)
Filed under:

Attachment(s): StatusBarExtension.zip

How to Create a Custom View

One of the most powerful features of ListView is supporting custom views. If built-in views do not apply your scenarios, you can easily write your own. ListView will handle your views the same way with built-in views.

Creating a custom view is pretty simple. ListView control has done quiet a bit of work to make the implementation easy. Here are main steps to follow. Firstly, a class should be created and it should derive from ViewBase directly or indirectly. Then define the style of the view in a resource file. Finally link them together using ComponentResourceKey. In this blog I will show you how to create a custom view named ImageView. ImageView is used to display thumbnail of image files. The snapshot is listed below.

 Step 1. Define ImageView class

All you have to do is to subclass ViewBase.

public class ImageView : ViewBase

{

}

 Step 2. Define Style for ImageView

Two styles should be defined in a resource file. One is for ListView and the other is for ListViewItem. If you only want to do a little modification on the original one, you can use the following code. It will merge your style into its original style. The magic lies in the keyword “BasedOn”. It means your style definition is based on another style definition.

<Style TargetType="{x:Type ListView}" BasedOn="{StaticResource {x:Type ListBox}}">

NOTE: if you forget to define Template in styles, ListView will show nothing.

The following XAML code is digested from the style definitions for ImageView.

<Style TargetType="{x:Type ListView}" BasedOn="{StaticResource {x:Type ListBox}}">

  <Setter Property="BorderBrush" Value="Black"/>

  <Setter Property="BorderThickness" Value="0.5"/>

  <Setter Property="Template">

    <Setter.Value>

      <ControlTemplate>

        <Border Name="bd" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"

                Background="{TemplateBinding Background}" Margin="{TemplateBinding Margin}">

          <ScrollViewer Margin="{TemplateBinding Padding}">

            <WrapPanel ItemWidth="150" IsItemsHost="True" MinWidth="100"

                       Width="{Binding ActualWidth,RelativeSource={RelativeSource AncestorType=ScrollContentPresenter}}">

            </WrapPanel>

          </ScrollViewer>

        </Border>

      </ControlTemplate>

    </Setter.Value>

  </Setter>

</Style>

 

<Style TargetType='{x:Type ListViewItem}' BasedOn='{StaticResource {x:Type ListBoxItem}}'>

  <Setter Property='Padding' Value='3'/>

  <Setter Property='Margin' Value='5'/>

  <Setter Property='HorizontalContentAlignment' Value='Center'/>

   <Setter Property="ContentTemplate">

    <Setter.Value>

      <DataTemplate>

        <Border Background="White">

          <Image Margin="3" Source="{Binding FullName}"/>

        </Border>

      </DataTemplate>

    </Setter.Value>

  </Setter>

</Style>

 

Step 3. Link them together

The link method here is using ComponentResourceKey. Firstly give a key to each style defined before.

<Style x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type l:ImageView},ResourceId=ImageView}">

</Style>

 

<Style x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type l:ImageView},ResourceId=ImageViewItem}"}">

</Style>

Then set DefaltStyleKey and ItemContainerStyleKey in ImageView class.

public class ImageView : ViewBase

{

    protected override object DefaultStyleKey

    {

        get { return new ComponentResourceKey(GetType(), "ImageView"); }

    }

 

 

    protected override object ItemContainerDefaultStyleKey

    {

        get { return new ComponentResourceKey(GetType(), "ImageViewItem"); }

    }

 

}

 

Step 4. Use it in ListView

It is used like GridView.

<ListView>

  <ListView.View>

    <l:ImageView />

  </ListView.View>

</ListView>

 We’re done!

Posted by ATC Avalon Team | 8 Comments

Attachment(s): CustomView.zip

How to display data from database?

It is common for ListView to display data from database. In this blog, I will briefly introduce how to do this. To achieve this, we need to resolve two problems:

  1. Binding data table to ListView;
  2. Binding fields of data table to GridViewColumns.

Binding data table to ListView

How to bind data of a DataTable to ListView depend on how to use DataTable, but the basic rules are the same:

 

1.      If use DataTable directly, first set the DataTable to the ListView.DataContext. And then bind ListView.ItemsSource to ListView.DataContext. The following code is a simple sample:

string connString = "CONNECTION STRING";

SqlConnection conn = new SqlConnection(connString);

SqlDataAdapter adapter = new SqlDataAdapter();

adapter.SelectCommand = new SqlCommand("COMMAND", conn);

DataSet _dataSet = new DataSet();

adapter.Fill(_dataSet, "DATATABLE NAME");

DataTable table = _dataSet.Tables["DATATABLE NAME"];

Binding bind = new Binding();

listView.DataContext = table;

listView.SetBinding(ListView.ItemsSourceProperty, bind);

 

2.      If define wrapper classes for DataTable, wrapper classes should implement IEnumerable, so that instances of the wrapper classes can be directly assigned to ListView.ItemsSource. Fortunately, the Data Source Manger in Visual Studio 2005 can automatically create wrapper classes if you use it to manage your data source in your application.

3.      In some cases, you may need to dynamically add data rows into the source. For example, start a background thread to insert items into the data source. In this case, you can use ObservableCollection directly or create a wrapper class implementing INotifyCollectionChanged interface.

 

Binding fields of data table to GridViewColumns

  1. When defining data source for ListView in the first way mentioned above, use data field names in the DisplayMemberBinding directly, for example:

<GridViewColumn DisplayMemberBinding=”{Binding FIELDNAME}”/>

 

  1. If use wrapper class for data rows, just use property names in the wrapper class.

 In fact, it is straightforward to display DataTable with ListView. Two bindings are keys: one for ItemsSource of ListView and the other for fields in a row.

 

Declaimer: This posting is provided "AS IS" with no warranties, and confers no rights.

Posted by ATC Avalon Team | 1 Comments
Filed under:

Fixed-Width Column in ListView: A Column that cannot be resized

A fixed-width column is a column that cannot be resized by mouse dragging or double-clicks. You can find instances in outlook. Currently two methods can be used to achieve the effect. One is to restyle GridViewColumnHeader to remove the gripper inside its Template. The other is to subclass GridViewColumn to restrict columns' width to a fixed size. Today's topic is about the second solution. The resulting column is a little bit different from the restyling one because there is still a gripper inside GridViewColumnHeader. When mouse is over the gap between columns, the cursor is still changed to the resizing cursor.

The main WPF elements we are going to cover in this session are GridViewColumn, dependency property definition, dependency property coercion.

 Step 1. Subclass GridViewColumn

The key in this step is to override WidthProperty's metadata to make it call CoerceWidth() to coerce its value when a new value is available.

public class FixedWidthColumn : GridViewColumn {

    static FixedWidthColumn() {

        WidthProperty.OverrideMetadata(typeof(FixedWidthColumn),

            new FrameworkPropertyMetadata(null, new CoerceValueCallback(OnCoerceWidth)));

}

 

private static object OnCoerceWidth(DependencyObject o, object baseValue) {

        return baseValue;

    }

}

 Step 2. Add a dependency property FixedWidth

The FixedWidth is used to set column's fixed width in spite of whatever the column's width is. The keys in this step are:

1.      Define a dependency property.

2.      When FixedWidth is changed, it calls CoerceValue() to coerce Width into the new value.

public double FixedWidth {

    get { return (double)GetValue(FixedWidthProperty); }

    set { SetValue(FixedWidthProperty, value); }

}

 

public static readonly DependencyProperty FixedWidthProperty =

    DependencyProperty.Register(

        "FixedWidth",

        typeof(double),          

        typeof(FixedWidthColumn),

        new FrameworkPropertyMetadata(double.NaN, new PropertyChangedCallback(OnFixedWidthChanged)));

 

private static void OnFixedWidthChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) {

    FixedWidthColumn fwc = o as FixedWidthColumn;

    if (fwc != null)

        fwc.CoerceValue(WidthProperty);

}

Step 3. Rewrite CoerceWidth() to make it always return FixedWidth

Then the Width will always get the same value whatever value is set on Width.

private static object OnCoerceWidth(DependencyObject o, object baseValue) {

    FixedWidthColumn fwc = o as FixedWidthColumn;

    if (fwc != null)

        return fwc.FixedWidth;

    return baseValue;

}

Step 4. Use it in GridView

It is used like normal GridViewColumn.

<GridView xmlns:l="clr-namespace:FixedWidthColumnSample">

  <l:FixedWidthColumn Header="Coerce Fixed-Width Column"

                      DisplayMemberBinding="{Binding Name}"

                      FixedWidth="100"/>

</GridView>

 

 We're done!

 

This sample is based on the February CTP.

 

Declaimer: This posting is provided "AS IS" with no warranties, and confers no rights.

Other solutions to implement Alternate Background

We have found three solutions to achieve alternative background scenarios so far. The first one is provided in the previous post. Now we will describe another two.

 

The second solution:

 Derive from ListView, override PrepareContainerForItemOverride method and return the container with the correct background. And in your xaml file, replace ListView with SubListView. When items change, call ListView.Items.Refresh method.

 

The code is as follows.

public class SubListView : ListView

    {

        protected override void PrepareContainerForItemOverride(DependencyObject element, object item)

        {

            base.PrepareContainerForItemOverride(element, item);

            if (View is GridView)

            {

                int index = ItemContainerGenerator.IndexFromContainer(element);

                ListViewItem lvi = element as ListViewItem;

                if (index % 2 == 0)

                {

                    lvi.Background = Brushes.LightBlue;

                }

                else

                {

                    lvi.Background = Brushes.LawnGreen;

                }

            }

        }

    }

 

 

The third solution:

Creat a StyleSelector for ListView.ItemContainerStyleSelector property, in which alternate styles. Reset the containerStyleSeletor property when collection changes.

 

The code is as follows.

public class ListViewItemStyleSelector : StyleSelector

    {

        public override Style SelectStyle(object item, DependencyObject container)

        {

            Style st = new Style();

            st.TargetType = typeof(ListViewItem);

            Setter backGroundSetter = new Setter();

            backGroundSetter.Property = ListViewItem.BackgroundProperty;

            ListView listView = ItemsControl.ItemsControlFromItemContainer(container) as ListView;

            int index = listView.ItemContainerGenerator.IndexFromContainer(container);

            if (index % 2 == 0)

            {

                backGroundSetter.Value = Brushes.LightBlue;

            }

            else

            {

                backGroundSetter.Value = Brushes.LawnGreen;

            }

            st.Setters.Add(backGroundSetter);

            return st;

        }

    }

 

Posted by ATC Avalon Team | 11 Comments
Filed under:

Alternate Background for ListViewItems

This sample shows how to create a ListView control that uses a GridView to display a list of dates that are defined as DateTime objects. Items in this example have alternate background.

 

Key Step 1. Restyle ListViewItem, and set its Background by converter in the new style.

 

    <l:BackgroundConvertor x:Key="myConverter"/>

    <Style x:Key="myItemStyle" TargetType="{x:Type ListViewItem}">

      <Setter Property="Background">

        <Setter.Value>

          <Binding RelativeSource="{RelativeSource Self}" Converter="{StaticResource myConverter}"/>

        </Setter.Value>

      </Setter>

</Style>

 

<ListView Name="lv" ItemContainerStyle="{StaticResource myItemStyle}" />

 

Key Step 2. Implement BackgroundConvertor. We need the index of a ListViewItem to determine its background. int ItemContainerGenerator.IndexFromContainer(DependencyObject container) method can tell the index of a ListViewItem.

 

public sealed class BackgroundConvertor : IValueConverter

{

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)

    {

        ListViewItem item = (ListViewItem)value;

        ListView listView = ItemsControl.ItemsControlFromItemContainer(item) as ListView;

        // Get the index of a ListViewItem

        int index = listView.ItemContainerGenerator.IndexFromContainer(item);

 

        if (index % 2 == 0)

        {

            return Brushes.LightBlue;

        }

        else

        {

            return Brushes.White;

        }

    }

 

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)

    {

        throw new NotSupportedException();

    }

}

 

Key Step 3. We’ve almost finished the sample, and it works well in sorting/grouping. However, when one item is removed from the ListView, you may find two adjacent rows have the same background color. That's because the background of all the other items will not be updated automatically after removing/inserting. Therefore, some extra code is needed to refresh the items after the inserting/removing operation.

 

private void Remove(object sender, RoutedEventArgs e)

{

    _data.RemoveAt(3);

 

    // Must refresh all the items to update the background

    ICollectionView dataView =

      CollectionViewSource.GetDefaultView(lv.ItemsSource);

    dataView.Refresh();

}

 

This sample is based on the February CTP.

Posted by ATC Avalon Team | 2 Comments
Filed under:

Attachment(s): AlternateBackground.zip

ListView with grouping (In GridView mode)

ListView inherits GroupStyle from ItemsControl, so we can easily achieve grouping for GridView. The only thing we should provide is a GroupStyle. Besides, in order to collapse and expande a group, an Expander is added in the GroupStyle.

The number on the group header indicates the count of items in that group.

Declaimer: This posting is provided "AS IS" with no warranties, and confers no rights.

Posted by ATC Avalon Team | 12 Comments
Filed under:

Attachment(s): GridViewWithGrouping.xaml

Editing In ListView

ListView does not support editing in V1, but for those who are familiar with File Explorer, editing is quite useful. I will introduce how to make a cell editable in ListView. Furthermore, you will find it is easy to build a custom control.

 

However, it is just a sample to enable simple editing in ListView. The target scenarios of ListView are displaying items not editing. As for those who have intensive requirements for editing, ListView control may be not a good choice, because we are not sure how far you can go.

 

Besides, cell keyboard navigation is not enabled.

 

Idea

To achieve our goal, create a customized control called EditBox for GridViewColumn.CellTemplate.  The EditBox has two modes, one for displaying content in normal way, the other one for displaying content in editable mode. In normal mode, the EditBox displays content in a TextBlock, when in editable mode, it displays content in a TextBox, which lies in the TextBlock's adorner

1. Create a customized Edit Control

To build an EditBox, we need to define its OM(object model) and behaviors first. In our context, the OM can be outlined by the following rules:

  • When the ListViewItem that contains an EditBox is selected, click on the EditBox should switch it into Editing mode.
  • When in editing mode, pressing Enter key or F2 can switch it into Normal mode.
  • When in editing mode, once the TextBox loses its focus, the EditBox should switch to its Normal model.

    Behaviors:

  • In Normal mode, a TextBlock is used to display content;
  • In Editing mode, a TextBox will pop up for editing.     

    Assumptions    

This control is built for a DataTemplate to specify GridViewColumn.CellTemplate. No guarantee that it can work out of GridView.

     Steps

1)     Create an EditBox inherited from Control. Since we need a property to store displayed value, we add a dependency property “Value”.

public class EditBox : Control

{

            static EditBox()

            {

          ......

           public static readonly DependencyProperty ValueProperty =

                    DependencyProperty.Register(

                            "Value",

                            typeof(object),

                            typeof(EditBox),

                            new FrameworkPropertyMetadata());

     ......

}

 

2)     Add a DependencyProperty to represent the mode: IsEditing. It is of type Boolean, when it is true, EditBox is in Editing mode. We use it to trigger whether to show the TextBox or not.

......

            public static DependencyProperty IsEditingProperty =

                    DependencyProperty.Register(

                            "IsEditing",

                            typeof(bool),

                            typeof(EditBox),

                            new FrameworkPropertyMetadata(false)

                            );

......

 

3)      Style the EditBox.

<Style x:Key="{x:Type l:EditBox}" TargetType="{x:Type l:EditBox}" >

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

      <Setter Property="Template">

        <Setter.Value>

          <ControlTemplate TargetType="{x:Type l:EditBox}">

                <TextBlock x:Name="PART_TextBlockPart" 

                     Text="{Binding Path=Value, RelativeSource = {RelativeSource TemplatedParent}}">

                </TextBlock>

          </ControlTemplate>

        </Setter.Value>

      </Setter>

    </Style>

 

4)    To enable editing, create EditBoxAdorner to host the TextBox. It needs to do some measuring and arranging work to make TextBox work as expected.

internal sealed class EditBoxAdorner : Adorner

 {

        /// <summary>

        /// Inialize the EditBoxAdorner.

        /// </summary>

        public EditBoxAdorner(UIElement adornedElement, UIElement adorningElement)

            : base(adornedElement)

        {

            _textBox = adorningElement as TextBox;

            Debug.Assert(_textBox != null, "No TextBox!");

 

            _visualChildren = new VisualCollection(this);

 

            BuildTextBox();

        }   

                        ......

 

     5)     To implement all the rules, add some event handling code.(See source code for details)

                  ......

        protected override void OnMouseEnter(MouseEventArgs e)

        {

            base.OnMouseEnter(e);

            if (!IsEditing && IsParentSelected)

            {

                _canBeEdit = true;

            }

        }

 

        protected override void OnMouseLeave(MouseEventArgs e)

        {

            base.OnMouseLeave(e);

            _isMouseWithinScope = false;

            _canBeEdit = false;

        }

        ...... 

 

2. Customize CellTemplate

By putting the EditBox in GridViewColumn.CellTemplate, the column can be edited when its ListViewItem is selected and then clicked. For example:

    ......

          <GridViewColumn Header="Month" >

            <GridViewColumn.CellTemplate>

              <DataTemplate>

                <l:EditBox  Value="{Binding Month}"/>

              </DataTemplate>

            </GridViewColumn.CellTemplate>

          </GridViewColumn>

          <GridViewColumn />

         ......

 

Conclusion: To implement editing in GridView, you need to build a customized control that can switch between different modes(Editing and Normal), and then put the control into GridViewColumn.CellTemplate. Here I just demonstrate the idea. You may change it to build a more powerful control that can be used in wider situations.

This sample is based on the February CTP.

 

Declaimer: This posting is provided "AS IS" with no warranties, and confers no rights.  

Posted by ATC Avalon Team | 24 Comments
Filed under:

Attachment(s): editing.zip

ListView Sorting

This sample shows how to create a ListView control that uses a GridView to display a list of dates that are defined as DateTime objects. The sample enables sorting of data in ascending or descending order according to the contents of one column.

Key Step 1. Add Sorting method in GridViewColumnHeader.Click event. Consider both last clicked header and last sorting direction, and decide current sorting direction. Sorting itself is very simple, as written in Sort method.

 

ListView x:Name='lv' GridViewColumnHeader.Click="GridViewColumnHeaderClickedHandler">

 

GridViewColumnHeader _lastHeaderClicked = null;

        ListSortDirection _lastDirection = ListSortDirection.Ascending;

 

        void GridViewColumnHeaderClickedHandler(object sender, RoutedEventArgs e)

        {

            GridViewColumnHeader headerClicked = e.OriginalSource as GridViewColumnHeader;

            ListSortDirection direction;

 

            if (headerClicked != null)

            {

                if (headerClicked != _lastHeaderClicked)

                {

                    direction = ListSortDirection.Ascending;

                }

                else

                {

                    if (_lastDirection == ListSortDirection.Ascending)

                    {

                        direction = ListSortDirection.Descending;

                    }

                    else

                    {

                        direction = ListSortDirection.Ascending;

                    }

                }

 

                string header = headerClicked.Column.Header as string;

                Sort(header, direction);

 

                _lastHeaderClicked = headerClicked;

                _lastDirection = direction;

               

            }

           

        }

  

        private void Sort(string sortBy, ListSortDirection direction)

        {

            lv.Items.SortDescriptions.Clear();

            SortDescription sd = new SortDescription(sortBy, direction);

            lv.Items.SortDescriptions.Add(sd);

            lv.Items.Refresh();

        }

 

 

Key Step 2(optional). In order to figure out current sorting status, add Template in the Column Header. Change Template along with sorting status, so it should also be added in GridViewColumnHeader.Click event. And in the Template, Use path to draw the arrows.

 

    <DataTemplate x:Key="HeaderTemplateArrowUp">

      <DockPanel>

        <TextBlock HorizontalAlignment="Center" Text="{Binding}"/>

        <Path x:Name="arrow"

           StrokeThickness = "1"                                     

           Fill            = "gray"

           Data            = "M 5,10 L 15,10 L 10,5 L 5,10"/>

     </DockPanel>

    </DataTemplate>

    <DataTemplate x:Key="HeaderTemplateArrowDown">

      <DockPanel>

        <TextBlock HorizontalAlignment="Center" Text="{Binding }"/>

        <Path x:Name="arrow"

              StrokeThickness = "1"                                  

              Fill            = "gray"

              Data            = "M 5,5 L 10,10 L 15,5 L 5,5"/>

      </DockPanel>

    </DataTemplate>

 

  void GridViewColumnHeaderClickedHandler(object sender, RoutedEventArgs e)

        {

           

 

            if (headerClicked != null)

            {

                if (_lastHeaderClicked != null)

                {

                    _lastHeaderClicked.Column.HeaderTemplate = null;

                }

                 

                if (direction == ListSortDirection.Ascending)

                {

                    headerClicked.Column.HeaderTemplate =

                      Resources["HeaderTemplateArrowUp"] as DataTemplate;

                }

                else

                {

                    headerClicked.Column.HeaderTemplate =

                      Resources["HeaderTemplateArrowDown"] as DataTemplate;

                }

 

               

            }

        }

 

Key Step 3. Put it all together and you have the following ListView sorting!

This sample is based on the February CTP.

 

Declaimer: This posting is provided "AS IS" with no warranties, and confers no rights.

Posted by ATC Avalon Team | 8 Comments
Filed under:

Attachment(s): ListViewSorting.zip

TreeListView: Show Hierarchy Data with Details in Columns

This is the first session of this topic. I’ll introduce the minimum steps needed to build a TreeListView. The TreeListView is able to present hierarchy data with details in columns. See the following diagram:

The main Avalon elements we are going to cover in this session are TreeView, TreeViewItem, GridViewColumnCollection, GridViewRowPresenter, GridViewHeaderRowPresenter and Binding of course.

 

Step 1. Sub-class TreeView and TreeViewItem

The key in this step is to override proper methods to make sure correct container is generated.

public class TreeListView : TreeView

{

    protected override DependencyObject GetContainerForItemOverride(object item)

    {

        return new TreeListViewItem();

    }

    protected override bool IsItemItsOwnContainerOverride(object item)

    {

        return item is TreeListViewItem;

    }

    public GridViewColumnCollection Columns
    {
        get
        {
            if (_columns == null)
            {
                _columns = new GridViewColumnCollection();
            }

            return _columns;
        }
    }

    private GridViewColumnCollection _columns;

}

 

public class TreeListViewItem : TreeViewItem

{

    // copy the above two overrides here, they are same.

}

 

Step 2. Add a Level property to TreeListViewItem

Make the item be aware of the level it belongs to.

public class TreeListViewItem : TreeViewItem

{

    public int Level {

        get {

            if (_level == -1) {

                TreeListViewItem parent = ItemsControl.ItemsControlFromItemContainer(this) as TreeListViewItem;

                _level = (parent != null) ? parent.Level + 1 : 0;

            }

            return _level;

        }

}

}

 

Step 3. Write a data template for the 1st column

The keys in this step are:

  1. Bind Margin to item’s Level to mimic the indent;
  2. Write a converter to convert Level to proper indent space;
  3. Use ‘ancestor’ binding to find the TreeListViewItem;
  4. When there is no child in the item, trigger the expander’s (+ sign) visibility to Hidden but not Collapsed, so that the space is kept and all the item texts align vertically.

<DataTemplate x:Key="CellTemplate_Name">

  <DockPanel>

    <ToggleButton x:Name="Expander" Style="" ClickMode="Press"

                  Margin="{Binding Level,Converter={StaticResource LevelToIndentConverter},RelativeSource={RelativeSource AncestorType={x:Type l:TreeListViewItem}}}"

                  IsChecked="{Binding Path=IsExpanded,RelativeSource={RelativeSource AncestorType={x:Type l:TreeListViewItem}}}"

                  />

    <TextBlock Text="{Binding Name}"/>

  </DockPanel>

  <DataTemplate.Triggers>

    <DataTrigger Binding="{Binding Path=HasItems,RelativeSource={RelativeSource AncestorType={x:Type l:TreeListViewItem}}}"

                 Value="False">

      <Setter TargetName="Expander"

              Property="Visibility"

              Value="Hidden"/>

    </DataTrigger>

  </DataTemplate.Triggers>

</DataTemplate>

 

public class LevelToIndentConverter : IValueConverter

{

    public object Convert(object o, Type type, object parameter, CultureInfo culture)

    {

        return new Thickness((int)o * c_IndentSize, 0, 0, 0);

    }

    public object ConvertBack() {…}

   

}

 

Step 4. Write control template for TreeListViewItem

The keys in this step are:

  1. Instead of using ContentPresenter, we use GridViewRowPresenter to present the content in multiple columns
  2. Presenter finds content from item’s Header property
  3. Bind GridViewRowPresenter.Colums property to TreeListView.Columns property

<ControlTemplate TargetType="{x:Type l:TreeListViewItem}">

  <StackPanel>

    <Border >

      <GridViewRowPresenter x:Name="PART_Header"

                            Content="{TemplateBinding Header}"

                            Columns="{Binding Path=Columns,RelativeSource={RelativeSource AncestorType={x:Type l:TreeListView}}}"/>

    </Border>

    <ItemsPresenter x:Name="ItemsHost"/>

  </StackPanel>

   

</ControlTemplate>

 

Step 5. Put it all together

The key in this step is: When setup the columns you want to show, the cell template for the 1st column is the one we just wrote that can do smart indent.

<Window >

  <Window.Resources>

    <Style x:Key="ExpandCollapseToggleStyle">

     

    <l:LevelToIndentConverter x:Key="LevelToIndentConverter"/>

<DataTemplate x:Key="CellTemplate_Name">

    <Style TargetType="{x:Type l:TreeListViewItem}">

    

    <Style TargetType="{x:Type l:TreeListView}">

     

  </Window.Resources>

 

  <l:TreeListView>

    <l:TreeListView.Columns>

      <GridViewColumn Header="Name" CellTemplate="{StaticResource CellTemplate_Name}"/>

      <GridViewColumn Header="IsAbstract" DisplayMemberBinding="{Binding IsAbstract}"/>

      <GridViewColumn Header="Namespace" DisplayMemberBinding="{Binding Namespace}"/>

    </l:TreeListView.Columns>

    <l:TreeListViewItem>

      <l:TreeListViewItem.Header>

        <x:Type TypeName="DependencyObject"/>

      </l:TreeListViewItem.Header>

      <l:TreeListViewItem>

        <l:TreeListViewItem.Header>

          <x:Type TypeName="Visual"/>

        </l:TreeListViewItem.Header>

      </l:TreeListViewItem>

     

    </l:TreeListViewItem>

   

  </l:TreeListView>

</Window>

We’re done!

I’ll introduce how to encapsulate the implementation details into a control in near future.

Posted by ATC Avalon Team | 17 Comments
Filed under:

Attachment(s): TreeListView.zip

A Step-by-Step to Create a Song List

How to create a song list above with some fancy effects? Now it is easy with ListView in Avalon.

1.      To simplify, we can use XmlDataProvider to provide data for ListView, the xaml looks like below:

 

  <XmlDataProvider x:Key="MyData" XPath="/Info">

     <x:XData>

<Info xmlns="">

                             <Song Name="Love at first sight" Time="4:04" Artist="kylie" Rating="3" Disk="Disk-1" />

                            <Song Name="At Night" Time="4:31" Artist="Shake Down" Rating="4" Disk="Disk-1" />

                            <Song Name="Believe" Time="3:54" Artist="Cher" Rating="5" Disk="Disk-2" />

                            <Song Name="Don't call me baby" Time="3:25" Artist="Madison Avenue" Rating="5" Disk="Disk-2" />

                            <Song Name="It's in your eyes" Time="4:32" Artist="Kylie" Rating="5" Disk="Disk-1" />

                            <Song Name="Rise up" Time="4:12" Artist="Sunkids" Rating="5" Disk="Disk-1" />  

             </Info>

     </x:XData>

</XmlDataProvider>

 

2.      Create a ListView with GridView mode and bind data to each column.

 

<ListView ItemsSource="{Binding Source={StaticResource MyData}, XPath=Song}">

                                        <ListView.View>

                                                            <GridView >

                                                                                <GridViewColumn Header="Name" DisplayMemberBinding="{Binding XPath=@Name}"  Width="100"/>

                                                                                <GridViewColumn Header="Time" DisplayMemberBinding="{Binding XPath=@Time}"  Width="80"/>

                                                                                <GridViewColumn Header="Artist" DisplayMemberBinding="{Binding XPath=@Artist}"  Width="80" />

                                                                                <GridViewColumn Header="Rating" DisplayMemberBinding="{Binding XPath=@Rating}"  Width="80"/>

                                                                                <GridViewColumn  Header="Disk" DisplayMemberBinding="{Binding XPath=@Disk}" Width="100"/>

                                                            </GridView>

                                        </ListView.View>

                    </ListView>

 

 

3.      To add an image to each column header, use GridViewColumn.HeaderTemplate property. The property’s type is DataTemplate, so a DataTemplate should be provided in Resources. Create one DataTemplate for each headerwith different Image Source.

 

<DataTemplate x:Key="ArtistHeader">

         <StackPanel Orientation="Horizontal">

               <Image Source="artist.png" Width="20" Height="20"/>

               <TextBlock Margin="10,0,0,0" Text="{Binding}" VerticalAlignment="Center"/>

         </StackPanel>

  </DataTemplate>

<GridViewColumn Header="Artist" HeaderTemplate="{StaticResource ArtistHeader}" Width="80"/>

 

 

4.      If we would like to see the selection of the ListView items by using CheckBox, create a DataTemplate for GridViewColumn.CellTemplate property. In that DataTemplate, add a checkbox and bind its IsChecked property with ListViewItem’s IsSelected property.

 

<DataTemplate x:Key="NameCell">

                <StackPanel Orientation="Horizontal">

<CheckBox Margin="1,0,5,2" VerticalAlignment="Center" IsChecked="{Binding Path=IsSelected, RelativeSource={RelativeSource AncestorType={x:Type ListViewItem}}}"/>

                   <TextBlock Text="{Binding XPath=@Name}"/>

                </StackPanel>

</DataTemplate> 

<GridViewColumn Header="Name" HeaderTemplate=" {StaticResource NameHeader}"  CellTemplate="{StaticResource   NameCell}" Width="100"/>

 

Note: In order to apply CellTemplate to the first Column, remove its DisplayMemberBinding property set in step 2 because the latter has higher priority.

 

5.      How to make those cute stars of Rating column? Customize CellTemplate of that Column and restyle its ToggleButtons using Path.  

 

<Style x:Key="MyToggleButton" TargetType="{x:Type ToggleButton}">

                <Setter Property="Template">

                                    <Setter.Value>

                                                        <ControlTemplate TargetType="{x:Type ToggleButton}">

                                                                            <Canvas Width="12" Height="12">

                                    <Path Name="_path" Fill="Gray" Data="M 5,0 L 4,4 L 0,4 L 3,7 L 2,11 L 5,9 L 6,9 L 9,11 L 8,7 L 11,4 L 7,4 L 6,0"/>

                                                                            </Canvas>

                                                        <ControlTemplate.Triggers>

                                                                            <Trigger Property="IsChecked" Value="True">

                                                                                                <Setter TargetName="_path" Property="Fill" Value="Gold"/>

                                                                            </Trigger>

                                                        </ControlTemplate.Triggers>

                                                                                                </ControlTemplate>

                                                                            </Setter.Value>

                                                        </Setter>

                                    </Style>    

<DataTemplate x:Key="RatingCell">

         <StackPanel Orientation="Horizontal">

               <ToggleButton Width="14" Height="20"  Style="{StaticResource MyToggleButton}" />

               <ToggleButton Width="14" Height="20"  Style="{StaticResource MyToggleButton}" />

               <ToggleButton Width="14" Height="20"  Style="{StaticResource MyToggleButton}" />

               <ToggleButton Width="14" Height="20"  Style="{StaticResource MyToggleButton}" />

               <ToggleButton Width="14" Height="20"  Style="{StaticResource MyToggleButton}" />

         </StackPanel>

</DataTemplate>

<GridViewColumn Header="Rating" CellTemplate="{StaticResource RatingCell}" HeaderTemplate="{StaticResource RatingHeader}" Width="80"/>

 

 

6.      You may want ListViewItem to have different MouseOver effect (in Red) and Selected effect (in Blue). To achieve those effects, trigger should be added in ListView.ItemContainerStyle.

 

<Style x:Key="MyContainer" TargetType="{x:Type ListViewItem}">

                                                        <Setter Property="Margin" Value="0,1,0,0"/>

                                                        <Setter Property="Height" Value="21"/>

                                                        <Style.Triggers>

                                                                            <Trigger Property="IsMouseOver" Value="true">

                                                                                                <Setter Property="Foreground" Value="White" />

                                                                                                <Setter Property="Background">

                                     <Setter.Value>

                                       <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">

                                         <LinearGradientBrush.GradientStops>

                                          <GradientStop Color="DarkBlue" Offset="0"/>

                                          <GradientStop Color="Blue" Offset="1"/>

                                         </LinearGradientBrush.GradientStops>

                                       </LinearGradientBrush>

                                    </Setter.Value>

                                 </Setter>

                                 <Setter Property="Cursor" Value="Hand"/>

                                                                            </Trigger>

                                                                            <MultiTrigger>

                                                                                                <MultiTrigger.Conditions>

                                                                                                                    <Condition Property="IsSelected" Value="true" />

                                                                                                                    <Condition Property="Selector.IsSelectionActive" Value="true" />

                                                                                                </MultiTrigger.Conditions>

                                                                                                <Setter Property="Background">

                                    <Setter.Value>

                                       <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">

                                         <LinearGradientBrush.GradientStops>

                                          <GradientStop Color="#0E4791" Offset="0"/>

                                          <GradientStop Color="#468DE2" Offset="1"/>

                                         </LinearGradientBrush.GradientStops>

                                       </LinearGradientBrush>

                                    </Setter.Value>

                                  </Setter>

                                                                                                <Setter Property="Foreground" Value="White" />

                                                                            </MultiTrigger>

                                                        </Style.Triggers>

                                    </Style>

 

 

7.      Put it all together and you have the following gorgeous Song List!

This sample is based on the February CTP.

 

Declaimer: This posting is provided "AS IS" with no warranties, and confers no rights.

Download the attached file to get the source code.

Posted by ATC Avalon Team | 12 Comments
Filed under:

Attachment(s): SongList.zip
 
Page view tracker