The Problem
I'm new to WPF and trying to learn. I have a basic ListView showing info about people such as Name, Age and Grade.
I want the Grade result text to be green if enum is "Pass" and red if "Fail", otherwise the text colour isn't changed.
What I tried
I know you can hard code all text in a column to be green, red etc with Foreground="" but this wouldn't work.
I tried implementing a function that checks if each enum in the list equals Pass etc but I couldn't get it and I'm quite stuck here.
XAML
<Grid Margin="10">
<ListView Name="lvUsers">
<ListView.View>
<GridView>
<GridViewColumn Header="Name" Width="120" DisplayMemberBinding="{Binding Name}" />
<GridViewColumn Header="Age" Width="50" DisplayMemberBinding="{Binding Age}" />
<GridViewColumn Header="Grade" Width="100" DisplayMemberBinding="{Binding Grade}"/>
</GridView>
</ListView.View>
</ListView>
</Grid>
CS
public partial class MainWindow : Window
{
public class User
{
public string Name { get; set; }
public int Age { get; set; }
public Grade Grade { get; set; }
}
public MainWindow()
{
InitializeComponent();
List<User> items = new List<User>();
items.Add(new User() { Name = "John Doe", Age = 42, Grade = Grade.fail });
items.Add(new User() { Name = "Jane Doe", Age = 39, Grade = Grade.pass });
items.Add(new User() { Name = "Sammy Doe", Age = 13, Grade = Grade.fail });
lvUsers.ItemsSource = items;
}
public enum Grade
{
none = 0,
pass = 1,
fail = 2
};
}
Expected result
I'm don't want to have all text in Grade column to be green/red. And I don't want to add a Colour property inside the User Class.
When the enum value is "Pass" for the User, the "Pass" text in the Grade column will be green. When it's "Fail", the text will be red. Otherwise, text colour is not changed.
Any help is much appreciated, because I'm quite stuck here.
You actually have a multitude of options to use here:
Firstly you will need to replace this GridViewColumn entry with the examples in one of the following sections:
<GridViewColumn Header="Grade" Width="100" DisplayMemberBinding="{Binding Grade}"/>
1 - DataTrigger
MSDN documentation here
This will work but is not reusable.
<!-- A custom cell template lets you customise how the cell will display -->
<GridViewColumn Header="Grade" Width="10">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Name="GradeText" Text="{Binding Grade}" />
<!-- define rules on how the ui will change based on the data bound -->
<DataTemplate.Triggers>
<!-- see NOTE below for how to get this working -->
<DataTrigger Binding="{Binding Grade}" Value="{x:Static enum:Grade.pass}">
<Setter TargetName="GradeText" Property="Foreground" Value="Green"/>
</DataTrigger>
<!-- you can add a second one for fail ;) -->
</DataTemplate.Triggers>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
NOTE you will also need to add a namespace declaration to where you enum is declared (full credit to this answer):
xmlns:enum="clr-namespace:YourEnumNamespace;assembly=YourAssembly"
2 - Converter
MSDN documentation here
This provides the least amount of XAML, allows you to reuse the logic elsewhere and if you really care about it, unit test the converter logic.
<!-- A custom cell template lets you customise how the cell will display -->
<GridViewColumn Header="Grade" Width="10">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Name="GradeText" Text="{Binding Grade}" Foreground="{Binding Grade, Converter={StaticResource GradeToBrushConverter}}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
And the converter code:
public GradeToBrushConverter : IValueConverter
{
public object Convert (object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is Grade grade)
{
switch (grade)
{
case Grade.pass:
return Brushes.Green;
case fail:
return Brushes.Red;
default:
return Brushes.Black; // Or a more sensible default.
}
}
return Brushes.Black;
}
// I haven't provided the ConvertBack but you should be able to work this bit out.
}
3 - Styling
Yes I appreciate that this example looks quite similar to point 1 but it has the added benefit that if you declare the style somewhere else it could be reused in multiple places.
<!-- A custom cell template lets you customise how the cell will display -->
<GridViewColumn Header="Grade" Width="10">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Name="GradeText" Text="{Binding Grade}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="Black"/>
<Style.Triggers>
<!-- see NOTE below for how to get this working -->
<DataTrigger Binding="{Binding Grade}" Value="{x:Static enum:Grade.pass}">
<Setter Property="Foreground" Value="Green"/>
</DataTrigger>
<!-- you can add a second one for fail ;) -->
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
Related
The source code can be found here : https://www.codeproject.com/Articles/24973/TreeListView
The way the original author has set it up, is the data is filled in the xaml itself. I need to create the TreeViewList inside of my ViewModel, but I can't figure out how to bind my own TreeViewList within the xaml to display it properly.
Here's an example of me creating a tree in the code behind and calling the window.
public TreeListView TreeList { get; set; } = new TreeListView();
private void generateTree()
{
TreeList.Columns.Add(new GridViewColumn() { Header = "Col1" });
TreeList.Columns.Add(new GridViewColumn() { Header = "Col2" });
TreeList.Columns.Add(new GridViewColumn() { Header = "Col3" });
}
public ICommand AssemblyTreeCommand => new RelayCommand(AssemblyTree, p => CanAssemblyTree);
public bool CanAssemblyTree { get; set; } = true;
private void AssemblyTree(object parameter)
{
generateTree();
AssemblyTreeDialogWindow dialog = new AssemblyTreeDialogWindow()
{
DataContext = this,
Topmost = true
};
dialog.ShowDialog();
}
AssemblyTreeDialog Window class looks like this:
<local:TreeListView AllowsColumnReorder="True" ItemsSource="{Binding TreeList}">
<!--Create an item template to specify the ItemsSource-->
<local:TreeListView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}" />
</local:TreeListView.ItemTemplate>
<local:TreeListView.Columns>
<!--Create the first column containing the expand button and the type name.-->
<GridViewColumn Header="Name" Width="200">
<GridViewColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<!--The Expander Button (can be used in any column (typically the first one))-->
<local:TreeListViewExpander/>
<!--Display the name of the DataElement-->
<TextBlock Text="{Binding}"/>
</StackPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<!--Create a second column containing the number of children.-->
<GridViewColumn Header="Children" Width="100">
<GridViewColumn.CellTemplate>
<DataTemplate>
<!--Display the size of the DataElement-->
<TextBlock Text="{Binding Children.Count}" HorizontalAlignment="Right"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<!--Create a third column containing the brush of the material.-->
<GridViewColumn Header="Brush" Width="100">
<GridViewColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<!--Border showing the actual color-->
<Border Background="{Binding Brush}" CornerRadius="2"
Width="16" Height="16"
BorderThickness="1" BorderBrush="DarkGray"/>
<!--Display the brush-->
<TextBlock Text="{Binding Brush}"/>
</StackPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</local:TreeListView.Columns>
<!--Create some sample data-->
<MaterialGroup>
<MaterialGroup>
<DiffuseMaterial Brush="Blue"/>
<DiffuseMaterial Brush="Red"/>
<SpecularMaterial Brush="Orange"/>
</MaterialGroup>
<EmissiveMaterial Brush="AliceBlue"/>
</MaterialGroup>
</local:TreeListView>
Interestingly if I bind the line <GridViewColumn Header="Name" Width="200"> so that it reads <GridViewColumn Header="{Binding TreeList}" Width="200">it gives me this:
I'll explain my end goal as best as possible.
The System is a giant list of parts. A main table displays all of the parts while a subtable displays all of the parts which make up that part. All parts (including those which are used to create other parts) exist within the MainTable. So a Parent part might have a set of children parts, which each individually have children parts which they are made up of. This is the relationship i'm trying to model using this tool.
The code that I've written maps the parts list out into a list of class objects which contain the data. I'll post it below. It's working to map to a TreeView right now.
A datastructure I've based it off is here : Treeview with multiple columns
private void generateTree(string PN)
{
Proces selectedRow = new Proces() { procesId = (int)Vwr.Table.SelectedRow.Row["PID"], procesName = (string)Vwr.Table.SelectedRow.Row["PN"], subProcesses = generateSubtable(PN) };
processes.Add(selectedRow);
}
public List<Proces> generateSubtable(string PN)
{
List<Proces> subTable = new List<Proces>();
foreach (DataRow mplrow in Vwr.Table.Tbl.Rows) if (mplrow["PN"].ToString() == PN)
MainVM.Modules.AllModules[0].SubVwr.Tables[0].LoadTableQuery.Prms[0].Val = mplrow[0];
MainVM.Modules.AllModules[0].SubVwr.Tables[0].Tbl = Db.GetTable(MainVM.Modules.AllModules[0].SubVwr.Tables[0].LoadTableQuery);
foreach (DataRow sub in MainVM.Modules.AllModules[0].SubVwr.Tables[0].Tbl.Rows)
{
Proces subItem = new Proces() { procesId = (int)sub["ItemNo"], procesName = sub["PN"].ToString(), subProcesses = generateSubtable(sub["PN"].ToString()) };
subTable.Add(subItem);
}
return subTable;
}
Found the answer! After some pretty extensive searching, and trying many different solutions. Thought i'd post incase someone else might also be trying to do the same.
credit:
http://dlaa.me/blog/post/9898803
I couldn't find a solution for my problem/idea, and I hope that someone could help me out.
In WPF I have a CollectionViewSource depending on an IEnumerable<Item>.
An Item has the Name, Height, Age fields.
In Xaml, the ListView's ItemsSource="{Binding CollectionViewSource.View}".
I know, the Listview has an AlternationCount property which can change the row background color.
But I want to change the row background color only if the Age field data is different from the above row's Age data.
Like this, ordered by Age:
The row background color is alternated only when the Age data is different.
When I set another sort order to the list, the alternating should also be changed.
In this picture the list is ordered by Name:
But the background color depends on the Age data.
Is there any way to make a solution for this concept?
You can use the RelativeSource.PreviousData for your task.
First, create an IMultivalueConverter which will accept the values you want to compare and return the current alternation index based on them:
class ComparisonConverter : IMultiValueConverter
{
private int currentAlternation = 0;
public int AlternationCount { get; set; } = 2;
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
// TODO: exception handling
if (values.Distinct().Count() != 1)
{
if (++currentAlternation >= AlternationCount)
{
currentAlternation = 0;
}
}
return currentAlternation;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
This converter accepts multiple values and returns an unchanged alternation index when all values are equal; otherwise, it first changes the alternation index to the next one and then returns a new index.
Now, create a MultiBinding that will provide the alternation index value to the Style, where you define your colors:
<!-- This is an incomplete ListView! Set the View and ItemsSource as required. -->
<ListView>
<ListView.Resources>
<local:ComparisonConverter x:Key="ComparisonConverter"/>
</ListView.Resources>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Style.Triggers>
<!-- This is the DataTrigger for the alternation index 1 -->
<DataTrigger Value="1">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource ComparisonConverter}">
<Binding Path="Age"/>
<Binding Path="Age" RelativeSource="{RelativeSource PreviousData}"/>
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Background" Value="Red"/>
</DataTrigger>
</Style.Triggers>
<Setter Property="Background" Value="Wheat"/>
</Style>
</ListView.ItemContainerStyle>
</ListView>
So in this style, the default Color for the alternation index 0 is Wheat. Using the DataTrigger, the alternation index 1 will generate the color Red.
The sort order changes will be reflected automatically, because the CollectionViewSource will rebuild the view, so the ListView will create all items from scratch using the MultiBinding for each item.
Run this and live life to fullest :
XAML :
<Window x:Class="WpfStackOverflow.Window6"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window6" Height="362.03" Width="563.91">
<Window.Resources>
<CollectionViewSource x:Key="CVS" Source="{Binding .}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Age"/>
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</Window.Resources>
<Grid>
<ListView x:Name="LstView" ItemsSource="{Binding Source={StaticResource CVS}}">
<ListView.Resources>
<AlternationConverter x:Key="AltCnvKey">
<SolidColorBrush Color="Snow"/>
<SolidColorBrush Color="LightBlue"/>
<SolidColorBrush Color="Orange"/>
</AlternationConverter>
</ListView.Resources>
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn DisplayMemberBinding="{Binding Name}" Header="Name"/>
<GridViewColumn DisplayMemberBinding="{Binding Height}" Header="Height"/>
<GridViewColumn DisplayMemberBinding="{Binding Age}" Header="Age"/>
</GridView.Columns>
</GridView>
</ListView.View>
<ListView.GroupStyle>
<GroupStyle AlternationCount="3">
<GroupStyle.ContainerStyle>
<Style TargetType="GroupItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="GroupItem">
<StackPanel Orientation="Horizontal">
<StackPanel.Resources>
<Style TargetType="StackPanel">
<Setter Property="Background" Value="{Binding RelativeSource={RelativeSource AncestorType=GroupItem, Mode=FindAncestor}, Path=(ItemsControl.AlternationIndex), Converter={StaticResource AltCnvKey}}"/>
</Style>
</StackPanel.Resources>
<ItemsPresenter/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ListView.GroupStyle>
</ListView>
</Grid>
Code :
using System.Linq;
using System.Windows;
namespace WpfStackOverflow
{
/// <summary>
/// Interaction logic for Window6.xaml
/// </summary>
public partial class Window6 : Window
{
public Window6()
{
InitializeComponent();
this.DataContext = new[] { new { Age = 32, Name = "Name1", Height = 6 }, new { Age = 34, Name = "Name1", Height = 6 }, new { Age = 34, Name = "Name1", Height = 6 }, new { Age = 32, Name = "Name1", Height = 6 }, new { Age = 32, Name = "Name1", Height = 6 }, new { Age = 39, Name = "Name1", Height = 6 }, new { Age = 40, Name = "Name1", Height = 6 } }.ToList();
}
}
}
In my WPF app I have a Listview with a couple columns and one column with a checkbox inside.
The xaml is as follows:
<ListView x:Name="listviewImported">
<ListView.View>
<GridView>
<GridViewColumn Header="Check" Width="50">
<GridViewColumn.CellTemplate>
<DataTemplate>
/*This line may be the prob.*/ <CheckBox IsChecked="{Binding MarkedForCheck}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Pos." Width="75" DisplayMemberBinding="{Binding PositionAsString}" />
<GridViewColumn Header="Value" Width="175" DisplayMemberBinding="{Binding ImportValue}" />
<GridViewColumn Header="Type" Width="175" DisplayMemberBinding="{Binding ImportValueTypeDescription}" />
</GridView>
</ListView.View>
</ListView>
The ItemSource is assigned like this:
listviewImported.ItemsSource = _catalog.ImportedList;
The ImportedList in the _catalog is of Type ImportedElem:
private List<ImportedElem> _importedList = new List<ImportedElem>();
This is the ImportedElem class:
public class ImportedElem : BaseElem {
public ImportedElem(int Position, string ImportValue) : base(Position, ImportValue) {
}
//MARKED FOR CHECK
public bool MarkedForCheck = true;
}
This is the problem:
When I add an ImportedElem to the List, then the Listview will update and the columns "Pos.", "Value" and "Type" contain the correct Data. However, the Column with the checkbox ("Check") always shows a nonchecked Checkbox after adding it to the List. The Data is correct, the column just shows the wrong Data. I can modify the checkbox in the list and it will update the Data, that is good. But the List should contain the correct Data right after adding the Element to the list, wich it is not doing. It would be very interesting to know why 3 columns are correct and the checkbox doesn't work.
change public bool MarkedForCheck = true; TO public bool MarkedForCheck {get;set;}
I'm calculating the logarithmic slopes between the points (that the user has inserted):
For that I'm using two DataGrid; first displays the coordinates, and the second displays the slopes. I'm using a pair of DataGrid because I need to update the curve distinguishing what has been changed, the coordinates or the slope.
I would like to display better the values of the slopes: rather that being displayed in the same line as the points, they should be displayed in between the points, as shown in the second figure.
I don't know a way of doing this in WPF, maybe setting a greater value to Height of the header of the second DataGrid, so all rows will be displaced a little (approx 5px), but I can't find a way of doing that.
Could someone guide me to achieve this?
The DataGrid is defined in a pretty standard way:
<DataGrid x:Name="gridSlope" ItemsSource="{Binding Points, Mode=TwoWay}"
AutoGenerateColumns="False" Width="100"
Background="#19B0C4DE" BorderThickness="1"
BorderBrush="#19D3D3D3" CanUserResizeColumns="False"
CanUserResizeRows="False" CanUserSortColumns="False"
ClipboardCopyMode="IncludeHeader"
CellEditEnding="s1GridPendiente_CellEditEnding">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Path=Slope, StringFormat='{}{0:0.00}',
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"
Header="dB/oct"/>
</DataGrid.Columns>
</DataGrid>
Here's a screenshot:
To achieve that, you can add this
<DataGrid.ColumnHeaderStyle>
<Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Margin" Value="0 0 0 10"></Setter>
</Style>
</DataGrid.ColumnHeaderStyle>
to your <DataGrid x:Name="gridSlope" ... before the <DataGrid.Columns>.
You can play with the margin there, or do what you want :) (and remove the bold if you don't like it ... I was just fooling around with it ...)
The following isn't perfect but it's close to what you are after. The problem with this idea is that the DataGridRow clips its content so you can't move the 3rd column as far down as would be ideal.
<DataGrid ItemsSource="{Binding Data}" AutoGenerateColumns="False" GridLinesVisibility="None">
<DataGrid.Columns>
<DataGridTextColumn Header="A" Binding="{Binding A}"/>
<DataGridTextColumn Header="B" Binding="{Binding B}"/>
<DataGridTemplateColumn Header="C" Width="Auto">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=C}" Margin="0,5,0,-2"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
public class DataClass
{
public int A { get; set; }
public int B { get; set; }
public String C { get; set; }
public DataClass(int a, int b, String c)
{
A = a;
B = b;
C = c;
}
}
public List<DataClass> Data { get; set; }
public MainWindow()
{
Data = new List<DataClass>() { new DataClass(1, 2, "3"), new DataClass(2, 3, "4"), new DataClass(3, 4, "5"), new DataClass(4, 5, "") };
DataContext = this;
InitializeComponent();
}
I have the following Attached Property:-
public partial class GridViewProperties
{
public static readonly DependencyProperty DoAutoSizeColumnsProperty =
DependencyProperty.RegisterAttached("DoAutoSizeColumns",
typeof(bool),
typeof(GridViewProperties),
new FrameworkPropertyMetadata(false,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault |
FrameworkPropertyMetadataOptions.AffectsMeasure |
FrameworkPropertyMetadataOptions.AffectsArrange |
FrameworkPropertyMetadataOptions.AffectsParentArrange |
FrameworkPropertyMetadataOptions.AffectsParentMeasure |
FrameworkPropertyMetadataOptions.AffectsRender,
DoAutoSizeColumnsChanged));
public static bool GetDoAutoSizeColumns(DependencyObject obj)
{
return (bool)obj.GetValue(DoAutoSizeColumnsProperty);
}
public static void SetDoAutoSizeColumns(DependencyObject obj, bool value)
{
obj.SetValue(DoAutoSizeColumnsProperty, value);
}
private static void DoAutoSizeColumnsChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var gv = obj as GridView;
if (gv == null)
return;
if (e.NewValue != null && (bool)e.NewValue)
{
AutoSizeColumns(gv.Columns);
SetDoAutoSizeColumns(gv, false);
gv.InvalidateProperty(GridView.ColumnCollectionProperty);
}
}
private static void AutoSizeColumns(GridViewColumnCollection gvcc)
{
// same code as double clicking column gripper
foreach (var gvc in gvcc)
{
// if already set to auto, toggle it so that we can re-run width="auto"
//if (double.IsNaN(gvc.Width))
gvc.Width = gvc.ActualWidth;
// now do it
gvc.Width = double.NaN;
//gvc.InvalidateProperty(GridViewColumn.WidthProperty);
//gvc.ClearValue(GridViewColumn.WidthProperty);
}
}
}
I use it in XAML in the following fashion:
<Style x:Key="AutoColumnStyle" TargetType="ListView">
<Setter Property="View">
<Setter.Value>
<GridView infra:GridViewProperties.AutoSizeColumns="{Binding Path=DataContext.DoAutoSizeColumns, Source={x:Reference uc}}">
<GridViewColumn Width="auto" Header="Title" CellTemplate="{StaticResource Name}" />
<GridViewColumn Width="auto" Header="First" CellTemplate="{StaticResource First}"/>
<GridViewColumn Width="auto" Header="Last" CellTemplate="{StaticResource Last}"/>
</GridView>
</Setter.Value>
</Setter>
The abaove is in UserControl.Resources.
The rest of the XAML is:
<ListView ItemsSource="{Binding Names}" VerticalAlignment="Top" HorizontalAlignment="Left"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
SelectionMode="Single"
x:Name="ListViewContracts"
KeyboardNavigation.TabNavigation="Continue"
KeyboardNavigation.DirectionalNavigation="Cycle"
Style="{StaticResource AutoColumnStyle}"
>
<ListView.ItemContainerStyle >
<Style BasedOn="{StaticResource ListViewItemContainerStyle}" TargetType="ListViewItem">
<Setter Property="Focusable" Value="False" />
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
</ListView.ItemContainerStyle>
</ListView>
(I have tried this with no Width="auto" too).
Whenever I set DoAutoSizeColumns = true in my ViewModel I see everything work as expected in the attached property except what it is desgined for that is the gridview columns are not resized according to the largest item in that column (even though I see gv.Width toggled from and to double.Nan which is how resize is meant to work).
As you can see I have tried a number of variations in the attached property mostly commented out including adding in all the FrameworkPoprertyMetadataOptions and trying various InvalidatePoperty attempts but also UIPropertyMetadata too (and DynamicResource too).
What am I missing?
UPDATE
This attached property works in other GridViews the only difference I can see here is that - I need to switch GridViews in the ListView which was not indicated above -but the key difference is that I inject as a style rather than directly. (On second thoughts this may not be the case since the firs item in a column in these other GridViews is always the largest item).
It was to do with the injection of a style.
I had two different GridViews switched by a boolean and used a DataTrigger to inject the alternate one and AutoSizeColumns worked fine on that altererate GridView! So I made both styles be injected by a DataTrigger rather having the default set by just a setter as in the XAML above.
As to why this is case I would be interested in finding out. At least I have a fully MVVM way of autosizing columns which I have not seen in other SO answers.
(BTW I have other attached properties that do not need me to manually trigger the autosize, I just wrote this version to identify the problem).