setting delay to update target in Binding in UWP - c#

I bind SelectedValue of my ListView and this binding used converter.
I want that ConvertBack method be executed after a delay, it seems to be easy in WPF but not in UWP.
How can I do this?

If you just want to delay in your ConvertBack, then you can use a task and call Task.Result to return your value. For example like this:
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
var val = value.ToString();
var task = Task.Run(async () =>
{
await Task.Delay(1000);
return val;
});
return task.Result;
}
For the scenario I used this code, I use two way binding to bind the SelectedIndex of a ListView to the Text of a TextBox, here is the demo:
<Page.DataContext>
<local:BlankPage6ViewModel x:Name="ViewModel" />
</Page.DataContext>
<Page.Resources>
<local:IndexToItemConverter x:Key="cvt" />
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="4*" />
</Grid.RowDefinitions>
<TextBox x:Name="tb" Text="5" Height="50" />
<ListView ItemsSource="{Binding MyItems}"
SelectionMode="Single"
SelectedIndex="{Binding ElementName=tb, Path=Text, Mode=TwoWay, Converter={StaticResource cvt}}" Grid.Row="1">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding ID}" />
<TextBlock Text="{Binding Name}" Margin="5,0" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
ViewModel and Model:
public class BlankPage6ViewModel
{
public BlankPage6ViewModel()
{
MyItems = new ObservableCollection<IDModel>();
for (int i = 0; i < 50; i++)
{
MyItems.Add(new IDModel { ID = i, Name = "Name " + i });
}
}
public ObservableCollection<IDModel> MyItems { get; set; }
}
public class IDModel
{
public int ID { get; set; }
public string Name { get; set; }
}
The whole converter is simple like this:
public class IndexToItemConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
Debug.WriteLine("CONVERT");
return Int32.Parse(value.ToString());
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
var val = value.ToString();
var task = Task.Run(async () =>
{
await Task.Delay(1000);
return val;
});
return task.Result;
}
}
Rendering image of this demo:
There is a very good blog for this scenario, you can have a look: Async Programming : Patterns for Asynchronous MVVM Applications: Data Binding.

Related

Binding and changing a listView dynamically using MVVM

I have an object consisting of a string and a string of arrays. I'm binding the string to a comboBox but what I want to also do is bind the array of that object to a listview and have it change dynamically depending on combox box value. The values in the array aren't populating, only the dataType of the array. I'm not married to using a listview but I thought it would be easiest.
Model -
namespace DataBinding_WPF.Model
{
public class ExampleModel { }
public class Example : INotifyPropertyChanged
{
private string _name;
private string[] _ids;
public string Name
{
get => _name;
set
{
if (_name != value)
{
_name = value;
RaisePropertyChanged("Name");
}
}
}
public string[] IDs
{
get => _ids;
set
{
if (_ids != value)
{
_ids = value;
RaisePropertyChanged("IDs");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new
PropertyChangedEventArgs(property));
}
}
}
ViewModel -
namespace DataBinding_WPF.ViewModel
{
public class ExampleViewModel
{
public ObservableCollection<Example> Examples
{
get;
set;
}
public void LoadExample()
{
ObservableCollection<Example> examples = new ObservableCollection<Example>();
examples.Add(new Example { Name = "Mark", IDs = new string[] { "123", "456" }});
examples.Add(new Example { Name = "Sally", IDs = new string[] { "789","101112" }});
Examples = examples;
}
}
}
XAML -
<Grid>
<StackPanel HorizontalAlignment = "Left" >
<ComboBox HorizontalAlignment="Left"
VerticalAlignment="Top"
Width="120"
ItemsSource="{Binding Path=Examples}"
SelectedItem="{Binding Path=Name, Mode=TwoWay}"
DisplayMemberPath="Name"/>
<ListView x:Name="myListView"
ItemsSource="{Binding Path=Examples}"
SelectedValue="{Binding Path=IDs}"
Height="200" Margin="0,50,0,0"
Width="Auto"
VerticalAlignment="Top"
Background="AliceBlue">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" >
<CheckBox
Name="myCheckBox"
IsChecked="{Binding IsSelected,
RelativeSource={RelativeSource AncestorType=ListViewItem}}"
Margin="5, 0"/>
<TextBlock Text= "{Binding Path=IDs}" FontWeight="Bold" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</Grid>
If you want to show in ListBox the Ids of selected item in comboBox.
You need to add SelectedItem property to your VM which also must implement INotifyPropertyChanged interface.
namespace DataBinding_WPF.ViewModel
{
public class ExampleViewModel : INotifyPropertyChanged
{
public ObservableCollection<Example> Examples
{
get;
set;
}
// SelectedItem in the ComboBox
// SelectedItem.Ids will be ItemsSource for the ListBox
private Example _selectedItem;
public Example SelectedItem
{
get => _selectedItem;
set {
_selectedItem = value;
OnPropertyChanged(nameof(SelectedItem));
}
}
// SelectedId in ListView
private string _selectedId;
public string SelectedId
{
get => _selectedId;
set {
_selectedId= value;
OnPropertyChanged(nameof(SelectedId));
}
}
public void LoadExample()
{
ObservableCollection<Example> examples = new ObservableCollection<Example>();
examples.Add(new Example { Name = "Mark", IDs = new string[] { "123", "456" }});
examples.Add(new Example { Name = "Sally", IDs = new string[] { "789","101112" }});
Examples = examples;
}
}
}
XAML
<Grid>
<StackPanel HorizontalAlignment = "Left" >
<ComboBox HorizontalAlignment="Left"
VerticalAlignment="Top"
Width="120"
ItemsSource="{Binding Path=Examples}"
SelectedItem="{Binding SelectedItem}"
DisplayMemberPath="Name"/>
<ListView x:Name="myListView"
ItemsSource="{Binding SelectedItem.Ids}"
SelectedItem="{Binding SelectedId}"
Height="200" Margin="0,50,0,0"
Width="Auto"
VerticalAlignment="Top"
Background="AliceBlue">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" >
<CheckBox
Name="myCheckBox"
IsChecked="{Binding IsSelected,
RelativeSource={RelativeSource AncestorType=ListViewItem}}"
Margin="5, 0"/>
<TextBlock Text= "{Binding}" FontWeight="Bold" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</Grid>
There could several solutions to your case but most common and proprietary way is to use ValueConverter.
For this example i'll assume you have grid in the Window control. You need to add static resource:
<Window.Resources>
<local:ArrayValueConverter x:Key="arrayConverter"/>
</Window.Resources>
Then in your ListView's DataTemplate add Converter:
<TextBlock Text= "{Binding Path=IDs, Converter={StaticResource arrayConverter}}" FontWeight="Bold" />
ValueConverter himself:
public class ArrayValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is string[] arr) {
return string.Join(',', arr);
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
}

Button visibility after enter value (XAML, C#, IValueConverter)

I have xaml file where I put 2 TextBox where I can put login and password. After then Button "Login" should be visible. Here is my code:
TreeView.xaml file:
<UserControl x:Class="LayoutMVVM.Views.TreeView"
xmlns:local="clr-namespace:LayoutMVVM.Views"
xmlns:Treemodels="clr-namespace:LayoutMVVM.ViewModels" .../>
<UserControl.Resources>
<Treemodels:VBConverter x:Key="VBConverter" />
</UserControl.Resources>
<Grid>
....
<TextBox Grid.Row="2" Grid.Column="2" Text="{Binding Login, UpdateSourceTrigger=PropertyChanged}" Background="AliceBlue" />
<TextBox Grid.Row="1" Grid.Column="2" Text="{Binding Password, UpdateSourceTrigger=PropertyChanged}" Background="AliceBlue" />
<Button Grid.Row="3" Grid.Column="3" Visibility="{Binding IsVerifyTrue, Converter={StaticResource VBConverter}}" Content="Login" />
</Grid>
TreeView.xaml.cs
namespace LayoutMVVM.Views
{
public partial class TreeView : UserControl
{
public TreeView()
{
InitializeComponent();
}
public TreeView _isVerifyTrue;
public TreeView IsVerifyTrue
{
get { return this; }
set
{
_isVerifyTrue = value;
OnPropertyChanged(() => IsVerifyTrue);
}
}
private void OnPropertyChanged(Func<object> p)
{
throw new NotImplementedException();
}
private string _login;
public string Login
{
get { return _login; }
set
{
_login = value;
IsVerifyTrue = this;
OnPropertyChanged(() => Login);
}
}
private string _password;
public string Password
{
get { return _password; }
set
{
_password = value;
IsVerifyTrue = this;
OnPropertyChanged(() => Password);
}
}
}
and seperate class for VBConverter.cs:
namespace LayoutMVVM.ViewModels
{
public class VBConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is TreeView)
{
var tv = value as TreeView;
bool result = !string.IsNullOrEmpty(tv.Login)
&& !string.IsNullOrEmpty(tv.Password);
return result? Visibility.Visible : Visibility.Hidden;
}
return Visibility.Hidden;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
When I start app button is always visible and I'm not know what is wrong.
You'll need a MultiValueConverter for this. See below example :
XAML :
<Window x:Class="SOSample1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SOSample1"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.Resources>
<local:VisibilityConverter x:Key="VisibilityConverter" />
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="100" />
<RowDefinition Height="100" />
<RowDefinition Height="100" />
</Grid.RowDefinitions>
<TextBox Grid.Row="0" Name="user" Height="100" Width="200" Text="{Binding Login, UpdateSourceTrigger=PropertyChanged}" Background="AliceBlue" />
<TextBox Grid.Row="1" Name="password" Height="100" Width="200" Text="{Binding Login, UpdateSourceTrigger=PropertyChanged}" Background="AliceBlue" />
<Button Grid.Row="2" Content="Login" Height="100" Width="200" >
<Button.Visibility>
<MultiBinding Converter="{StaticResource VisibilityConverter}" >
<Binding ElementName="user" Path="Text" />
<Binding ElementName="password" Path="Text" />
</MultiBinding>
</Button.Visibility>
</Button>
</Grid>
Converter :
public class VisibilityConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
bool result = false;
if (values != null)
{
foreach (var item in values)
{
result = (item as string).Length > 0;
if (!result) break;
}
}
return (Visibility)new BooleanToVisibilityConverter().Convert(result, targetType, parameter, culture);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
}
OUTPUT

Change DataTemplate for ListBox item if selected in UWP

In UWP Xaml,I have a ListView. In the ListView i use a DataTemplate with a stack panel,
I want change backgrund color of stackpanel when selectedItem is True
I do want to do it in Xaml
the other words,
Change DataTemplate for Listview item if selected
I do want to do it in Xaml
code in Xaml:
<ListView.ItemTemplate >
<DataTemplate x:Name="mydt">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="40" />
</Grid.ColumnDefinitions>
<StackPanel x:Name="MyStack">
<ContentControl Content="{Binding rtb}"
q:APGolAyah.MyProperty="{Binding AyahNo}" />
<TextBlock Text="{Binding Text}"
TextWrapping="Wrap" />
</StackPanel>
<Image Grid.Column="1"
Source="{Binding HezbNo,Converter={StaticResource HezbNoToIconConverter}}"
Width="25" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
As you've found that in UWP Style.Triggers cannot be used, and I understand that you want to do this work pure in xaml, but sorry to say that there is no such pure way now.
Since you only want to change the background color of the StackPanel when the item is selected, I think it is not needed to changed the whole DataTemplate, I'm writing the answer here to introduce a method which uses Converter for data binding and a little bit c# code here since maybe you interest in this method.
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.Resources>
<local:BoolToBrushConverter x:Key="cvt" />
</Grid.Resources>
<ListView x:Name="listview" ItemsSource="{x:Bind Collection, Mode=OneWay}"
SelectionChanged="listview_SelectionChanged">
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="40" />
</Grid.ColumnDefinitions>
<StackPanel Background="{Binding IsSelected, Converter={StaticResource cvt}}">
<TextBlock Text="{Binding Name}" />
</StackPanel>
<TextBlock Grid.Column="1" Text="{Binding Age}" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
Code behind and data model:
private ObservableCollection<Model> Collection = new ObservableCollection<Model>();
protected override void OnNavigatedTo(NavigationEventArgs e)
{
for (int i = 0; i < 50; i++)
{
Collection.Add(new Model { Name = "Name " + i + ", ", Age = i });
}
}
private void listview_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
foreach (Model item in e.AddedItems)
{
item.IsSelected = true;
}
foreach (Model item in e.RemovedItems)
{
item.IsSelected = false;
}
}
public class Model : INotifyPropertyChanged
{
public string Name { get; set; }
public int Age { get; set; }
private bool _IsSelected;
public bool IsSelected
{
get { return _IsSelected; }
set
{
if (value != _IsSelected)
{
_IsSelected = value;
OnPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName]string propertyName = "")
{
if (this.PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Converter:
public class BoolToBrushConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
bool? b = (bool?)value;
if (b == true)
return new SolidColorBrush(Colors.BlueViolet);
return new SolidColorBrush(Colors.Transparent);
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
And if you also interest in changing DataTemplate, you may refer to my answer in another case: UWP ListView: How to expand an item when select it?.

WPF ListBox: Change border per item according boolean value

In my app I load image and verify I haven't too much blobs.
In additional, I put all images I loaded in list box and all image have a border.
Now, I want to change border color pet item according the Boolean value (true or false according numbers of blobs and their size).
If my image passed the border should be green, else red.
my relevent code:
public List<String> ImagePath = new List<String>();
public MainWindow()
{
InitializeComponent();
lb_Images.ItemsSource = ImagePath;
}
private void bu_addImage_Click(object sender, RoutedEventArgs e)
{
addImageToListBox();
}
private void addImageToListBox()
{
String imagePath = getImage();
// verfiy img haven't too much blobs
Boolean passed = imagePassed(imagePath);
// add the image to the list box
// I want to change border according passed value
// True - green; False- red.
ImagePath.Add(imagePath);
lb_Images.Items.Refresh();
}
XAML:
<Window x:Class="forQuestionWPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="216" Width="519">
<Window.Resources>
<DataTemplate x:Key="ImageGalleryDataTemplate">
<Grid>
<Border BorderBrush="Green" BorderThickness="2" Width="120" Height="120" Padding="5" Margin="5" CornerRadius="6">
<Image Source="{Binding}" Stretch="Fill" HorizontalAlignment="Center">
<Image.ToolTip>
<Grid>
<Image Source="{Binding}" Stretch="Fill" HorizontalAlignment="Center" Height="200" Width="200" />
</Grid>
</Image.ToolTip>
</Image>
</Border>
</Grid>
</DataTemplate>
<ItemsPanelTemplate x:Key="ImageGalleryItemsPanelTemplate">
<UniformGrid Rows="1" Columns="25" HorizontalAlignment="Center" VerticalAlignment="Stretch"/>
</ItemsPanelTemplate>
</Window.Resources>
<Grid>
<Canvas Height="177" HorizontalAlignment="Left" Name="canvas1" VerticalAlignment="Top" Width="497">
<ListBox Canvas.Left="6" Canvas.Top="5" Height="166" Name="lb_Images" Width="441"
ItemTemplate="{StaticResource ImageGalleryDataTemplate}"
ItemsSource="{Binding Path=ImagePath}"
ItemsPanel="{StaticResource ImageGalleryItemsPanelTemplate}">
</ListBox>
<Button Canvas.Left="453" Canvas.Top="26" Content="Add" Height="64" Name="bu_addImage" Width="38" Click="bu_addImage_Click" />
</Canvas>
</Grid>
</Window>
For example, in the below image latest two items need to be with red border:
Thank you on any help!
AsfK
Instead of binding to imagePath (string) you can define a class:
public class ImageStuff {
public string ImagePath {get; set;}
public bool Passed {get; set;}
}
and add instance of this to the listbox.
Then, you can use a converter for your border like this:
<Border BorderThickness="2" Width="120" Height="120" Padding="5" Margin="5" CornerRadius="6" BorderBrush="{Binding Path=Passed, Mode=OneWay, Converter={StaticResource borderConverter}}">
where borderConverter is something like this:
public class borderConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
if (value is bool)
{
if (!(bool)value == true)
return new SolidColorBrush(Colors.Red);
}
return new SolidColorBrush(Colors.Green);
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
throw NotImplementedException();
}
}
You have to include borderConverter in your resources like this:
<Window.Resources>
<src:borderConverter x:Key="borderConverter" />
</Window.Resources>
where src is the namespace.
If you want to change property of ImageStuff class dinamically you have to implement INotifyPropertyChanged so the binding will be updated.
You can do it without Converter, just add DataTrigger with Tag property to DataTemplate. This can affect to the performance because the Converter will work little much longer.
Add this class:
public class MyImage
{
public string ImagePath
{
get;
set;
}
public bool Flag
{
get;
set;
}
}
And in code-behind use like this:
public List<MyImage> ImagePath = new List<MyImage>();
Full example:
XAML
<DataTemplate x:Key="ImageGalleryDataTemplate">
<Grid>
<Border Name="MyBorder" BorderBrush="#FFFF9800" BorderThickness="1" Width="120" Height="120" Padding="5" Margin="5" CornerRadius="6">
<Image Name="MyImage" Tag="{Binding Path=Flag}" Source="{Binding Path=ImagePath}" Stretch="Fill" HorizontalAlignment="Center">
<Image.ToolTip>
<Grid>
<Image Source="{Binding Path=ImagePath}" Stretch="Fill" HorizontalAlignment="Center" Height="200" Width="200" />
</Grid>
</Image.ToolTip>
</Image>
</Border>
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=Tag, ElementName=MyImage}" Value="True">
<Setter TargetName="MyBorder" Property="BorderBrush" Value="Red" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=Tag, ElementName=MyImage}" Value="False">
<Setter TargetName="MyBorder" Property="BorderBrush" Value="Green" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
Code-behind
public partial class MainWindow : Window
{
int imageNumber = 0;
public List<MyImage> ImagePath = new List<MyImage>();
public MainWindow()
{
InitializeComponent();
lb_Images.ItemsSource = ImagePath;
}
private void bu_addImage_Click(object sender, RoutedEventArgs e)
{
addImageToListBox();
}
private void addImageToListBox()
{
imageNumber++;
if (imageNumber == 4) imageNumber = 0;
string directoryPath = AppDomain.CurrentDomain.BaseDirectory;
// load input image
string ImageFilename = directoryPath + "img";
ImageFilename += imageNumber.ToString();
ImageFilename += ".jpg";
ImagePath.Add(new MyImage
{
ImagePath = ImageFilename,
Flag = false
});
lb_Images.Items.Refresh();
}
}
public class MyImage
{
public string ImagePath
{
get;
set;
}
public bool Flag
{
get;
set;
}
}
In order to the properties of MyImage class successfully changed, you need to implement the INotifyPropertyChanged interface.
You neeed to write BoolToColorConverter for this purpose. Find following converter code.
public sealed class BoolToColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
bool bValue = (bool)value;
if (bValue)
return Color.Green;
else
return Color.Red;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
Color color = (Color)value;
if (color == Color.Green)
return true;
else
return false;
}
}

Automatically number items in a wpf listbox [duplicate]

I have a sorted listbox and need to display each item's row number. In this demo I have a Person class with a Name string property. The listbox displays a a list of Persons sorted by Name. How can I add to the datatemplate of the listbox the row number???
XAML:
<Window x:Class="NumberedListBox.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="300" Width="300">
<ListBox
ItemsSource="{Binding Path=PersonsListCollectionView}"
HorizontalContentAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Window>
Code behind:
using System;
using System.Collections.ObjectModel;
using System.Windows.Data;
using System.Windows;
using System.ComponentModel;
namespace NumberedListBox
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
Persons = new ObservableCollection<Person>();
Persons.Add(new Person() { Name = "Sally"});
Persons.Add(new Person() { Name = "Bob" });
Persons.Add(new Person() { Name = "Joe" });
Persons.Add(new Person() { Name = "Mary" });
PersonsListCollectionView = new ListCollectionView(Persons);
PersonsListCollectionView.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending));
DataContext = this;
}
public ObservableCollection<Person> Persons { get; private set; }
public ListCollectionView PersonsListCollectionView { get; private set; }
}
public class Person
{
public string Name { get; set; }
}
}
Finally! If found a way much more elegant and probably with better performance either.
(see also Accessing an ItemsControl item as it is added)
We "misuse" the property ItemsControl.AlternateIndex for this. Originally it is intended to handle every other row within a ListBox differently. (see http://msdn.microsoft.com/en-us/library/system.windows.controls.itemscontrol.alternationcount.aspx)
1. Set AlternatingCount to the amount of items contained in the ListBox
<ListBox ItemsSource="{Binding Path=MyListItems}"
AlternationCount="{Binding Path=MyListItems.Count}"
ItemTemplate="{StaticResource MyItemTemplate}"
...
/>
2. Bind to AlternatingIndex your DataTemplate
<DataTemplate x:Key="MyItemTemplate" ... >
<StackPanel>
<Label Content="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplatedParent.(ItemsControl.AlternationIndex)}" />
...
</StackPanel>
</DataTemplate>
So this works without a converter, an extra CollectionViewSource and most importantly without brute-force-searching the source collection.
This should get you started:
http://weblogs.asp.net/hpreishuber/archive/2008/11/18/rownumber-in-silverlight-datagrid-or-listbox.aspx
It says it's for Silverlight, but I don't see why it wouldn't work for WPF. Basically, you bind a TextBlock to your data and use a custom value converter to output the current item's number.
The idea in David Brown's link was to use a value converter which worked. Below is a full working sample. The list box has row numbers and can be sorted on both name and age.
XAML:
<Window x:Class="NumberedListBox.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:NumberedListBox"
Height="300" Width="300">
<Window.Resources>
<local:RowNumberConverter x:Key="RowNumberConverter" />
<CollectionViewSource x:Key="sortedPersonList" Source="{Binding Path=Persons}" />
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ListBox
Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2"
ItemsSource="{Binding Source={StaticResource sortedPersonList}}"
HorizontalContentAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock
Text="{Binding Converter={StaticResource RowNumberConverter}, ConverterParameter={StaticResource sortedPersonList}}"
Margin="5" />
<TextBlock Text="{Binding Path=Name}" Margin="5" />
<TextBlock Text="{Binding Path=Age}" Margin="5" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Grid.Row="1" Grid.Column="0" Content="Name" Tag="Name" Click="SortButton_Click" />
<Button Grid.Row="1" Grid.Column="1" Content="Age" Tag="Age" Click="SortButton_Click" />
</Grid>
</Window>
Code behind:
using System;
using System.Collections.ObjectModel;
using System.Windows.Data;
using System.Windows;
using System.ComponentModel;
using System.Windows.Controls;
namespace NumberedListBox
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
Persons = new ObservableCollection<Person>();
Persons.Add(new Person() { Name = "Sally", Age = 34 });
Persons.Add(new Person() { Name = "Bob", Age = 18 });
Persons.Add(new Person() { Name = "Joe", Age = 72 });
Persons.Add(new Person() { Name = "Mary", Age = 12 });
CollectionViewSource view = FindResource("sortedPersonList") as CollectionViewSource;
view.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending));
DataContext = this;
}
public ObservableCollection<Person> Persons { get; private set; }
private void SortButton_Click(object sender, RoutedEventArgs e)
{
Button button = sender as Button;
string sortProperty = button.Tag as string;
CollectionViewSource view = FindResource("sortedPersonList") as CollectionViewSource;
view.SortDescriptions.Clear();
view.SortDescriptions.Add(new SortDescription(sortProperty, ListSortDirection.Ascending));
view.View.Refresh();
}
}
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
}
Value converter:
using System;
using System.Windows.Data;
namespace NumberedListBox
{
public class RowNumberConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
CollectionViewSource collectionViewSource = parameter as CollectionViewSource;
int counter = 1;
foreach (object item in collectionViewSource.View)
{
if (item == value)
{
return counter.ToString();
}
counter++;
}
return string.Empty;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
Yet another answer. I tried the above, which works in WPF (AlternationCount solution), but I needed code for Silverlight, so I did the following. This is more elegant than the other brute force method.
<UserControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:RowNumber" x:Name="userControl"
x:Class="RowNumber.MainPage" Width="640" Height="480">
<Grid x:Name="LayoutRoot" Background="White">
<ListBox ItemsSource="{Binding Test, ElementName=userControl}">
<ListBox.Resources>
<local:ListItemIndexConverter x:Key="IndexConverter" />
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Width="30"
Text="{Binding RelativeSource={RelativeSource AncestorType=ListBoxItem}, Converter={StaticResource IndexConverter}}" />
<TextBlock Text="{Binding}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</UserControl>
And behind
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
namespace RowNumber
{
public class ListItemIndexConverter : IValueConverter
{
// Value should be ListBoxItem that contains the current record. RelativeSource={RelativeSource AncestorType=ListBoxItem}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var lbi = (ListBoxItem)value;
var listBox = lbi.GetVisualAncestors().OfType<ListBox>().First();
var index = listBox.ItemContainerGenerator.IndexFromContainer(lbi);
// One based. Remove +1 for Zero based array.
return index + 1;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); }
}
public partial class MainPage : UserControl
{
public MainPage()
{
// Required to initialize variables
InitializeComponent();
}
public List<string> Test { get { return new[] { "Foo", "Bar", "Baz" }.ToList(); } }
}
}
This is newly available in Silverlight 5 with the introduction of RelativeSource binding.
Why not just binding to count property of the listbox.
<ListView x:Name="LstFocusImageDisplayData"
>
<ListView.ItemTemplate>
<DataTemplate>
<GroupBox Header="Item Count">
<DockPanel>
<TextBlock Text="{Binding ElementName=LstFocusImageDisplayData, Path=Items.Count, Mode=OneTime}" />
</DockPanel>
</GroupBox>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

Categories