I have a TabControl with it's ItemsSource bound to a ObservableCollection<string>. In this case, the TabControl has no logical children (LogicalTreeHelper.GetChildren(tabctrl) returns an empty list).
If i add a TabItem manually to the TabControl.Items collection, the TabItem is a logical child of the TabControl.
Why do these ways behave differently? Shouldn't the TabControl have a logical child in both scenarios?
Example code:
XAML
<Window x:Class="WpfApplication29.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<TabControl Name="tabctrl"/>
<Button Content="count children" Click="Button_Click_2"/>
</StackPanel>
</Window>
code behind
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
namespace WpfApplication29
{
public partial class MainWindow : Window
{
public ObservableCollection<string> TabItems
{
get { return (ObservableCollection<string>)GetValue(TabItemsProperty); }
set { SetValue(TabItemsProperty, value); }
}
public static readonly DependencyProperty TabItemsProperty =
DependencyProperty.Register("TabItems", typeof(ObservableCollection<string>), typeof(MainWindow), new PropertyMetadata(null));
public MainWindow()
{
InitializeComponent();
TabItems = new ObservableCollection<string>();
TabItems.Add("foo");
//scenario 1 (Visual Children count: 1, logical children count: 0)
tabctrl.SetBinding(TabControl.ItemsSourceProperty, new Binding("TabItems") { Source = this });
//scenario 2 (Visual Children count: 1, logical children count: 1)
//tabctrl.Items.Add(new TabItem() { Header = "bar", Content = "bar" });
}
private void Button_Click_2(object sender, RoutedEventArgs e)
{
var visualChildrenCount = VisualTreeHelper.GetChildrenCount(tabctrl);
var logicalChildrenCount = LogicalTreeHelper.GetChildren(tabctrl).Cast<object>().Count();
MessageBox.Show(string.Format("Visual Children: {0}, Logical Children: {1}", visualChildrenCount, logicalChildrenCount));
}
}
}
if you add a control to your usercontrol, or page, it is added to its LogicalTree. However, the UIElements from the control's template are not part of the LogicalTree.
IMHO, when you add TabItem directly to TabControl, you expect to be present in LogicalTree, intuitively. You have direcly added it there.
But when the items are generated from itemssource, the TabItems are generated by the control's internal logic and are not added to LogicalTree.
Maybe better example is ListBox, or DataGrid. Imagine that you have ItemsSource bound to very large collection and you need to enable virtualization. Then items (UIElements) are generated, only when needed (they are in visible area of the scrollviewver). If they were in logical tree, the logical tree would be changing while scrolling. but scrolling is more about visual than the "UI logic".
this article helps to understand the logical tree a little bit better: http://www.codeproject.com/Articles/21495/Understanding-the-Visual-Tree-and-Logical-Tree-in
Related
I want to change a complete TabControl based on the ListItem selection. So, I generate a Dictionary with Key(list item) and Value(TabControl) in a loop. Each TabControl contains 10 TabItems.
MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WpfApp1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
ListboxContainer listBoxContainer = new ListboxContainer();
TabControlContainer tabControlContainer = new TabControlContainer();
Dictionary<string, TabControl> tabControlDict = new Dictionary<string, TabControl>();
public MainWindow()
{
InitializeComponent();
listBoxContainer.ListBoxItems = new string[] { "TabControl1", "TabControl2", "TabControl3" };
DataContext = listBoxContainer;
// Generate tabcontrols per listbox item
foreach (var key in listBoxContainer.ListBoxItems)
{
TabControl tabControl = new TabControl();
TabItem tabItem = new TabItem();
for (int i = 0; i < 10; i++)
{
tabItem.Header = $"Header: {i}";
}
tabControl.Items.Add(tabItem);
tabControlDict.Add(key, tabControl);
}
tabControlContainer.TabControls = tabControlDict.Values.ToArray();
}
}
public class TabControlContainer : INotifyPropertyChanged
{
private TabControl[] _tabControls;
public TabControl[] TabControls
{
get => _tabControls;
set
{
if (value != _tabControls)
{
_tabControls = value;
NotifyPropertyChanged("TabControls");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class ListboxContainer : INotifyPropertyChanged
{
private string[] _listBoxitems;
public string[] ListBoxItems
{
get => _listBoxitems;
set
{
if (value != _listBoxitems)
{
_listBoxitems = value;
NotifyPropertyChanged("ListBoxItems");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I tried to bind the stackpanel to the tabcontrols, but I can't get it to work.
Here is the MainWindow.xaml code.
<Window x:Class="WpfApp1.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:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<DockPanel>
<ListBox x:Name="Listbox" ItemsSource="{Binding ListBoxItems}"/>
<StackPanel x:Name="TabControlContainer">
<ItemsControl ItemsSource="{Binding TabControls}">
<!--<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>-->
</ItemsControl>
</StackPanel>
</DockPanel>
</Window>
So, basically, I want to achieve a Master/Detail view, where Master is the listbox and Detail is its belonding TabControl.
I know, that I can manually add/delete children of the Stackpanel, based on list item selection, but it seems to me not the MVVM way.
You want a ListBox and a TabControl.
Both controls you have there are itemscontrols.
They both inherit from Selector so they have SelectedItem.
Bind a collection of viewmodels to your listbox, call these something meaningful. PersonViewModel or FruitViewModel or something. Don't call them listboxitem or anything that is the same name or anything like the name of a UI control. One per row. But let's call them RowVM as in a viewmodel holding the data for a listbox row.
If you bind to SelectedItem of that ListBox you get an instance of RowVM.
You can bind that from your listbox to a property in your WindowViewModel. You can also bind to that property from some other control.
Let's go back to meaningful names though.
Maybe a Person has Shoes. A collection of Shoe that you're going to show in that tabcontrol. Choose a Person in the listbox and you see their shoes in the tabcontrol. So PersonViewModel would have an observablecollection of shoe.
You can bind itemssource of that tabcontrol to Shoes of the selected item of the listbox. Which will then grab that collection of shoes for whichever person you choose in the listbox.
That'll be quite a clunky sort of a binding though and binding both to a property in your viewmodel is usually cleaner.
If you bind that listbox to a collectionview, you can alternatively use the current item / notation in your binding.
https://social.technet.microsoft.com/wiki/contents/articles/29859.wpf-tips-bind-to-current-item-of-collection.aspx
And of course that'll just give you the type name because it doesn't know how to display a shoe.
But that's no problem because wpf has a whole data templating system. That allows you to give it a template to use for a shoe.
You should be able to google examples of all that stuff but here's one on the tabcontrol specifically:
How do I bind a TabControl to a collection of ViewModels?
I hope that's enough to point you in the right direction. Or at least along a path with less rocks.
I have what I believe to be a potentially unique situation.
My ListBox items consist of the following:
StackPanel
Image
ListItem
The ListItem and Image are inserted into the StackPanel, then the StackPanel is the inserted into the ListBox for each item in the array.
Now the challenging part comes in sorting the content by the ListItem's Content (text) as it's a child of the StackPanel. Naturally, the StackPanel does not contain a Content member, so using the below code fails.
this.Items.SortDescriptions.Add(new System.ComponentModel.SortDescription("Content",
System.ComponentModel.ListSortDirection.Ascending));
So I figured, what if I set my StackPanel's data context to my ListItem, then surely it will find it.
stackPanel.DataContext = this.Items;
However, that also fails.
I'm creating my ListItems programatically in the code behind, via data that is loaded in via Json.Net.
My goal here is to sort the items from A-Z, based on the Items Content. I would prefer to keep my current implementation (creating the data programatically) as it gives me more control over the visuals. Plus, it's only about 20 lines of code.
Is it possible to use SortDescriptions when the ListItem's content is a StackPanel ?
Thank you
PS: Only started with WPF today, but have been developing WinForms apps for nearly 2 months.
The WPF way to do it would be to bind your ListBox ItemsSource to an ObservableCollection containing your items.
You would then be able to sort your observableCollection liks so :
CollectionViewSource.GetDefaultView(YourObservableCollection).SortDescriptions.Add(new SortDescription("PropertyToSort", ListSortDirection.Ascending));
Here is a small project that highlights this :
XAML :
<Window x:Class="stackPanelTest.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:stackPanelTest"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ListBox ItemsSource="{Binding Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Label Content="{Binding Image}" />
<TextBlock Text="{Binding Item.Content}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
Code Behind :
public partial class MainWindow : Window
{
public ViewModel Items { get; set; } = new ViewModel();
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
}
ViewModel :
public class ViewModel : ObservableCollection<ListItem>
{
public ViewModel()
{
populateItems();
CollectionViewSource.GetDefaultView(this).SortDescriptions.Add(new SortDescription("Item.Content", ListSortDirection.Ascending));
}
private void populateItems()
{
addOneItem(0, "zero");
addOneItem(1, "one");
addOneItem(2, "two");
addOneItem(3, "three");
addOneItem(4, "four");
}
private void addOneItem(int img, string content)
{
ListItem item = new ListItem();
item.Image = img;
item.Item = new SomeItem { Content = content };
Add(item);
}
}
public class ListItem
{
public int Image { get; set; }
public SomeItem Item { get; set; }
}
public class SomeItem
{
public string Content { get; set; }
}
I took the liberty of renaming your "ListItem" into a "SomeItem" class because I didn't know what it was.
Then I made a "ListItem" class which is used to contain a Image/SomeItem pair (which is what your ListBox is composed of).
Also I used an int instead of an actual image but that should be easily changable.
Here's a screenshot of what I get when executing this code :
Hope this helps, good luck.
PS : if your items values are susceptible to change, don't forget to implement INotifyPropertyChanged in "SomeItem" and "ListItem", otherwise the change won't be updated in your view.
Is it possible to use SortDescriptions when the ListItem's content is a StackPanel ?
No. You will have to implement the sorting logic yourself.
There is no easy way to apply custom sorting to the ItemCollection that is returned from the Items property of the ListBox so instead of adding items to this one you could add the items to a List<StackPanel> and sort this one.
You could still create the data programatically just as before.
Here is an example for you:
Code:
public partial class MainWindow : Window
{
private List<StackPanel> _theItems = new List<StackPanel>();
public MainWindow()
{
InitializeComponent();
//create the items:
StackPanel sp1 = new StackPanel();
ListBoxItem lbi1 = new ListBoxItem() { Content = "b" };
Image img1 = new Image();
sp1.Children.Add(lbi1);
sp1.Children.Add(img1);
_theItems.Add(sp1);
StackPanel sp2 = new StackPanel();
ListBoxItem lbi2 = new ListBoxItem() { Content = "a" };
Image img2 = new Image();
sp2.Children.Add(lbi2);
sp2.Children.Add(img2);
_theItems.Add(sp2);
StackPanel sp3 = new StackPanel();
ListBoxItem lbi3 = new ListBoxItem() { Content = "c" };
Image img3 = new Image();
sp3.Children.Add(lbi3);
sp3.Children.Add(img3);
_theItems.Add(sp3);
//sort the items by the Content property of the ListBoxItem
lb.ItemsSource = _theItems.OrderBy(x => x.Children.OfType<ListBoxItem>().FirstOrDefault().Content.ToString()).ToList();
}
}
XAML:
<ListBox x:Name="lb" />
I have some sort of cascaded containers that contain notes, where there is one master container, that contains all notes. The notes containers are made in a tree like hierarchy that get more specific the deeper you are in tree-structure. The reason why I have only one list has to do with a very complex house keeping of the data and it's not part of the problem.
The master note container has an ObservableCollection and all sub notes containers are bound to the ObservableCollection via CollectionView. The sub notes containers have a filter that filter out their notes. In regular code everything works fine and the view always shows the elements, but when I bind them to a e. g. ListBox, the elements are not filtered and all elements from the master list are shown without filtering. Of course I know there is a ListCollectionView, but since CollectionView is from IEnumerable, I'm curious how the ListBox has access to the master list, if it does not access the SourceCollection from the CollectionView.
In other words, I'm not quite sure why I need the ListCollectionView for very basic behavior where the ColletionView should fit. It seems to me that the ListCollectionView is mandatory and not other view really fits to the ListBox?
Here is a small sample
XAML:
<Window x:Class="ListCollection.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<StackPanel Orientation="Horizontal">
<ListBox Width="100" ItemsSource="{Binding Model}"></ListBox>
<ListBox Width="100" ItemsSource="{Binding View1}"></ListBox>
<ListBox Width="100" ItemsSource="{Binding View2}"></ListBox>
</StackPanel>
</Window>
C#:
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using System.Windows.Data;
namespace ListCollection
{
/// <summary>
/// Interaktionslogik für MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public ObservableCollection<int> Model
{
get; private set;
}
public ICollectionView View1
{
get; private set;
}
public ICollectionView View2
{
get; private set;
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
Model = new ObservableCollection<int>();
View1 = new CollectionView(Model);
View1.Filter = (o) =>
{
return ((int)o) > 50;
};
View2 = new CollectionView(View1);
View2.Filter = (o) =>
{
return ((int)o) > 70;
};
for (int i = 0; i < 100; i++)
Model.Add(i);
}
}
}
Thanks
Martin
The very first line in the remarks section of the documentation for the CollectionView class says:
You should not create objects of this class in your code.
So, I am guessing it is probably not designed to be used the way you are using it.
I always use CollectionViewSource.GetDefaultView(collection) (which will end up returning a ListCollectionView for ObservableCollection<T>, but it is returned as an ICollectionView).
EDIT: To hopefully clear some things up, here is some additional information about collection views.
There is a pretty decent overview of collection views on the Data Binding Overview documentation page which is a good read. It explains why there is one default view per collection. It is created by the framework any time a collection is used as the source of a data binding. The GetDefaultView method gets that view (and creates it if it did not already exist). Subsequent calls to the method will always return the same view.
If you bind an ItemsControl.ItemsSource directly to a collection, it will use the default view. So, binding to the default view and binding to the collection itself have the same result. If you want to have multiple views into the same collection, then you will want to create your own collection views and bind to those explicitly, rather than binding to the collection or the default view.
There are a couple ways to create collection views, depending if you are creating them from code or xaml.
Creating from Viewmodel Code
Create a new ListCollectionView, passing the collection to the constructor. If you are in viewmodel code, you can then expose the view as a property (usually of type ICollectionView).
Viewmodel code:
private ObservableCollection<Item> mItems;
public ICollectionView MyView { get; private set; }
public MyVM()
{
mItems = new ObservableCollection<Item>();
ListCollectionView myView = new ListCollectionView(mItems);
// Do whatever you want with the view here
MyView = myView;
}
View code:
<ItemsControl ItemsSource="{Binding MyView}" />
Creating from XAML View
Create a CollectionViewSource and set its Source property to the collection. You can also set other properties such as Filter, which will call into the code-behind to run the filter.
Viewmodel code:
private ObservableCollection<Item> mItems;
public IEnumerable<Item> Items { get { return mItems; } }
public MyVM()
{
mItems = new ObservableCollection<Item>();
}
View code:
<Grid>
<Grid.Resources>
<CollectionViewSource
x:Key="MyItemsSource"
Source="{Binding Items}" />
</Grid.Resources>
<ItemsControl ItemsSource="{Binding Source={StaticResource MyItemsSource}}" />
</Grid>
I am trying to inject a LayoutGrid and a canvas into my windows, but this causes a little headache:
Here is my WindowBase class:
public class WindowBase : Window
{
protected override void OnInitialized(EventArgs e)
{
base.OnInitialized(e);
if (Content is FrameworkElement)
{
var originalContent = Content as FrameworkElement;
var grid = new Grid();
grid.DataContext = originalContent.DataContext;
Content = grid;
grid.Children.Add(originalContent);
var canvas = new Canvas() { HorizontalAlignment = HorizontalAlignment.Stretch, VerticalAlignment = VerticalAlignment.Stretch };
grid.Children.Add(canvas);
}
}
}
My MainWindow thats inheriting from WindowBase looks like this:
XAML:
<local:WindowBase x:Class="InsertCanvasTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:InsertCanvasTest"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Border>
<Grid>
<ComboBox SelectedIndex="1"
ItemsSource="{Binding ItemSource1}" />
</Grid>
</Border>
</local:WindowBase>
Code Behind of MainWindow:
public partial class MainWindow : WindowBase
{
private List<int> _itemSource1;
public List<int> ItemSource1
{
get
{
if (_itemSource1 == null)
_itemSource1 = new List<int>(){1,2,3};
return _itemSource1;
}
}
public MainWindow()
{
InitializeComponent();
}
}
As you can see in my XAML I have specified that the SelectedIndex should be 1, but with the code in my WindowBase where I am trying to inject the Canvas and the Grid this information gets lost and the SelectedIndex is at -1.
Is there a way to fix this?
I would like to keep the MainWindow as a Window and not implement it as a control and load this into some different Window inside a ContentPresenter of so.
I know this problem wouldnt exist if I declared the Canvas/Grid in the XAML directly instead of trying to inject it in codebehind, but doing this with 100+ windows and trying to maintain them if something changes is annoying.
Change your WindowBase class like that :
WindowBase
[ContentProperty("InternalContent")]
public class WindowBase : Window
{
// InternalContent
public static readonly DependencyProperty InternalContentProperty =
DependencyProperty.Register( "InternalContent", typeof(object),
typeof(WindowBase), new FrameworkPropertyMetadata(null));
public object InternalContent
{
get { return GetValue(InternalContentProperty); }
set { SetValue(InternalContentProperty, value); }
}
...
}
<Window ...>
<Grid>
...
<ContentControl IsTabStop="false"
Content="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}, Path=InternalContent}" />
<Canvas />
...
</Grid>
</Window>
In essence, what it does is to create a new InternalContent property that subclasses will see as the default content (thanks to ContentPropertyAttribute), and display that content with a ContentControl.
That said, there is most likely better ways to do what you're trying to do than inheritance. Using templates comes to mind. Or maybe Adorners if what you want is an "above all layer to display things" like your Canvas suggest.
I suspect your problem is the setting of Content property of your Superclass Window to a "new" grid and then inserting the original content into that grid.
Where I think the problem may be is that you are setting the variable originalContent equal to Content BUT Content is an Object meaning it's a Reference Type. Problem shown below
var originalContent = Content as FrameworkElement;
//Reference Type: originalContent POINTS AT Content;
var grid = new Grid();
Content = grid;
//Reference Type: Content POINTS AT grid vsv. originalContent now POINTS AT grid
// Now because of Pointers and Reference Types
// originalContent = grid
If you want to preserve the Original Content in your Window Base Class using the Code you've shared, you would need to clone the control and use that reference in
var originalContent = CLONE OF CONTENT.
your variable originalContent has a parent and so kannt be added to a new Frameworkelement. You must remove originalContent from the old parent before you add it to a new one.
I want to "Listen" to the my main ListView and if one or more of the items are removed, their clones in the other ListViews will be removed aswell.
For example:
I have 5 items in the main ListView
In the second ListView I have 2 cloned items (their position in the main ListView are 0 and 2)
In the third ListView I have another 2 cloned items (their position in the main `ListView are 0 and 4)
When I remove item number 0 from the main ListView, I want it to be removed from all its clones from the other ListViews.
Right now, what I am doing, is when item removed from the main ListView, I loop through the other ListViews to check if it matches to the removed one, if it does I remove them aswell.
I read about the ObservableCollection but I cant understand how to implement it for my needs... would appreciate help / easy guide that will explain simply how to do it.
EDIT:
This one is WinForms solution - I dont know how to convert it to WPF solution plus I am not even sure if this is what I need
http://www.codeproject.com/Articles/4406/An-Observer-Pattern-and-an-Extended-ListView-Event
To illustrate my point from the comments section, Say you have a UserControl called WindowItem that implements a Clone method:
<UserControl x:Class="WpfApplication1.WindowItem"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Button Content="Click me"/>
</Grid>
</UserControl>
I create a class that will hold mutiple collections of WindowItem
public class MainWindowViewModel
{
public MainWindowViewModel()
{
}
public ObservableCollection<WindowItem> FirstCollection { get; set; }
public ObservableCollection<WindowItem> SecondCollection { get; set; }
public ObservableCollection<WindowItem> ThirdCollection { get; set; }
}
I also create a View contaning three ListViews, binded to my collections via Data Bindings
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Grid>
<StackPanel>
<ListView ItemsSource="{Binding FirstCollection}"/>
<ListView ItemsSource="{Binding SecondCollection}"/>
<ListView ItemsSource="{Binding ThirdCollection}"/>
</StackPanel>
</Grid>
</Window>
Next, in the constructor of my MainWindow (known as View), I set it's DataContext to be the class I created earlier (known as ViewModel in MVVM).
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
}
}
Now, I can create cloning of my user controls for the different collection, shown in different lists in my view and register to the CollectionChanged event of the first collection. Here is an example of doing so from the ViewModel constructor
public MainWindowViewModel()
{
FirstCollection = new ObservableCollection<WindowItem>();
SecondCollection = new ObservableCollection<WindowItem>();
ThirdCollection = new ObservableCollection<WindowItem>();
var windowItem = new WindowItem();
FirstCollection.Add(windowItem);
SecondCollection.Add(windowItem.Clone());
// Register to collection changes notifications
FirstCollection.CollectionChanged += FirstCollectionChanged;
}
FirstCollectionChanged will fire whenever the first collection changes. You can "listen" for a removal action, and then remove the matching items from you other collections.
private void FirstCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Remove)
{
// Remove matching item from second and third collection.
}
}
You can test it by removing an item from the first collection
FirstCollection.RemoveAt(0);
Hope this helps
Instead of adding and removing items directly to/from ListView object bind it to some collection(preferably Observable Collection) and add/remove the items from ObservableCollection, it will be reflected on your UI also.
eg
ObservableCollection<WindowItem> MyCollection=new ObservableCollection<WindowItem>();
then add items by
MyCollection.Add(new WindowItem(parameters));
And for displaying your items from cloned listviews add an event handler to your main ObservableCollection like
MyCollection.CollectionChanged+=new System.Collections.Specialized.NotifyCollectionChangedEventHandler(CollectionChanged);
and
void CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
//bind your another listviews to cloned items
}