I would like to sort a ListView Column by MultiConverter Output
I am using a ListView and its GridViewColumns to display data from a Binding.
A handler "SortClickUniversal()" was added to the ListView, which in turn calls "Sort()"
If "Sort()" finds the Content String of the GridViewColumnHeader above it creates a SortDescription by the binding Element ("WartVPreis") and adds it to the Default View of the ListView ItemSource
This works fine for a simple Binding ("WartVPreis"). But I additionally have a Column which is filled by a Multibinding Converter:
<GridViewColumn Width="110" >
<GridViewColumn.Header>
<GridViewColumnHeader Content="WV Aktuell Netto €"
</GridViewColumnHeader>
</GridViewColumn.Header>
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock
Text="{Binding WartVPreis, ConverterCulture=de-DE, StringFormat={}{0:F2}}"
HorizontalAlignment="Right"
/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
C#
private void SortClickUniversal(object sender, RoutedEventArgs e)
{
//...
var sortBy = (e.OriginalSource as GridViewColumnHeader).Content.ToString();
bool sortieren = Sort(sortBy, direction, sender);
//...
}
private bool Sort(string sortBy, ListSortDirection direction, object sender)
{
switch (sortBy)
{
//...
case "WV Aktuell Netto €": sortBy = "WartVPreis"; break;
//...
SortDescription sd = new SortDescription(sortBy, direction);
dataView.SortDescriptions.Add(sd);
}
}
XAML
<GridViewColumn Width="90" >
<GridViewColumn.Header>
<GridViewColumnHeader Content="WV Ant. Net €"></GridViewColumnHeader>
</GridViewColumn.Header>
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock HorizontalAlignment="Right" >
<TextBlock.Text>
<MultiBinding Converter="{StaticResource MultiWVAnteilConverterKey}" ConverterCulture="de-DE" StringFormat="{}{0:F2}">
<Binding Path="WartVPreis" />
<Binding Path="Dtvon" />
<Binding Path="Dtbis" />
<Binding Path="WartVZyklus" />
<Binding Path="WartVBJVON" />
<Binding Path="WartVBJBIS" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
How can I sort the Column by the MultiConverter Output with its MultiBinding?
I have a WPF application that uses a ListView with a grid that displays images directly from the web. When the list is populated the images load as expected, but as I scroll down (the list contains around 200 items on average) it starts reusing the items that aren't in view (as it should). However, this causes the images to be released from memory and as a result they get reloaded all over again when the user scrolls back up.
MainWindow.xaml
<ListView Grid.Row="3" ItemsSource="{Binding SearchResults}" Background="{StaticResource PrimaryBackground}" Foreground="{StaticResource PrimaryForeground}"
ui:GridViewSort.AutoSort="True" ui:GridViewSort.ShowSortGlyph="False" IsSynchronizedWithCurrentItem="True" VirtualizingStackPanel.IsVirtualizing="False">
<ListView.View>
<GridView>
<GridViewColumn Width="80">
<GridViewColumn.CellTemplate>
<DataTemplate DataType="{x:Type Foo}">
<Image>
<Image.Source>
<BitmapImage CacheOption="OnDemand" UriSource="{Binding PreviewImageUrl}" />
</Image.Source>
</Image>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Title" DisplayMemberBinding="{Binding Title}" ui:GridViewSort.PropertyName="Title" />
<GridViewColumn Header="Remix" DisplayMemberBinding="{Binding Remix}" ui:GridViewSort.PropertyName="Remix" />
<GridViewColumn Header="Artist" DisplayMemberBinding="{Binding Artist}" ui:GridViewSort.PropertyName="Artist" />
<GridViewColumn Header="Duration" DisplayMemberBinding="{Binding Duration}" ui:GridViewSort.PropertyName="Duration" />
<GridViewColumn Header="BPM" DisplayMemberBinding="{Binding Bpm}" ui:GridViewSort.PropertyName="Bpm" />
<GridViewColumn Header="Year" DisplayMemberBinding="{Binding Date}" ui:GridViewSort.PropertyName="Date" />
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<StackPanel.Resources>
<Style TargetType="Button">
<Setter Property="Margin" Value="0,0,10,0" />
</Style>
</StackPanel.Resources>
<Button Command="{Binding ElementName=Window, Path=DataContext.Download}" CommandParameter="{Binding}">Download</Button>
<Button Command="{Binding ElementName=Window, Path=DataContext.CopyLink}" CommandParameter="{Binding}">Copy link</Button>
</StackPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
Setting the Image.Source property with a different CacheOption makes no difference. You can also see I disabled virtualization which is bad but it's the only way to have it keep the images in memory. Is there an easy way to stop this from happening while also enabling virtualization?
Add a readonly PreviewImage property to your search results item class that creates and holds the BitmapImage when it is first accessed:
public class SearchResult : INotifyPropertyChanged
{
private Uri previewImageUrl;
public Uri PreviewImageUrl
{
get { return previewImageUrl; }
set
{
previewImageUrl = value;
previewImage = null;
NotifyPropertyChanged(nameof(PreviewImageUrl));
NotifyPropertyChanged(nameof(PreviewImage));
}
}
private ImageSource previewImage;
public ImageSource PreviewImage
{
get
{
if (previewImage == null && previewImageUrl != null)
{
previewImage = new BitmapImage(previewImageUrl);
}
return previewImage;
}
}
...
}
and bind to it like this:
<GridViewColumn.CellTemplate>
<DataTemplate>
<Image Source="{Binding PreviewImage}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
Try this:
<Image
HorizontalOptions="CenterAndExpand"
VerticalOptions ="CenterAndExpand">
<Image.Source>
<UriImageSource Uri="{Binding Image}"
CacheValidity="14"
CachingEnabled="true"/>
</Image.Source>
</Image>
How do I get my listview to do both grouping and filtering because I can only get it to do one of the thing at once. I had tried almost everything I could but none of it worked. when i remove
public string SelectedParam { get { return _selectedParam; } set { _selectedParam = value; OnPropertyChanged("SelectedParam");
if (_selectedParam == "Krydsmål") { BindData(); } else { hjuldata.ItemsSource = FilterKategori().Tables[0].DefaultView; ; } } }
then the grouping works but the filtering doesn't
i wonder if i can use the sql for filling instead for both filling and filtering and then get the listview to do the filtering like you can do with manualy added items
My combobox for filtering:
<ComboBox x:Name="Krydsmålbox" Foreground="#FFEAEAEA" Background="#FF303030" FontSize="12"
Style="{StaticResource ComboBoxTest2}" ItemTemplate="{StaticResource cmbTemplate2}"
ItemsSource="{Binding}" SelectedValuePath="Krydsmålene"
SelectedValue = "{Binding SelectedParam, RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Window}},UpdateSourceTrigger=PropertyChanged}" BorderBrush="#FF303030" Height="40" DockPanel.Dock="Top" Margin="586,42,379,0"/>
Listview
<ListView x:Name="hjuldata" BorderBrush="#FF303030" Foreground="#FF00FB0B" ScrollViewer.VerticalScrollBarVisibility="Auto" ScrollViewer.HorizontalScrollBarVisibility="Auto" Background="#FF303030" ItemsSource="{Binding}" Margin="-160,242,11,22" Grid.ColumnSpan="6" Grid.Row="3" Style="{DynamicResource ListViewStyle2}" DockPanel.Dock="Bottom" Height="576">
<ListView.View>
<GridView>
<GridView.ColumnHeaderContainerStyle>
<Style TargetType="{x:Type GridViewColumnHeader}">
<Setter Property="Background" Value="Black" />
<Setter Property="Foreground" Value="#FFEAEAEA"/>
<Setter Property="FontWeight" Value="Bold" />
</Style>
</GridView.ColumnHeaderContainerStyle>
<GridViewColumn Header="" >
<GridViewColumn.CellTemplate>
<DataTemplate>
<Image Source="{Binding Billed, Converter={StaticResource nullImageConverter}}" Width="20" Height="20" Stretch="Fill" VerticalAlignment="Center" HorizontalAlignment="Center" Margin="0,0,15,0"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Model" Width="140" >
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock x:Name="Txt" Text="{Binding Model}" Foreground="#FF00FB0B" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Årgang" Width="100">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock x:Name="Txt" Text="{Binding Årgang}" Foreground="#FF00FB0B" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Motor Type" Width="150" >
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock x:Name="Txt" Text="{Binding [Motor Type]}" Foreground="#FF00FB0B" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock x:Name="Txt" Text="{Binding Krydsmålet}" Foreground="#FF00FB0B" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock x:Name="Txt" Text="{Binding Centerhul}" Foreground="#FF00FB0B" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="ET" Width="auto">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock x:Name="Txt" Text="{Binding ET}" Foreground="#FF00FB0B" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Bolter" Width="100">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock x:Name="Txt" Text="{Binding Bolter}" Foreground="#FF00FB0B" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Dæk" Width="300">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock x:Name="Txt" Text="{Binding Dæk}" Foreground="#FF00FB0B" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Fælge" Width="200">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock x:Name="Txt" Text="{Binding Fælge}" Foreground="#FF00FB0B" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
grouping style
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Expander IsExpanded="False" BorderBrush="#FFEAEAEA" BorderThickness="0,0,0,1" >
<Expander.Header>
<StackPanel Orientation="Horizontal" DataContext="{Binding Items}">
<Image Source="{Binding Billed, Converter={StaticResource nullImageConverter}}" Width="20" Height="20" Stretch="Fill" VerticalAlignment="Center" Margin="0,0,15,0"/>
<TextBlock Text="{Binding Mærke}" FontWeight="Bold" Foreground="#FFEAEAEA" FontSize="22" VerticalAlignment="Bottom" />
<TextBlock Text="{Binding Krydsmålene}" FontWeight="Bold" Foreground="#FFFBFB00" FontSize="22" VerticalAlignment="Bottom" Margin="0,0,150,0" TextAlignment="Center" />
</StackPanel>
</Expander.Header>
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ListView.GroupStyle>
</ListView>
CS:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _selectedParam;
public MainWindow()
{
InitializeComponent();
BindData();
ICollectionView dataView = CollectionViewSource.GetDefaultView(hjuldata.ItemsSource);
dataView.GroupDescriptions.Add(new PropertyGroupDescription("Mærke"));
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public string SelectedParam { get { return _selectedParam; } set { _selectedParam = value; OnPropertyChanged("SelectedParam");
if (_selectedParam == "Krydsmål") { BindData(); } else { hjuldata.ItemsSource = FilterKategori().Tables[0].DefaultView; ; } } }
private void BindData()
{
hjuldata.ItemsSource = Kategori().Tables[0].DefaultView;
}
public DataSet Kategori()
{
Data = #"Select ps.Mærket AS Mærke, P.DataID, P.Billed, P.Model, P.Årgang, P.[Motor Type], P.Krydsmålet, P.Centerhul, P.ET,P.Bolter, P.Dæk, P.Fælge ,PS.Krydsmålene from Data.Hjuldata P inner join Data.Mærke PS on P.MærkeID = PS.MærkeID ORDER BY ps.Mærket";
//SQL statement to fetch entries from Hjuldata
DataSet dsdata = new DataSet();
//Open SQL Connection
using (conn = new SqlConnection(connStrings))
{
conn.Open();
//Initialize command object
using (cmd = new SqlCommand(Data, conn))
{
SqlDataAdapter adapters = new SqlDataAdapter(cmd);
//Fill the result set
adapters.Fill(dsdata);
conn.Close();
}
}
return dsdata;
}
public DataSet FilterKategori()
{
Data = #"Select ps.Mærket AS Mærke, P.DataID, P.Billed, P.Model, P.Årgang, P.[Motor Type], P.Krydsmålet, P.Centerhul, P.ET,P.Bolter, P.Dæk, P.Fælge ,PS.Krydsmålene from Data.Hjuldata P inner join Data.Mærke PS on P.MærkeID = PS.MærkeID WHERE Krydsmålet = #param1";
//SQL statement to fetch entries from products
DataSet dsdata = new DataSet();
//Open SQL Connection
using (conn = new SqlConnection(connStrings))
{
conn.Open();
//Initialize command object
using (cmd = new SqlCommand(Data, conn))
{
cmd.Parameters.AddWithValue("#param1", SelectedParam);
SqlDataAdapter adapters = new SqlDataAdapter(cmd);
//Fill the result set
adapters.Fill(dsdata);
conn.Close();
}
}
return dsdata;
}
Can somebody help me with this please?
I think your problem is that you are using the ADO.NET data view abstraction rather than the WPF data view abstraction which should be easier to use. In WPF when you bind a collection or DataTable to an ItemsControl a data view object is created that basically serves as a layer over your collection or DataTable. By doing this you can potentially have the same collection bound to multiple ItemsControls but have different "views" of that data by having different filtering and grouping for the two distinct data views that are created for that same collection.
In your case you are binding to a DataTable which is messier than binding just to a collection that implements IList. For DataTable there is a DataView class that is part of ADO.NET that your WPF data view will basically serve as a layer over and this DataView is more limited in functionality (this ADO.NET DataView is what you are using in your code currently).
Either way, to get the WPF data view you just need to ask the ItemsSource for it like so:
ICollectionView dataView = CollectionViewSource.GetDefaultView(myListView.ItemsSource);
Now you will want to cast the ICollectionView to something more useful for setting the filter and grouping. In your case you have a DataTable so you will want to cast that to a BindingListCollectionView. Unfortunately this is more limited than the data view you would get for an IList (which is a ListCollectionView), but you have a DataTable so we will just roll with it for now (I always go with IList so I have never actually implemented a DataTable binding in production before).
The BindingListCollectionView doesn't have an operational Filter property so you have to use its CustomFilter property to specify the partial SQL that you want to use to filter your collection (basically what you would have put in a WHERE clause). As far as grouping goes I haven't ever used it on a DataTable binding but I would hope it just works by updating GroupDescriptions on the data view.
So basically in summary I would grab the WPF data view abstraction rather than the ADO.NET data view abstraction that you are currently using and set the filter and grouping on that. Also I would recommend just bringing in your data as an IList or else converting it to an IList since those are easier to work with in WPF.
i think this is something you should read
How to: Group, Sort, and Filter Data in the DataGrid Control
i had tested that one and it works with your datastructure and you dont have to do anything with your filtering only change out the grouping part and colectionview
I am working with a WPF control that I created and I am trying to only show certain rows of my list by values of a property. An example is the following, I have a User class that holds a property of Active. How do I tell the .xaml that the list should only show the people that are Active?
Right now I am basically using linq to generate a new list and hand it to the listview based on what I want. However, I would rather just hand the ListView my entire list and let it do the work for me.
Here is my ListView code.
<ListView ItemsSource="{Binding}" DataContext="{Binding }" >
<ListView.View>
<GridView>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Index}"/>
<TextBlock Text=". " />
<TextBlock Text="{Binding FirstName}" />
<TextBlock Text="{Binding LastName}" />
</StackPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
You'll need some code behind to add a filter:
See: WPF filtering
ICollectionView view = CollectionViewSource.GetDefaultView(lstMovies.ItemsSource);
view.Filter = null;
view.Filter = new Predicate<object>(FilterMovieItem);
private bool FilterMovieItem(object obj)
{
MovieItem item = obj as MovieItem;
if (item == null) return false;
string textFilter = txtFilter.Text;
if (textFilter.Trim().Length == 0) return true; // the filter is empty - pass all items
// apply the filter
if (item.MovieName.ToLower().Contains(textFilter.ToLower())) return true;
return false;
}
I'm trying to use the event trigger from Blend to fire a button click event of a listview item, it should work so that the item does not have to be selected for the relevant row to be referenced.
My code is...
Public void MyCommand(object obj)
{
// the tag of this has the search type
ListViewItem item = obj as ListViewItem;
// do my dreary domain work...
}
my xaml is...
<ListView ItemsSource="{Binding Path=SystemSetupItems}"
SelectedItem="{Binding Selected, Mode=TwoWay}"
MinHeight="120" >
<ListView.View>
<GridView>
<GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}" />
<GridViewColumn Header="Description" DisplayMemberBinding="{Binding Description}" />
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<Button >
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseClick">
<i:InvokeCommandAction CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=ListViewItem, AncestorLevel=1}}" Command="{Binding MyCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
but this doesn't work at all, alternatively I can do this in my xaml button definition
<GridViewColumn.CellTemplate>
<DataTemplate>
<Button Command="{Binding OpenWorkSpaceCommand}" CommandParameter="{Binding Path=Name}" Content="Edit..." DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType=ListView}}" >
</Button>
</DataTemplate>
</GridViewColumn.CellTemplate>
but this required the listview item to be previously selected, which is not the behaviour I want.
For my DataGrid I have a button on each item using the cell template. Each item is an object of type Meal. In my Meal.cs file I have an event definition like so:
public Meal()
{
RemoveMealCommand = new RelayCommand(() => RemoveMealCommandExecute());
}
public RelayCommand RemoveMealCommand
{
get;
set;
}
public delegate void RemoveMealEventHandler(object sender, EventArgs e);
public event RemoveMealEventHandler RemoveMealEvent;
private void RemoveMealCommandExecute()
{
RemoveMealEvent(this, null);
}
In my viewmodel for every meal in my list I can just add a handler to that event. And for my xaml button I just set the command to the Meal's RelayCommand.
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Command="{Binding Path=RemoveMealCommand}">
<Image Width="13" Height="13" Source="/Images/delete-icon.png"/>
</Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
Now when you click the button the Meal is responsible for firing the event and the viewmodel is responsible for handling it.