Automatically number items in a wpf listbox [duplicate] - c#

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>

Related

How to bind a List of colors to property that requires Brush?

I've spent all day trying to figure out how I can simply have a list of colors (for example: Colors.AliceBlue) as my ItemsSource for a GridView and bind those colors to a Fill property of a Rectangle inside the DataTemplate. I know the Fill property must be a brush, so I have tried using a converter to convert the color to a SolidColorBrush, but it has not worked. I've also tried not using a converter and instead changing the List to List but that did not work either. No matter what I do, I keep getting binding errors that say:
Converter failed to convert value of type '#FFF0F8FF' to type 'Brush'. Binding: Path='' DataItem='#FFF0F8FF'; target element is 'Microsoft.UI.Xaml.Shapes.Rectangle' (Name='null'); target property is 'Fill' (type 'Brush')
Everything I try always seems to return my color as an ARGB, in this case "#FFF0F8FF", which is not what the property accepts. Any ideas on how to bind my list of colors to my item/data template? I definitely want to use color names in my list, as it is easier to access colors this way rather than looking up their RGB codes and whatnot.
Page.xaml
<GridView ItemsSource="{x:Bind ColorOptions}" IsItemClickEnabled="True" SelectionMode="Single">
<GridView.ItemTemplate>
<DataTemplate>
<Rectangle Fill="{Binding}" Width="40" Height="40" />
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
Page.xaml.cs
using System.Windows.Media;
...
public readonly List<Color> ColorOptions = new()
{
Colors.AliceBlue,
Colors.Black,
Colors.DarkBlue,
Colors.Brown,
Colors.DarkGreen,
Colors.Magenta
};
Also, if you're interested, here's the converter I created and tried, but also did not work.
BrushConverter.cs
using System.Windows.Media;
using Microsoft.UI.Xaml.Data;
namespace App.Helpers;
public class BrushConverter : IValueConverter
{
public object? Convert(object value, Type targetType, object parameter, string language)
{
return new SolidColorBrush((Color)value);
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
return ((SolidColorBrush)value).Color;
}
}
Page.xaml (using converter)
<GridView ItemsSource="{x:Bind ColorOptions}" IsItemClickEnabled="True" SelectionMode="Single">
<GridView.ItemTemplate>
<DataTemplate>
<Rectangle Fill="{Binding Converter={StaticResource BrushConverter}}" Width="40" Height="40" />
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
This way you don't need a converter:
using Microsoft.UI;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using System.Collections.Generic;
using Windows.UI;
namespace GridViews;
public class ColorOption
{
public ColorOption(string name, Color color)
{
Name = name;
Color = new SolidColorBrush(color);
}
public string Name { get; set; }
public Brush Color { get; set; }
}
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
}
public List<ColorOption> ColorOptions { get; } = new()
{
new ColorOption("Alice Blue", Colors.AliceBlue),
new ColorOption("Black", Colors.Black),
new ColorOption("Dark Blue", Colors.DarkBlue),
new ColorOption("Brown", Colors.Brown),
new ColorOption("Dark Green", Colors.DarkGreen),
new ColorOption("Magenta", Colors.Magenta),
};
}
<GridView
IsItemClickEnabled="True"
ItemsSource="{x:Bind ColorOptions}"
SelectionMode="Single">
<GridView.ItemTemplate>
<DataTemplate x:DataType="local:ColorOption">
<StackPanel>
<TextBlock Text="{x:Bind Name}" />
<Rectangle
Width="40"
Height="40"
Fill="{x:Bind Color}" />
</StackPanel>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
Had to change some things as I can't use System.Windows.Media:
On Page/Window.xaml
<Window
...
xmlns:helpers="using:TestApp.Helpers"
xmlns:vm="using:TestApp.ViewModels"
...>
<Grid>
<Grid.Resources>
<vm:MainViewModel x:Name="ViewModel" />
<helpers:BrushConverter x:Name="BrushConverter" />
</Grid.Resources>
<GridView ItemsSource="{x:Bind ViewModel.ColorOptions, Mode=OneWay}">
<GridView.ItemTemplate>
<DataTemplate>
<Rectangle Width="40" Height="40" Fill="{Binding Converter={StaticResource BrushConverter}}" />
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
</Grid>
</Window>
Use a ViewModel (e.g. MainViewModel.cs)
using System.Collections.Generic;
using System.Drawing;
namespace TestApp.ViewModels;
public class MainViewModel
{
public List<Color> ColorOptions { get; } = new()
{
Color.Red,
Color.Orange,
Color.Yellow
};
}
BrushConverter.cs (Drawing.Color to Windows.UI.Color) for my end
public object Convert(object value, Type targetType, object parameter, string language)
{
var drawingColor = (System.Drawing.Color)value;
return new SolidColorBrush(Windows.UI.Color.FromArgb(drawingColor.A, drawingColor.R, drawingColor.G, drawingColor.B));
}
Output

Cannot locate xaml page in my WPF MVVM application

hi i have 3 folders in project namely Model,View, View Model. I moved my Xaml to View folder. My solution gets build properly buy when i run it shows the Error "cannot locate the source Userregistration.xaml".
Heres is my complete code
i have added themy folder structure for the reference. please help me i m not able to figure out this issue.
View
<Window x:Class="MVVMDemo.UserRegistrationView"
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:viewmodel="clr-namespace:MVVMDemo"
Title="Registration Window" Height="300" Width="575.851">
<Window.Resources>
<viewmodel:ViewModel x:Key="ViewModel"/>
<viewmodel:DatetimeToDateConverter x:Key="MyConverter"/>
</Window.Resources>
<Grid DataContext="{Binding Source={StaticResource ViewModel}}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="Name" HorizontalAlignment="Center"/>
<TextBox Grid.Row="0" Grid.Column="1" Width="100" HorizontalAlignment="Center" Text="{Binding Student.Name, Mode=TwoWay}" Margin="76,0"/>
<TextBlock Grid.Row="1" Grid.Column="0" Text="Age" HorizontalAlignment="Center"/>
<TextBox Grid.Row="1" Grid.Column="1" Width="100" HorizontalAlignment="Center" Text="{Binding Student.Age, Mode=TwoWay}"/>
<Button Content="Submit" Command="{Binding SubmitCommand}" HorizontalAlignment="Right" Grid.Row="2" Grid.Column="0"/>
<ComboBox Grid.Column="1" ItemsSource="{Binding FillCourseId}" Name="cmb_CourseIDName" HorizontalAlignment="Left" Margin="164.191,5,0,0"
Grid.Row="3" VerticalAlignment="Top" Width="168.342" Grid.RowSpan="2" DataContext="{Binding Source={StaticResource ViewModel}}" FontSize="8" IsReadOnly="True"
SelectedValue="{Binding Student.SCourseIDName,Mode=TwoWay}" RenderTransformOrigin="1.431,0.77">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" >
<TextBlock Text="{Binding Path=CourseName, Mode=TwoWay}" FontSize="12"/>
<TextBlock Text=" - "/>
<TextBlock Text="{Binding Path=CourseID, Mode=TwoWay}" FontSize="12"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<ListView ItemsSource="{Binding Students}" Grid.Row="4" Grid.Column="1" Margin="62.551,0,78.762,76.829"
ScrollViewer.CanContentScroll="False"
ScrollViewer.VerticalScrollBarVisibility="Visible" Height="85.152" VerticalAlignment="Bottom">
<ListView.View >
<GridView >
<GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}" Width="60"/>
<GridViewColumn Header="Age" DisplayMemberBinding="{Binding Age}" Width="60"/>
<GridViewColumn Header="Joining Date" DisplayMemberBinding="{Binding JoiningDate, Converter={StaticResource MyConverter}}" Width="80" />
<GridViewColumn Header="Course Name" DisplayMemberBinding="{Binding SCourseIDName.CourseName}" Width="80"/>
<GridViewColumn Header="CourseId" DisplayMemberBinding="{Binding SCourseIDName.CourseID}" Width="60"/>
</GridView>
</ListView.View>
</ListView>
</Grid>
</Window>
Model
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MVVMDemo
{
public class Student
{
public string Name { get; set; }
public int Age { get; set; }
public string Course { get; set; }
public DateTime JoiningDate { get; set; }
public string CourseName { get; set; }
public string CourseID { get; set; }
public Student SCourseIDName { get; set; }
}
}
ViewModel
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Input;
using System.Collections.ObjectModel;
using System.Data.SqlClient;
using System.Data;
using System.ComponentModel;
namespace MVVMDemo
{
public class ViewModel : ViewModelBase
{
static String connectionString = #"Data Source=RITESH-PC\SQLEXPRESS;Initial Catalog=SIT_Ritesh_DB;Integrated Security=True;";
SqlConnection con;
SqlCommand cmd;
private Student _student;
private ObservableCollection<Student> _students;
private ICommand _SubmitCommand;
public Student Student
{
get
{
return _student;
}
set
{
_student = value;
NotifyPropertyChanged("Student");
}
}
private ObservableCollection<Student> _fillCourseId = new ObservableCollection<Student>();
public ObservableCollection<Student> FillCourseId
{
get { return _fillCourseId; }
set
{
_fillCourseId = value;
OnPropertyChanged("SystemStatusData");
}
}
public ObservableCollection<Student> Students
{
get
{
return _students;
}
set
{
_students = value;
NotifyPropertyChanged("Students");
}
}
private Student _selectedcourseIdname;
public Student SelectedCourseIdName
{
get { return _selectedcourseIdname; }
set
{
_selectedcourseIdname = value;
OnPropertyChanged("SelectedCourseIdName");
}
}
public string SelectedCourseId
{
get { return _selectedcourseIdname.CourseID; }
set
{
_selectedcourseIdname.CourseID = value;
OnPropertyChanged("SelectedCourseId");
}
}
public string SelectedCourseName
{
get { return _selectedcourseIdname.CourseName; }
set
{
_selectedcourseIdname.CourseName = value;
OnPropertyChanged("SelectedCourseName");
}
}
public ICommand SubmitCommand
{
get
{
if (_SubmitCommand == null)
{
_SubmitCommand = new RelayCommand(param => this.Submit(),
null);
}
return _SubmitCommand;
}
}
//********************************************* Functions*******************************************//
public void GetCourseIdFromDB()
{
try
{
con = new SqlConnection(connectionString);
con.Open();
cmd = new SqlCommand("select * from dev_Course", con);
SqlDataAdapter adapter = new SqlDataAdapter(cmd);
DataTable dt = new DataTable();
adapter.Fill(dt);
// Student Student = new Student();
for (int i = 0; i < dt.Rows.Count; ++i)
FillCourseId.Add(new Student
{
CourseID = dt.Rows[i][0].ToString(),
CourseName = dt.Rows[i][2].ToString()
});
}
catch (Exception ex)
{
}
}
public ViewModel()
{
Student = new Student();
Students = new ObservableCollection<Student>();
Students.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(Students_CollectionChanged);
GetCourseIdFromDB();
}
void Students_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
NotifyPropertyChanged("Students");
}
private void Submit()
{
Student.JoiningDate = DateTime.Today.Date;
//Students.Add(SelectedCourseIdName);
Students.Add(Student);
Student = new Student();
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyname)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyname));
}
}
}
DateTimeConverter
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Globalization;
using System.Windows.Data;
namespace MVVMDemo
{
public class DatetimeToDateConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
DateTime date = (DateTime)value;
return date.ToString("MM/d/yyyy");
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
namespace MVVMDemo
{
public class ViewModelBase:INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged!=null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
you need to change it in the app.xmal in the line of the StartupUri
that he will be contact to the xmal in the View
example:
in the app.xmal file
<Application x:Class="UserRegistration.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:UserRegistration"
StartupUri="View/UserRegistrationView.xaml">
<Application.Resources>
</Application.Resources>
You need to edit app.xaml to point to the correct view file (including the folder name in the path) which in your case is the UserRegistrationView.Xaml. The app.xaml is responsible for starting your app not to view xaml code.
The folder name may not be required. I use Resharper which creates a namespace for each folder within a project in visual studio.

WPF - How to correctly show text in a TextBlock binded with a string list

For a future professional project, I need to evaluate the WPF capabilities.
In this context, I created a small test project, which contains 1 string tree, and 1 image grid. I want that my image grid shows all the jpeg images contained inside a given directory, and for each image, to show the extracted file name below the image, without its path and extension.
Actually, my demo works correctly according to my goal, except for one point: I added each formatted file name to show inside a List collection, which I tried to bind with a TextBlock shown on the bottom of each images. However this formatted name isn't visible, instead I see the complete file name, as if the TextBlock extracted it directly from the Image object.
I tried to resolve this issue by myself, following several tutorials, nothing worked for me. I cannot figure out what I'm doing wrong. Can someone explain to me?
Here is my xaml file content
<Window x:Class="VirtualTrees.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:VirtualTrees"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<Style x:Key="myHeaderStyle" TargetType="{x:Type GridViewColumnHeader}">
<Setter Property="Visibility" Value="Collapsed" />
</Style>
<DataTemplate x:Key="itImageCell">
<WrapPanel>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="100"/>
<RowDefinition Height="20"/>
</Grid.RowDefinitions>
<Image Width="120" Stretch="Uniform" Source="{Binding}"/>
<TextBlock Grid.Row="1" Width="120" Text="{Binding}" TextTrimming="CharacterEllipsis"/>
</Grid>
</WrapPanel>
</DataTemplate>
<local:ListToStringConverter x:Key="ListToStringConverter" />
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="400"/>
<ColumnDefinition Width="400*"/>
</Grid.ColumnDefinitions>
<ListView Margin="10" Name="lvStringTree">
<ListView.View>
<GridView>
<GridViewColumn Header="Name" Width="120" DisplayMemberBinding="{Binding Name}" />
<GridViewColumn Header="Age" Width="50" DisplayMemberBinding="{Binding Age}" />
<GridViewColumn Header="Mail" Width="150" DisplayMemberBinding="{Binding Mail}" />
</GridView>
</ListView.View>
</ListView>
<Grid x:Name="grImages" Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<ListView Grid.Row="1" Name="lvImages" ItemsSource="{Binding Path=m_ImageList}" ItemTemplate="{StaticResource itImageCell}">
<ListView.Background>
<ImageBrush/>
</ListView.Background>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="3" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
<TextBlock Name="tbImageName" Text="{Binding Path=m_ImageNames, Converter={StaticResource ResourceKey=ListToStringConverter}}" DataContext="{StaticResource itImageCell}" />
</Grid>
</Grid>
</Window>
And my c# code
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Data;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace VirtualTrees
{
[ValueConversion(typeof(List<string>), typeof(string))]
public class ListToStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (targetType != typeof(string))
throw new InvalidOperationException("The target must be a string");
return string.Join(", ", ((List<string>)value).ToArray());
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public class User
{
public string Name { get; set; }
public int Age { get; set; }
public string Mail { get; set; }
}
List<ImageSource> m_ImageList = new List<ImageSource>();
List<string> m_ImageNames = new List<string>();
string m_RegexPattern = #"\\([\w ]+).(?:jpg|png)$";
public MainWindow()
{
InitializeComponent();
PopulateStringTree();
PopulateImageGrid();
}
public void PopulateStringTree()
{
List<User> vstItems = new List<User>();
for (ulong i = 0; i < 100000; ++i)
{
vstItems.Add(new User() { Name = "John Doe", Age = 42, Mail = "john#doe-family.com" });
vstItems.Add(new User() { Name = "Jane Doe", Age = 39, Mail = "jane#doe-family.com" });
vstItems.Add(new User() { Name = "Sammy Doe", Age = 7, Mail = "sammy.doe#gmail.com" });
}
lvStringTree.ItemsSource = vstItems;
}
public void PopulateImageGrid()
{
// get jpeg image file list from target dir
string moviePosterPath = #"W:\Labo\WPF\VirtualTrees\VirtualTrees\Resources\Images";
List<string> fileNames = new List<string>(System.IO.Directory.EnumerateFiles(moviePosterPath, "*.jpg"));
// iterate through files
foreach (string fileName in fileNames)
{
// load image and add it to image list
m_ImageList.Add(new BitmapImage(new Uri(fileName)));
Console.WriteLine("filename " + fileName);
// extract image file name and add it to name list
Match regexMatch = Regex.Match(fileName.Trim(), m_RegexPattern);
m_ImageNames.Add(regexMatch.Groups[1].Value);
Console.WriteLine("Movie Name: " + regexMatch.Groups[1].Value);
}
// bind data to image grid
lvImages.ItemsSource = m_ImageList;
}
}
}
Your DataTemplate is the origin of the error. You have to check the binding of the TextBlock. You are binding to the DataContext which is a BitmapSource. The TextBlock implicitly calls BitmapSource.ToString() to get the string representation of the type. BitmapSource has ToString() overriden to return the full file path. To fix this, you need to use a IValueConverter.
Modified DataTemplate. The TextBlock binding now uses a converter to convert the BitmapSource to the filename:
<DataTemplate x:Key="itImageCell">
<WrapPanel>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="100" />
<RowDefinition Height="20" />
</Grid.RowDefinitions>
<Image Width="120"
Stretch="Uniform"
Source="{Binding}" />
<TextBlock Grid.Row="1"
Width="120"
Text="{Binding ., Converter={StaticResource BitmapSourceToFilenameConverter}}"
TextTrimming="CharacterEllipsis" />
</Grid>
</WrapPanel>
</DataTemplate>
The IValueConverter for the TextBlock binding to convert the BitmapSource to filename:
[ValueConversion(typeof(BitmapSource), typeof(string))]
public class BitmapSourceToFilenameConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is BitmapSource bitmapSource)
return bitmapSource.UriSource.AbsolutePath;
return Binding.DoNothing;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
A small mistake I realized in your code:
You are first setting a binding on the ListView:
<ListView Name="lvImages" ItemsSource="{Binding Path=m_ImageList}" />
and then you override (remove) it
// bind data to image grid
lvImages.ItemsSource = m_ImageList;
This is not a binding (the comment is not true).
You should make m_ImageList a ObservableCollection<ImageSource> instead of List. The ObservableCollection will automatically update the ListView when items are added, moved or removed. Then remove this line from your MainWindow class: lvImages.ItemsSource = m_ImageList;

Footer on ListView WPF

I would like to add a footer to the ListView with the sum of two columns in his respective columns, Licenses and Scans, I found something similar (Here) but the footer is not being shown, if I add a 4 column into the GridViewthen the footer row is shown but without any values.The GridViewColumnHeadercontains a style which I would like to apply to the Footer as well (I have not pasted it to provide a Minimal, functional example)
This is the expected view:
xaml
<Window x:Class="WpfApp2.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:WpfApp2"
xmlns:system="clr-namespace:System;assembly=mscorlib"
xmlns:utils="Helper"
mc:Ignorable="d"
Title="MainWindow"
Height="450"
Width="800">
<Window.DataContext>
<local:ViewModel />
</Window.DataContext>
<Window.Resources>
<CollectionViewSource x:Key="ViewSource"
Source="{Binding DataList}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="ToOrder" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</Window.Resources>
<Grid>
<ListView ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ItemsSource="{Binding Source={StaticResource ViewSource}}">
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<DockPanel>
<Grid DockPanel.Dock="Bottom">
<Grid.Resources>
<local:SumConverter x:Key="sumConverter" />
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding ElementName=Col1, Path=ActualWidth}"/>
<ColumnDefinition Width="{Binding ElementName=Col2, Path=ActualWidth}"/>
<ColumnDefinition Width="{Binding ElementName=Col3, Path=ActualWidth}"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="1"
Text="Sum: "
FontWeight="Bold" />
<TextBlock Grid.Column="1"
Grid.Row="1"
Text="{Binding Path=Items, Converter={StaticResource sumConverter}, ConverterParameter=1}"/>
<TextBlock Grid.Column="2"
Grid.Row="1"
Text="{Binding Path=Items, Converter={StaticResource sumConverter}, ConverterParameter=2}"/>
<Line Grid.Column="0"
Grid.Row="0"
Grid.ColumnSpan="3"
Stroke="Black"
X2="500"
Fill="Black"
VerticalAlignment="Center" />
</Grid>
<ItemsPresenter />
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ListView.GroupStyle>
<ListView.View>
<GridView x:Name="OverviewGridView">
<GridViewColumn DisplayMemberBinding="{Binding Date, StringFormat=dd.MM.yyyy}"
Header="Date"
x:Name="Col1"/>
<GridViewColumn DisplayMemberBinding="{Binding LicenseCount}"
Header="Licenses"
x:Name="Col2"/>
<GridViewColumn DisplayMemberBinding="{Binding ScansCount}"
Header="Scans"
x:Name="Col3"/>
</GridView>
</ListView.View>
</ListView>
</Grid>
</Window>
ViewModel
namespace WpfApp2
{
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using WpfApp2.Annotations;
public class OverView
{
public int ToGroup { get; set; } = 1;
public DateTime Date { get; set; }
public int LicenseCount { get; set; }
public int ScansCount { get; set; }
}
public class ViewModel : INotifyPropertyChanged
{
private int _totalLicenses;
private ObservableCollection<OverView> _dataList;
private int _totalScans;
public ViewModel()
{
OverView a = new OverView
{
Date = DateTime.Now.Subtract(new TimeSpan(1, 0, 0, 0)),
LicenseCount = 2,
ScansCount = 7
};
OverView b = new OverView
{
Date = DateTime.Now.Subtract(new TimeSpan(5, 0, 0, 0)),
LicenseCount = 3,
ScansCount = 2
};
OverView c = new OverView { Date = DateTime.Now, LicenseCount = 5, ScansCount = 4 };
OverView d = new OverView
{
Date = DateTime.Now.Subtract(new TimeSpan(7, 0, 0, 0)),
LicenseCount = 1,
ScansCount = 3
};
DataList = new ObservableCollection<OverView> { a, b, c, d };
TotalLicenses = DataList.Sum(overview => overview.LicenseCount);
TotalScans = DataList.Sum(overview => overview.ScansCount);
}
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<OverView> DataList
{
get => _dataList;
set
{
_dataList = value;
OnPropertyChanged();
}
}
public int TotalLicenses
{
get => _totalLicenses;
set
{
_totalLicenses = value;
OnPropertyChanged();
}
}
public int TotalScans
{
get => _totalScans;
set
{
_totalScans = value;
OnPropertyChanged();
}
}
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
SumConverter
namespace WpfApp2
{
#region Using
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Windows.Data;
#endregion
public class SumConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
int sum = 0;
if (value is IEnumerable<object> total)
{
foreach (object o in total)
{
if (o is OverView overview)
{
int col = int.Parse((string)parameter);
sum += col == 1 ? overview.LicenseCount : overview.ScansCount;
}
}
return sum;
}
return 0;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => 1;
}
}
Thanks in advance!
Edit, after further test, I had kind of success creating a new Property in the Overview object to group by it, then use a converter to sum the columns I need.
This is the result: The code is updated with the solution.
You cannot integrate it into the listview but you can put it below like this:
<StackPanel>
<GridViewRowPresenter DockPanel.Dock="Bottom"
Content="Sum"
Columns="{Binding ElementName=OverviewGridView, Path=Columns}">
</GridViewRowPresenter>
<ListView
HorizontalAlignment="Stretch"
ItemsSource="{Binding DataList}">
<ListView.View>
<GridView x:Name="OverviewGridView">
<GridViewColumn DisplayMemberBinding="{Binding Date}" Header="Date"/>
<GridViewColumn DisplayMemberBinding="{Binding LicenseCount}" Header="Licenses"/>
<GridViewColumn DisplayMemberBinding="{Binding ScansCount}" Header="Scans"/>
</GridView>
</ListView.View>
</ListView>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding ElementName=OverviewGridView, Path=Columns[0].ActualWidth}"/>
<ColumnDefinition Width="{Binding ElementName=OverviewGridView, Path=Columns[1].ActualWidth}"/>
<ColumnDefinition Width="{Binding ElementName=OverviewGridView, Path=Columns[2].ActualWidth}"/>
</Grid.ColumnDefinitions>
<TextBlock Text="Summe" Grid.Column="0" Margin="10,0"/>
<TextBlock Text="{Binding DataList, FallbackValue=0,Converter={StaticResource Summierer}, ConverterParameter=1}" Margin="10,0" Grid.Column="1"/>
<TextBlock Text="{Binding DataList, FallbackValue=0,Converter={StaticResource Summierer}, ConverterParameter=2}" Margin="10,0" Grid.Column="2"/>
</Grid>
</StackPanel>
this is my converter (needs rewriting!! I didnt put any effort in it)
public class SummUp: IValueConverter
{
#region Convert
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var rtn = 0;
var coll = (value as ObservableCollection<OverView>);
var param = System.Convert.ToInt32(parameter);
foreach (var item in coll)
{
if (param == 1)
{
rtn += item.LicenseCount;
}
else if (param == 2)
{
rtn += item.ScansCount;
}
}
return rtn;
}//END Convert
#endregion Convert
#region ConvertBack
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}//END ConvertBack
#endregion ConvertBack
}//END class boolToNotVisibilityConverter : IValueConverter

UserControl having binding that propagates to inner control

This is probably not a rocket science question, so forgive me for being a newcomer!
I have a UserControl that is for setting the name of a person (simple for test purposes).
PersonNameControl.xaml:
<UserControl x:Class="BindingPropagationTest.Controls.PersonNameControl"
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"
mc:Ignorable="d" Width="120" Height="23" Margin="0,0,0,0"
>
<TextBox Name="TextBox" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
</UserControl>
and as you can see, it contains a TextBox that is the "real" textbox. The code behind looks like this.
PersonNameControl.xaml.cs:
using System.Windows.Controls;
using System.Windows;
using System.Diagnostics;
namespace BindingPropagationTest.Controls
{
public partial class PersonNameControl : UserControl
{
public static DependencyProperty nameProperty
= DependencyProperty.Register("PersonName", typeof(string), typeof(PersonNameControl));
public string PersonName
{
get
{
Debug.WriteLine("get PersonNameControl.PersonName = " + TextBox.Text);
return TextBox.Text;
}
set
{
Debug.WriteLine("set PersonNameControl.PersonName = " + value);
TextBox.Text = value;
}
}
public PersonNameControl()
{
InitializeComponent();
}
}
}
I use the usercontrol in MainWindow.xaml:
<Window x:Class="BindingPropagationTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:my="clr-namespace:BindingPropagationTest.Controls"
xmlns:items="clr-namespace:BindingPropagationTest.ComboBoxItems"
Title="Testing binding in UserControl"
Width="179" Height="310">
<Canvas Height="241" Width="144">
<Label Canvas.Left="11" Canvas.Top="10" Content="Name" Height="28" Padding="0" />
<my:PersonNameControl x:Name="nameControl"
Width="120" Height="23"
HorizontalAlignment="Left" VerticalAlignment="Top"
PersonName="{Binding name}"
Canvas.Left="11" Canvas.Top="28" />
<Label Canvas.Left="11" Canvas.Top="57" Content="Address" Height="28" Padding="0" />
<TextBox Canvas.Left="11" Canvas.Top="75" Width="120" Text="{Binding address}"></TextBox>
<Label Canvas.Left="11" Canvas.Top="103" Content="Age" Height="28" Padding="0" />
<TextBox Canvas.Left="11" Canvas.Top="122" Height="23" Name="textBox1" Width="120" Text="{Binding age}" />
<ComboBox Canvas.Left="11" Canvas.Top="173" Height="23"
Name="comboBox1" Width="120" SelectionChanged="comboBox1_SelectionChanged">
<items:PersonComboBoxItem age="41" name="Donald Knuth" address="18 Donut Street" Height="23" />
<items:PersonComboBoxItem age="23" name="Vladimir Putin" address="15 Foo Street" Height="23" />
<items:PersonComboBoxItem age="32" name="Mike Hammer" address="10 Downing Street" Height="23" />
</ComboBox>
</Canvas>
</Window>
together with some normal TextBoxes as you can see.
In the code behind for MainWindow
MainWindow.xaml.cs:
using System.Windows;
using System.Windows.Controls;
using BindingPropagationTest.ComboBoxItems;
namespace BindingPropagationTest
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new Person();
}
private void comboBox1_SelectionChanged
(object sender, SelectionChangedEventArgs e)
{
updateForm();
}
private void updateForm()
{
var item = (PersonComboBoxItem)comboBox1.SelectedItem;
DataContext = new Person()
{ age = item.age, name = item.name, address = item.address };
}
}
}
you see that I set DataContext to a "person".
Person.cs:
namespace BindingPropagationTest
{
public class Person
{
public string name {get; set; }
public int age { get; set; }
public string address { get; set; }
}
}
and as you notice I have invented an own ComboBoxItem like this.
PersonComboBoxItem.cs:
using System.Windows.Controls;
using System.Diagnostics;
namespace BindingPropagationTest.ComboBoxItems
{
public class PersonComboBoxItem : ComboBoxItem
{
private string _name = "";
public string name
{
get
{
return _name;
}
set
{
_name = value;
Content = _name;
}
}
public int age { get; set; }
public string address { get; set; }
public override string ToString()
{
Debug.WriteLine("name: " + name);
return name;
}
}
}
Running this application gives you this window:
And selecting a combobox item gives you this:
and as you can see, the name will not be filled in.
Why not?
You are almost there, a few things you need to change
Dependency properties are used like
public static DependencyProperty NameProperty = DependencyProperty.Register(
"Name",
typeof(string),
typeof(PersonNameControl));
public string Name
{
get
{
return (string)GetValue(NameProperty);
}
set
{
SetValue(NameProperty, value);
}
}
There is a very strict convention that is necessary for dependency properties. It must be called "NameProperty" if the Property is called Name. Also the property just sets and gets the dependency property.
You can them Bind the textbox to the property in the usercontrol like
<UserControl x:Class="BindingPropagationTest.Controls.PersonNameControl"
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"
x:Name="UserControl"
mc:Ignorable="d" Width="120" Height="23" Margin="0,0,0,0"
>
<TextBox Text="{Binding ElementName=UserControl, Path=Name}" Name="TextBox" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
</UserControl>
I added the x:Name="UserControl" directive at the top, you could name it anything, as long as it matches the ElementName in the Binding
An extra note on how you are populating your comboBox
You could add a property on your MainWindow
private ObservableCollection<Person> _thePeople;
public ObservableCollection<Person> ThePeople
{
get
{
if (_thePeople == null)
{
List<Person> list = new List<Person>()
{
new Person() { name = "Bob" , address = "101 Main St." , age = 1000 },
new Person() { name = "Jim" , address = "101 Main St." , age = 1000 },
new Person() { name = "Mip" , address = "101 Main St." , age = 1000 },
};
_thePeople = new ObservableCollection<Person>(list);
}
return _thePeople;
}
}
then you can add to your main window the x:Name directive used on the usercontrol say
x:Name="TheMainWindow"
You can then use a datatemplate in your combobox as follows
<ComboBox ItemsSource="{Binding ElementName=TheMainWindow, Path=ThePeople}"
Height="23"
Name="comboBox1" Width="120" >
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding name}" />
<TextBlock Text="{Binding address}" />
<TextBlock Text="{Binding age}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Because ObservableCollection was used in the code-behind the combobox will automatically get items added or removed whenever the code-behind adds or removes items from the "ThePeople" collection. Just call
ThePeople.Add(new Person());
and the combobox will automatically populate with a new entry

Categories