5

Background Explanation

Okay so I'm currently binding a ContextMenu ItemsSource to an ObservableCollection of lets say TypeA

The following code is within the singleton class DataStore

private ObservableCollection<TypeA> TypeACollection = new ObservableCollection<TypeA>();

public ObservableCollection<TypeA> GetTypeACollection
{
    get { return TypeACollection; }
}
public ObservableCollection<TypeA> GetFirstFiveTypeACollection
{
    get 
    {
        return TypeACollection.Take(5);
    }
}

Now I've already successfully bound an ItemsControl to GetTypeACollection with the following XAML code:

The following code is within the MainWindow.xaml

<ItemsControl x:Name="TypeAList" ItemsSource="{Binding GetTypeACollection, Source={StaticResource DataStore}, UpdateSourceTrigger=PropertyChanged}" >
<ItemsControl.ItemsPanel>
    <ItemsPanelTemplate>
        <VirtualizingStackPanel />
    </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <User_Controls:TypeAUserControl Type="{Binding }"/>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

This works as expected, showing the TypeAUserControl, correctly formatted as intended with the data from TypeA

Now when i try to repeat this on a ContextMenu MenuItem by binding the ItemsSource to GetFirstFiveTypeACollection i initially see the expected results however upon deleting a TypeA object the MainWindow ItemsControl is updated where the ContextMenu is not.

I believe this is due to the fact that the binding itself is between the ContextMenu and a 'new' ObservableCollection<TypeA> (as seen in GetFirstFiveTypeAColletion ).

I have also attempted this through use of an IValueConverter

The following code is within the class ValueConverters

public class ObservableCollectionTypeAResizeConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        ObservableCollection<TypeA> TypeACollection = value as ObservableCollection<TypeA>;

        return TypeACollection.Take(System.Convert.ToInt32(parameter));
    }
    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return null;
    }
}

The XAML code I have tried.

Using IValueConverter

<MenuItem Header="First 5 Type A" Name="MI_FirstFiveTypeA" 
    ItemsSource="{Binding DATA_STORE.GetTypeACollection, ConverterParameter=5,
            Converter={StaticResource ObservableCollectionTypeAResizeConverter}, 
            Source={StaticResource DataStore}}" 
/>

Using GetFirstFiveTypeACollection

<MenuItem Header="First 5 Type A" Name="MI_FirstFiveTypeA" 
    ItemsSource="{Binding DATA_STORE.RecentTimers, Source={StaticResource DataStore}, 
            UpdateSourceTrigger=PropertyChanged}"
/>

I've no idea what to try and do next or how i should be doing this, Any help would be greatly appreciated!

Edit

Okay so I have changed the following

DataStore.cs

private ObservableCollection<TimerType> TimerTypes = new ObservableCollection<TimerType>();

public ObservableCollection<TimerType> getTimerTypes
{
    get 
    { 
        return new ObservableCollection<TimerType>(TimerTypes.OrderByDescending(t => t.LastUsed)); 
    }
}
public ObservableCollection<TimerType> RecentTimers
{
    get 
    { 
        return new ObservableCollection<TimerType>(TimerTypes.OrderByDescending(t => t.LastUsed).Take(5)); 
    }
}

public event PropertyChangedEventHandler PropertyChanged;

// Create the OnPropertyChanged method to raise the event
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
    if (PropertyChanged != null)
    {
        PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}
//THIS IS IN THE ADD METHOD
TimerTypes.Add(timer);
NotifyPropertyChanged("getTimerTypes");
NotifyPropertyChanged("RecentTimers");
NotifyPropertyChanged("TimerTypes");

//THIS IS IN THE REMOVE METHOD
TimerTypes.Remove(Timer);
NotifyPropertyChanged("getTimerTypes");
NotifyPropertyChanged("RecentTimers");
NotifyPropertyChanged("TimerTypes");

MainWindow.xaml

<ItemsControl x:Name="TimersList" ItemsSource="{Binding Path=getTimerTypes, UpdateSourceTrigger=PropertyChanged}" >

MainWindow.xaml.cs

//Lists are populated in DataStore.cs before this.
DataContext = DataStore.DATA_STORE;
InitializeComponent();

NotifyIcon.xaml

<MenuItem Header="Recent Timers" Name="MIRecent" ItemsSource="{Binding RecentTimers, UpdateSourceTrigger=PropertyChanged}"/>

NotifyIcon.xaml.cs

DataContext = DataStore.DATA_STORE;
InitializeComponent();

So everything binds correctly at start but when a TimerType is deleted the PropertyChangedEventHandler is always NULL. I thought this was a DataContext issue but I'm pretty sure I have the DataContext and all of the Bindings correct now?

Singleton Instance Creation

private static readonly DataStore Instance = new DataStore();

private DataStore() { }

public static DataStore DATA_STORE
{
    get { return Instance; }
    set { }
}
user990423
  • 1,397
  • 2
  • 12
  • 32
  • You talk in code which is great, but what is the actual goal description? Maybe the question can be answered by a design which doesn't rely on the current architecture. – ΩmegaMan Oct 21 '15 at 21:37
  • @OmegaMan So the application has a Taskbar icon with a Context Menu which contains an item 'Recent Timers' this should show up to 5 previous timers in order of their property 'LastUsed'. In the main window of the application there is a ItemsControl as a stackpanel, this should show all previous timers in order of property 'LastUsed'. Each item in the stackpanel has a Context Menu allowing the User to 'Start', 'Rename' or 'Delete' the timer. The user can also create new timers from the main window. My intention was to store the timers in the singleton class DataStore and bind to there. – Chris Walters Oct 22 '15 at 09:26

2 Answers2

3

Unfortunately, by using Take (or any of the LINQ methods) you get a straight IEnumerable back. When you have bound to it, there is no INotifyCollectionChanged so changes to the collection will not cause the UI to update.

There's no real workaround due to the lack of INotifyCollectionChanged. Your best bet is to raise PropertyChanged against your "subset" property when you add/remove items to force the UI to renumerate the IEnumerable.

BradleyDotNET
  • 60,462
  • 10
  • 96
  • 117
  • Could you clarify what you mean by "subset" property? Would that be the `TypeACollection` property? – Chris Walters Oct 21 '15 at 16:16
  • @ChrisWalters That would be "GetFirstFiveTypeACollection" in your first example – BradleyDotNET Oct 21 '15 at 16:18
  • Okay so i have added this, I believe correctly however i am finding that the PropertyChanged event handler is null and therefore not being fired. would this be a DataContext issue perhaps? Sorry if this seems obvious I'm still learning the ropes with bindings! – Chris Walters Oct 21 '15 at 17:06
  • @ChrisWalters If `PropertyChanged` is null then it is very likely you forgot to set your `DataContext`, yes. – BradleyDotNET Oct 21 '15 at 17:08
  • Most likely I've done something stupid wrong but I've edited the question based on the issues I'm now having with `PropertyChanged` being Null. – Chris Walters Oct 21 '15 at 20:54
  • @ChrisWalters What is the nature of the static `DATA_STORE` property? – BradleyDotNET Oct 21 '15 at 20:59
  • The DATA_STORE property is the singleton instance of my DataStore class. Possibly not the correct way to go about doing this but I was unsure of another way to ensure only a single instance of the DataStore class was created – Chris Walters Oct 21 '15 at 21:05
  • @ChrisWalters It may be an issue with how you set up your singleton then. Everything else looks right at first glance. – BradleyDotNET Oct 21 '15 at 21:07
  • I've added the singleton instance definition to the end of the question, I don't think there's anything wrong there though – Chris Walters Oct 21 '15 at 21:13
  • @ChrisWalters Oh, I just realized you are setting stuff *before* calling `InitializeComponent` try setting the context *after* that method call (which should always be first) – BradleyDotNET Oct 21 '15 at 21:22
  • Unfortunately this just causes a binding error for the ContextMenu ItemsSource and no change to the ItemsControl implementation – Chris Walters Oct 21 '15 at 21:40
  • @ChrisWalters If that causes a binding error that means you are making progress! Before it just wasn't seeing anything to bind to. Now its telling you where your mistake is. – BradleyDotNET Oct 21 '15 at 21:41
  • Marked as answer, the reason behind my issues with PropertyChanged event being null was that I had in fact somewhere throughout this removed and forgotten that I had removed INotifyPropertyChanged from my class declaration - Yup.. thats how stupid it was! haha Thanks for all your help! – Chris Walters Oct 22 '15 at 10:32
0

You might be able to solve this using a CollectionView

public class MyViewModel
{
    CollectionView _mostRecentDocuments;

    public MyViewModel()
    {
        Documents = new ObservableCollection<Document>();
        _mostRecentDocuments = new CollectionView(Documents);
        _mostRecentDocuments .Filter = x => Documents.Take(5).Contains(x);
    }

    public ObservableCollection<Document> Documents { get; private set; }    

    public CollectionView MostRecentDocuments
    {
        get 
        {
            return _mostRecentDocuments;
        }
    }
}
Chui Tey
  • 5,436
  • 2
  • 35
  • 44