I have a ListView bound to an instance of data
ObservalbeCollection<ActivityItem> ActivityItems
<ListView
x:Name="ActivityItemsList"
ItemsSource="{Binding ActivityItems}"
ItemTemplate="{StaticResource Herke80ItemTemplate}"
Header="{Binding DateFilterListBox.SelectedItem}" />
When I run a filter, I want to select the ListViewItem bound to the ActivityItem within the ListView, and change its visibility depending on the filter selected.
I was doing this by keeping another ObservableCollection instance, meaning the data instance was duplicated. I then removed or added items accordingly. This took up a lot of loading time. So I figured I'd try keep to one binding, and disable or enable the UI elements.
foreach (ActivityItem activityItem in ActivityItemsList.Items)
{
if (activityItem == null) continue;
var index = ActivityItemsList.Items.IndexOf(activityItem);
(ActivityItemsList.Items[index] as ListViewItem).Visibility = Visibility.Collapsed;
int startComparer = DateTime.Compare(activityItem.Start, selectedStartDate);
int endComparer = DateTime.Compare(selectedEndDate, activityItem.End);
if (OverdueToggleSwitch.IsOn)
{
(ActivityItemsList.Items[index] as ListViewItem).Visibility = Visibility.Visible;
}
else
{
if (startComparer >= 0 && endComparer >= 0)
{
(ActivityItemsList.Items[index] as ListViewItem).Visibility = Visibility.Visible;
}
}
}
ex is a NullReferenceException, due to the fact that the ListViewItem is not actually a ListViewItem but an ActivityItem.
What is an alternative or the correct way of doing this?
I think you have over complicated it. If you use Snoop you can see that when you bind a ListView to a collection via ItemSource it ends up populated with a collection of ListViewItems where each ListViewItem has two things.
a DataContext set to the data (e.g. ActivityItem)
a ContentPresenter filled with the controls defined in your DataTemplate
This means that in theory you can browse both collections
from x in listView.Items select x as ListViewItem
or
from x in listView.Items select x.DataContext as ActivityItem
However if you simply want a filtered list then can I suggest changing your binding,
<ListView
x:Name="ActivityItemsList"
ItemsSource="{Binding FilteredItems}"
and
public class MyViewModel :INotifyPropertyChanged
{
public IEnumerable<ActivityItem> AllItems {get;set;} //needs to NotifyPropertyChanged(FilteredItems)
public Func<ActivityItem, bool> Filter { get;set;} //needs to NotifyPropertyChanged(FilteredItems)
public IEnumerable<ActivityItem> FilteredItems { get { return from x in AllItems where Filter(x) select x; }}
}
If you are still struggling with performance issues then have a quick read through Bea Costa's series on TreeView performance part one, part two and part three. It might help you to get WPF doing all the hard work for you.
Related
I have a Telerik ComboBox :
<telerik:RadComboBox Grid.Row="0" Grid.Column="1" VerticalAlignment="Center" IsReadOnly="True" SelectedIndex="0" x:Name="CbBoxUltra" SelectionChanged="CbBoxUltra_SelectionChanged" MinWidth="100"/>
I'm doing a "for each" loop to store items into it :
foreach (var ultra in line2)
{
...
if (deviceType != null && !String.IsNullOrEmpty(vehicleName) && vehicleName != "null" && deviceType == "ultra")
{
_listUltra.Add(new UltraModel { Id = Convert.ToInt32(idUltra), Name = nameUltra, NameVehicle = vehicleName });
CbBoxUltra.Items.Add(vehicleName);
}
}
I want to sort the items in my ComboBox (from a to z for exemple).
So I tried to do what's in the doc : Telerik's sorting
But in the .xaml, i can't acces the property "Sort", like I do with "VerticalAlignment" for example, and i can't access the property in the .xaml.cs neither :
CbBoxUltra.Items.Sort(); //It doesn't match anything here
I just can acces .SortDescription, but it's not what I'm searching for...
From what I gathered from Telerik's documentation, the Sort property is available only for ASP.NET RadComboBox Control (server-side), and not for WPF RadComboBox Control.
That given, there are two most common ways to proceed.
I. Take advantage of the existing WPF implementation
RadComboBox.Items property is actually inherited from ItemsControl. It is of type ItemCollection, which implements ICollectionView, and supports sorting via SortDescriptions collection. So if you want to sort by property values, you can simply add appropriate items to that collection:
CbBoxUltra.Items.SortDescriptions.Add(new SortDescription
{
PropertyName = "XXX",
Direction = ListSortDirection.Descending
});
CbBoxUltra.Items.SortDescriptions.Add(new SortDescription
{
PropertyName = "YYY",
Direction = ListSortDirection.Ascending
});
This code will cause the items to be sorted first by the XXX property in descending order, then by YYY property in ascending order. However if your sorting logic requires more than just referencing item property values, you can add a single sort description, leaving out the PropertyName:
CbBoxUltra.Items.SortDescriptions.Add(new SortDescription
{
Direction = ListSortDirection.Descending
});
This will cause the sorting mechanism to sort using the default comparer for your item type. In order to apply custom logic, make sure your items implement IComparable<T> interface. Ddefault comparer for string sorts in lexical order.
II. Pre-sort items before adding
This approach is somewhat easier provided you add items only once. Here's code excerpt:
foreach (var ultra in line2.OrderByDescending(...))
{
...
}
Note that in case you'll be adding items multiple times (e.g. upon user input), this will get much trickier since you'll have to manually insert items at proper indexes.
I'am currently developing an WinRT app an need a ListView ordered by date and grouped by day. The ListView is bound to an ICollectionView in my ViewModel
public Windows.UI.Xaml.Data.ICollectionView GroupedData {
get
{
return cvSource.View;
}
}
private Windows.UI.Xaml.Data.CollectionViewSource cvSource;
In my XAML I can bind then the ListView to this property:
<ListView ItemsSource="{Binding GroupedData}"
Now I'am doing some calculations and Filtering on my basicData, which is stored in a List<>. After i've done this, the grouping happens via LINQ:
var result = from DataObject in basicData
group DataObject by DataObject.Date
into date_grp orderby date_grp.Key
select date_grp;
Finally I set the source of the CollectionView to this new result and fire OnPropertyChanged
cvSource.Source = result.ToList();
OnPropertyChanged("GroupdedData");
This is working as I expected, but the ListView now selects the first element every time I populate a new source. I got rid of this as described on Stackoverflow by sellmeadog
NowI like to manually select an item. This should be the previous selected item before the source of the CollectionView is changed. What is the best way to save this previous item, see if its in the newly created CollectionView, select it and scroll to it?
Best regards
For the selecting senario, add a new property to the ViewModel in and bind SelectedItem property of the ListView to it:
public Windows.UI.Xaml.Data.ICollectionView GroupedData {
get
{
return cvSource.View;
}
}
public YourObjectType CurrentItem {
get {
return this.currentItem;
}
set {
if (this.currentItem != value) {
this.currentItem = value;
this.OnPropertyChanged("CurrentItem");
}
}
}
private YourObjectType currentItem;
private Windows.UI.Xaml.Data.CollectionViewSource cvSource;
Then before setting the source, hold a reference to the current item
var current = this.CurrentItem;
cvSource.Source = result.ToList();
this.CurrentItem = current;
assuming that your DataObjects type overrides Equals method, ListView finds and selects it in the collection. If not, you may need to add code finding it's instance in the new collection and assign it to CurrentItem property.
But by selecting the item doesn't mean ListViewScrolls to it. You may need to call ListView.BringIntoView in order to scroll to the selected item.
You need ObservableComputations. Using this library you can code like this:
private INotifyCollectionChanged _groupedData
public INotifyCollectionChanged GroupedData =>
_groupedData = _groupedData ?? basicData.Grouping(dataObject => dataObject.Date)
.Selecting(date_grp => date_grp.Key);
GroupedData reflects all the changes in the basicData collection. Do not forget to add the implementation of the INotifyPropertyChanged interface to DataObject class, so that GroupedData collection reflects the changes in dataObject.Date property. GroupedData is a singleton instance so you do not lost item selection in the ListView.
Here am using the code in datagridview and it works fine.but how to get the rowindex in datagrid based on without selecteding the rows in datagrid in wpf.please help me to do.
below the code am tried:
private int GetRowIndex(string ID)
{
int num = -1;
//Get the row based on ID
foreach (DataGridViewRow dataGridVV in (IEnumerable)this.dataGridView.Cells)
{
if (dataGridVV.Cells[0].Value.Equals((object)ID))
num = dataGridVV.Index;
}
return num;
}
#RajnathKumar, you need to use WPF properly... as your comments say, you shouldn't try to use it as if it were WinForms... it's not WinForms and trying to use it in that way will only cause you problems. This is how I would achieve your requirements:
First, data bind a data collection to the DataGrid.ItemsSource property (of a DataGrid):
<DataGrid ItemsSource="{Binding YourCollection}" ... />
Note that this YourCollection property should be an ObservableCollection of any type that you want... regardless of the type, I understand that it will have a unique Id property. Therefore, your required item can be found from the data bound collection directly using some basic LinQ:
var itemToRemove = YourCollection.FirstOrDefault(i => i.Id == someIdValue);
if (itemToRemove != null) YourCollection.Remove(itemToRemove);
I'm using a Telerik RadListBox (multi-select) in a Silverlight/C# application. First they wanted all items in the list to be selected by default. Ok, no problem:
RadListBox.SelectAllCommand.Execute(null, listboxname);
But now, one of the four items needs to be not selected by default, the other three selected. I've searched and searched for a code sample, fruitlessly. How can I accomplish this seemingly simple task?
Since the SelectedItems property of a RadListBox is of type IList, it is possible to still add items to that list instead of explicitly setting SelectedItems equal to another list.
For example, this will select all names not equal to 'Bobby' by default.
XAML:
<Grid>
<telerik:RadListBox x:Name="ListBox"
SelectionMode="Multiple"/>
</Grid>
Code-Behind:
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
IList<string> names = new List<string>();
names.Add("Alexander");
names.Add("Bobby");
names.Add("Chris");
names.Add("Dean");
ListBox.ItemsSource = names;
foreach (var name in names.Where(x => x != "Bobby"))
{
ListBox.SelectedItems.Add(name);
}
}
}
So lets say I have these classes:
public class Person
{
public string Name { get; set; }
}
public class PersonCollection : ObservableCollection<Person> { }
And lets say I have a ListView whose ItemsSource is bound to a PersonCollection. Now lets say I have this code:
public void AddPeople()
{
Person p = new Person() { Name = "Someone" };
MyPersonCollection.Add(p);
MyPersonCollection.Add(p);
MyPersonCollection.Add(p);
}
So now I have a ListView with three items in which all three items are references to the SAME object. So now I select lets say items with index 0 and 2 in the ListView.
The ListView.SelectedItems property will say I have ONE item selected since both visually selected items are the SAME object.
So how can I get the visually selected items so I can remove the items at indices 0 and 2, without removing the item at index 1?
In WinForms there is the ListBox.SelectedIndices property that would be useful here, but we don't have that in WPF, unfortunately...
You could iterate through the ListViewItems using ItemContainerGenerator.ContainerFromIndex, check ListViewItem.IsSelected and then remove them by index. However, this doesn't play well with virtualization because ContainerFromIndex could return null if you scroll away from the item and it gets virtualized.
The code would look something like this:
for(int ixPerson = myListView.Items.Count - 1; ixPerson >= 0; ixPerson--)
{
ListViewItem personItem = myListView.ItemContainerGenerator.ContainerFromIndex(ixPerson);
if (personItem.IsSelected)
{
mySourcePersonCollection.RemoveAt(ixPerson);
}
}
There are cases where this makes sense, adding people to a queue where appearing more than once is desirable for instance. For this case it seems like WPF is designed poorly. Is it possible to manually iterate between all items in the collection and check their selection state?
I think there's something wrong with your model! Whatever it is you are trying to achieve, I would try and find a more robust way of doing it.