Binding on Elements of an ObservableCollection only change when Page is reloaded - c#

I am programming a Windows 8.1 App using C#/XAML as well as the MVVM-Light Toolkit.
In my program there is a Schedule that consists of 3 components:
a GridView with 5 elements for Monday, Tuesday, ....
a ListView with x elements each showing the start- and end-time of
the current period. x depends on the number of period the user chose
for his schedule to have.
a GridView with 5*x elements that represent the places for the events
set by the user.
These 3 components are again inside a FlipView to enable multiple Schedules.
I enabled this in code via the following objects:
public class Schedule
{
public int WeekNumber { get; set; }
public ScheduleComponentSettings ScheduleComponentSettings { get; set; }
public ScheduleComponents ScheduleComponents { get; set; }
}
public class ScheduleComponents
{
public ObservableCollection<WeekDay> WeekDayItems { get; set; }
public ObservableCollection<FreePeriod> FreePeriodItems { get; set; }
public ObservableCollection<PeriodTime> PeriodTimesItems { get; set; }
public ObservableCollection<LessonTime> LessonTimesItems { get; set; }
}
In my ViewModel I have an ObservableCollection of the Schedule class:
public ObservableCollection<Schedule> ScheduleComponentsList
{
get
{
return _ScheduleComponentsList;
}
set
{
if (_ScheduleComponentsList == value)
{
return;
}
RaisePropertyChanging(ScheduleWeekListPropertyName);
_ScheduleComponentsList = value;
RaisePropertyChanged(ScheduleWeekListPropertyName);
}
}
The FlipView and its elements bind to that as follows (this is ofc. shortened to show only the ItemsSources):
<FlipView
ItemsSource="{Binding Main.ScheduleComponentsList, Source={StaticResource Locator}}"
<FlipView.ItemTemplate>
<DataTemplate>
<GridView
ItemsSource="{Binding ScheduleComponents.WeekDayItems}"/>
<ListView
ItemsSource="{Binding ScheduleComponents.PeriodTimesItems}"/>
<GridView
ItemsSource="{Binding ScheduleComponents.FreePeriodItems}"/>
</DataTemplate>
</FlipView.ItemTemplate>
</FlipView>
Now here is the problem:
When I change a property on an element inside the FreePeriodItems or even when I replace the collection completely, the View only updates when I reload the entire Page. Same for all the other properties I update in one on the ScheduleComponents.
This does not occur however when I change the ScheduleComponentList itself. When I add items to it for example they are automatically being updated in the view.
Now I am sitting on the problem for ages now.

This does not occur however when I change the ScheduleComponentList itself. When I add items to it for example they are automatically being updated in the view.
That's exactly how ObservableCollection<T> works. It raises events only when the list itself changes, when you add or remove elements:
Represents a dynamic data collection that provides notifications when items get added, removed, or when the whole list is refreshed.
from ObservableCollection Class
To make it work implement INotifyPropertyChanged on your WeekDay, FreePeriod, PeriodTime and LessonTime. When it's done you'll get event not only when collection changes but also when any of the items that already are part of collection is modified.

Related

How to trigger redraw of Xamarin BindableLayout

I have a BindableLayout that uses a List<PricingLevel> as the datasource. One of the columns in the resulting layout uses a converter to derive a calculated result based on a unit price in the view model and the PricingLevel property of the datasource.
public bool IsTaxInclusive { get; set; }
public decimal UnitPrice { get; set; }
public List<PricingLevel> PricingLevels { get; set; }
If the IsTaxInclusive flag is switched I need to update the calculated results (note PropertyChanged events are raised). But because the PricingLevels don't change, I'm not sure how to trigger the update. At the moment I just refresh the PricingLevels list which works but is not ideal. Is there a simple way to force the update?
So the fix was simple. Rather than refresh the entire list, I used a FastObservableCollection and called its RaiseCollectionChanged() method.
public FastObservableCollection<PricingLevel> PricingLevels { get; set; }
...
PricingLevels.RaiseCollectionChanged();

WPF's DataGrid Sort Descriptions works only for static instances

I have a problem with sorting DataGrid items using SortDescriptions.
I had a static ObservableCollection that stored my data and after binding that collection to the DataGrid I was able to sort the items ascendingly by using SortDescription.
After a while I needed to change my code - a static instance of that collection was no longer sensible. After making those changes I've built the same DataGrid view but sorting the items was no longer working.
I need to create a DataGrid on WPF that would store my application's history records.
For that, I created some classes to be used as history records and then I bound that data to my DataGrid. Becuase those are historical records, I would like them to be shown to the user in an ascending order. Meaning the last item I've added will be shown at the very top.
This is my HistoryRecord class:
public class HistoryRecord {
public TransactionSender Sender { get; set; }
public string Description { get; set; }
public DateTime Timestamp { get; set; }
}
I've created a History class with an observable collection, as follows:
public static class History {
public static ObservableCollection<HistoryRecord> UserHistory { get; set; } =
new ObservableCollection<HistoryRecord>();
}
I chose to use an observable collection because that way I will be able to update the DataGrid more easily.
This is my xaml code for the DataGrid at my history tab:
<DataGrid x:Name="HistoryGrid" HorizontalAlignment="Left" AutoGenerateColumns="False" IsReadOnly="True" >
<DataGrid.Columns>
<DataGridTextColumn Header="Sender" Binding="{Binding Sender}"/>
<DataGridTextColumn Header="Description" Binding="{Binding Description}"/>
<DataGridTextColumn Header="Timestamp" Binding="{Binding Timestamp}"/>
</DataGrid.Columns>
</DataGrid>
This is my C# code for the history tab:
public partial class HistoryTab : UserControl {
public HistoryTab() {
InitializeComponent();
HistoryGrid.ItemsSource = History.UserHistory;
HistoryGrid.Items.SortDescriptions.Add(new SortDescription("Timestamp", ListSortDirection.Ascending));
HistoryGrid.Items.Refresh();
}
}
The code above works just fine. It does sort my items ascendingly.
Last week I needed to change my code a little bit.
Up until now, it was fine to have a static History class but now I need to create multiple instances of that History class for several things.
That being said, the app as a whole still has a unified history record.
In order to save that unified history record I created a new class named HistoryUtil:
public static class HistoryUtil {
public static HistoryStorage AppHistory = new HistoryStorage();
}
The History class is now called HistoryStorage and it contains the observable collection just as before:
public class HistoryStorage {
public ObservableCollection<HistoryRecord> UserHistory { get; set; } =
new ObservableCollection<HistoryRecord>();
}
The xaml code from before stayed the same.
I updated the C# code for the tab as follows:
public partial class HistoryTab : UserControl {
public HistoryTab() {
InitializeComponent();
HistoryGrid.ItemsSource = HistoryUtil.AppHistory.UserHistory;
HistoryGrid.Items.SortDescriptions.Add(new SortDescription("Timestamp", ListSortDirection.Ascending));
HistoryGrid.Items.Refresh();
}
}
This updated code does not sort the items.
This code shows the items but without the ascending order of the timestamp.
I can't figure out how and why the view is not sorted ascendingly according to the timestamp.
This is basically the same code, there are only a few changes to the structure and the fact that I've changed the static instance to be non-static.
Here are some sample data that demonstrates the issue:
HistoryRecord action1 = new HistoryRecord() {
Sender = TransactionSender.User,
Timestamp = DateTime.Now,
Description="First record, should appear last"
};
action1.StoreRecord();
//After few seconds...
HistoryRecord action2 = new HistoryRecord() {
Sender = TransactionSender.User,
Timestamp = DateTime.Now,
Description="Second record, should appear first"
};
action2.StoreRecord();
Where the StoreRecord method is set like this:
//For the static class:
public void StoreRecord() {
History.UserHistory.Add(this);
}
//For the non-static class:
public void StoreRecord() {
HistoryUtil.AppHistory.UserHistory.Add(this);
}
This is the History DataGrid:
Please note this isn't about ascending or descending - I tried them both.
It just doesn't seem to sort the items anymore.
I added a breakpoint at HistoryGrid.Items.SortDescriptions and it doesn't seem to get there.
I would appreciate your help figuring out the problem.

How to bind data from View to UserControl with SoapBox

I have my SoapBox.Document 'Register'
[Export(SoapBox.Core.ExtensionPoints.Workbench.Documents, typeof(IDocument))]
[Export(CompositionPoints.Workbench.Documents.Register, typeof(Register))]
[Document(Name = DOC_NAME)]
class Register : AbstractDocument
{
public Receipt actualReceipt;
private const string DOC_NAME = "Register";
public Register()
{
Name = DOC_NAME;
Title = "Recipe Document Title";
SomeProperty = "Hello from the recipe document!";
}
}
In this Document I want to user UserControls witch are kind of a own "View"
Like a ListView for all ReceiptPositions
So now I got my Model Receipt and ReceiptPosition
Model Receipt
class Receipt
{
public int Id { get; set; }
public string Receiptnumber { get; set; }
public IList<ReceiptPositions> ReceiptPositions { get; set; }
and Model ReceiptPosition
class ReceiptPosition
{
public int Id { get; set; }
//public Receipt Receipt { get; set; } using for Database
public int Position { get; set; }
public string Article { get; set; }
}
So now I want to add a UserControl witch displays a List of all articles in ReceiptPositions.
But how do I bind the data so that when a new ReceiptPosition gets added to the IList in Receipt the UserControl get 'refreshed' automatically?
Here is a visual example of whatI need..
Host with Data and two PLugins wich each show the same Data but in a different way.
You can use an ItemsControl for this purpose.
xaml:
<ItemsControl ItemsSource="{Binding MyReceipt.ReceiptPositions}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<!-- Where you put your view -->
<TextBox Text="{Binding Article}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<!-- Can be whatever Panel type you want -->
<StackPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
cs:
private Receipt _myReceipt;
public Receipt MyReceipt { get { return _myReceipt; } set { _myReceipt = value; OnPropertyChanged("MyReceipt"); } }
public MainWindow()
{
InitializeComponent();
DataContext = this;
MyReceipt = new Receipt { ReceiptPositions = new ObservableCollection<ReceiptPosition>() };
MyReceipt.ReceiptPositions.Add(new ReceiptPosition { Article = "Foo" });
MyReceipt.ReceiptPositions.Add(new ReceiptPosition { Article = "Bar" });
MyReceipt.ReceiptPositions.Add(new ReceiptPosition { Article = "Baz" });
MyReceipt.ReceiptPositions[0].Article = "Frabazamataz";
}
Explanation:
The ItemsControl allows you to bind a list to its ItemsSource Property to use as the DataContext to each view created by the DataTemplate.
The Observable Collection gives PropertyChange notifications automatically with each item added, removed, or changed.
This allows you to have a very flexible list of items based solely on your data.
Death's answer is correct, i.e. you use DataTemplates. If your views and data templates are in a MEF plugin then you need to import both the plugins and the data templates that map the view models to the views. In the other question you posted about this it was obvious that you're trying to export your plugin user controls...personally I think this is a bit misguided. If your main application is using MVVM then your plugins should as well. In this case your plugins should export an IPlugin class and also specify a DataTemplate that maps it to a view. As I indicated on the other page, the data template must be imported as well so that you can add it to the global resources.
I've created a project that shows this in action using the classes you provided in your uother question, you can download it here. The main points to look at are the data templates in the plugin project and the two places where things are imported in the main project.
Note that in my demo I'm requiring each plugin to explicitly specify a DataTemplate for its view and view model, but you may not want to do this so I've also added a chunk of commented-out code at the bottom of App.xaml.cs that shows how to avoid that (to make it work I had to add the view type to the IPlugData class, but that's only needed for this one example). If you choose to create the DataTemplates manually then the plugins don't need to specify the data templates and they also don't need the custom ResourceDictionary that holds them.
If you have an questions feel free to post back here in the comments.

MvvmCross ICommand for elements inside ListView item [duplicate]

I have a list of items bound to a MvxBindableListView with a MvxItemTemplate.
I usually have 4 items in my list bound to my view. Data gets updated and the view displays the new data just fine.
Now, I want to add two buttons to this item template. However, relative source binding is not available with MvvmCross. (see image)
But I'm having difficulties working out a solution to this.
I have tried the ItemClick binding of the list item, but that only gives me 1 possibility of click and I need 2.
Can anyone help?
See the second option in the answer in MVVMCross changing ViewModel within a MvxBindableListView - this covers one way to do this.
Using that approach you'd expose a list of objects like:
public class Wrapped
{
public ICommand GoThruCommand { get; set; }
public ICommand OpenCommand { get; set; }
public string Name { get; set; }
}
and you'd use an axml list template with bound controls like:
<TextView
...
local:MvxBind="{'Text':{'Path':'Name'}}" />
<Button
...
local:MvxBind="{'Click':{'Path':'GoCommand'}}" />
<Button
...
local:MvxBind="{'Click':{'Path':'ThruCommand'}}" />
if you've got suggestions/requests for relative source in mvx, please add them to https://github.com/slodge/MvvmCross/issues/35

WPF DataBinding ObservableCollection<T> to DataGrid

I'm trying to create DataGrid in a separate UserControl whose DataContext is a List of T.
In the code behind, I create a List, populate the list, then send it to the constructor for the UserControl on which I have the DataGrid I am trying to populate.
The UserControl class is as follows.
public partial class QuotePreview : UserControl
{
private static SelectionList previewList = new SelectionList();
public SelectionList PreviewList
{
get { return previewList; }
}
public QuotePreview()
{
InitializeComponent();
}
public QuotePreview(SelectionList selectedOptions)
{
InitializeComponent();
previewList = selectedOptions;
QuotePreviewDataGrid.DataContext = previewList;
}
}
And the Xaml looks like:
<DataGrid Name="QuotePreviewDataGrid"
AutoGenerateColumns="False"
ItemsSource="{Binding}">
<DataGrid.Columns>
<DataGridTextColumn Header="Model Number" Binding="{Binding ModelNumber}"/>
<DataGridTextColumn Header="Description" Binding="{Binding Description}"/>
<DataGridTextColumn Header="List Price per Unit" Binding="{Binding Price}"/>
</DataGrid.Columns>
</DataGrid>
I've tried setting the ItemSource as well using
QuotePreviewDataGrid.ItemsSource = PreviewList;
I've also tried setting both the data context and the itemsource as well as refreshing:
QuotePreviewDataGrid.Items.Refresh();
The databinding I have set to listboxes in the rest of my application works perfectly. In the list boxes I have the itemsource set to {Binding} and the ListItems binding set to {Binding Property}. The datacontext for the listboxes set in the code behind.
My datagrid here is setup in the same manner, yet for some reason nothing is being displayed inside the grid.
When I go through the debugger and watch the flow of information, I can see the List of T, SelectionsList being created and passed to the constructor for the user control where the data grid lies. I can see that the DataContext is indeed being set and shows the items in the list, but when I go back to my appication and try to view the data grid, it's blank.
Any help would be greatly appreciated. I've been trying to wrap my mind around this problem for the last day and a half. Thanks!
UPDATE
The SelectionList is setup like:
public class SelectionList : List<Selection>
{
public List<Selection> availableSelections = new List<Selection>();
public List<Selection> AvailableSelections
{
get { return availableSelections; }
}
}
and a Selection is then defined by:
public class Selection : DependencyObject
{
public bool IsChecked { get; set; }
public string ModelNumber { get; set; }
public string Description { get; set; }
public string Price { get; set; }
}
When the application starts, I build a catalog of existing products (Selections). On different tabs, one for each product family, the datacontext for the products list box is initialized with with available products that it grabs from the catalog. Then pending which product a user selects, the available options or child selections associated with that product are populated into the appropriate list boxes, accessories and warranties.
Once a user selects the options they want, a button is clicked to preview the selected items which is supposed to populate the data grid explained above.
I can build the list of selected options, however when I try to set the data context of the data grid, nothing appears. The Lists for available selections are built and set to the appropriate data context the same way I am trying to do it for the data grid, however the data grid doesn't want to display my information.
UPDATE
So after some more debugging, I've narrowed the problem down a bit. The data binding works as it should. I have no real problems there, I don't think. However, the issue I'm running into now is what I believe to be 2 different instances of my User Control, but only the original is being displayed, not the updated copy.
Here's a copy of the class from about with a couple lines I added to help debug the problem.
public partial class QuotePreview : UserControl
{
private SelectionList _selectionList;
private SelectionList temp;
public QuotePreview()
{
InitializeComponent();
_selectionList = (SelectionList)this.DataContext;
}
private void QuotePreview_Loaded(object sender, RoutedEventArgs e)
{
_selectionList.SelectedOptions.Add(
new Selection
{
ModelNumber = "this",
Description = "really",
Price = "sucks"
});
}
public QuotePreview(SelectionList selectedOptions)
{
InitializeComponent();
_selectionList = (SelectionList)this.DataContext;
temp = selectedOptions;
_selectionList.AddRange(selectedOptions);
QuotePreview_Loaded();
}
private void QuotePreview_Loaded()
{
foreach (var options in temp.SelectedOptions)
{
_selectionList.SelectedOptions.Add(options);
}
QuotePreviewDataGrid.ItemsSource = _selectionList.SelectedOptions;
}
}
The implementation of the default constructor, is called every time the user control / tab, is clicked on. When that happens, _selectionList is set to the data context of the user control, followed by the Loaded Event which adds a line to my data grid.
In another user control where I select the options I want to add to my data grid user control, I click a button that creates a list of the options I want to be added and calls the custom constructor I wrote. Once the constructor finishes, it calls a custom Loaded Event method that I created for shits and giggles, that adds the selected options to my _selectionList.
Now once I click on the data grid user control again, it goes through the whole default process, and adds another default line.
If I go back a tab and say I want these options again and go back to the data grid, it again goes through the default process and adds another default line.
Whats most intriguing though is that I can see both of the selectionLists build since I dont clear the in between processes. I see a list build of the options i want to display and a list build of the default options build...
Oh, also, SelectionList does implement ObservableCollection.
I finally came up with a solution to the problem.
public static class QuotePreview
{
public static ObservableCollection<PurchasableItem> LineItems { get; private set; }
static QuotePreview()
{
LineItems = new ObservableCollection<PurchasableItem>();
}
public static void Add(List<PurchasableItems> selections)
{
foreach (var selection in selections)
{
LineItems.Add(selection);
}
}
public static void Clear()
{
LineItems.Clear();
}
}
public class QuoteTab : TabItem
{
public ObservableCollection<PurchasableItem> PreviewItems { get; private set; }
public QuoteTab()
{
Initialize()
PreviewItems = QuotePreview.LineItems;
DataGrid.ItemSource = PreviewItems
}
}
Try changing:
QuotePreviewDataGrid.DataContext = previewList;
to
this.DataContext = previewList;
My suspicion is that the ItemsSource="{Binding}" in your xaml is overriding the DataContext code in your constructor.
By changing the previewList to be DataContext of the entire UserControl, then the binding of the DataGrid's ItemsSource can correctly evaluate.
On a side note, I would start looking into the use of ObservableCollection<T> and the MVVM design pattern. An issue you might end up with is that your DataGrid doesn't update when the underlying list changes, using the ObservableCollection<T> will fix this.
Using the MVVM design pattern will give you a good separation of your logic and data (in this case your list and how it's loaded) from the physical display (the DataGrid)

Categories