In my windows phone 8 application I've created truly observable collection for noticing when item changes in collection. Here is collection code:
public class TrulyObservableCollection<T> : ObservableCollection<T> where T : INotifyPropertyChanged
{
public TrulyObservableCollection()
: base()
{
CollectionChanged += new NotifyCollectionChangedEventHandler(TrulyObservableCollection_CollectionChanged);
}
void TrulyObservableCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (Object item in e.NewItems)
{
var test = item as INotifyPropertyChanged;
(item as INotifyPropertyChanged).PropertyChanged += new PropertyChangedEventHandler(item_PropertyChanged);
}
}
if (e.OldItems != null)
{
foreach (Object item in e.OldItems)
{
(item as INotifyPropertyChanged).PropertyChanged -= new PropertyChangedEventHandler(item_PropertyChanged);
}
}
}
void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
NotifyCollectionChangedEventArgs a = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
OnCollectionChanged(a);
}
}
The situation which im going to describe happens for both controls: expander view (silverlight toolkit) and expander control (telerik controls).
In my view I have list of expander views/controls. On Item tap event I would like to change something in tapped item and refresh list. Here is the code how i'm doing it:
ViewModel:
public TrulyObservableCollection<SymbolRecord> Symbols {get; set;}
private RelayCommand<SymbolRecord> tapCommand;
public RelayCommand<SymbolRecord> TapCommand
{
get
{
return tapCommand ?? (tapCommand = new RelayCommand<SymbolRecord>((item) =>
{
item.Symbol = "test";
}));
}
}
View:
<telerikData:RadJumpList x:Name="ListControl" Grid.Row="1"IsCheckModeEnabled="False" GroupPickerItemTemplate="{StaticResource JumpListHeaderItemTemplate}" GroupHeaderTemplate="{StaticResource JumpListHeaderTemplate}" ItemsSource="{Binding Path=Symbols}" >
<telerikData:RadJumpList.ItemTemplate>
<DataTemplate>
<StackPanel>
<toolkit:ExpanderView ItemsSource="{Binding}" Expander="{Binding}" VerticalAlignment="Stretch" VerticalContentAlignment="Stretch" ExpanderTemplate="{StaticResource ExpanderControlContentTemplate}" >
<toolkit:ExpanderView.Items> <TextBox Text="TEST"></TextBox></toolkit:ExpanderView.Items>
</toolkit:ExpanderView>
</StackPanel>
</DataTemplate>
</telerikData:RadJumpList.ItemTemplate>
</telerikData:RadJumpList>
<i:Interaction.Triggers>
<i:EventTrigger EventName="ItemTap" SourceName="ListControl" >
<cmd:EventToCommand Command="{Binding TapCommand}" CommandParameter="{Binding SelectedItem,
ElementName=ListControl}" />
</i:EventTrigger>
</i:Interaction.Triggers>
When I click on item in emulator the property Symbol is changing but what's more random items are being expanded. Dont know why this is happening. Please help me
What do you mean by refreshing your list?
Do you want to put some new data int it? If so there's no need to use TrulyObser... nor call CollectionChanged at all. Simply use methods like remove, add, insert etc.
Item template automatically get's as datacontext proper collection element. And binding works normal. Simple INotifyCollectionChanged will be enough.
If you want to expand an expander do it in view's code-behind. If code-behind is something that you want to avoid create property IsExpanded in VM and bind it to Expander's IsExpanded property.
You can set your's VM IsExpanded property using that TrulyObservableCollection. When one of item's in the collection changed set it's IsExpanded to true.
Anyways. In most cases there's no need for "TrulyObservableCollection"
Related
So I am trying to bind the following ViewModel:
public class ViewModel : INotifyPropertyChanged
{
private ObservableCollection<ListBoxItem> _PlacesOrCities;
public ObservableCollection<ListBoxItem> PlacesOrCities
{
get { return _PlacesOrCities; }
set { _PlacesOrCities = value; RaisePropertyChanged("PlacesOrCities"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public ViewModel()
{
_PlacesOrCities = new ObservableCollection<ListBoxItem>();
}
}
To the following xaml:
<ListBox Name="lbPlacesCity" ItemsSource="{Binding Path=(gms:MainWindow.ViewModel).PlacesOrCities, UpdateSourceTrigger=PropertyChanged}">
<ListBox.ItemTemplate>
<DataTemplate DataType="models:ListBoxItem">
<TextBlock Style="{StaticResource MaterialDesignBody2TextBlock}" Text="{Binding Name}" Visibility="{Binding Visibility}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
In the codebehind as such:
public ViewModel ViewModel { get; set; }
public MainWindow()
{
InitializeComponent();
ViewModel = new ViewModel();
DataContext = ViewModel;
}
And upon firing a button click event- I try to set the values of the observable collection using a in memory list:
private void StateProvince_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
_CurrentSelectionPlaces = Canada.Provinces
.FirstOrDefault(x => x.Abbreviation == _SelectedStateProvince_ShortName)
.Place.OrderBy(x => x.Name).ToList();
foreach (var currentSelectionPlace in _CurrentSelectionPlaces)
{
ViewModel.PlacesOrCities.Add(currentSelectionPlace);
}
}
But it seems like none of the items are being added to the collection. Am I binding it incorrectly?
I've tried quite a few solutions but none of them seem to change the result- where no items in the list are being loaded into the collection properly.
EDIT:
It may be worth noting that the ListBoxItem as seen in the ViewModel is a custom model:
public class ListBoxItem
{
[J("Name")] public string Name { get; set; }
[J("PostalCodes")] public string[] PostalCodes { get; set; }
public Visibility Visibility { get; set; } = Visibility.Visible;
}
You should try to fit to the MVVM pattern, so the population of the list should occur at viewmodel level and not in the view's code behind.
You mentioned that you use a click event, instead of doing so, try to bind the command property of the button to a command in the viewmodel, see this link with an explanation of several types of commands and how to use them: https://msdn.microsoft.com/en-us/magazine/dn237302.aspx
In the other hand, if you already set the data context in the window constructor, to bind the ListBox items source you only need the name of the property to bind, "PlacesOrCities":
<ListBox Name="lbPlacesCity" ItemsSource="{Binding Path=PlacesOrCities, UpdateSourceTrigger=PropertyChanged}">
<ListBox.ItemTemplate>
<DataTemplate DataType="models:ListBoxItem">
<TextBlock Style="{StaticResource MaterialDesignBody2TextBlock}" Text="{Binding Name}" Visibility="{Binding Visibility}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
It would also be recommendable trying to load the items in the list without any template, you can use ListBox DisplayMemberPath property to display the name, and once you are able to load items, apply the style.
Also in the way you use ObservableCollection, you actually need to replace the whole collection instead of adding to fire RaisePropertyChanged, try a normal property instead.
public ObservableCollection<ListBoxItem> PlacesOrCities {get;set;} = new ObservableCollection<ListBoxItem>();
Modifying the collection will update the UI, so whenever you use Add or Clear, the UI should know it.
Hope it helps.
this is my first project in WPF and I am currently stuck on my Shopping Cart.
I create an object newbasketItem with the member ItemQuantity with the value "1". My listview works perfectly fine through Binding. I can add items, remove items and clear my Collection. Now to my problem:
I want to select an item in my viewlist and increment the ItemQuantity with a button.
Is there any way to edit the ItemQuantitywhen it has already been added to the Collection?
BasketItem newBasketItem = new BasketItem();
newBasketItem.ItemQuantity = 1;
basketitems.Add(newBasketItem);
newBasketItem is my ObservableCollection.
Thank you!
Your BasketItem class should implement the INotifiedPropertyChange interface and the listview content will be automatically updated (provided you have a binding on ItemQuantity).
For example:
public class BasketItem : INotifyPropertyChanged
{
private int quantity = 0;
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public int ItemQuantity
{
get
{
return this.quantity;
}
set
{
if (value != this.quantity)
{
this.quantity = value;
NotifyPropertyChanged();
}
}
}
}
The ObservableCollection already implements this interface but it is not enough since it only allows to trigger event when items are added, deleted or moved. But if you want the UI to be notified when an item is modified, this item must also implements the INotifyPropertyChanged interface.
About your click event, you should use an ICommand as suggested by #Sir Rufo. You can also do the following with a XAML like this:
<ListView ItemsSource="{Binding Items}" IsItemClickEnabled="True" ItemClick="ListView_ItemClick" Margin="0,50,0,0" >
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ItemQuantity}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
and in your code behind:
private void ListView_ItemClick(object sender, ItemClickEventArgs e)
{
var item = e.ClickedItem as BasketItem;
item.ItemQuantity++;
}
I've the following situation in my project and i'm wondering what's the best way to achieve my goal.
Goal: Having a combobox with itemsource binding and one entry which is functioning like a refresh button (fetching items from database and update combobox items).
Currently I set up my combobox with itemsource binding (see below), but currently i'm struggling with the binding of the command for refreshing.
ItemsSource Binding:
<UserControl.Resources>
<CollectionViewSource x:Key="ProjectSource" Source="{Binding Projects, ElementName=Ancestor}"/>
<CompositeCollection x:Key="ProjectCollection">
<CollectionContainer Collection="{Binding Source={StaticResource ProjectSource}}"/>
<Button Content="Refresh!"/>
</CompositeCollection>
</UserControl.Resources>
Where Projects is a dependency property with an enumeration of items, another dependency property with the refresh command (an ICommand) is also available.
My ComboBox ist defined as follows:
<ComboBox SelectedValue="{Binding Project}"
ItemsSource="{StaticResource ProjectCollection}"
VerticalContentAlignment="Center"
HorizontalAlignment="Left"
Name="Box"
IsHitTestVisible="{Binding IsEditable}"
IsEnabled="{Binding IsEnabled, Mode=OneWay, IsAsync=True}">
<ComboBox.Resources>
<DataTemplate DataType="{x:Type viewModels:ProjectViewModel}">
<TextBlock Text="{Binding Name}"/>
</DataTemplate>>
</ComboBox.Resources>
</ComboBox>
The problem is that the Command can't find the source of the binding, so the question is am i on the right way doing it and there is a solution, or am I on the wrong way (which would be better?).
Sure i could just add a button next to my combobox, but i'd like to have it in my combobox. :)
Btw.: I'm trying to follow the MVVM pattern.
I have solved this issue in the past by using code behind. When the combobox loads, create a new List<objects> of the Projects and add a Refresh string (maybe "<Refresh...>") to the list, and finally setting the ItemsSource to this list. Use a template selector to show the appropriate DataTemplate. When the selection changes, check if the Refresh string was selected, and if so, do your refresh, and reload the combobox. When you refresh, you can try to set the selection back to the previously selected item, or index 0, so the user can never have "refresh" selected in the combobox.
Some snippets to demonstrate.
in ctor
SelectedProjectComboBoxTemplateSelector.StringTemplate = FindResource("StringTemplate") as DataTemplate;
SelectedProjectComboBoxTemplateSelector.ProjectTemplate = FindResource("ProjectTemplate") as DataTemplate;
SelectedProjectComboBox.SelectionChanged += SelectedProjectComboBox_SelectionChanged;
SelectedProjectComboBox.ItemTemplateSelector = new SelectedProjectComboBoxTemplateSelector();
and
void SelectedProjectComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e) {
if (SelectedProjectComboBox.SelectedItem is string && ((string)SelectedProjectComboBox.SelectedItem) == RefreshProjectSelectionItem) {
object current = e.RemovedItems.Count > 0 ? e.RemovedItems[0] : null;
bool ret = RefreshData(); // from db
if (ret) {
LoadData(); // repopulate combobox
} else {
SelectedProjectComboBox.SelectedItem = current;
}
}
}
and
public class SelectedProjectComboBoxTemplateSelector : DataTemplateSelector {
public static DataTemplate StringTemplate { get; set; }
public static DataTemplate ProjectTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container) {
if (item == null || Designer.IsInDesignMode) return null;
if (item is string) return StringTemplate;
if (item is Project) return ProjectTemplate;
return null;
}
}
You get the idea... This should be enough to get you going if this solution meets your needs.
I have a class PlayerVM.cs, that has a property:
public ObservableCollection<PlaylistVM> PlayLists
{
get { return _playLists; }
set { _playLists = value; }
}
PlaylistVM.cs also has its own collection:
public ObservableCollection<CompositionVM> Songs
{
get
{
return _songs;
}
set
{
_songs = value;
}
}
By that, my PlayerVM has a list of playlists, and each that playlist has its own list of songs.
I need to display list of songs being sorted alphabetically. But I don't want to change their order in ObservableCollections, I want change their order only in GUI (only display them alphabetically, not to change real order).
I am new in wpf (and xaml), and today I've learned only how to display PLAYLISTS NAMES ordered alphabetically. I am doing it by code:
<Window x:Class="Player_beta.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Player_beta">
<Window.Resources>
<CollectionViewSource Source="{Binding PlayLists}" x:Key="plView"><!--here I would like to get access to PlayLists.Songs-->
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="plNAME"/> <!--and here I would like to get NAME(that property of 'Songs' which contains songtitle) instead of plNAME - name of playlist)-->
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
</Window.Resources>
<!-- etc...-->
<TreeView Name="treeCategories" ItemsSource="{Binding Source={StaticResource plView}}" >
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Songs}">
<TextBlock Text="{Binding plNAME}"/> <!-- <-- here is where the name of PLAYLIST shows-->
<HierarchicalDataTemplate.ItemTemplate >
<DataTemplate >
<TextBlock Text="{Binding NAME}"/> <!-- <-- here is where the name of song shows-->
</Grid>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
What you can do is, you can add a CollectionView property to the PlaylistVM class:
public CollectionView songsCollectionView;
public CollectionView SongsCollectionView
{
get
{
if (songsCollectionView == null)
{
songsCollectionView = new CollectionView(Songs);
songsCollectionView.SortDescriptions.Add(new SortDescription("plNAME", ListSortDirection.Ascending));
}
return songsCollectionView;
}
}
and then you can bind your xaml like this:
<HierarchicalDataTemplate ItemsSource="{Binding SongsCollectionView}">
This should fulfill your requirement of showing sorted songs in GUI without changing the original collection.
You can add the ICollectionView instance in PlaylistVM.cs class and bind with it. This way original list will remain unaffected as well.
private ICollectionView songsView;
public ICollectionView SongsView
{
get
{
if (songsView== null)
{
songsView= CollectionViewSource.GetDefaultView(Songs);
songsView.SortDescriptions.Add(new SortDescription("Name",
ListSortDirection.Ascending));
}
return songsView;
}
}
and in XAML:
<HierarchicalDataTemplate ItemsSource="{Binding SongsView}">
This is not the most efficient, but will get the job done. Note that it does NOT detect if the name of any of the songs has changed. It also will rebuild the entire list in the UI everytime a song is added or removed.
public class PlaylistVM: System.ComponentModel.INotifyPropertyChanged
{
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<CompositionVM> _songs;
public ObservableCollection<CompositionVM> Songs
{
get
{
return _songs;
}
set
{
if(_songs != value)
{
//if old value not null, unhook event
if (_songs != null)
{
_songs.CollectionChanged -= FireSongsChanged;
}
//change the value
_songs = value;
//if new value !=null, then attach handlers.
if (_songs != null)
{
_songs.CollectionChanged += FireSongsChanged;
}
//this will fire the
FireSongsChanged(null, null);
}
}
}
void FireSongsChanged(object sender, EventArgs e)
{
//the collection of songs has changed, tell UI that it needs to requery the list of ordered songs.
var ev = this.PropertyChanged;
if (ev != null)
{
ev(this, new System.ComponentModel.PropertyChangedEventArgs("OrderedSongs"));
}
}
//set the UI to bind to the following list of the songs. ordered by name.
public IEnumerable<CompositionVM> OrderedSongs
{
get
{
return Songs.OrderBy(song => song.Name);
}
}
}
I am using MVVM light and have a list box with multiple selection. In my Mainpage.xaml I have
<ListBox Name="ListBox1" ItemsSource="{Binding Items}" Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="Transparent" Margin="15,15,18,0" SelectionMode="Multiple" Height="100" />
In MainPage.xaml.cs I have (I do not want to use dependency property for some reason).
MainPage()
{
ListBox1.SelectionChanged = new SelectionChangedEventHandler(ListBox1_SelectionChanged);
}
void ListBox1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var listBox = sender as ListBox;
var viewModel = listBox.DataContext as MainViewModel;
viewModel.SelectedItems.Clear();
foreach (string item in listBox.SelectedItems)
viewModel.SelectedItems.Add(item);
}
and this works fine and binds to my MainViewModel. But When the page is loaded I want the first item of the the collection items to be selected by default. Please let me know how to implement this
I'd recommend using the ListBox's Loaded event and then bind to the first item in the collection:
MainPage()
{
ListBox1.Loaded += new RoutedEventHandler( OnListBox1Loaded );
ListBox1.SelectionChanged += new SelectionChangedEventHandler(ListBox1_SelectionChanged);
}
private void OnListBox1Loaded( object sender, RoutedEventArgs e )
{
// make sure the selection changed event doesn't fire
// when the selection changes
ListBox1.SelectionChanged -= MyList_SelectionChanged;
ListBox1.SelectedIndex = 0;
e.Handled = true;
// re-hook up the selection changed event.
ListBox1.SelectionChanged += MyList_SelectionChanged;
}
Edit
If you can not use the Loaded event, then you'll need to create another property in your Model that will hold the item you want selected and then assign that property to the SelectedItem property of the ListBox.
public class MyModel : INotifyPropertyChanged
{
private ObservableCollection<SomeObject> _items;
public ObservableCollection<SomeObject> Items
{
get { return _items; }
set
{
_items = value;
NotifyPropertyChanged( "Items" );
}
}
private SomeObject _selected;
public SomeObject Selected
{
get { return _selected; }
set
{
_selected = value;
NotifyPropertyChanged( "Selected" );
}
}
public void SomeMethodThatPopulatesItems()
{
// create/populate the Items collection
Selected = Items[0];
}
// Implementation of INotifyPropertyChanged excluded for brevity
}
XAML
<ListBox ItemsSource="{Binding Path=Items}"
SelectedItem="{Binding Path=Selected}"/>
By having another property that holds the currently selected item, you also have access in your Model to that item as well whenever the selected item is changed by the user.