WPF Listen to ListViewItem Removal - c#

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
}

Related

adding a list to itemsource, WPF

I am starting with WPF and I have this problem. I have a file called MainWindow.xaml with this code:
<Window x:Class="View.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:View" xmlns:system="clr-namespace:System;assembly=System.Runtime"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid >
<ItemsControl ItemsSource="{Binding}" x:Name="boardView">
</ItemsControl>
</Grid>
</Window>
And I have another file called MainWindow.xaml.cs with this code
namespace ViewModel
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var items = new List<string> { "a", "b", "c", "d", "e" };
}
}
}
Now I have to assign this list to boardView.ItemsSource. How can I do that?
You have four problems here that I can see that would need to get fixed for this to work.
In order for data binding to work, you need to set the DataContext of your MainWindow.
MainWindow.xaml.cs:
// Put this in the constructor after InitializeComponents();
this.DataContext = this;
Another requirement for data binding is to implement the INotifyPropertyChanged interface on the class you wish to having data binding (in your case this is MainWindow, but I recommend you read on MVVM design):
MainWindow.xaml.cs:
public partial class MainWindow : Window, INotifyPropertyChanged
Data bindings only work on public properties, so using var items isn't following this requirement. Instead, make var items a public property that updates itself with the PropertyChanged event whenever the value changes.
MainWindow.xaml.cs:
private List<string> items;
public List<string> Items
{
get => this.items;
set
{
this.items = value;
PropertyChanged?.Invoke(this, new PropertyName(nameof(Items)));
}
}
Lastly, you need to fix your binding in the xaml to bind to your public property.
MainWindow.xaml:
<ItemsControl ItemsSource="{Binding Items}" x:Name="boardView">

CollectionView: Filter does not work when binding to e. g. ListBox

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>

How will i show the first item of an observablecollection in a Listbox?

i have this declaration:
public ObservableCollection<SomeType> Collection { get; set; }
i tried something, like:
myListBox.ItemsSource = Collection[0];
to show the first item of Collection in the Listbox control, but it gives error.
how will i do this? what change should i make on the right side?
You need to bind the ItemSource to your collection, and then set the selected index to the one you want (0 in your case)
Here's the smallest example. XAML:
<Window x:Class="WPFSOTETS.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"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<ComboBox ItemsSource="{Binding Collection}" SelectedIndex="2"></ComboBox>
</Grid>
</Window>
Code behind:
using System.Collections.ObjectModel;
using System.Windows;
namespace WPFSOTETS
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
public ObservableCollection<string> Collection
{
get
{
return new ObservableCollection<string> {"one","two","three"};
}
}
}
}
I set the index to 2, just for fun, but you can play with it.
As of comment, if you want to set this in the codebehind, you'll have to name your control, and then you can use it from your codebehind and do your binding and setting there. You can have a look at this answer for example.

WPF Custom Control Error

I have some problems with a WPF custom control, I'm trying to make it work but just don't get it:
Here is my problem, I'm creating a simple custom control that's almost the same to a TextBox. This control has a dependency property named "Texto", and the binding between the XAML and back-code of the custom control works fine, here is the code:
<UserControl x:Class="WpfCustomControlLibrary1.UserControl1"
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="47" d:DesignWidth="147">
<Grid Height="43" Width="142">
<TextBox Height="23" HorizontalAlignment="Left" Margin="12,8,0,0" Name="textBox1" VerticalAlignment="Top" Width="120" Text="{Binding Texto}"/>
</Grid>
And the dependency property code:
public static readonly DependencyProperty TextoProperty = DependencyProperty.Register("Texto", typeof(string), typeof(UserControl1));
public string Texto
{
get
{
return (string)GetValue(TextoProperty);
}
set
{
SetValue(TextoProperty, value);
}
}
Ok, now the problem: When I use this control in other windows I try to bind the "Texto" property to a viewmodel (as simple as everything else) but the property on the view model just dont change:
The ViewModel code:
public class ViewModelTest
{
public string SomeText { get; set; }
}
And the code of the applicatoin Window:
public ViewModelTest test;
public Window1()
{
InitializeComponent();
}
private void button1_Click_1(object sender, RoutedEventArgs e)
{
MessageBox.Show(test.SomeText);
MessageBox.Show(uc.Texto);
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
test = new ViewModelTest();
this.DataContext = test;
}
And the binding with the property of the view model:
<my:UserControl1 HorizontalAlignment="Left" Margin="27,12,0,0" Name="uc" VerticalAlignment="Top" Texto="{Binding Path=SomeText,Mode=TwoWay}"/>
Just for make it clearer, if I write "Hello" in the custom control and then I push the "button1", the first message shows nothing and the second message shows "Hello".
As you can see I'm fairly new into this, so I hope some of you can help me. Thanks.
Your binding Texto="{Binding SomeText}" works fine, the problem is the rebinding from your user control to the inner textbox. Remember binding will ALWAYS, if not modified, refere to the DataContext. But your DataContext doesn't contain the property Texto. Your control has that, To refere to that you need something called TemplateBinding, but this only works when you are in a ControlTemplate. Which you aren't so what is the solution?
You can use a special form of binding, by changing the source from the DataContext to a control with a given name: First give your UserControl a name
<UserControl x:Class="WpfCustomControlLibrary1.UserControl1"
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"
x:Name="mainControl"
d:DesignHeight="47" d:DesignWidth="147">
and now change the binding to refere to the control, not the DataContext of the control anymore.
<TextBox Text="{Binding ElementName=mainControl, Path=Texto}"/>
Now your ViewModel binds to your user control and the content of the user control binds to the user controls Texto property.
Also one minor thing, what you called custom control, is in fact a user control, custom controls are something else.

A Simple Wpf MVVM Binding Issue

I am trying my hands on WPF MVVM. I have written following code in XAML
<UserControl x:Class="Accounting.Menu"
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"
xmlns:local="clr-namespace:Accounting"
mc:Ignorable="d"
d:DesignHeight="105" d:DesignWidth="300">
<UserControl.DataContext>
<local:MenuViewModel/>
</UserControl.DataContext>
<StackPanel>
<StackPanel>
<TextBlock Text="{Binding Path=MenuHeader}"/>
</StackPanel>
<ListBox ItemsSource="{Binding Path=MenuItems}" Height="70"/>
</StackPanel>
</UserControl>
I have got a MenuViewModel with properties MenuHeader and MenuItems. I get values in both the properties during runtime. Former is bound to text of TextBlock and latter to ItemSource of ListBox. But when I run the solution, TextBlock and ListBox are empty.
Edit: Code of ViewModel
public class MenuViewModel: ViewModelBase
{
AccountingDataClassesDataContext db;
private string _menuType;
public string MenuHeader { get; set; }
public ObservableCollection<string> MenuItems { get; set; }
public MenuViewModel()
{
}
public MenuViewModel(string menuType)
{
this._menuType = menuType;
db = new AccountingDataClassesDataContext();
if (menuType == "Vouchers")
{
var items = db.Vouchers.OrderBy(t => t.VoucherName).Select(v => v.VoucherName).ToList<string>();
if (items.Any())
{
MenuItems = new ObservableCollection<string>(items);
MenuHeader = "Vouchers";
}
}
else
{
System.Windows.MessageBox.Show("Menu not found");
}
}
}
Thanks in advance.
You are creating your ViewModel in the XAML using your ViewModel's default contructor which does nothing. All your population code is in the non-default contructor which is never called.
The more usual way is to create the ViewModel in code, and inject it into the view either explicitly using View.DataContext = ViewModel, or impllcitly using a DataTemplate.
I think you have to trigger the OnPropertyChanged event. I am not sure if you are using a MVVM library (since you inherit from ViewModelBase you might be using MVVM Light for example), there they wrap the OnPropertyChanged in the RaisePropertyChanged event handler.
Triggering the event will inform WPF to update the UI.
string m_MenuHeader;
public string MenuHeader
{
get
{
return m_MenuHeader;
}
set
{
m_MenuHeader=value; OnPropertyChanged("MenuHeader");
}
}

Categories