Recursive binding in WPF with templates - c#

So I have a custom class that is designed to contain menu items and sub menu items formatted as such:
public class ApplicationMenuItem
{
public ImageSource Image { get; set; }
public string Text { get; set; }
public string Tooltip { get; set; }
public ICollection<ApplicationMenuItem> Items { get; set; }
public EventHandler Clicked {get;set;}
public void Click(object sender, EventArgs e)
{
if (Clicked!=null)
Clicked(this, e);
}
public ApplicationMenuItem(string Text)
{
this.Text = Text;
Items = new List<ApplicationMenuItem>();
}
public ApplicationMenuItem()
{
Items = new List<ApplicationMenuItem>();
}
}
Before anybody asks why I don't inherit Menu or just create a Menu object and bind it, its because this class may be used on platforms and frameworks that don't necessarily use the Menu UI object, not to mention this class will drive nav menus, context menus, sidebars, toolbars etc....
My question is as you can see I have a self referencing list Items contained within to allow sub menus; binding the first level menu elements is easy enough, but how do I recursively bind sub elements while creating a template for its elements in WPF?

Here's an example of a recursive XAML template using your ApplicationMenuItem class exactly as you defined it (except that I put it in a namespace called Wobbles). This is not finished, releasable code. But it demonstrates a recursive DataTemplate, and some bonus goodies like displaying the popup. You can add an IsEnabled property to your menu item class and implement it in the XAML with additional trigger that sets colors, and an additional condition in the multitrigger that drives SubmenuPopup.IsOpen. If you want to support horizontal separators, you could add a property bool ApplicationMenuItem.IsSeparator and give the template a trigger which replaces the grid content below with a horizontal line when that property is True.
RecursiveTemplate.xaml
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wbl="clr-namespace:Wobbles"
>
<DataTemplate DataType="{x:Type wbl:ApplicationMenuItem}">
<Grid
Name="RootGrid"
Background="BlanchedAlmond"
Height="Auto"
UseLayoutRounding="True"
SnapsToDevicePixels="True"
>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="24" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="24" />
</Grid.ColumnDefinitions>
<Image
Grid.Column="0"
Source="{Binding Image}"
/>
<Label
Grid.Column="1"
Content="{Binding Text}"
/>
<Border
Name="PopupGlyphBorder"
Grid.Column="2"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
Background="{Binding ElementName=RootGrid, Path=Background}"
>
<Path
Height="10"
Width="5"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Data="M 0,0 L 5,5 L 0,10 Z"
Fill="Black"
/>
</Border>
<Popup
Name="SubmenuPopup"
PlacementTarget="{Binding ElementName=PopupGlyphBorder}"
Placement="Right"
StaysOpen="True"
>
<Border
BorderBrush="DarkGoldenrod"
BorderThickness="1"
>
<ItemsControl
Name="SubmenuItems"
ItemsSource="{Binding Items}"
/>
</Border>
</Popup>
</Grid>
<DataTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="RootGrid" Property="Background" Value="Wheat" />
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsMouseOver" Value="True" />
<Condition SourceName="SubmenuItems" Property="HasItems" Value="True" />
</MultiTrigger.Conditions>
<Setter TargetName="SubmenuPopup" Property="IsOpen" Value="True" />
</MultiTrigger>
<Trigger SourceName="SubmenuItems" Property="HasItems" Value="False">
<Setter TargetName="PopupGlyphBorder" Property="Visibility" Value="Hidden" />
</Trigger>
</DataTemplate.Triggers>
</DataTemplate>
</ResourceDictionary>
MainWindow.xaml
<Window
x:Class="RecursiveTemplate.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wbl="clr-namespace:Wobbles"
Title="MainWindow"
Height="350"
Width="525"
>
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="RecursiveTemplate.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<Window.DataContext>
<wbl:TestViewModel />
</Window.DataContext>
<Grid>
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<ContentControl
Content="{Binding Menu}"
Width="100"
Height="24"
/>
</StackPanel>
</StackPanel>
</Grid>
</Window>
ViewModel.cs
namespace Wobbles
{
public class TestViewModel
{
public TestViewModel()
{
Menu = CreateMenu();
}
public Wobbles.ApplicationMenuItem Menu { get; protected set; }
protected Wobbles.ApplicationMenuItem CreateMenu()
{
var m = new Wobbles.ApplicationMenuItem("Menu");
var msub = new Wobbles.ApplicationMenuItem("Submenu");
msub.Items.Add(new Wobbles.ApplicationMenuItem("Sub Sub 1"));
msub.Items.Add(new Wobbles.ApplicationMenuItem("Sub Sub 2"));
// LOL
msub.Items.Add(msub);
m.Items.Add(msub);
m.Items.Add(new Wobbles.ApplicationMenuItem("Foo"));
m.Items.Add(new Wobbles.ApplicationMenuItem("Bar"));
m.Items.Add(new Wobbles.ApplicationMenuItem("Baz"));
return m;
}
}
}
Nits, Cavils, Kvetches, and a Brief Homily
Working with XAML, I'd suggest making a practice of using ObservableCollection<T> instead of List<T>. If the items in the collection change after the UI is constructed, ObservableCollection<T> will cause the UI to update appropriately. For the same reason, you'll want ApplicationMenuItem to implement INotifyPropertyChanged. I'd also prefer supporting an ICommand Command property as well as the Click event, and I'd further name the Click event Click in accordance with standard XAML practice.
"What Would XAML Do?" You'll pretty much never go wrong if you do your utmost to write code that could be mistaken for the standard library that shipped with the environment you're working in.

Related

for what do i need the Error porperty in IDataErrorInfo?

I just implement the IDataErrorInfo and I always wonder why public string Error => null; is neccesary for the interface.
I implement it as following:
//this works fine
public string this[string propertyName] => propertyName switch
{
//do my check
nameof(my property) => thing i want to validate ? error message : nothing
}
//what to do with this?
//0 references
public string Error => null;
Can anybody explain me please why do I need it, or why does it even exist?
Thank you very much :-)
The this[] indexer property lists the errors per property.
string Error is "An error message indicating what is wrong with this object".
So it is for any problem that is not directly related to a specific property.
It is used far less often, returning null is customary.
MS Spec
says it "Gets an error message indicating what is wrong with this object." I can't see anything in WPF or MVC 4.X that actually uses it automatically. But you could have a summary string that would have to be manually retrieved and displayed.
see below
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<Style x:Key="validationTooltip" TargetType="{x:Type TextBox}" >
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={x:Static RelativeSource.Self},
Path=(Validation.Errors)[0].ErrorContent}" />
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="{x:Type TextBox}" x:Key="asterisk" BasedOn="{StaticResource validationTooltip}" >
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<Grid ToolTip="{Binding ElementName=customAdorner,Path=AdornedElement.(Validation.Errors)[0].ErrorContent}" >
<TextBlock Foreground="Red" FontSize="16pt" HorizontalAlignment="Right" VerticalAlignment="Center"
Margin="0,0,10,0" FontWeight="Bold">*</TextBlock>
<Border BorderBrush="Red" BorderThickness="1" Margin="6"
ToolTip="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors)[0].ErrorContent}">
<AdornedElementPlaceholder Name="customAdorner" />
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid Name="grdData">
<TextBox Style="{StaticResource asterisk}" Width="550" Height="44" Name="txtValue" Text="{Binding Path=SomeProperty, ValidatesOnDataErrors=True}" />
<TextBox Text="{Binding Path=Error, Mode=OneWay}" HorizontalAlignment="Left" Height="44" Margin="122,257,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="550"/>
</Grid>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var item= new MyClass() { SomeProperty = "Testing" };
grdData.DataContext = item;
}
}
public class MyClass : IDataErrorInfo
{
public string SomeProperty { get; set; }
public string this[string columnName] => "Some Specific Error";
public string Error => "Something is wrong";
}

WPF mvvm routes

I'm in progress to create a desktop application in C# with WPF but i'm stuck with my menu.
I have a left panel menu to switch between functionality.
I created one viewmodel with 2 views which need to be display in my main window. if i'm clicking on Monitoring, the content view switch from Home to Monitoring but when i'm on Monitoring, If I try to back on Home, it's not working and I don't understand why.
Below my project folder
([abc] = Folder; - abc = file) :
[PROJECT]
[assets]
- Home.png
- Stats.png
[ViewModels]
- MainWindowViewModel.cs
[Views]
- Home.xaml
- Home.cs
- Stats.xaml
- Stats.cs
- MainWindow.xaml
- MainWindow.cs
- App.xaml
- App.cs
- App.config
Below important part of my code :
MainWindow.xaml
<Window.Resources>
<DataTemplate x:Key="HomeViewTemplate" DataType="{x:Type viewmodels:MainWindowViewModel}">
<views:Home DataContext="{Binding}" />
</DataTemplate>
<DataTemplate x:Key="StatsViewTemplate" DataType="{x:Type viewmodels:MainWindowViewModel}">
<views:Stats DataContext="{Binding}" />
</DataTemplate>
</Window.Resources>
<Grid Grid.Row="0" MouseLeftButtonDown="OpenHome">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="70" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<Image Source="/assets/home.png" Height="32" Width="32" />
<Label Content="Home" Grid.Column="1" Foreground="White" FontSize="12" VerticalAlignment="Center" Padding="0,5,5,5" />
</Grid>
<Grid Grid.Row="1" MouseLeftButtonDown="OpenStats">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="70" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<Image Source="/assets/stats.png" Height="32" Width="32" />
<Label Content="Monitoring" Grid.Column="1" Foreground="White" FontSize="12" VerticalAlignment="Center" Padding="0,5,5,5" />
</Grid>
<Grid Grid.Row="1">
<ContentControl Content="{Binding }">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource HomeViewTemplate}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Route}" Value="Home">
<Setter Property="ContentTemplate" Value="{StaticResource HomeViewTemplate}" />
</DataTrigger>
<DataTrigger Binding="{Binding Route}" Value="Stats">
<Setter Property="ContentTemplate" Value="{StaticResource StatsViewTemplate}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</Grid>
MainWindow.cs
private MainWindowViewModel MV = new MainWindowViewModel();
public MainWindow()
{
InitializeComponent();
}
private void OpenHome(object sender, RoutedEventArgs e)
{
MV.Route = "Home";
DataContext = MV;
}
private void OpenStats(object sender, RoutedEventArgs e)
{
MV.Route = "Stats";
DataContext = MV;
}
ViewModels>MainWindowViewModel.cs
public class MainWindowViewModel
{
public string Route
{
get;
set;
}
public MainWindowViewModel(){}
}
Send notifications about changes for Route property via INotifyPropertyChanged interface implementation:
public class MainWindowViewModel : INotifyPropertyChanged
{
public string _Route;
public string Route
{
get { return _Route; }
set { _Route = value; OnPropertyChanged("Route"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public MainWindowViewModel(){}
}
Such notification will activate DataTrigger which in turn will change ContentTemplate

Validating items in ItemsControl

I am developing a WPF application and in one window I used a wizard component from WPF toolkit. In this wizard I'm creating a new person. In second step I am using an enumeration as a source for possible contact types (for example Phone, Email...).
This is my wizard page in XAML:
<xctk:WizardPage x:Name="NewContactPage" PageType="Interior"
Title="Contacts" Style="{DynamicResource NewContactPage}"
CanCancel="True" CanFinish="False"
Loaded="NewContactPage_Loaded"
PreviousPage="{Binding ElementName=NewPersonPage}">
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Top">
<control:DataLoader x:Name="ctrNewContactLoader" />
<StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Top" Orientation="Vertical">
<ItemsControl ItemsSource="{Binding Path=Person.PersonContacts, Mode=TwoWay,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType=Window}}"
Name="icContacts">
<ItemsControl.ItemTemplate>
<ItemContainerTemplate>
<StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Top" Orientation="Vertical"
Margin="5" Background="WhiteSmoke">
<CheckBox IsChecked="{Binding Path=IsValid}"
Content="{Binding Path=ContactType.Description}"
Name="cbContactVisible"/>
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Top"
Visibility="{Binding ElementName=cbContactVisible, Path=IsChecked,
Converter={StaticResource BooleanToVisibilityConverter}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
</Grid.RowDefinitions>
<TextBox Grid.Row="0" Grid.Column="0"
HorizontalAlignment="Stretch" MaxLength="64"
Name="txtContactValue"
Text="{Binding Path=Contact,
ValidatesOnDataErrors=True,
ValidatesOnNotifyDataErrors=True,
ValidatesOnExceptions=True}" />
</Grid>
</StackPanel>
</ItemContainerTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>
The source of ItemsControl is a List of PersonContactModel class:
public class PersonContactModel : BaseObjectModel
{
public PersonContactModel()
{
this.Created = DateTime.Now;
this.Updated = DateTime.Now;
this.IsValid = true;
this.ContactType = new ContactTypeModel();
}
public string Contact { get; set; }
public ContactTypeModel ContactType { get; set; }
public DateTime Created { get; set; }
public int Id { get; set; }
public bool IsValid { get; set; }
public DateTime Updated { get; set; }
public override string this[string columnName]
{
get
{
string retVal = string.Empty;
switch (columnName)
{
case "Contact":
retVal = base.Concat(base.RequeiredField(this.Contact), base.MinLength(this.Contact, 5), base.MaxLength(this.Contact, 62));
break;
}
return retVal;
}
}
}
the base class implement a IDataErrorInfo interface with validation info about Contact property.
The desired behavior is that if the checkbox is checked, it is visible grid with a field for entering a contact, otherwise not. Button next step should be seen only when selected contact types are valid. This functionality is trying to accomplish the following styles in app.xaml:
<Style TargetType="xctk:WizardPage" x:Key="NewContactPage">
<Setter Property="NextButtonVisibility" Value="Hidden" />
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Path=(Validation.HasError), ElementName=txtContactValue}" Value="False" />
</MultiDataTrigger.Conditions>
<Setter Property="NextButtonVisibility" Value="Visible" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
Unfortunately, the button for the next step is invisible, even if it asks all kinds of contact for the new person and will fulfill all the conditions for a valid entry.
What's wrong? Where is an error?
You are trying to achieve what you want in a not very good way. Error in this particular code is because you reference element "txtContactValue" from your style trigger, and style has no idea at all what this element is. By the way, if you look at output window when debugging your code, I bet you will see this error there.
Now, even if you will try to reference "txtContactValue" without style, like this:
NextButtonVisibility="{Binding ElementName=txtContactValue, Path=(Validation.HasError), Converter={StaticResource BooleanToVisibilitConverter}}"
It won't work, because txtContactValue is in different scope. BUT you should not do this in a first place! You have a model for your data, and that is model which controls if data is valid or not. Just add some property to your model which indicates if data which you create on this wizard page is valid (like PersonContact.IsValid) and you can proceed to the next page, and bind to this property.

Disable a subview depending viewmodel bool property (MVVM architecture)

I have a view that contains multiple subview (UserControl).
<StackPanel>
<local:SubViewGPS/>
<local:SubViewText/>
</StackPanel>
This view binds to a ViewModel and I would like to load or not load subview depending on bool properties of my ViewModel
private bool isGPSCompatible;
public bool IsGPSCompatible {
get { return isGPSCompatible; }
set {
if (isGPSCompatible != value) {
isGPSCompatible = value;
NotifyPropertyChanged();
}
}
}
private bool isTextCompatible;
public bool IsTextCompatible {
get { return isTextCompatible; }
set {
if (isTextCompatible != value) {
isTextCompatible = value;
NotifyPropertyChanged();
}
}
}
I actually don't want to "Disable" or change the "Visibility" but really avoid to load the component if the property is false. According this post: Different views / data template based on member variable the combination of DataTemplate and DataTrigger seems to be a way to reach the goal but I was wondering if it exist something simpler. Thanks for your help
I finally used this solution:
<UserControl x:Class="RLinkClient.LocationView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:client"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
<DataTemplate x:Key="GPSLocationViewTemplate">
<local:GPSControl/>
</DataTemplate>
<DataTemplate x:Key="NoGPSViewTemplate">
<TextBlock Text="GPS Disabled" TextAlignment="Center" VerticalAlignment="Center" FontStyle="Italic" Opacity="0.1" FontWeight="Bold" FontSize="14"/>
</DataTemplate>
</UserControl.Resources>
<Grid>
<ContentControl>
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource NoGPSViewTemplate}" />
<Style.Triggers>
<DataTrigger Binding="{Binding GPSCapable}" Value="True">
<Setter Property="ContentTemplate" Value="{StaticResource GPSLocationViewTemplate}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</Grid>
</UserControl>
but Frank answer is totally acceptable if I could change the ViewModel structure.
As you're using MVVM, you could create separate sub-viewmodels for the different views and add them to some ObservableCollection, depending on their properties:
<!-- This would replace your StackPanel -->
<ItemsControl ItemsSource="{Binding Capabilities}">
<ItemsControl.Resources>
<DataTemplate DataType="localVm:GpsViewModel">
<local:SubViewGPS />
</DataTemplate>
<DataTemplate DataType="localVm:TextViewModel">
<local:SubViewText />
</DataTemplate>
<!-- ... -->
</ItemsControl.Resources>
</ItemsControl>
You would have an ObservableCollection in your view model, like this:
public ObservableCollection<ICapability> Capabilities { get; private set; }
and add sub-viewmodels implementing ICapability to this as needed.
Based on the condition, I suggest adding the view object in code instead of XAML. Only instantiate when needed to avoid unnecessary initialization routines. That is, don't instantiate the view before checking the condition:
if (IsGPSCompatible )
myStackPanel.Children.Add((new SubViewGPSView()));
if (IsTextCompatible )
myStackPanel.Children.Add((new SubViewText()));

Bind to index in ItemsControl from DataTemplate

I have a simple class which creates a list of objects:
namespace TestWPF2
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public ObservableCollection<TestObj> SomeList { get; set; }
public string WindowTitle { get; set; }
public MainWindow()
{
this.DataContext = this;
WindowTitle = "People";
SomeList = new ObservableCollection<TestObj>();
SomeList.Add(new TestObj("Bob"));
SomeList.Add(new TestObj("Jane"));
SomeList.Add(new TestObj("Mike"));
InitializeComponent();
}
}
}
The TestObj class is as follows:
namespace TestWPF2
{
public class TestObj
{
public string FirstName { get; set; }
public TestObj(string firstName)
{
this.FirstName = firstName;
}
}
}
I then attempt to display each item in the list with the following:
<Window x:Class="TestWPF2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestWPF2"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate DataType="{x:Type local:TestObj}">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Pos: "/>
<TextBlock x:Name="posText"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Name: "/>
<TextBlock Text="{Binding FirstName}"/>
</StackPanel>
</StackPanel>
<!-- THESE TRIGGERS DONT WORK -->
<DataTemplate.Triggers>
<Trigger Property="ItemsControl.AlternationIndex" Value="0">
<Setter Property="Text" Value="First" TargetName="posText"/>
</Trigger>
<Trigger Property="ItemsControl.AlternationIndex" Value="1">
<Setter Property="Text" Value="Second" TargetName="posText"/>
</Trigger>
<Trigger Property="ItemsControl.AlternationIndex" Value="3">
<Setter Property="Text" Value="Third" TargetName="posText"/>
</Trigger>
</DataTemplate.Triggers>
</DataTemplate>
</Window.Resources>
<StackPanel>
<TextBlock Text="{Binding Title}"/>
<ItemsControl HorizontalAlignment="Stretch"
ItemsSource="{Binding SomeList}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</StackPanel>
</Window>
What I would like to display is something like:
Pos: First
Name: Bob
Pos: Second
Name: Jane
etc.
It's pretty straight-forward to bind to the FirstName property of each item in the list, but I would also like bind to the index in the list. I know I can do this from inside an ItemsControl using ItemsControl.AlternationIndex, but how do I link to the AlternationIndex from within in DataTemplate?
You need to understand that your context is TestObj and with your trigger, you are basicly checking the value of a property named ItemsControl which should have a property AlternationIndex.
You should changed the context of your triggers with a RelativeSource binding to the control that hold your object, named the ContentPresenter:
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContentPresenter}}, Path=(ItemsControl.AlternationIndex)" Value="0">
<Setter Property="Text" Value="First" TargetName="posText"/>
</Trigger>
<!--- here be the other triggers -->
</DataTemplate.Triggers>
Hope this helps..

Categories