none
AutomationPeer for ListViewItem RRS feed

  • Question

  • Scenario: I have a ListView with a GridView as its View, and I want to set the CellTemplate to a custom textblock because I want to set style to the cell.
    Problem: I can't detect the Cell value with any Automation tool such as Inspector, UISpy.
    The Source Code of the Xaml is:

    <Window x:Class="HelloWorld.RPF" 
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
        Title="RPF" Height="300" Width="300">  
        <Grid> 
            <ListView Name="list">  
                <ListView.View> 
                    <GridView> 
                        <GridViewColumn Header="column0" DisplayMemberBinding="{Binding column0}"></GridViewColumn> 
                        <GridViewColumn Header="column1">  
                            <GridViewColumn.CellTemplate> 
                                <DataTemplate> 
                                    <TextBlock HorizontalAlignment="Center" Foreground="Red" TextAlignment="Center" Text="{Binding column1}"></TextBlock> 
                                </DataTemplate> 
                            </GridViewColumn.CellTemplate> 
                        </GridViewColumn> 
                    </GridView> 
                </ListView.View> 
            </ListView> 
        </Grid> 
    </Window> 
     
    The Code Behind is:
        /// <summary>  
        /// Interaction logic for RPF.xaml  
        /// </summary>  
        public partial class RPF : Window  
        {  
            DataTable dt;  
     
            public RPF()  
            {  
                InitializeComponent();  
     
                dt = new DataTable();  
                for (int i = 0; i < 2; i++)  
                    dt.Columns.Add("column" + i.ToString());  
                for (int i = 0; i < 10; i++)  
                    dt.Rows.Add(new string[] { "itema" + i.ToString(), "itemb" + i.ToString() });  
                Binding b = new Binding();  
                b.Source = dt;  
                list.SetBinding(ListView.ItemsSourceProperty, b);  
            }  
        }  
     
    In this test example, the GridViewColumn without CellTemplate can be detected by Automation tool, and the CellTemplate Column can't be detected. After Searching on the internet, I think there might be the AutomationPeer of the ListViewItem can't detect the CellAutomationPeer of the Celltemplate, so I tried to write a custom AutomationPeer for the ListViewItem and the problem is: the GetChildrenCore of the AutomationPeer is not even invoked, here's the code:
    the Xaml code:
    <Window x:Class="HelloWorld.RPF" 
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
        xmlns:local="clr-namespace:HelloWorld" 
        Title="RPF" Height="300" Width="300">  
        <Grid> 
            <local:SpecialListView x:Name="list">  
                <local:SpecialListView.View> 
                    <GridView> 
                        <GridViewColumn Header="column0" DisplayMemberBinding="{Binding column0}"></GridViewColumn> 
                        <GridViewColumn Header="column1">  
                            <GridViewColumn.CellTemplate> 
                                <DataTemplate> 
                                    <TextBlock HorizontalAlignment="Center" Foreground="Red" TextAlignment="Center" Text="{Binding column1}"></TextBlock> 
                                </DataTemplate> 
                            </GridViewColumn.CellTemplate> 
                        </GridViewColumn> 
                    </GridView> 
                </local:SpecialListView.View> 
            </local:SpecialListView> 
        </Grid> 
    </Window> 
     
    the code behind is:
    using System;  
    using System.Collections.Generic;  
    using System.Linq;  
    using System.Text;  
    using System.Windows;  
    using System.Windows.Controls;  
    using System.Windows.Data;  
    using System.Windows.Documents;  
    using System.Windows.Input;  
    using System.Windows.Media;  
    using System.Windows.Media.Imaging;  
    using System.Windows.Shapes;  
    using System.Data;  
    using System.Windows.Automation.Peers;  
     
    namespace HelloWorld  
    {  
        public class SpecialListView : ListView   
        {  
            protected override DependencyObject GetContainerForItemOverride()  
            {  
                return new SpecialListViewItem();  
            }  
        }  
     
        public class SpecialListViewItem : ListViewItem  
        {  
            protected override AutomationPeer OnCreateAutomationPeer()   
            {  
                return new SpecialListBoxItemWrapperAutomationPeer(this);   
            }   
        }  
     
        public class SpecialListBoxItemWrapperAutomationPeer : ListBoxItemWrapperAutomationPeer  
        {  
            public SpecialListBoxItemWrapperAutomationPeer(SpecialListViewItem owner)  
                : base(owner)  
            {  
            }  
     
            protected override string GetLocalizedControlTypeCore()  
            {  
                return "SpecialListViewItem";  
            }  
     
            // why this function is not invoked???  
            protected override List<AutomationPeer> GetChildrenCore()  
            {  
                List<AutomationPeer> children = base.GetChildrenCore();  
                return children;  
            }  
     
            protected override String GetNameCore()  
            {  
                return "XYZ";  
            }  
        }  
     
        /// <summary>  
        /// Interaction logic for RPF.xaml  
        /// </summary>  
        public partial class RPF : Window  
        {  
            DataTable dt;  
     
            public RPF()  
            {  
                InitializeComponent();  
     
                dt = new DataTable();  
                for (int i = 0; i < 2; i++)  
                    dt.Columns.Add("column" + i.ToString());  
                for (int i = 0; i < 10; i++)  
                    dt.Rows.Add(new string[] { "itema" + i.ToString(), "itemb" + i.ToString() });  
                Binding b = new Binding();  
                b.Source = dt;  
                list.SetBinding(ListView.ItemsSourceProperty, b);  
            }  
        }  
    }  
     
    I haven't actually add the celltemplate cell the Children List<AutomationPeer>, but the problem that the GetChildrenCore is not even invoked blocked me from doing anything further, could anybody help me out with this or provide me another solution to let the celltemplate been detected by Automation tool?
    Tuesday, September 16, 2008 3:39 PM

Answers

  • It takes me several hours to work out a working solution, basically because that I don't want to use reflection initially, and the debugger doesn't get attached when the automation code is called from UISpy (sigh!), anyway, I got a working example as follows:

    <Window x:Class="HelloWorld.RPF"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:HelloWorld"
        Title="RPF" Height="300" Width="300">
      <Grid>
        <ListView Name="list">
          <ListView.View>
            <local:MyGridView>
              <GridViewColumn Header="column0" DisplayMemberBinding="{Binding column0}"></GridViewColumn>
              <GridViewColumn Header="column1">
                <GridViewColumn.CellTemplate>
                  <DataTemplate>
                    <TextBlock HorizontalAlignment="Center" Foreground="Red" TextAlignment="Center" Text="{Binding column1}"/>
                  </DataTemplate>
                </GridViewColumn.CellTemplate>
              </GridViewColumn>
            </local:MyGridView>
          </ListView.View>
        </ListView>
      </Grid>
    </Window>

    public partial class RPF : Window
    {
        DataTable dt;

        public RPF()
        {
            InitializeComponent();

            dt = new DataTable();
            for (int i = 0; i < 2; i++)
                dt.Columns.Add("column" + i.ToString());
            for (int i = 0; i < 3; i++)
                dt.Rows.Add(new string[] { "itema" + i.ToString(), "itemb" + i.ToString() });
            Binding b = new Binding();
            b.Source = dt;
            list.SetBinding(ListView.ItemsSourceProperty, b);
        }
    }

    public class MyGridView : GridView
    {
        protected override IViewAutomationPeer GetAutomationPeer(ListView parent)
        {
            return new MyGridViewAutomationPeer(this, parent);
        }
    }

    public class MyGridViewAutomationPeer : GridViewAutomationPeer, IViewAutomationPeer
    {
        private ListView listView = null;
        public MyGridViewAutomationPeer(GridView owner, ListView listView)
            : base(owner, listView)
        {
            this.listView = listView;
        }

        ItemAutomationPeer IViewAutomationPeer.CreateItemAutomationPeer(object item)
        {
            return new MyGridViewItemAutomationPeer(item, UIElementAutomationPeer.FromElement(this.listView) as ListViewAutomationPeer);
        }
    }

    public class MyGridViewItemAutomationPeer : GridViewItemAutomationPeer
    {
        private ListViewAutomationPeer listviewAP;
        public MyGridViewItemAutomationPeer(object owner, ListViewAutomationPeer listviewAP)
            : base(owner, listviewAP)
        {
            this.listviewAP = listviewAP;
        }

        protected override List<AutomationPeer> GetChildrenCore()
        {
            List<AutomationPeer> peers = base.GetChildrenCore();
            for (int i = 0; i < peers.Count; i++)
            {
                GridViewCellAutomationPeer peer = peers[i] as GridViewCellAutomationPeer;
                if (peer != null && peer.Owner != null && peer.Owner.GetType() == typeof(ContentPresenter))
                {
                    TextBlock textBlock = GetVisualChild<TextBlock>(peer.Owner);
                    if (textBlock != null)
                    {
                        GridViewCellAutomationPeer newPeer = CreateCellAutomationPeer(textBlock, listviewAP);
                        peers[i] = newPeer;
                    }
                }
            }
            return peers;
        }

        private static T GetVisualChild<T>(Visual referenceVisual) where T : Visual
        {
            Visual child = null;
            for (Int32 i = 0; i < VisualTreeHelper.GetChildrenCount(referenceVisual); i++)
            {
                child = VisualTreeHelper.GetChild(referenceVisual, i) as Visual;
                if (child != null && (child.GetType() == typeof(T)))
                {
                    break;
                }
                else if (child != null)
                {
                    child = GetVisualChild<T>(child);
                    if (child != null && (child.GetType() == typeof(T)))
                    {
                        break;
                    }
                }
            }
            return child as T;
        }

        private GridViewCellAutomationPeer CreateCellAutomationPeer(TextBlock owner, ListViewAutomationPeer peer)
        {
            //This is ugly, but it works.
            GridViewCellAutomationPeer cellPeer = null;
            ConstructorInfo[] infos = typeof(GridViewCellAutomationPeer).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly);
            if (infos != null && infos.Length == 2)
            {
                cellPeer = infos[1].Invoke(new Object[] { owner, peer }) as GridViewCellAutomationPeer;
            }

            return cellPeer;
        }
    }

    Hope this helps
    • Marked as answer by Shuxing Friday, September 19, 2008 1:57 PM
    Friday, September 19, 2008 11:48 AM

All replies

  • has any tester encountered this problem while doing automation test for customized listview? why can't I get the data of celltemplated gridviewcolumn value? I find nothing useful searching with google.
    any hint is appreciated.

    Tuesday, September 16, 2008 11:06 PM
  • It takes me several hours to work out a working solution, basically because that I don't want to use reflection initially, and the debugger doesn't get attached when the automation code is called from UISpy (sigh!), anyway, I got a working example as follows:

    <Window x:Class="HelloWorld.RPF"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:HelloWorld"
        Title="RPF" Height="300" Width="300">
      <Grid>
        <ListView Name="list">
          <ListView.View>
            <local:MyGridView>
              <GridViewColumn Header="column0" DisplayMemberBinding="{Binding column0}"></GridViewColumn>
              <GridViewColumn Header="column1">
                <GridViewColumn.CellTemplate>
                  <DataTemplate>
                    <TextBlock HorizontalAlignment="Center" Foreground="Red" TextAlignment="Center" Text="{Binding column1}"/>
                  </DataTemplate>
                </GridViewColumn.CellTemplate>
              </GridViewColumn>
            </local:MyGridView>
          </ListView.View>
        </ListView>
      </Grid>
    </Window>

    public partial class RPF : Window
    {
        DataTable dt;

        public RPF()
        {
            InitializeComponent();

            dt = new DataTable();
            for (int i = 0; i < 2; i++)
                dt.Columns.Add("column" + i.ToString());
            for (int i = 0; i < 3; i++)
                dt.Rows.Add(new string[] { "itema" + i.ToString(), "itemb" + i.ToString() });
            Binding b = new Binding();
            b.Source = dt;
            list.SetBinding(ListView.ItemsSourceProperty, b);
        }
    }

    public class MyGridView : GridView
    {
        protected override IViewAutomationPeer GetAutomationPeer(ListView parent)
        {
            return new MyGridViewAutomationPeer(this, parent);
        }
    }

    public class MyGridViewAutomationPeer : GridViewAutomationPeer, IViewAutomationPeer
    {
        private ListView listView = null;
        public MyGridViewAutomationPeer(GridView owner, ListView listView)
            : base(owner, listView)
        {
            this.listView = listView;
        }

        ItemAutomationPeer IViewAutomationPeer.CreateItemAutomationPeer(object item)
        {
            return new MyGridViewItemAutomationPeer(item, UIElementAutomationPeer.FromElement(this.listView) as ListViewAutomationPeer);
        }
    }

    public class MyGridViewItemAutomationPeer : GridViewItemAutomationPeer
    {
        private ListViewAutomationPeer listviewAP;
        public MyGridViewItemAutomationPeer(object owner, ListViewAutomationPeer listviewAP)
            : base(owner, listviewAP)
        {
            this.listviewAP = listviewAP;
        }

        protected override List<AutomationPeer> GetChildrenCore()
        {
            List<AutomationPeer> peers = base.GetChildrenCore();
            for (int i = 0; i < peers.Count; i++)
            {
                GridViewCellAutomationPeer peer = peers[i] as GridViewCellAutomationPeer;
                if (peer != null && peer.Owner != null && peer.Owner.GetType() == typeof(ContentPresenter))
                {
                    TextBlock textBlock = GetVisualChild<TextBlock>(peer.Owner);
                    if (textBlock != null)
                    {
                        GridViewCellAutomationPeer newPeer = CreateCellAutomationPeer(textBlock, listviewAP);
                        peers[i] = newPeer;
                    }
                }
            }
            return peers;
        }

        private static T GetVisualChild<T>(Visual referenceVisual) where T : Visual
        {
            Visual child = null;
            for (Int32 i = 0; i < VisualTreeHelper.GetChildrenCount(referenceVisual); i++)
            {
                child = VisualTreeHelper.GetChild(referenceVisual, i) as Visual;
                if (child != null && (child.GetType() == typeof(T)))
                {
                    break;
                }
                else if (child != null)
                {
                    child = GetVisualChild<T>(child);
                    if (child != null && (child.GetType() == typeof(T)))
                    {
                        break;
                    }
                }
            }
            return child as T;
        }

        private GridViewCellAutomationPeer CreateCellAutomationPeer(TextBlock owner, ListViewAutomationPeer peer)
        {
            //This is ugly, but it works.
            GridViewCellAutomationPeer cellPeer = null;
            ConstructorInfo[] infos = typeof(GridViewCellAutomationPeer).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly);
            if (infos != null && infos.Length == 2)
            {
                cellPeer = infos[1].Invoke(new Object[] { owner, peer }) as GridViewCellAutomationPeer;
            }

            return cellPeer;
        }
    }

    Hope this helps
    • Marked as answer by Shuxing Friday, September 19, 2008 1:57 PM
    Friday, September 19, 2008 11:48 AM
  •  

    Marco, Thanks very much for your time, and your solution do solved the problem, thanks a tons, this problem have blocked me for several weeks.

     

    After investigate a lot of time, I really want to know what’s wrong behind the scene, I can see clearly using RPF that it’s the contentpresenter that failed to return the AutomationPeer for the TextBlock in the CellTemplate,  Could you please explain a bit about this?

    Friday, September 19, 2008 2:03 PM