How to display row numbers in a ListView? - wpf

The obvious solution would be to have a row number property on a ModelView element, but the drawback is that you have to re-generate those when you add records or change sort order.
Is there an elegant solution?

I think you have the elegant solution, but this works.
XAML:
<ListView Name="listviewNames">
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn
Header="Number"
DisplayMemberBinding="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ListViewItem}},
Converter={StaticResource IndexConverter}}" />
<GridViewColumn
Header="Name"
DisplayMemberBinding="{Binding Path=Name}" />
</GridView.Columns>
</GridView>
</ListView.View>
</ListView>
ValueConverter:
public class IndexConverter : IValueConverter
{
public object Convert(object value, Type TargetType, object parameter, CultureInfo culture)
{
ListViewItem item = (ListViewItem) value;
ListView listView = ItemsControl.ItemsControlFromItemContainer(item) as ListView;
int index = listView.ItemContainerGenerator.IndexFromContainer(item);
return index.ToString();
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}

If you have a dynamic list where items are added, deleted or moved, you can still use this very nice solution and simply let the currentview of your listview refresh itself after the changements in your source list are done.
This code sample removes the current item directly in the data source list "mySourceList" (which is in my case an ObservableCollection) and finally updates the line numbers to correct values .
ICollectionView cv = CollectionViewSource.GetDefaultView(listviewNames.ItemsSource);
if (listviewNames.Items.CurrentItem != null)
{
mySourceList.RemoveAt(cv.CurrentPosition);
cv.Refresh();
}

First you need to set the AlternationCount to items count+1, for instance:
<ListView AlternationCount="1000" .... />
Then AlternationIndex will show the real index, even during the scrolling:
<GridViewColumn
Header="#" Width="30"
DisplayMemberBinding="{Binding (ItemsControl.AlternationIndex),
RelativeSource={RelativeSource AncestorType=ListViewItem}}" />

This will work like a charm,
I don't know about performance,
Still we can give it a try
Create a Multi Value Converter
public class NumberingConvertor : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (values != null && values.Any() && values[0] != null && values[1] != null)
{
//return (char)(((List<object>)values[1]).IndexOf(values[0]) + 97);
return ((List<object>)values[1]).IndexOf(values[0]) + 1;
}
return "0";
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
}
}
and
your Xaml like this
<ItemsControl ItemsSource="{Binding ListObjType}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label>
<MultiBinding Converter="{StaticResource NumberingConvertor}">
<Binding Path="" />
<Binding Path="ItemsSource"
RelativeSource="{RelativeSource AncestorType=ItemsControl}" />
</MultiBinding>
</Label>
<TextBlock Text="{Binding }" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Idea is to send Object and list both to the converter and let converter decide the number. You can modify converter to display ordered list.

Here is another way, including code comments that will help you understand how it works.
public class Person
{
private string name;
private int age;
//Public Properties ....
}
public partial class MainWindow : Window
{
List<Person> personList;
public MainWindow()
{
InitializeComponent();
personList= new List<Person>();
personList.Add(new Person() { Name= "Adam", Agen= 25});
personList.Add(new Person() { Name= "Peter", Agen= 20});
lstvwPerson.ItemsSource = personList;
//After updates to the list use lstvwPerson.Items.Refresh();
}
}
The XML
<GridViewColumn Header="Number" Width="50"
DisplayMemberBinding="{
Binding RelativeSource= {RelativeSource Mode=FindAncestor, AncestorType={x:Type ListViewItem}},
DELETE Path=Content, DELETE
Converter={StaticResource IndexConverter},
ConverterParameter=1
}"/>
RelativeSource is used in particular binding cases when we try to bind a property of a given object to another property of the object itself [1].
Using Mode=FindAncestor we can traverse the hierarchy layers and get a specified element, for example the ListViewItem (we could even grab the GridViewColumn). If you have two ListViewItem elements you can specify which you want with "AncestorLevel = x".
Path: Here I simply take the content of the ListViewItem (which is my object "Person").
Converter Since I want to display row numbers in my Number column and not the object Person I need to create a Converter class which can somehow transform my Person object to a corresponding number row. But its not possible, I just wanted to show that the Path goes to the converter. Deleting the Path will send the ListViewItem to the Converter.
ConverterParameter Specify a parameter you want to pass to the IValueConverter class. Here you can send the state if you want the row number to start at 0,1,100 or whatever.
public class IndexConverter : IValueConverter
{
public object Convert(object value, Type TargetType, object parameter, System.Globalization.CultureInfo culture)
{
//Get the ListViewItem from Value remember we deleted Path, so the value is an object of ListViewItem and not Person
ListViewItem lvi = (ListViewItem)value;
//Get lvi's container (listview)
var listView = ItemsControl.ItemsControlFromItemContainer(lvi) as ListView;
//Find out the position for the Person obj in the ListView
//we can get the Person object from lvi.Content
// Of course you can do as in the accepted answer instead!
// I just think this is easier to understand for a beginner.
int index = listView.Items.IndexOf(lvi.Content);
//Convert your XML parameter value of 1 to an int.
int startingIndex = System.Convert.ToInt32(parameter);
return index + startingIndex;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}

I found solution that will work even in case when you need to move your elements inside the collection. So actually what we need to do for it is notify dummy property "ListNumbersNotify" every time our collection is changed and bind everything with that tricky MultiBinding converter.
XAML:
<Window ...
x:Name="This">
...
<ListView Name="ListViewCurrentModules">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label>
<MultiBinding Converter="{helpers:NumberingConvertor}">
<Binding Path="" />
<Binding ElementName="ListViewCurrentModules" />
<Binding Path="ListNumbersNotify" ElementName="This" />
</MultiBinding>
</Label>
<Border>
...
</Border>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Converter:
public abstract class MultiConvertorBase<T> : MarkupExtension, IMultiValueConverter
where T : class, new()
{
public abstract object Convert(object[] values, Type targetType, object parameter, CultureInfo culture);
public virtual object[] ConvertBack(object value, Type[] targetType, object parameter, CultureInfo culture)
{
return null;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
if (_converter == null)
_converter = new T();
return _converter;
}
private static T _converter = null;
}
public class NumberingConvertor : MultiConvertorBase<NumberingConvertor>
{
public override object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return ((ListView)values[1]).Items.IndexOf(values[0]) + 1;
}
}
Code behind:
public partial class AddModulesWindow: Window, INotifyPropertyChanged
{
...
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string prop)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
}
public object ListNumbersNotify { get; }
public AddModulesWindow(ICore core)
{
InitializeComponent();
this.core = core;
CurrentModuleInfos = new ObservableCollection<ModuleInfo>(core.Modules.Select(m => m?.ModuleInfo));
CurrentModuleInfos.CollectionChanged += CurrentModuleTypes_CollectionChanged;
ListViewCurrentModules.ItemsSource = CurrentModuleInfos;
}
private void CurrentModuleTypes_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
OnPropertyChanged("ListNumbersNotify");
}

It's the addition to answer of amaca for problems found by Allon Guralnek and VahidN. Scrolling problem is solved with setting ListView.ItemsPanel to StackPanel in XAML:
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
This replacement of default VirtualizingStackPanel with simple StackPanel disables automatic regeneration of internal collection of ListViewItem. So indices would not chaotically change when scrolling. But this replacement can decrease perfomance on large collections. Also, dynamic numeration changes can be achieved with call CollectionViewSource.GetDefaultView(ListView.ItemsSource).Refresh() when ItemsSource collection changed. Just like with ListView filtering. When I tried to add handler with this call on event INotifyCollectionChanged.CollectionChanged my ListView output was duplicating last added row (but with correct numeration). Fixed this by placing refresh call after every collection change in code. Bad solution, but it works perfect for me.

amaca answer is great for static lists. For dynamic:
We should use MultiBinding, second binding is for changing collection;
After deleting ItemsControl not contains deleted object, but ItemContainerGenerator contains.
Converter for dynamic lists (I use it for TabControl TabItem's):
public class TabIndexMultiConverter : MultiConverterBase
{
public override object Convert(object[] value, Type targetType, object parameter, CultureInfo culture)
{
TabItem tabItem = value.First() as TabItem;
ItemsControl ic = ItemsControl.ItemsControlFromItemContainer(tabItem);
object context = tabItem?.DataContext;
int idx = ic == null || context == null // if all objects deleted
? -1
: ic.Items.IndexOf(context) + 1;
return idx.ToString(); // ToString necessary
}
}

By following best answer solution I found an issue when indexes still not updated after removing/replacing items inside list view. To solve that there is one not so clear hint (I propose to use it in small collections): after executing item removing/replacing you should invoke ObservableCollection(INotifyCollectionChanged).CollectionChanged event with Reset action. This is possible to make with extending existing ObservableCollection, which is ItemsSource or use reflection when this is not possible.
Ex.
public class ResetableObservableCollection<T> : ObservableCollection<T>
{
public void NotifyReset()
{
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
private void ItemsRearranged()
{
Items.NotifyReset();
}

Here's my little converter which works great as of WPF in 2017 with .NET 4.7.2, including with the VirtualizingStackPanel fully enabled:
[ValueConversion(typeof(IList), typeof(int))]
public sealed class ItemIndexConverter : FrameworkContentElement, IValueConverter
{
public Object Convert(Object data_item, Type t, Object p, CultureInfo _) =>
((IList)DataContext).IndexOf(data_item);
public Object ConvertBack(Object o, Type t, Object p, CultureInfo _) =>
throw new NotImplementedException();
};
Add an instance of this IValueConverter to the Resources of the GridViewColumn.CellTemplate, or elsewhere. Or, instantiate it in-situ on the Binding of the bound element, like I show here. In any case, you need to create an instance of the ItemIndexConverter and don't forget to bind the whole source collection to it. Here I'm pulling a reference to the source collection out of the ItemsSource property of the ListView--but this entails some unrelated hassles over accessing the XAML root, so if you have a better and easier way to refer to the source collection, you should do so.
As for accessing a property on the XAML root, the ListView root in XAML is given the name w_root, and the XAML markup extension {x:Reference ...} is used to access the XAML root element. I don't think "ElementName" binding will work here since the reference occurs in a template context.
<ListView x:Class="myApp.myListView"
x:Name="w_root"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:myApp"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Recycling">
<ListView.View>
<GridView>
<GridViewColumn Width="50">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock>
<TextBlock.Text>
<Binding>
<Binding.Converter>
<local:ItemIndexConverter DataContext="{Binding
Source={x:Reference w_root},
Path=(ItemsControl.ItemsSource)}" />
</Binding.Converter>
</Binding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
That's it! It seems to work pretty quickly with a large number of rows, and again, you can see that the reported indices are correct when arbitrarily scrolling around, and that VirtualizingStackPanel.IsVirtualizing is indeed set to True.

Related

WPF Visibility Converter not Firing

I can't seem to get my visibilty converter to work. I think the issue is that i'm setting the relevant property in the constructor, so it's not picking it up down the line. Code is below, any suggestions as to how I can fix this?
MainWindowViewModel: (this is the main page; clicking a button will open a second window)
var newWindow = new SecondaryWindow
{
Title = title,
DataContext = new SecondaryWindowViewModel{MyData = data, ShowAdditionalColumns = false}
};
newWindow.Show();
Secondary Window:
Here's the relevant XAML:
<Window.Resources>
<myApp:DataGridVisibilityConverter x:Key="gridVisibilityConverter" />
</Window.Resources>
<DataGrid ItemsSource="{Binding Path=MyData}" AutoGenerateColumns="False" >
<DataGrid.Columns>
<DataGridCheckBoxColumn Header="Print" Binding="{Binding Path=IsSelected}"/>
<DataGridTextColumn Header="FirstName" Binding="{Binding Path=FirstName}" IsReadOnly="True"/>
<DataGridTextColumn Header="LastName" Binding="{Binding Path=LastName}" IsReadOnly="True"/>
<DataGridTextColumn Header="Lines" Binding="{Binding Path=TotalLines}" IsReadOnly="True" Visibility="{Binding Path=ShowAdditionalColumns, Converter={StaticResource gridVisibilityConverter}}"/>
And the Secondary ViewModel code:
private bool showAdditionalColumns;
public bool ShowAdditionalColumns
{
get { return showAdditionalColumns; }
set
{
showAdditionalColumns= value;
NotifyPropertyChanged(() => ShowAdditionalColumns);
}
}
Here's the converter; pretty standard stuff here. I've put a breakpoint in the convert method; and it's never hit:
[ValueConversion(typeof(bool), typeof(Visibility))]
public class DataGridVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var booleanValue = (bool)value;
return booleanValue ? Visibility.Visible : Visibility.Hidden;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
The DataGrid's columns are not part of the visual/logical tree, so they don't get the DataContext inheritance. If you debug the program, you'll see the error in the output window:
System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or
FrameworkContentElement for target element.
There are a few options here.
Use this solution: http://blogs.msdn.com/b/jaimer/archive/2008/11/22/forwarding-the-datagrid-s-datacontext-to-its-columns.aspx
Reference some control that does have an appropriate data context. It's easiest to use the root container (e.g. user control).
First initialize a resource named "This" in code (must be called before InitializeComponent):
public MyUserControl()
{
Resources["This"] = this;
InitializeComponent();
}
Then reference it in XAML:
<DataGridTextColumn Visibility="{Binding Source={StaticResource This},
Path=DataContext.ShowAdditionalColumns,
Converter={StaticResource gridVisibilityConverter}}" />
(As a side note, the binding you provide for the column's data works because it is copied to the row in the grid, where the data context is set by the DataGrid to the data item.)

WPF - Button IsEnabled Binding Converter

I have an ObservableCollection<MyEntity> and MyEntity has a IsChecked property with a PropertyChanged event.
I have a Button and I would like to change IsEnabled property to true when at least one of MyEntity of the MyObservableCollection is checked.
I created a converter which takes the ObservableCollection and return true when a MyEntity is checked at least.
But the return "null" is returned.
What is wrong ? Thank you for your help.
XAML
<Window.Resources>
<CollectionViewSource x:Key="MyObservableCollection"/>
<src:MyConverter x:Key="MyConverter"/>
</Window.Resources>
<Button IsEnabled="{Binding Converter={StaticResource MyConverter}, Source={StaticResource MyObservableCollection}}"/>
C# Converter
class MyConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (null == value)
return "null";
ReadOnlyObservableCollection<object> items = (ReadOnlyObservableCollection<object>)value;
List<MyEntity> myEntities = (from i in items select (MyEntity)i).ToList();
foreach (MyEntity entity in myEntities)
{
if (entity.IsChecked)
{
return true;
}
}
return false;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new System.NotImplementedException();
}
}
I think your Binding is wrong. The Converter want's the underlying collection not the CollectionView. And set the CollectionViewSource.Source after InitializeComponent(), the Binding will be refreshed.
<Button IsEnabled="{Binding Path=SourceCollection,
Converter={StaticResource MyConverter},
Source={StaticResource MyObservableCollection}}" />
Since StaticResources are resolved at the time of intializing itself i.e. at the time of InitializeComponent() but till that time your collection is yet not intialized that's why null value is passed to the converter.
So, better choice would be to move that property in your code behind and bind to that property since binding will be resloved after InitializeComponent(). Create property in your code-behind-
public CollectionViewSource MyObservableCollection { get; set; }
and bind to your button -
<Button IsEnabled="{Binding MyObservableCollection, RelativeSource=
{RelativeSource AncestorType=Window}, Converter={StaticResource MyConverter}}"/>

How Do You Bind To An Item In A Dictionary?

I'm using the RadCarousel control from Telerik in a WPF application with C#. RadCarousel is similar to a GridView in that it binds to a collection and show's each item in the collection (so my question isn't specific to Telerik or RadCarousel).
I'm using a DataTemplate to specify how each record should get displayed.
If I do this it work's fine:
<DataTemplate>
<TextBlock Text="{Binding Path=oMySubObject.TheAmount}" />
</DataTemplate>
But what if I need to point to an item in a dictionary?
<DataTemplate>
<TextBlock Text="{Binding Path=myDictionaryOfSubObjects[TheCurrentIndex].TheAmount}" />
</DataTemplate>
This I can't get working and don't know how. Basically...I need the index to be specified at runtime and when it gets updated, the binding updates.
Any advice?
<Window.Resources>
<NAMESPACEWHERECONVERTERRESIDES:DictionaryConverter x:Key="cDictionaryConverter"/>
</WindowResources>
<TextBlock Text="{Binding Path=myDictionaryOfSubObjects, Converter={StaticResource cDictionaryConverter}}"/>
// Something like this:
[ValueConversion(typeof(Dictionary), typeof(string))]
public class DictionaryConverter: IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Dictionary<type, type> dict = value as Dictionary<type, type>;
return dict[CurrentIndex].TheAmount;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return 0;
}
}
You can only use constant values in the indexer, TheCurrentIndex will not be resolved. There are a few workarounds, like passing the dictionary and the index to a multi value converter to resolve the item there.
With anything complicated, you should probably make a getter that does the job, and bind to that.
What is "TheCurrentIndex"?
Window1.xaml
<Window x:Class="QuizBee.Host.Window1"
x:Name="Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ListView ItemsSource="{Binding ElementName=Window1, Path=myDictionary}" />
</Window>
Window1.xaml.cs
public partial class Window1:Window
{
// the property must be public, and it must have a getter & setter
public Dictionary<string, myClass> myDictionary { get; set; }
public Window1()
{
// define the dictionary items in the constructor
// do the defining BEFORE the InitializeComponent();
myDictionary = new Dictionary<string, myClass>()
{
{"item 1", new myClass(1)},
{"item 2", new myClass(2)},
{"item 3", new myClass(3)},
{"item 4", new myClass(4)},
{"item 5", new myClass(5)},
};
InitializeComponent();
}
}

using a list<> as converterparameter in a gridview

I want to be able to convert an integer to a string using a lookup table (list) in my code.
Both the integer and the list is passed on from a COM and bound to observables in my code.
<ListView Name="IdList" MaxWidth="310" Height="190" Margin="5" SelectionMode="Single"
ItemsSource="{Binding Path=TypeItem.Ids}">'
<GridViewColumn Width="Auto"
DisplayMemberBinding="{Binding Path=ShipType,
Converter={StaticResource ShipTypeConverter},
ConverterParameter={x:Static vm:ConfigStaticItem.alternatives_shiptype}}"/>`
I have tried using multibinding, but only got DependencyProperty.UnsetValue for the list value
<GridViewColumn Width="Auto">
<GridViewColumnHeader Content="ShipType"/>
<GridViewColumn.DisplayMemberBinding >
<MultiBinding Converter="{StaticResource ShipTypeMultiConverter}">
<Binding Path="ShipType"/>
<Binding Path="ConfigStaticItem.alternatives_shiptype"/>
</MultiBinding>
</GridViewColumn.DisplayMemberBinding>
</GridViewColumn>
[ValueConversion(typeof(byte), typeof(string))]
public class ShipTypeMultiConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
try
{
byte state = (byte)values[0];
List<StaticId> list = (List<StaticId>)values[1];
return state.ToString();
}
catch
{
return "";
}
}
Also tried using templates, but I think I got lost in the XAML :).
vm is a reference to my ViewModel
the TypeItem.Ids is defined as List, where Static is an observable class containing amongst other stuff the ShipType value
Does anybody have any suggestions how to solve this?enter code here
And the solution was:
Adding a source to my resource dictionary
<CollectionViewSource
x:Key="ShipTypeSource"
Source="{Binding ConfigStaticItem.alternatives_shiptype}"
/>
Using a standard binding with the static resource as converter parameter
<GridViewColumn
Width="Auto"
Header="Ship Type"
DisplayMemberBinding="{Binding Path=ShipType, Converter={StaticResource ShipTypeConverter},
ConverterParameter={StaticResource ShipTypeSource}}"
/>
And using a singlevalue converter
[ValueConversion(typeof(byte), typeof(string))]
public class ShipTypeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
try
{
Alternatives ShipTypes = (Alternatives)((parameter as CollectionViewSource).Source);
byte Type = (byte)value;
foreach (var key in ShipTypes)
{
if ((uint)Type == key.Key)
{
return key.Value;
}
}
return ""; // Ship type is undefined
}
catch
{
return "";
}
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
return null;
}
}

WPF ListBox Binding ItemsSource

In a related question I asked about binding to a particular element of an array indexed by another property. The answer provided worked really well for the sample code example provided.
Where I'm running into trouble is that I specify an ItemSource for a ListBox and I get a DependencyProperty.UnsetValue in my converter when I step through it. No doubt this is a problem with my understanding of Binding.
My ListBox looks like so:
<ListBox ItemsSource="{Binding Path=MyList}">
<ListBox.Resources>
<local:FoodIndexConverter x:Key="indexConverter" />
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource indexConverter}">
<Binding Path="MyIndex" />
<Binding Path="Fields" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
and the code behind looks as so:
public MainWindow()
{
InitializeComponent();
MyList.Add(new SomeData() { Fields = new object[] {"liver", "onions", "cake" } } );
MyList.Add(new SomeData() { Fields = new object[] {"liver", "onions", "candy" } } );
MyList.Add(new SomeData() { Fields = new object[] {"liver", "onions", "pie" } } );
DataContext = this;
}
MyList is a List.
MyIndex is an int.
The converter code is
public class FoodIndexConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (values == null || values.Length != 2)
return null;
int? idx = values[0] as int?;
object[] food = values[1] as object[];
if (!idx.HasValue || food == null)
return null;
return food[idx.Value];
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
When I step through the debugger in the converter code the MyIndex (value[0]) is DependencyProperty.UnsetValue - the object array is as I'd expect.
I'm assuming it is a Binding issue with:
in that it doesn't know what MyIndex is.
If MyIndex were a property of the SomeData class, it works as I would expect but it's not, it's a property of the class MainWindow, just like MyList.
How do I specify that I'm interested in the MyIndex property that is part of my DataContext and not the MyData List?
It is looking for the MyIndex property on the ListBoxItem (in this case, a SomeData class) and that property doesn't exist.
Set the ElementName in your binding to the window name to get it to look for the property elsewhere.
<Binding ElementName=RootWindow, Path=DataContext.MyIndex />

Resources