MVVM List and ObservableCollection - c#

So I'm trying to build a small cookbook application using WPF and MVVM light. I've run into a situation where I'm binding a List from the model to the view model. And it works fine for displaying and removing items, but when adding items I couldn't get the display to update.
I came across ObserableCollections which seemed to be just what I wanted, but I'm not sure I'm using them correctly because it seems wrong to be creating a new OC every time. How am I supposed to be retrieving an observable collection when the model is using a list?
Model(s):
public class Recipe
{
public int Id { get; set; }
public string Title { get; set; }
public List<RecipeIngredient> Ingredients { get; set; }
}
public class RecipeIngredient
{
// ... //
}
ViewModel:
public Recipe SelectedRecipe
{
get
{
return this.selectedRecipe;
}
set
{
this.selectedRecipe = value;
RaisePropertyChanged("SelectedRecipe");
RaisePropertyChanged("RecipeIngredients");
}
}
public ObservableCollection<RecipeIngredient> RecipeIngredients
{
get
{
return new ObservableCollection<RecipeIngredient>(selectedRecipe.Ingredients.ToList());
}
}
public RelayCommand<EventArgs> AddIngredientCommand { get; private set; }
public RelayCommand<string> DeleteIngredientCommand { get; private set; }
private void AddIngredient(EventArgs eventArgs)
{
SelectedRecipe.Ingredients.Add(new RecipeIngredient() { Name = "New Ingredient" });
RaisePropertyChanged("RecipeIngredients");
}
private void DeleteIngredient(string name)
{
SelectedRecipe.Ingredients = SelectedRecipe.Ingredients.Where(i => i.Name != name).ToList();
RaisePropertyChanged("RecipeIngredients");
}
public MainViewModel()
{
DBController db = new DBController();
recipes = db.GetRecipeList();
RecipeSelectionChangedCommand = new RelayCommand<SelectionChangedEventArgs>((args) => RecipeSelectionChanged(args));
SaveRecipeCommand = new RelayCommand<EventArgs>((args) => SaveRecipe(args));
AddIngredientCommand = new RelayCommand<EventArgs>((args) => AddIngredient(args));
DeleteIngredientCommand = new RelayCommand<string>((args) => DeleteIngredient(args));
}
Am I way off track here?

Should have read more carefully. If you're displaying the selected recipe's ingredients in an alternate view, you should be using data binding in the view <ListBox ItemsSource="{Binding SelectedRecipe.Ingredients}"/> You could consider using linq to entities (Entity Framework) for ORM..
public class RecipeVM
{
public RecipeVM(Recipe r)
{
recipe = r;
}
Recipe recipe;
public int Id
{
get
{
return recipe.Id;
}
set
{
PropertyChanged("Id");
recipe.id = value;
}
}
public string Title
{
get
{
return recipe.Title;
}
set
{
PropertyChanged("Title");
recipe.Title = value;
}
}
ObservableCollection<RecipeIngredient> ingredients;
public ObservableCollection<RecipeIngredient> Ingredients
{
get
{
if (ingredients == null)
ingredients = new ObservableCollection<RecipeIngredient>(recipe.Ingredients);
return ingredients;
}
set
{
PropertyChanged("Ingredients");
ingredients = value;
}
}
}
You'll need to modify that a bit if you want to keep the collections in sync though..

Related

checking if datagrid has values

I am working on a C# project which includes WPF. I was wondering, If I could somehow check If my data grid contains certain element.
For example,
I have combo box whose itemsSource is some list of objects. Now, when an user selects an item from the combo box and presses the button
below in data grid (in same window) that item shows up.
I want to forbid the user to select same item more than once and for example put MessageBox with error message. How could I do that?
Code
This Window:
public partial class AvioWindowAddNEdit : Window
{
Avio avio;
public enum Stage { ADD, EDIT};
Stage stage;
public AvioWindowAddNEdit(Avio avio, Stage stage = Stage.ADD)
{
InitializeComponent();
this.avio= avio;
this.stage= stage;
textboxCode.DataContext = avio;
comboboxListofFlights.ItemsSource = Aplikacija.Instance.Flights;
comboboxListofFlights.DataContext = avio;
datagridListofFlights.ItemsSource = avio.ListofFlights;
datagridListofFlights.ColumnWidth = new DataGridLength(1, DataGridLengthUnitType.Auto);
if (stage== Stage.EDIT)
{
textboxCode.IsEnabled = false;
}
}
}
Button which adds selected item to data grid:
private void btnAddFlight_Click(object sender, RoutedEventArgs e)
{
avio.ListOfFlights.Add(comboboxListOfFlights.SelectedItem as Flight);
}
Singleton class for loading in all of my data:
class Aplication
{
public ObservableCollection<User> Users { get; set; }
public ObservableCollection<Airport> Airports { get; set; }
public ObservableCollection<Flight> Flights{ get; set; }
public ObservableCollection<Avio> Avios { get; set; }
public string LoggedInUser { get; set; }
private static Aplication instance = new Aplication();
public static Aplication Instance
{
get
{
return instance;
}
}
private Aplication()
{
Users= new ObservableCollection<User>();
Airports = new ObservableCollection<Airport>();
Flights = new ObservableCollection<Flight>();
Avios= new ObservableCollection<Avio>();
FillInData(); //method where I filled in all of these ObservableCollections
}
}
My class:
public class Avio : ObservableObject, ICloneable
{
//observableobject is an object where I implemented INotifyPropertyChanged
private string code;
public string Code
{
get { return code; }
set { code= value; OnPropertyChanged("Code"); }
}
private ObservableCollection<Flight> listOfFlights;
public ObservableCollection<Flight> ListOfFlights
{
get { return listOfFlights; }
set { listOfFlights= value; OnPropertyChanged("ListOfFlights"); }
}
private bool active;
public bool Active
{
get { return active; }
set { active= value; OnPropertyChanged("Active"); }
}
public Avio()
{
active= true;
ListOfFlights = new ObservableCollection<Flight>();
}
public Avio(string code)
{
active= true;
ListOfFlights = new ObservableCollection<Flight>();
Code= code;
}
}
You could use an ObservableCollection as an ItemsSource for your DataGrid. In that way you'll always have easy access to the data via code.
Check out this tutorial as a starting point (this uses ListBox instead of DataGrid, but it's easily adaptable to DataGrid).

Using MvvmCross, what are best practices for transforming models to view models to show in lists?

I have a data model which cannot be changed, say:
public class TodoItem
{
public string Id { get; set; }
[JsonProperty(PropertyName = "userid")]
public Guid UserId { get; set; }
[JsonProperty(PropertyName = "text")]
public string Text { get; set; }
[JsonProperty(PropertyName = "complete")]
public bool Complete { get; set; }
}
Online service returns ObservableCollection. I would like to show this in my view, but I need to add PropertyChanged, and a few static Actions relating to Complete being toggled, perhaps using MvxViewModel as a base class. What are some good MvvmCross ways of transforming the model to a view model while still ensuring that changes in the view model change the model? Would you have a reference to the original object in the view model object?
public class ToDoItemViewModel : MvxViewModel
{
private TodoItem _item;
public ToDoItemViewModel(TodoItem item)
{
_item = item;
}
public Guid UserId
{
get
{
return _item.UserId;
}
set
{
if (_item.UserId == value) return;
_item.UserId = value;
RaisePropertyChanged(() => UserId);
}
}
public string Text
{
get
{
return _item.Text;
}
set
{
if (_item.Text == value) return;
_item.Text = value;
RaisePropertyChanged(() => Text);
}
}
public bool Complete
{
get
{
return _item.Complete;
}
set
{
if (_item.Complete == value) return;
_item.Complete = value;
RaisePropertyChanged(() => Complete);
CompleteChanged.Invoke(_item);
}
}
public static Action<TodoItem> CompleteChanged;
}
If so, is it possible to automatically generate this so that instead of re-writing all the properties in the ViewModel, it will track the model properties? Is it even wise to hold a reference to the original item in the viewmodel?
Here is how I am getting from an ObservableCollection<TodoItem> to ObservableCollection<TodoItemViewModel>
var todoItems = await todoTable
.Where(todoItem => todoItem.Complete == false)
.ToCollectionAsync();
Items = new ObservableCollection<TodoItemViewModel>(todoItems.Select(i => new TodoItemViewModel(i)));
Update. Slight shortcut
Items = await _todoTable
.Where(item => item.Complete == false)
.Select(item => new TodoItemViewModel(item))
.ToCollectionAsync();

property accessors for List<T>, and entity framework 6 linq queries

Is it possible to have a (EF6) Linq select statement instantiate a class, such that a property's set accessor is triggered with the values returned from the Linq statement?
I have tried code (similar) to that below, but despite the query returning data, the set's value is an empty list. I am assuming that Linq creates a new instance of MyModel, and then adds the records from the record set.
Is it possible to trigger doSomething() after MyList is fully populated, but from within the scope of MyModel (i.e. not adding a subsequent call after context.SomeEntities.Select(...))?
public class MyModel{
private List<string> _myList;
public List<string> MyList {
get { return _myList; }
set { _myList = value; doSomething(); }
}
private void doSomething() {...}
}
..
context.SomeEntities.Select(x => new MyModel { MyList = x.YourList });
The answer to both of your questions is "Yes." The following worked for me:
class Program
{
static void Main(string[] args)
{
Context context = new Context();
List<MyModel> myModelsList = context.Products.Include("Names").AsEnumerable().Select(x =>
{
MyModel model = new MyModel();
model.MyList = x.Names.Select(pn => pn.Name).ToList();
return model;
}).ToList();
}
static void CreateAndSeedDatabase()
{
Context context = new Context();
Product product1 = new Product() { Names = new List<ProductName> { new ProductName { Name = "1" } } };
Product product2 = new Product() { Names = new List<ProductName> { new ProductName { Name = "2" } } };
context.Products.Add(product1);
context.Products.Add(product2);
context.SaveChanges();
}
}
public class Context : DbContext
{
public Context()
{
Database.SetInitializer<Context>(new CreateDatabaseIfNotExists<Context>());
Database.Initialize(true);
}
public DbSet<Product> Products { get; set; }
public DbSet<ProductName> ProductNames { get; set; }
}
public class Product
{
public int ProductId { get; set; }
public List<ProductName> Names { get; set; }
}
public class ProductName
{
public int ProductNameId { get; set; }
public string Name { get; set; }
}
public class MyModel
{
private List<string> _myList;
public List<string> MyList
{
get { return _myList; }
set { _myList = value; doSomething(); }
}
private void doSomething()
{
Console.WriteLine(_myList[0]);
}
}
}

construct an object into another object

this should be a stupid problem but i dont know where to start,so i'll ask here.i've got a class named Routing which is binded to a datagrid. Inside this class theres an object from another class:
public class Routing : INotifyPropertyChanged
{
public int Sequenza { get; set; }
private ObservableCollection<Prodotti> availableProducts;
public ObservableCollection<Prodotti> AvailableProducts
{
get { return availableProducts; }
set
{
if (availableProducts != value)
{
availableProducts = value;
OnPropertyChanged("AvailableProducts");
}
}
}
private Prodotti product;
public Prodotti Product
{
get { return product; }
set
{
if (product != value)
{
product = value;
UpdateAvailableCosts();
OnPropertyChanged("Product");
}
}
}
}
and then the Product class:
public class Prodotti
{
public int Product_id { get; set; }
public string Product_description { get; set; }
public int Product_treshold { get; set; }
}
Everything works as intended,and if i just declare the "Sequenza = 1" in the routing the datagrid adds the 1 in the first row/column. But i would like to add more initial values,maybe based on data present in the database. but i cant come up with the constructor for it
Routes.Add(new Routing { Sequenza = 1,Prodotti=... });
Routes.Add(new Routing { Sequenza = 1,Prodotti= new Prodotti{Product_id =1, Product_description = "str", Product_treshold =1} });

Is there a name for this creation pattern?

What should I be calling the "BFactory" below. Its not really a Factory since there is no selection of a concrete class happening, and its not necessarily creating an object each time. Its kind of a Pool but the users do not return the Bs they get to the pool after they are done with them. It could be called a Cache but performance is not the primary intention. The intention is that everyone who is using the same BFactory will get the same B when they pass the same A which starts to sound kind of like a singleton-ish.
public class A
{
public int MyProperty { get; set; }
}
public class B
{
public B(A wrapped)
{
Wrapped = wrapped;
}
public A Wrapped { get; set; }
}
public class BFactory
{
private Dictionary<A,B> _created = new Dictionary<A,B>();
public B GetB(A a)
{
if (_created.ContainsKey(a) == false)
{
_created[a] = new B(a);
}
return _created[a];
}
}
here is a slightly more real example:
The value from MyModel is shown in several locations in the app by binding a TextBlock to the ValueString property of MyViewModel. The user can select to present the value as a percent or a decimal and it should be updated in all locations if it is updated in one.
public class MyModel
{
public int Value { get; set; }
}
public class MyViewModel
{
private readonly MyModel _model;
public MyViewModel(MyModel model)
{
_model = model;
}
public string ValueString
{
get { return string.Format(FormatString, _model.Value); }
}
public string FormatString { get; set; }
}
public class MyViewModelFactory
{
private readonly Dictionary<MyModel, MyViewModel> _created = new Dictionary<MyModel, MyViewModel>();
public MyViewModel GetViewModel(MyModel model)
{
if (_created.ContainsKey(model) == false)
{
_created[model] = new MyViewModel(model);
}
return _created[model];
}
}

Categories

Resources