WPF Listview group by sub class - c#

I have a class that contains a subclass
public class CustomerDate
{
public string Date { set; get; }
public Customerdetails _Customerdetails { set; get; }
public CustomerDate()
{
_Customerdetails = new Customerdetails();
}
}
public class Customerdetails
{
public int Id { set; get; }
public string Name { set; get; }
public Customerdetails() { }
}
I Have a list of CustomerDate Objects that I want to bind to a listview grouped by subClass(Customerdetails).
My problem is that I can show SubClass property in listview's Gridview ({Binding Customerdetails.Name}) but I cannot show SubClass details in GroupStyle section.
<TextBlock Text="{Binding Path=Customerdetails.Name}" />
Not working But
<GridViewColumn DisplayMemberBinding="{Binding Customerdetails.Name}"
is working.
Any Idea?
List<CustomerDate> CustomerDateList = new List<CustomerDate>();
.
.
.
lv.ItemsSource = DBAccess.GetBadMonthlyPaymentCustomers();
CollectionView view =
(CollectionView)CollectionViewSource.GetDefaultView(lv.ItemsSource);
PropertyGroupDescription gd = new PropertyGroupDescription("Customerdetails");
view.GroupDescriptions.Add(gd);
<ListView x:Name="lv">
<ListView.View>
<GridView x:Name="GridView" >
<GridViewColumn DisplayMemberBinding="{Binding Date}" />
<GridViewColumn DisplayMemberBinding="{Binding Customerdetails.Name}" />
</GridView>
</ListView.View>
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<StackPanel Margin="0,10,0,0" Orientation="Horizontal">
<TextBlock Text="{Binding Path=Customerdetails.Name}" />
</StackPanel>
<ItemsPresenter Grid.Row="1" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ListView.GroupStyle>
</ListView>

The DataContext of a GroupItem is a CollectionViewGroup and not Customerdetails.
A group may contain several Customerdetails. You can bind to the Name property of any of them using the Items property of the CollectionViewGroup, e.g.:
<TextBlock Text="{Binding Items[0].Name}" />

This two solutions work for me thank to #mm8 :
<local:NameConverter x:Key="NameConverter"/>
<TextBox Text="{Binding Converter={StaticResource NameConverter}, Mode=OneWay}"
public class NameConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return ((CustomerDate)((CollectionViewGroup)value).Items[0]).Customerdetails.Name;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Items[0] is an Object of Class CustomerDate so its working this way too:
<TextBlock Text="{Binding Items[0].Customerdetails.Name}" />

Related

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

ListView checkbox to visible or hide textbox in same row

I have a listview with a column have checkbox and other column having textbox, with multiple row in the listview.
I want to set visible and hide property of textbox depending on checkbox checked and unchecked.
Its a WPF project using MVVM pattern and PRISM.
Please help, I am stuck.
<ListView Width="Auto" ItemsSource="{Binding Path=PayFeeDetails}">
<ListView.View>
<GridViewColumn Header="Description" Width="110" >
<GridViewColumn.CellTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Path=Description}"/>
<CheckBox Grid.Column="1" Width="30" Name="CommentCheckBox"/>
</Grid>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Width="140">
<GridViewColumnHeader Tag="GameName" Content="Game Name" />
<GridViewColumn.CellTemplate>
<DataTemplate>
<Grid>
<TextBox Text="{Binding Path=Balance}"/>
<TextBlock Text="{Binding Path=Balance}"/>
</Grid>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
You would need to add a binding to the Visibility of second Gridview Grid.
Now on Gridview1 itemclick create a INotify property change and update that particular item's Grid Visibility from the collection thats been set as source in second grid.
I would go this route
1) Create an object with a property that lets me know I can edit.
public class ItemToBindTo:INotifyPropertyChanged
{
#region Fields
public event PropertyChangedEventHandler PropertyChanged;
private bool _canEdit;
private string _description;
private string _balance;
#endregion
#region Properties
public bool CanEdit
{
get
{
return _canEdit;
}
set
{
_canEdit = value;
OnPropertyChanged("CanEdit");
}
}
public string Description
{
get
{
return _description;
}
set
{
_description = value;
OnPropertyChanged("Description");
}
}
public string Balance
{
get
{
return _balance;
}
set
{
_balance = value;
OnPropertyChanged("Balance");
}
}
#endregion
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
2) I will create converters for visibility
public class BoolToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var val = value as bool? ;
return val.HasValue && val.Value ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
public class InvertedBoolToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var val = value as bool?;
return !(val.HasValue && val.Value) ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
I will have my converters as static resources
<sigColor:InvertedBoolToVisibilityConverter x:Key="InvertedBoolToVisibilityConverter"/>
<sigColor:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
And this will be your new xaml
<ListView Width="Auto" ItemsSource="{Binding Path=PayFeeDetails}">
<ListView.View>
<GridView>
<GridViewColumn Header="Description" Width="110" >
<GridViewColumn.CellTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Path=Description}"/>
<CheckBox Grid.Column="1" Width="30" Name="CommentCheckBox" IsChecked="{Binding CanEdit, Mode=TwoWay}"/>
</Grid>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn >
<GridViewColumnHeader Tag="GameName" Content="Game Name" />
<GridViewColumn.CellTemplate>
<DataTemplate>
<Grid>
<TextBox Text="{Binding Path=Balance}" Visibility="{Binding CanEdit, Converter={StaticResource BoolToVisibilityConverter}}"/>
<TextBlock Text="{Binding Path=Balance}" Visibility="{Binding CanEdit, Converter={StaticResource InvertedBoolToVisibilityConverter}}"/>
</Grid>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>

WPF DataGrid Grouping with sums and other fields

I have a DataGrid that is bound to collection and that I want to be grouped. Here is the code
Collection:
private string _ID;
private string _Descript;
private decimal _Amount;
public string ID
{
get { return _ID; }
set { _ID = value; NotifyPropertyChanged("ID"); }
}
public decimal Amount
{
get { return _Amount; }
set { _Amount = value; NotifyPropertyChanged("Amount"); }
}
public string Descript
{
get { return _Descript; }
set { _Descript = value; NotifyPropertyChanged("Descript"); }
}
C#;
ListCollectionView groupcollection = new ListCollectionView(myCollection);
groupcollection.GroupDescriptions.Add(new PropertyGroupDescription("ID"));
myDataGrid.ItemsSource = groupcollection;
XAML:
<DataGrid Name="myDataGrid">
<DataGrid.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=Name}" />
</StackPanel>
</DataTemplate>
</GroupStyle.HeaderTemplate>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander>
<Expander.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" Margin="5"/>
<TextBlock Text="Count" Margin="5" />
<TextBlock Text="{Binding Path=ItemCount}" Margin="5"/>
</StackPanel>
</Expander.Header>
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</DataGrid.GroupStyle>
This works perfectly but now in the Expander.Header I want to added a summary of a "Amount" and "Descript" value. So for example if there were 3 records in the collection with ID "ABC" each one being 20 and the description for ABC being "My Count" I would want to see;
ABC My Count total 60
How would I do that?
You could use a converter that's passed the Items property of the group header e.g.
<Window.Resources>
<local:GroupsToTotalConverter x:Key="groupsConverter" />
</Window.Resources>
<Expander.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" Margin="5"/>
<TextBlock Text="total" Margin="5" />
<TextBlock Text="{Binding Path=Items, Converter={StaticResource groupsConverter}}" Margin="5" />
</StackPanel>
where the converter performs the calculation and passes back the total as the string for the text block:
public class GroupsToTotalConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is ReadOnlyObservableCollection<Object>)
{
var items = (ReadOnlyObservableCollection<Object>)value;
Decimal total = 0;
foreach (GroupItem gi in items)
{
total += gi.Amount;
}
return total.ToString();
}
return "";
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value;
}
}
As for the description I'd suggest also grouping by that, and writing another converter to pull out the description from the Items in a similar manner to above.

wpf editable ListView

I am trying to make a field in ListView editable.
I mean when I select row in listView one field becomes textbox instead of textblock.
But it doesnt work - I see both controls all the time.
xaml:
<Window x:Class="fastnn_speedTest.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:local="clr-namespace:fastnn_speedTest"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Title="fastnn: not saved" Height="300" Width="300">
<Window.Resources>
<local:BoolToVisibilityConverter x:Key="VisibilityOfBool" />
<local:StringToIntValueConverter x:Key="StringToInt" />
<local:StringToFloatValueConverter x:Key="StringToFloat" />
<ObjectDataProvider MethodName="GetValues" ObjectType="{x:Type sys:Enum}" x:Key="ActivationEnum">
<ObjectDataProvider.MethodParameters>
<x:Type x:TypeName="local:Network+Layer+ActivFunction" />
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
<ObjectDataProvider MethodName="GetValues" ObjectType="{x:Type sys:Enum}" x:Key="ComputeMethodEnum">
<ObjectDataProvider.MethodParameters>
<x:Type x:TypeName="local:Training+ComputeMethodEnum" />
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
<Style TargetType="{x:Type TextBlock}" x:Key="ListTextBlockStyle">
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Visibility" Value="{Binding Path=IsSelected, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListViewItem}}, Converter={StaticResource VisibilityOfBool}, ConverterParameter=True}" />
</Style>
<Style TargetType="{x:Type FrameworkElement}" x:Key="ListTextBoxStyle">
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Visibility" Value="{Binding Path=IsSelected, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListViewItem}}, Converter={StaticResource VisibilityOfBool}, ConverterParameter=False}" />
</Style>
<DataTemplate x:Key="ActivationTemplate">
<ComboBox ItemsSource="{Binding Source={StaticResource ActivationEnum}}" SelectedValue="{Binding Path=Activation}" Width="100"/>
</DataTemplate>
<DataTemplate x:Key="NeuronsTemplate">
<Grid>
<TextBox Text="{Binding Path=Neurons}" Width="40" Style="{Binding Source=StaticResource ListTextBoxStyle }"></TextBox>
<TextBlock Text="{Binding Path=Neurons}" Width="40" Style="{Binding Source=StaticResource ListTextBlockStyle }"></TextBlock>
</Grid>
</DataTemplate>
</Window.Resources>
<Grid Background="LightGray">
<Grid.RowDefinitions>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
</Grid.RowDefinitions>
<Menu Grid.Row="0">
<MenuItem Header="File">
<MenuItem Header="New" Click="MenuNew_Click"></MenuItem>
<MenuItem Header="Open" Click="MenuOpen_Click"></MenuItem>
<Separator></Separator>
<MenuItem Header="Save" Click="MenuSave_Click"></MenuItem>
<MenuItem Header="Save As" Click="MenuSaveAs_Click"></MenuItem>
<Separator></Separator>
<MenuItem Header="Exit"></MenuItem>
</MenuItem>
</Menu>
<TabControl Grid.Row="1" Name="Tabs">
<TabItem Header="Network">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
</Grid.RowDefinitions>
<ListView Grid.Row="0" x:Name="NetworkListview" ItemsSource="{Binding network.Layers}" IsSynchronizedWithCurrentItem="True">
<ListView.View>
<GridView>
<GridViewColumn Width="100" Header="layer name" DisplayMemberBinding="{Binding Name}"/>
<GridViewColumn Width="60" Header="neurons" CellTemplate="{StaticResource NeuronsTemplate}"/>
<GridViewColumn Width="110" Header="activation" CellTemplate="{StaticResource ActivationTemplate}"/>
</GridView>
</ListView.View>
</ListView>
<UniformGrid Grid.Row="1" Rows="1">
<Button Name="AddLayerButton" Click="AddLayerButton_Click">Add layer</Button>
<Button Name="RemoveLayerButton" Click="RemoveLayerButton_Click">Remove layer</Button>
</UniformGrid>
</Grid>
</TabItem>
<TabItem Header="Data set">
<UniformGrid Columns="2" Height="70" Width="220">
<Label>input dimmension</Label>
<TextBox Text="{Binding Path=data_set.InputDimmension, Mode=TwoWay, Converter={StaticResource StringToInt}}"></TextBox>
<Label>output dimmension</Label>
<TextBox Text="{Binding Path=data_set.OutputDimmension, Mode=TwoWay, Converter={StaticResource StringToInt}}"></TextBox>
<Label>number of samples</Label>
<TextBox Text="{Binding Path=data_set.SamplesNumber, Mode=TwoWay}"></TextBox>
</UniformGrid>
</TabItem>
<TabItem Header="Training">
<UniformGrid Columns="2" Width="230" Height="170">
<Label>learning rate</Label>
<TextBox Text="{Binding Path=training.LearningRate, Mode=TwoWay, Converter={StaticResource StringToFloat}}"></TextBox>
<Label>epochs</Label>
<TextBox Text="{Binding Path=training.Epochs, Mode=TwoWay, Converter={StaticResource StringToInt}}"></TextBox>
<Label>weights stddev</Label>
<TextBox Text="{Binding Path=training.WeightsStddev, Mode=TwoWay, Converter={StaticResource StringToFloat}}"></TextBox>
<Label>srand(0)</Label>
<CheckBox Margin="0,4,0,0" IsChecked="{Binding Path=training.SrandZero}"></CheckBox>
<Label>compute method</Label>
<ComboBox Name="ComputeMethodCombo" ItemsSource="{Binding Source={StaticResource ComputeMethodEnum}}" SelectedValue="{Binding Path=training.ComputeMethod}"></ComboBox>
<Label>select device</Label>
<ComboBox Name="DeviceComboBox" SelectedIndex="{Binding Path=training.DeviceIndex}"></ComboBox>
<Label>click to run test</Label>
<Button Margin="2" Name="RunTestButton" Click="RunTestButton_Click">Run test</Button>
</UniformGrid>
</TabItem>
<TabItem Header="Output">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
</Grid.RowDefinitions>
<TextBox Grid.Row="0" Name="LogTextBox"></TextBox>
<UniformGrid Grid.Row="1" Rows="1">
<Button>Save</Button>
<Button Name="ClearButton" Click="ClearButton_Click">Clear</Button>
<Button>Eerror chart</Button>
<Button>Speed chart</Button>
</UniformGrid>
</Grid>
</TabItem>
</TabControl>
<StatusBar Grid.Row="2">
<StatusBarItem>
<TextBlock>text</TextBlock>
</StatusBarItem>
</StatusBar>
</Grid>
converter:
public class BoolToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
bool param = bool.Parse(parameter as string);
bool val = (bool)value;
return val == param ? Visibility.Visible : Visibility.Hidden;
}
public object ConvertBack(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
and a Network class which contains ObservableCollection which is bound to listview:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using System.Xml.Serialization;
namespace fastnn_speedTest
{
public class Network
{
public class Layer : INotifyPropertyChanged
{
public enum ActivFunction { LINEAR, EXPONENTIAL, ARCUSTANGENT }
private string name;
[XmlIgnore]
public string Name
{
get
{
return name;
}
set
{
name = value;
RaisePropertyChanged("Name");
}
}
[XmlAttribute]
public ActivFunction Activation { get; set; }
[XmlAttribute]
public int Neurons { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void RaisePropertyChanged(String propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public ObservableCollection<Layer> Layers { get; set; }
public Network()
{
Layers = new ObservableCollection<Layer>();
}
public void AddLayer(Layer layer)
{
int last = Layers.Count;
if (last > 0)
{
Layers.Last().Name = "Layer " + last + " (hidden)";
}
layer.Name = "Layer " + (last + 1) + " (output)";
Layers.Add(layer);
}
public void RemoveLayer(int index)
{
if( index >= 0 && index < Layers.Count )
Layers.RemoveAt(index);
}
public void Clear()
{
Layers.Clear();
}
public void Validate()
{
if (Layers.Count < 1)
throw new ArgumentException("minimum number of layers is 1");
foreach (Layer layer in Layers)
{
if (layer.Neurons <= 0)
throw new ArgumentException("neurons in each layer must be > 0");
}
if(Layers.Last().Activation != Layer.ActivFunction.LINEAR)
throw new ArgumentException("last layer must be linear");
}
}
}
Have a look at the below links
http://www.switchonthecode.com/tutorials/wpf-tutorial-using-the-listview-part-3-in-place-edit
EDIT
You may have followed the tut but couple of things you missed is what causing the probs.First of all you are trying to use the inbuilt BooleanToVisibility converter but i think in your scenario its not gonna help.You have to crate a custom value converter like mentioned in the tutorial.
public class BoolToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
bool param = bool.Parse(parameter as string);
bool val = (bool)value;
return val == param ? Visibility.Visible : Visibility.Hidden;
}
public object ConvertBack(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Also you have to mention your converter like this
<Setter Property="Visibility" Value="{Binding Path=IsSelected,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ListViewItem}},
Converter={StaticResource VisibilityOfBool}, ConverterParameter=False}" />
Make these changes and you are good to go....
Why don't you use the data grid? You can style it to look like a list and it already provides the edit mode functionality. You can use a TemplateColumn to provide custom display and editing views.

Categories