Many people advice to update observablecollection on UI thread using dispatcher. But I want to make something like this. Whit what evil can i face using this implementation? I do not want to use dispatcher because it can make deadlocks in multithreading.
namespace WpfApplication162
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new Data();
}
}
public class Data:INotifyPropertyChanged
{
private ObservableCollection<string> nameList;
public ObservableCollection<string> NameList
{
get
{
return this.nameList;
}
set
{
this.nameList = value;
this.RaisePropertyChaged("NameList");
}
}
public ObservableCollection<string> TempList { get; set; }
public Data()
{
NameList = new ObservableCollection<string>();
NameList.Add("Loading");
Action Start = new Action(UpdateAysnc);
IAsyncResult result = Start.BeginInvoke(new AsyncCallback(SetList), null);
}
public void SetList(object param)
{
NameList = TempList;
}
public void UpdateAysnc()
{
TempList = new ObservableCollection<string>();
System.Threading.Thread.Sleep(10000);
for (int i=0;i<10;i++)
{
TempList.Add(i.ToString());
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChaged(string info)
{
if (PropertyChanged!= null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
}
Xaml
<Grid>
<ListView ItemsSource="{Binding NameList}">
<ListView.ItemTemplate>
<DataTemplate>
<Label Content="{Binding .}"></Label>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
Try this:
public Data()
{
NameList = new ObservableCollection<string>();
NameList.Add("Loading");
UpdateAysnc(results => SetList(results));
}
public void SetList(object param)
{
NameList = TempList;
}
public void UpdateAysnc(Action<ObservableCollection<string>> onUpdateComplete)
{
var tempList = new ObservableCollection<string>();
System.Threading.Thread.Sleep(10000);
for (int i=0;i<10;i++)
{
tempList.Add(i.ToString());
}
onUpdateComplete(tempList);
}
Related
I want to show user's Folder
(C:\Food\BBQ\Recipe\Recipefile.txt)
enter image description here
enter image description here
like that
but result is ...
enter image description here
I make a project MVVM patteron wpf
Using ViewModel.cs and View
with HierarchicalDataTemplate
this is my codes
window1.xaml
<Window.Resources>
<ObjectDataProvider x:Key="ObjectDataProviderKey">
<ObjectDataProvider.ObjectInstance>
<vm:FolderViewModel FullPath="C:\Food"/>
</ObjectDataProvider.ObjectInstance>
</ObjectDataProvider>
<HierarchicalDataTemplate
DataType="{x:Type vm:FolderViewModel}"
ItemsSource="{Binding Path=SubFolderCollection}">
<TextBlock Text="{Binding Path=Name}" />
</HierarchicalDataTemplate>
<TreeView Name="folderTreeView" Grid.ColumnSpan="2" Grid.Row="2">
<TreeViewItem
Header="Favorit"
ItemsSource="{Binding Source={StaticResource ObjectDataProviderKey}, Path=SubFolderCollection}" />
</TreeView>
and
viewModel
FolderViewModel.cs
namespace TEST.ViewModels.TreeView
{
public class FolderViewModel : INotifyPropertyChanging
{
namespace TEST.ViewModels.TreeView
{
public class FolderViewModel : INotifyPropertyChanging
{
#region Field
private DirectoryInfo directoryInfo;
private ObservableCollection<FolderViewModel> subFolderCollection;
private ObservableCollection<FileInfo> fileInfoCollection;
#endregion
#region - FullPath
public string FullPath
{
get
{
return directoryInfo.FullName;
}
set
{
if (Directory.Exists(value))
{
directoryInfo = new DirectoryInfo(value);
}
else
{
throw new ArgumentException("No exist.", "FullPath");
}
}
}
#endregion
#region - Name
private string _Name = string.Empty;
public event PropertyChangingEventHandler PropertyChanging;
public string Name
{
get
{
_Name = directoryInfo.Name;
return _Name;
}
set
{
_Name = value;
OnpropertyChanaged("Name");
}
}
private void OnpropertyChanaged(string v)
{
throw new NotImplementedException();
}
#endregion
#region - SubFolderCollection
public ObservableCollection<FolderViewModel> SubFolderCollection
{
get
{
if (subFolderCollection == null)
{
subFolderCollection = new ObservableCollection<FolderViewModel>();
DirectoryInfo[] directoryInfoArray = directoryInfo.GetDirectories();
//DirectoryInfo[] directoryInfoArray = (DirectoryInfo[])this.directoryInfo.GetFileSystemInfos();
for (int i = 0; i < directoryInfoArray.Length; i++)
{
FolderViewModel folder = new FolderViewModel();
FullPath = directoryInfoArray[i].FullName;
this.subFolderCollection.Add(folder);
}
}
return subFolderCollection;
}
}
#endregion
#region FileInfoCollection
public ObservableCollection<FileInfo> FileInfoCollection
{
get
{
if (this.fileInfoCollection == null)
{
this.fileInfoCollection = new ObservableCollection<FileInfo>();
FileInfo[] fileInfoArray = this.directoryInfo.GetFiles();
for (int i = 0; i < fileInfoArray.Length; i++)
{
this.fileInfoCollection.Add(fileInfoArray[i]);
}
}
return this.fileInfoCollection;
}
}
#endregion
#region - Folder()
public FolderViewModel()
{
FullPath = #"C:\Food\";
}
#endregion
}
}
what should i do ??
If I understood you correctly. To Achieive the way you wanted you can do something like this.
MainWindow.xaml
<TreeView ItemsSource="{Binding TreeModel}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Child}">
<Grid>
<Label Content="{Binding Name}"/>
</Grid>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
And Then your ViewModel you can create a collection which goes through your folders and add folder and file name
MainViewModel.cs
public ObservableCollection<DirModel> TreeModel { get; set; }
public MainViewModel()
{
TreeModel = new ObservableCollection<DirModel>();
Load();
}
void Load()
{
var root = "C:\\TEST";
foreach (var dirInfo in new DirectoryInfo(root).GetDirectories())
{
var dir = new DirModel() { Name = dirInfo.Name };
foreach (var sb in dirInfo.GetDirectories())
{
var sDir = new ChildDirModel() { Name = sb.Name };
var sFile = new FileModel() { Name = sb.GetFiles().First().Name };
sDir.Child.Add(sFile);
dir.Child.Add(sDir);
}
TreeModel.Add(dir);
}
}
Finally create a Model class which represent your structure
public class DirModel
{
public string Name { get; set; }
public ObservableCollection<ChildDirModel> Child { get; set; }
public DirModel()
{
Child = new ObservableCollection<ChildDirModel>();
}
}
public class ChildDirModel
{
public string Name { get; set; }
public ObservableCollection<FileModel> Child { get; set; }
public ChildDirModel()
{
Child = new ObservableCollection<FileModel>();
}
}
public class FileModel
{
public string Name { get; set; }
}
Your application will look like this
In the shown code i need to know the coding to be replaced in place of question mark in the code. I need to delete,edit and update the item in the list view without writing any code in code behind. I only want to do these operations by bindin view with view model through Icommand
This a class in my model Playlist.cs
namespace MvvmDemo.Models
{
public class Playlist
{
public string Title { get; set; }
}
}
This is a class in my viewmodel PlaylistsViewModel.cs
namespace MvvmDemo.ViewModels
{
public class PlaylistsViewModel
{
public ObservableCollection Playlists { get; private set; } = new ObservableCollection();
public ICommand AddPlaylistCommand { get; private set; }
public ICommand DeletePlaylistCommand { get; private set; }
public ICommand EditPlaylistCommand { get; private set; }
public PlaylistsViewModel()
{
AddPlaylistCommand = new Command(AddPlaylist);
DeletePlaylistCommand = new Command(DeletePlaylist);
}
public void AddPlaylist()
{
var newPlaylist = "Playlist " + (Playlists.Count + 1);
Playlists.Add(new Playlist { Title = newPlaylist });
}
public void DeletePlaylist()
{
????????????????
}
public void EditPlaylist()
{
????????????????
}
}
}
you have to make the command is parameterised and pass binding data through the parameter.
and from that data you can get the index value of selected.using that remove the item from the list.
Playlists.RemoveAt("INDEX_NUMBER");
To update it in the view use "INotifyProperty" also
If you want to delete and edit item in ListView, firstly, you should need to use ICommand, then you could need to use INotifyPropertyChanged to implement Inotify.
I do one sample that you can take a look. Choosing one Item and long press with the left mouse button, you will see two ways, delete Item and Edit Item action.
<ContentPage.Content>
<StackLayout>
<ListView
x:Name="mylistview"
ItemsSource="{Binding lists}"
SelectedItem="{Binding selecteditem}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.ContextActions>
<MenuItem
Command="{Binding BindingContext.DeletePlaylistCommand, Source={x:Reference Name=mylistview}}"
IsDestructive="true"
Text="Delete Item" />
<MenuItem
Command="{Binding BindingContext.EditPlaylistCommand, Source={x:Reference Name=mylistview}}"
IsDestructive="true"
Text="Edit Item" />
</ViewCell.ContextActions>
<StackLayout Padding="15,0">
<Label Text="{Binding Title}" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage.Content>
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class Page19 : ContentPage, INotifyPropertyChanged
{
public ObservableCollection<Playlist> lists { get; set; }
//public RelayCommand1 AddPlaylistCommand { get; set; }
public RelayCommand DeletePlaylistCommand { get; set; }
public RelayCommand EditPlaylistCommand { get; set; }
private Playlist _selecteditem;
public Playlist selecteditem
{
get { return _selecteditem; }
set
{
_selecteditem = value;
RaisePropertyChanged("selecteditem");
}
}
public Page19 ()
{
InitializeComponent ();
lists = new ObservableCollection<Playlist>()
{
new Playlist(){Id=1,Title="list 1"},
new Playlist(){Id=2, Title="list 2"},
new Playlist(){Id=3,Title="list 3"},
new Playlist(){Id=4,Title="list 4"},
new Playlist(){Id=5,Title="list 5"},
new Playlist(){Id=6,Title="list 6"},
};
DeletePlaylistCommand = new RelayCommand(DeletePlaylist);
EditPlaylistCommand = new RelayCommand(EditPlaylist);
selecteditem = lists[0];
this.BindingContext = this;
}
public void AddPlaylist()
{
}
public void DeletePlaylist()
{
Playlist item = selecteditem;
lists.Remove(item);
}
public void EditPlaylist()
{
Playlist item = selecteditem;
int id = item.Id;
foreach(Playlist playl in lists.Where(a=>a.Id==id))
{
playl.Title = "chenge title";
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class Playlist: INotifyPropertyChanged
{
private int _Id;
public int Id
{
get { return _Id; }
set
{
_Id = value;
RaisePropertyChanged("Id");
}
}
private string _Title;
public string Title
{
get { return _Title;}
set
{
_Title = value;
RaisePropertyChanged("Title");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Here is the RelayCommd:
public class RelayCommand : ICommand
{
readonly Action _execute;
public RelayCommand(Action execute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
_execute();
}
}
You can use observablecollection. It will reflect add,remove operation of item to the listview. And for editing item you have to raise property changed for all property you are editing.To simplify that property changed you can implement property changed event to your Playlist model class.
Like
public void DeletePlaylist()
{
Playlists.Remove(newPlaylist);
}
public void EditPlaylist()
{
newPlaylist.Title="Refreshed Playlist"
}
public class Playlist:INotifyPropertyChanged
{
private string title;
public string Title
{
get{return title;}
set{title=value;
NotifyPropertyChanged();}
}
}
I have a customlistbox whose itemssource is bound to an observablecollection in the viewmodel. I have created a SelectedItemsList DependencyProperty in the customListbox so that the user can select items and the viewmodel will updated. This works perfectly.
I would also like the bound list in the viewmodel, when changed, to update the selected items in the customListbox.
static FrameworkPropertyMetadata fpm = new FrameworkPropertyMetadata(
new ObservableCollection<MyItem>(),
(FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault),
new PropertyChangedCallback(OnSelectedItemsChanged)
);
private static void OnSelectedItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
//the code
}
public static readonly DependencyProperty SelectedItemsListProperty =
DependencyProperty.Register("SelectedItemsList",
typeof(ObservableCollection<MyItem>),
typeof(CustomListBox), fpm);
SelectedItems is read only. Is there anyway to update the selected items from the viewModel? Is there an alternative to ListBox that would be more suitable?
I figured that I would post my solution just in case it helps anyone.
Here is my very simple Item class
class MyItem
{
public string MyString { get; set; }
public MyItem(string m)
{
MyString = m;
}
}
Here is my CustomListBox Code
class CustomListBox : ListBox
{
public CustomListBox()
{
this.SelectionChanged += CustomListBox_SelectionChanged;
}
void CustomListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ObservableCollection<MyItem> tempList = new ObservableCollection<MyItem>();
foreach (MyItem i in this.SelectedItems)
{
tempList.Add(i);
}
this.SelectedItemsList = tempList;
}
#region SelectedItemsList
public ObservableCollection<MyItem> SelectedItemsList
{
get { return (ObservableCollection<MyItem>)GetValue(SelectedItemsListProperty); }
set { SetValue(SelectedItemsListProperty, value); }
}
public static readonly DependencyProperty SelectedItemsListProperty =
DependencyProperty.Register("SelectedItemsList", typeof(ObservableCollection<MyItem>), typeof(CustomListBox),
new PropertyMetadata(new ObservableCollection<MyItem>(), new PropertyChangedCallback(OnSelectionChanged)));
public static void OnSelectionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
CustomListBox clb = d as CustomListBox;
var selectedItems = e.NewValue as ObservableCollection<MyItem>;
if (selectedItems != null)
{
clb.SetSelectedItems(selectedItems);
}
}
#endregion
}
The XAML Binding in my window
<local:CustomListBox Height="500" Width="200" x:Name="listview" Margin="0,40,0,0" ItemsSource="{Binding MyItemsList}"
Grid.Column="1" HorizontalAlignment="Left" VerticalAlignment="Top" TabIndex="50"
SelectionMode="Multiple" SelectedItemsList="{Binding SelectedMyItems, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" >
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=MyString}" />
</DataTemplate>
</ListBox.ItemTemplate>
</local:CustomListBox>
And my ViewModel
class MyViewModel : ViewModelBase
{
public MyViewModel()
{
this.SelectChangeMyItemsCommand = new BaseCommand(new Action(SelectChangeMyItems));
}
private ObservableCollection<MyItem> selectedMyItems = null;
public ObservableCollection<MyItem> SelectedMyItems
{
get
{
if (selectedMyItems == null)
{
selectedMyItems = new ObservableCollection<MyItem>();
}
return selectedMyItems;
}
set
{
selectedMyItems = value;
OnPropertyChanged("SelectedMyItems");
}
}
private ObservableCollection<MyItem> myItemsList = null;
public ObservableCollection<MyItem> MyItemsList
{
get
{
if (myItemsList == null)
{
MyItemsRefresh();
}
return myItemsList;
}
set
{
myItemsList = value;
OnPropertyChanged("MyItemsList");
}
}
public void MyItemsRefresh()
{
ObservableCollection<MyItem> tempMyList = new ObservableCollection<MyItem>();
tempMyList.Add(new MyItem("Angry Apple"));
tempMyList.Add(new MyItem("Big Bird"));
tempMyList.Add(new MyItem("Candy Cane"));
tempMyList.Add(new MyItem("Daring Dart"));
MyItemsList = tempMyList;
}
private static bool iseven = true;
public ICommand SelectChangeMyItemsCommand { get; private set; }
public void SelectChangeMyItems()
{
ObservableCollection<MyItem> items = new ObservableCollection<MyItem>();
for(int i = 0; i < myItemsList.Count; i++)
{
if (iseven && IsEven(i))
{
items.Add(MyItemsList[i]);
}
else if (!iseven && !IsEven(i))
{
items.Add(MyItemsList[i]);
}
}
this.SelectedMyItems = items;
iseven = !iseven;
}
public static bool IsEven(int value)
{
return value % 2 == 0;
}
}
I have a single list of objects of different types that I want to present in a WPF TreeView. Some of the objects should be children of other objects, but I don't want the parent objects to have to maintain their own list of the children. I have tried to do this by implementing a property that returns an IEnumerator for the subitems in that group, and binding that as the ItemsSource, but it doesn't seem to work. I've created the following to demonstrate the problem.
CS:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
namespace WpfApplication3
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private Items MyItems;
private int NextGroupId = 0;
public MainWindow()
{
InitializeComponent();
MyItems = new Items();
tvMain.ItemsSource = MyItems.ItemList;
}
private void btnNewGroup_Click(object sender, RoutedEventArgs e)
{
MyItems.Add(new ItemGroup(MyItems)
{
Name = Guid.NewGuid().ToString(),
GroupId = NextGroupId
});
NextGroupId++;
}
private void btnNewSubItem_Click(object sender, RoutedEventArgs e)
{
MyItems.Add(new SubItem(MyItems)
{
Name = Guid.NewGuid().ToString(),
GroupId = (tvMain.SelectedItem as ItemGroup).GroupId
});
}
private void tvMain_SelectedItemChanged(object sender,
RoutedPropertyChangedEventArgs<object> e)
{
btnNewSubItem.IsEnabled = tvMain.SelectedItem is ItemGroup;
}
}
public class Items
{
private ObservableCollection<BaseItem> _ItemList;
public ObservableCollection<BaseItem> ItemList { get { return _ItemList; } }
public Items()
{
_ItemList = new ObservableCollection<BaseItem>();
}
public void Add(BaseItem I)
{
_ItemList.Add(I);
}
}
public class BaseItem
{
protected Items _List;
public BaseItem(Items List)
{
_List = List;
}
public string Name { get; set; }
public int GroupId { get; set; }
}
public class ItemGroup : BaseItem
{
public ItemGroup(Items _List)
: base(_List) { }
public IEnumerator Children
{
get
{
return _List.ItemList
.OfType<SubItem>()
.Where(SI => SI.GroupId == this.GroupId)
.GetEnumerator();
}
}
}
public class SubItem : BaseItem
{
public SubItem(Items _List)
: base(_List) { }
}
}
XAML:
<Window x:Class="WpfApplication3.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:self="clr-namespace:WpfApplication3"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TreeView x:Name="tvMain" HorizontalAlignment="Left" Height="273" Margin="50,25,0,0"
VerticalAlignment="Top" Width="265"
SelectedItemChanged="tvMain_SelectedItemChanged">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type self:ItemGroup}"
ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Name}"/>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type self:SubItem}">
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</TreeView.Resources>
</TreeView>
<Button x:Name="btnNewGroup" Content="New Group" HorizontalAlignment="Left"
VerticalAlignment="Top" Width="100" Margin="336,142,0,0"
Click="btnNewGroup_Click"/>
<Button x:Name="btnNewSubItem" Content="New SubItem" HorizontalAlignment="Left"
Margin="336,185,0,0" VerticalAlignment="Top" Width="100"
Click="btnNewSubItem_Click" IsEnabled="False"/>
</Grid>
</Window>
To be clear, I want the SubItems to appear as children of the ItemGroup with the same GroupId. What is the best approach, please?
You have to make a few significant changes in your code:
Firstly, Items.ItemList should contain only elements of type ItemGroup. Now it contains also elements of type SubItem and they are displayed in a tree view as parents. It is not what we want to achieve.
However, in order to keep track of all items we need another collection Items.AllItems.
ItemGroup.Children property should be modified to use Items.AllItems collection instead of Items.ItemList.
Finally we need to inform a tree view when a new sub item was created. We will use INotifyPropertyChanged to do so.
Here is a working code. However, please note that it is not a production code. I changed only what was absolutely necessary. The purpose of it is only to show you an idea.
You should be also aware that this kind of solution might be slower in comparison to the approach when parent objects have their own lists of children. It will depend on the number of items in a tree view.
public class Items
{
public List<BaseItem> AllItems { get; private set; }
public ObservableCollection<BaseItem> ItemList { get; private set; }
public Items()
{
ItemList = new ObservableCollection<BaseItem>();
AllItems = new List<BaseItem>();
}
public void Add(BaseItem item)
{
AllItems.Add(item);
if (item is ItemGroup)
ItemList.Add(item);
else
AllItems.OfType<ItemGroup>().Single(i => i.GroupId == item.GroupId).Notify();
}
}
public class ItemGroup : BaseItem, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ItemGroup(Items _List): base(_List) { }
public IEnumerator Children
{
get
{
return _List.AllItems
.OfType<SubItem>()
.Where(SI => SI.GroupId == this.GroupId)
.GetEnumerator();
}
}
public void Notify()
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(nameof(Children)));
}
}
I'm going to accept Michal's answer because it showed me what I was doing wrong and how to fix it. I have actually found a different solution that I prefer, though, since it does not require an extra collection:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Data;
namespace WpfApplication3
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private Items MyItems;
private int NextGroupId = 0;
public MainWindow()
{
InitializeComponent();
MyItems = new Items();
tvMain.ItemsSource = MyItems.Groups as IEnumerable;
}
private void btnNewGroup_Click(object sender, RoutedEventArgs e)
{
MyItems.Add(new ItemGroup(MyItems)
{
Name = Guid.NewGuid().ToString(),
GroupId = NextGroupId
});
NextGroupId++;
}
private void btnNewSubItem_Click(object sender, RoutedEventArgs e)
{
MyItems.Add(new SubItem(MyItems)
{
Name = Guid.NewGuid().ToString(),
GroupId = (tvMain.SelectedItem as ItemGroup).GroupId
});
}
private void tvMain_SelectedItemChanged(object sender,
RoutedPropertyChangedEventArgs<object> e)
{
btnNewSubItem.IsEnabled = tvMain.SelectedItem is ItemGroup;
}
}
public class Items
{
public ObservableCollection<BaseItem> ItemList { get; private set; }
public ICollectionView Groups { get; private set; }
public Items()
{
ItemList = new ObservableCollection<BaseItem>();
Groups = CollectionViewSource.GetDefaultView(ItemList);
Groups.Filter = item => item is ItemGroup;
}
public void Add(BaseItem item)
{
ItemList.Add(item);
if (item is SubItem)
ItemList
.OfType<ItemGroup>()
.Where(g => g.GroupId == item.GroupId)
.Single()
.NotifyPropertyChanged("Children");
}
}
public class BaseItem : INotifyPropertyChanged
{
protected Items _List;
public BaseItem(Items List)
{
_List = List;
}
public string Name { get; set; }
public int GroupId { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
public class ItemGroup : BaseItem
{
public ItemGroup(Items _List)
: base(_List) { }
public IEnumerator Children
{
get
{
return _List.ItemList
.OfType<SubItem>()
.Where(SI => SI.GroupId == this.GroupId)
.GetEnumerator();
}
}
}
public class SubItem : BaseItem
{
public SubItem(Items _List)
: base(_List) { }
}
}
I've faced a really strange behavior while using CollectionViews and wonder if somebody can explain me that behavior.
I've got an ObservableCollection, lets call it _sourcelist. Then I create several CollectionViews of _sourcelist, in that way:
CollectionViewSource cvs = new CollectionViewSource() { Source = _sourcelist };
ICollectionView list = cvs.View;
These CollectionViews are each used with a different filter and one CollectionView with no filter (so representing the original source). In my Application I have a TabControl where every TabItem contains a ListBox with one of these CollectionViews as ItemsSource. When I know delete an item from the sourcelist, the ListBox with the CollectionView with no filter gets updated, but the other ListBoxes that contains filtered CollectionViews and contain the deleted item don't get updated. So the deleted item remains in that ListBoxes.
Now, the strange behavior is following:
When I add the following code after code written above:
list.CollectionChanged += (o,e) => { };
Then the other ListBoxes get updated, too.
Why that? I mean, I just registered an event handler for the CollectionChanged event, which does nothing!
So, here is my Code Sample. I know it is a big amount of code, but it is ready to run and reproduces the strange behavior.
When you run the code and try to delete an item in the "All" List, then the corresponding item in the filtered "Under 30" or "Over 30" list will not be deleted. Now uncomment the commented line in the ListViewModel (it is just a registration of an empty Eventhandler), then the corresponding items in the filtered lists will be deleted.
If you have any questions, then please ask! :-)
Thank you for your interest in solving the problem. :-)
P.S.: The RelayCommand is from the assembly: Microsoft.TeamFoundation.Controls
MainWindow.xaml:
<Window x:Class="CollectionTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:CollectionTest"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate DataType="{x:Type local:ListViewModel}">
<ListBox ItemsSource="{Binding List}" SelectedItem="{Binding Selected}" BorderBrush="Transparent" Margin="0" HorizontalContentAlignment="Stretch"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:PersonViewModel}">
<TextBlock Margin="5,0" VerticalAlignment="Center" Text="{Binding Name}"/>
</DataTemplate>
<Style x:Key="{x:Type TabItem}" TargetType="{x:Type TabItem}">
<Setter Property="Header" Value="{Binding Name}"/>
</Style>
</Window.Resources>
<DockPanel>
<Button DockPanel.Dock="Bottom" Name="DeleteButton" Content="Delete" Command="{Binding DeleteCommand}"/>
<TabControl Name="TabControl" ItemsSource="{Binding Lists}" Padding="0" SelectedItem="{Binding Selected}"/>
</DockPanel>
</Window>
MainWindow.xaml.cs:
public partial class MainWindow : Window
{
private MainViewModel mvm;
public MainWindow()
{
InitializeComponent();
mvm = new MainViewModel(CreateRepo());
this.DataContext = mvm;
}
private Repository CreateRepo()
{
Repository repo = new Repository();
for (int i = 20; i < 45; i++)
{
Person p = new Person() { Name = "Person" + i, Age = i };
repo.Add(p);
}
return repo;
}
}
Person:
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
Repository:
public class Repository
{
private readonly List<Person> _list;
public event EventHandler<RepositoryChangedEventArgs> Added;
public event EventHandler<RepositoryChangedEventArgs> Removed;
public Repository()
{
this._list = new List<Person>();
}
public void Add(Person person)
{
if (person == null)
{
throw new ArgumentNullException("person");
}
if (!this._list.Contains(person))
{
this._list.Add(person);
if (this.Added != null)
{
this.Added(this, new RepositoryChangedEventArgs(person));
}
}
}
public void Remove(Person person)
{
if (person == null)
{
throw new ArgumentNullException("person");
}
if (this._list.Contains(person))
{
this._list.Remove(person);
if (this.Removed != null)
{
this.Removed(this, new RepositoryChangedEventArgs(person));
}
}
}
public List<Person> Get()
{
return new List<Person>(this._list);
}
}
public class RepositoryChangedEventArgs : EventArgs
{
public Person Entity { get; set; }
public RepositoryChangedEventArgs(Person entity)
{
this.Entity = entity;
}
}
PersonViewModel:
public class PersonViewModel : INotifyPropertyChanged
{
private Person _person;
private Repository _repository;
public string Name
{
get
{
return this._person.Name;
}
set
{
if (this._person.Name != value)
{
this._person.Name = value;
this.OnPropertyChanged("Name");
}
}
}
public int Age
{
get
{
return this._person.Age;
}
set
{
if (this._person.Age != value)
{
this._person.Age = value;
this.OnPropertyChanged("Age");
}
}
}
public PersonViewModel(Person person, Repository repository)
{
this._person = person;
this._repository = repository;
}
public void Delete()
{
this._repository.Remove(this._person);
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string name)
{
if (this.PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
}
ListViewModel:
public class ListViewModel : INotifyPropertyChanged
{
private IEnumerable _sourceList;
private ICollectionView _list;
private PersonViewModel _selected;
private string _name;
public ICollectionView List
{
get
{
return this._list;
}
}
public PersonViewModel Selected
{
get
{
return this._selected;
}
set
{
if (this._selected != value)
{
this._selected = value;
this.OnPropertyChanged("Selected");
}
}
}
public string Name
{
get
{
return this._name;
}
set
{
if (this._name != value)
{
this._name = value;
this.OnPropertyChanged("Name");
}
}
}
public ListViewModel(string name, IEnumerable list)
{
this.Name = name;
this._sourceList = list;
CollectionViewSource cvs = new CollectionViewSource() { Source = list };
this._list = cvs.View;
// cvs.View.CollectionChanged += (o, e) => { }; // uncomment this line to let it work
}
public ListViewModel(String name, IEnumerable list, Predicate<object> filter)
: this(name, list)
{
this._list.Filter = filter;
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
}
MainViewModel:
public class MainViewModel : INotifyPropertyChanged
{
protected ObservableCollection<PersonViewModel> _sourceList;
protected ObservableCollection<ListViewModel> _lists;
protected Repository _repository;
protected Dictionary<Person, PersonViewModel> _dictionary;
private RelayCommand _deleteCommand;
private ListViewModel _selected;
public ListViewModel Selected
{
get
{
return this._selected;
}
set
{
if (this._selected != value)
{
this._selected = value;
this.OnPropertyChanged("Selected");
}
}
}
public RelayCommand DeleteCommand
{
get
{
if (this._deleteCommand == null)
{
this._deleteCommand = new RelayCommand(this.ExecutedDelete, this.CanExecuteDelete);
}
return this._deleteCommand;
}
}
public IEnumerable<ListViewModel> Lists
{
get
{
return this._lists;
}
}
public MainViewModel(Repository repository)
{
this._sourceList = new ObservableCollection<PersonViewModel>();
this._lists = new ObservableCollection<ListViewModel>();
this._dictionary = new Dictionary<Person, PersonViewModel>();
this._repository = repository;
this._repository.Added += this.OnAdded;
this._repository.Removed += this.OnRemoved;
this.CreateSourceList(repository);
this.CreateLists();
if (this._lists.Count > 0)
{
this.Selected = this._lists[0];
}
}
protected void CreateSourceList(Repository repository)
{
foreach (Person person in repository.Get())
{
PersonViewModel pvm = new PersonViewModel(person, repository);
this._dictionary.Add(person, pvm);
this._sourceList.Add(pvm);
}
}
protected void CreateLists()
{
this._lists.Add(new ListViewModel("All", this._sourceList));
this._lists.Add(new ListViewModel("Under 30", this._sourceList,
delegate(object o)
{
PersonViewModel pvm = o as PersonViewModel;
return (pvm != null && pvm.Age < 30);
}));
this._lists.Add(new ListViewModel("Over 30", this._sourceList,
delegate(object o)
{
PersonViewModel pvm = o as PersonViewModel;
return (pvm != null && pvm.Age > 30);
}));
}
protected void Remove(Person person)
{
PersonViewModel pvm = this._dictionary[person];
this._dictionary.Remove(person);
this._sourceList.Remove(pvm);
}
protected void OnAdded(object sender, RepositoryChangedEventArgs args)
{
Person addedPerson = args.Entity;
if (addedPerson != null)
{
PersonViewModel pvm = new PersonViewModel(addedPerson, this._repository);
this._dictionary.Add(addedPerson, pvm);
this._sourceList.Add(pvm);
}
}
protected void OnRemoved(object sender, RepositoryChangedEventArgs args)
{
Person removedPerson = args.Entity;
if (removedPerson != null)
{
this.Remove(removedPerson);
}
}
private bool CanExecuteDelete(object o)
{
return true;
}
private void ExecutedDelete(object o)
{
PersonViewModel pvm = null;
if (this.Selected != null && (pvm = this.Selected.Selected) != null)
{
pvm.Delete();
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
}