Updating ViewModel content in MVVM - c#

I have a list on my XAML page bind to my ViewModel. The list Show only the entries - there is no Feature to edit or update them (they are read from Server api).
In the application bar I have a button for reloading the list (sending again the request to the Server).
What must I do for this "reload Feature"?
I think about following:
removing the existing collection of my entries
firering the LoadData again
Are there any snippets for my question?
What is about Memory issues because of my previous existing collection?

Something like this would work if you think your callback will be pretty light. If you think it may be heavy with a lot of items coming back then this may not be the most efficient way but would still work:
public class YourViewModel
{
public ObservableCollection<YourDataType> YourCollection { get; set; }
public ICommand ReloadDataCommand { get; set; }
public YourViewModel()
{
YourCollection = new ObservableCollection<YourDataType>();
ReloadDataCommand = new DelegateCommand(ReloadData);
}
private void ReloadData()
{
//Get your new data;
YourCollection = new ObservableCollection(someService.GetData());
RaisePropertyChange("YourCollection");
//Depending on how many items your bringing in will depend on whether its a good idea to recreate the whole collection like this. If its too big then you may be better off removing/adding these items as needed.
}
}
In XAML:
<Button Content="Reload" Command="{Binding ReloadDataCommand}" />
<List ItemsSource="{Binding YourCollection}">
<!-- All your other list stuff -->
</List>
Hope this helps

Related

Make List Available to all Windows in WPF Application

A list of products is used through a WPF application. The list lstProducts is created in the business tier of the application. The list is fairly stable over time ... products only updated every 6 months.
How can that list be instantiated in C# such that it is available throughout the application?
My C# Class creates a list
namespace BusinessObjects
{
public class Products
{
public class Product
{
public Int64 ProductId { get; set; }
public string FileAs { get; set; }
}
public List<Product> lstFileAs { get; set; }
public Products()
{
//populate lstFileAs
}
}
}
ComboBoxes on various forms are databound as follows
products = new Products());
cboProducts.DataContext = products;
cboProducts.ItemsSource = products.lstFileAs;
cboCustomer.DisplayMemberPath = "FileAs";
Let us please consider it as read that we all prefer to avoid global variables. However we are putting a new front end on an old and widely used application which does use global variables. The old application is written in VB6 and runs well in spite of using global variables. We are instructed to make the minimum changes to avoid unnecessarily introducing bugs.
You can create an ObjectDataProvider and CollectionViewSource in App.xaml (for instance) and reference that in your project. I demonstrate here a possible implementation. This code assumes you create a GetProducts() method.
App.xaml:
<ObjectDataProvider x:Key="ProductsObjDataProvider"
ObjectType="{x:Type BusinessObjects:Products}"
MethodName="GetProducts">
</ObjectDataProvider>
<CollectionViewSource x:Key="ProductsView" Source="{Binding Source={StaticResource ProductsObjDataProvider}}"/>
To bind a combobox:
<ComboBox Name="cboProducts"
DisplayMemberPath="FileAs"
ItemsSource="{Binding Source={StaticResource ProductsView}}"
SelectedValue="{Binding Path=ProductID, Mode=TwoWay}"/>

Submenu not displaying when bound to IsSubmenuOpen

I'm trying to show a SubMenu of items. Every time IsSubmenuOpen is set to true, I make a call in my view model to refresh the list that the MenuItem is bound to.
Unfortunately, this doesn't work illustrated here (even though Windows has 3 subitems):
View Model:
public ObservableCollection<Item> CustomLayoutList { get; set; }
public bool LayoutOptionsOpen { set { UpdateCustomLayoutList(); } }
private void UpdateCustomLayoutList()
{
CustomLayoutList.Clear();
var items = GetItems();
foreach(var item in items)
{
CustomLayoutList.Add(item);
}
}
Binding in Xaml:
<MenuItem ItemsSource="{Binding CustomLayoutList }" IsSubmenuOpen="{Binding LayoutOptionsOpen, Mode=OneWayToSource}"></MenuItem>
EDIT: Apparently on my computer this works fine. The list populates correctly and shows the SubMenu. For some other people, however, the SubMenu wouldn't open - despite verifying that the list has items in it.
To resolve this, I changed my solution to listen to the SubMenuOpen event, and in my xaml.cs code-behind I call UpdateCustomLayoutList directly. This works, but why did my initial solution not work, and is there a better way to solve this? Ultimately I would like to have zero code-behind.

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)

How do I include an 'all items' item in a ListView?

I have a window which displays sales between a certain time in a restaurant/shop. When the user selects the time period to query, it shows sales data between that time. I am also Programatically creating a list of users which one can then select to filter a query. For example, I choose the 'Michael' user which is then used to show all sales that have been attributed to him (in the time frame previously selected).
Creating the ListView of users is fairly easy but I am trying to append this list with an item which would read 'All Users'. This would then be passed back to the query, which would then recognize this user by some Property (UserId = 999 or whatever. Not important) to populate the page with the data of all users again.
Right now I have to exit the page and go back in to do this. Not very elegant!
I was going to append a User object in the ViewModel to the list that is generated from the database EF but it creates a list of IUsers so I can't instantiate an actual instance of it (maybe I am being incredibly stupid here and am missing something fundamental?).
Any help in achieving this goal would be most appreciated.
Your UI would typically create a view model that wraps the underlying user information. Then you would have a collection of these view models, to which the view binds. Assuming you have that, it is a simple matter of adding a sentinel instance to this collection. It might look something like this:
// this is your DAL class
public class User
{
}
// a view model to wrap the DAL class
public class UserViewModel
{
// a special instance of the view model to represent all users
public static readonly UserViewModel AllUsers = new UserViewModel(null);
private readonly User user;
public UserViewModel(User user)
{
...
}
// view binds to this to display user
public string Name
{
get { return this.user == null ? "<<All>>" : this.user.Name; }
}
}
public class MainViewModel()
{
private readonly ICollection<UserViewModel> users;
public MainViewModel()
{
this.users = ...;
this.users.Add(UserViewModel.AllUsers);
}
public ICollection<UserViewModel> Users
{
...
}
}
In your code to construct the query, all you need to do is check whether the user in the user view model is present. If not, there is no need to append any user selection onto the query.
You can try to use CompositeCollection to set ItemSource of your ListBox -
<ListBox>
<ListBox.ItemsSource>
<CompositeCollection>
<CollectionContainer Collection="{Binding YourCollection}" />
<ListBoxItem Foreground="Magenta">Select All</ListBoxItem>
</CompositeCollection>
</ListBox.ItemsSource>
</ListBox>
But you will have to apply some workaround(like using BindingProxy) to make Binding work as CollectionContainer doesn't support bindings, refer these links -
http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/637b563f-8f2f-4af3-a7aa-5d96b719d5fd/
How do you bind a CollectionContainer to a collection in a view model?

Categories