I am trying to implement a searchbar using MVVM in Xamarin.forms. so far I have managed to borrow some code from around the internet and it seems to do go through the motions of the search. the only issue is I don't know what code to put in the command.
I would like the search bar to search recipeNames from a list of Recipes. this information is all stored on a local database and displayed using an observable collection.
please can you help me work it out.
XAML
<SearchBar x:Name="SearchBar"
Placeholder="Search"
SearchCommand="{Binding SearchCommand}"
SearchCommandParameter="{Binding Text, Source={x:Reference SearchBar}}"
Text="{Binding SearchText, Mode=TwoWay}">
<SearchBar.Behaviors>
<local:TextChangedBehavior />
</SearchBar.Behaviors>
</SearchBar>
<ListView x:Name="ListViewItems"
ItemsSource="{Binding Recipes}"
IsPullToRefreshEnabled="True"
Refreshing="ListViewItems_Refreshing"
SelectedItem="{Binding SelectedRecipe}">
Text changed Behaviour
class TextChangedBehavior: Behavior<Xamarin.Forms.SearchBar>
{
protected override void OnAttachedTo(Xamarin.Forms.SearchBar bindable)
{
base.OnAttachedTo(bindable);
bindable.TextChanged += Bindable_TextChanged;
}
protected override void OnDetachingFrom(Xamarin.Forms.SearchBar bindable)
{
base.OnDetachingFrom(bindable);
bindable.TextChanged -= Bindable_TextChanged;
}
private void Bindable_TextChanged(object sender, TextChangedEventArgs e)
{
((Xamarin.Forms.SearchBar)sender).SearchCommand?.Execute(e.NewTextValue);
}
}
and viewModel
public class RecipeListViewModel : ObservableCollection<Recipe>
{
private ObservableCollection<Recipe> Recipes {get; set;}
public INavigation Navigation { get; internal set; }
public ICommand NewAddPage { get; protected set; }
public RecipeListViewModel(INavigation navigation)
{
this.Navigation = navigation;
Recipes = new ObservableCollection<Recipe>();
this.NewAddPage = new Command(async () => await CreateNewAddPage());
Init();
}
// Gets all recipes from the database and adds them to the observable collection
private void Init()
{
var enumarator = App.RecipeDbcontroller.GetRecipe();
if (enumarator == null)
{
App.RecipeDbcontroller.SaveRecipe(new Recipe { RecipeName = "Moussaka", Serves = 6, PrepTime = "30", CookTime = "2 Hours", MealType = "Dinner" });
enumarator = App.RecipeDbcontroller.GetRecipe();
}
while (enumarator.MoveNext())
{
//cleans database of all empty records
if (enumarator.Current.RecipeName == null || enumarator.Current.CookTime == null)
{
App.RecipeDbcontroller.DeleteRecipe(enumarator.Current.RecipeID);
}
else
Add(enumarator.Current);
}
}
private ICommand _searchCommand;
public ICommand SearchCommand
{
get
{
return _searchCommand ?? (_searchCommand = new Command<string>((text) =>
{
**// THIS IS WHAT I DON'T KNOW WHAT TO DO**
}));
}
}
private string _searchText { get; set; }
public string SearchText
{
get { return _searchText; }
set
{
if (_searchText != value)
{
_searchText = value;
}
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
RecipeDatabaseController Class
public RecipeDatabaseController()
{
this.database = DependencyService.Get<ISQLite>().GetConnection();
this.database.CreateTable<Recipe>();
}
//Recipe CRUD
public IEnumerator<Recipe> GetRecipe()
{
lock (locker)
{
if (database.Table<Recipe>().Count() == 0)
{
return null;
}
else
{
return this.database.Table<Recipe>().GetEnumerator();
}
}
}
public IEnumerator<Recipe> GetRecipeBySearchTerm(text)
{
var enumarator = GetRecipe();
lock (locker)
{
while (enumarator.MoveNext)
{
if(enumarator.Current.RecipeName.Contains(text)
return this.
}
}
}
public int SaveRecipe(Recipe recipe)
{
lock (locker)
{
if (recipe.RecipeID != 0)
{
this.database.Update(recipe);
return recipe.RecipeID;
}
else
{
return this.database.Insert(recipe);
}
}
}
public int DeleteRecipe(int Id)
{
lock (locker)
{
return this.database.Delete<Recipe>(Id);
}
}
right so the search command ought to look like this.
public ICommand SearchCommand => _searchCommand ?? (_searchCommand = new Command<string>((text) =>
{
if (text.Length >=1)
{
Recipes.Clear();
Init();
var suggestions = Recipes.Where(c => c.RecipeName.ToLower().StartsWith(text.ToLower())).ToList();
Recipes.Clear();
foreach (var recipe in suggestions)
Recipes.Add(recipe);
}
else
{
Recipes.Clear();
Init();
ListViewVisible = true;
SuggestionsListViewVisible = false;
}
}));
using System.Linq;
//Recipe CRUD
public IEnumerable<Recipe> GetRecipe()
{
lock (locker)
{
return this.database.Table<Recipe>();
}
}
public IEnumerable<Recipe> GetRecipeBySearchTerm(string text)
{
var recipes = GetRecipe();
lock (locker)
{
return recipes.Where(m => m.RecipeName.ToLower().Contains(text));
}
}
Add the using System.Linq reference
Change those two methods and return IEnumerable
Note. RecipeName is the property you want to filter your Recipe with.
And your search command as below
private ICommand _searchCommand;
public ICommand SearchCommand
{
get
{
return _searchCommand ?? (_searchCommand = new Command<string>((text) =>
{
var filteredRecipes = App.RecipeDbcontroller.GetRecipeBySearchTerm(text);
recipes.Clear();
foreach(var recipe in filteredRecipes )
recipes.Add(recipe);
}));
}
}
I have not tested this code, so not sure where I might get errors, but you can work out the rest because the logic is given to you
Good luck
Related
I make a Notes application using WPF with MVVM.
I want to make a counter for each i
So there is a task list, every task has importance (none, regular and important).
I want to make listbox, that displays a count of tasks of each importances and bind it to view (counter for each importance), but i don`t know how. Something like that -
total - 10
none - 5
regular - 3
important - 2
Task Model:
public enum TaskStates
{
None,
Regular,
Important,
}
[DataContractAttribute]
public class Task : INotifyPropertyChanged
{
private string _name;
private string _desc;
private TaskStates taskState;
public SolidColorBrush TaskBG { get; set; }
[DataMember]
public DateTime CreationDate { get; private set; }
[DataMember]
public TaskStates TaskState
{
get { return taskState; }
set
{
TaskBG ??= new SolidColorBrush();
switch (value)
{
case TaskStates.None:
TaskBG.Color = Color.FromRgb(0, 113, 127);
break;
case TaskStates.Important:
TaskBG.Color = Color.FromRgb(180, 60, 60);
break;
case TaskStates.Regular:
TaskBG.Color = Color.FromRgb(53, 165, 75);
break;
}
taskState = value;
OnPropertyChanged(nameof(TaskState));
}
}
[DataMember]
public string Name
{
get => _name;
set
{
_name = value;
OnPropertyChanged(nameof(Name));
}
}
[DataMember]
public string Description
{
get => _desc;
set
{
_desc = value;
OnPropertyChanged(nameof(Description));
}
}
public Task(string name, string desc,DateTime creationDate, TaskStates taskState)
{
this._name = name;
this._desc = desc;
CreationDate = creationDate;
TaskState = taskState;
}
public Task()
{
}
protected void OnPropertyChanged ([CallerMemberName]string prop = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
public event PropertyChangedEventHandler PropertyChanged;
}
ViewModel:
class TaskViewModel : INotifyPropertyChanged
{
public Task CurrentTask { get; set; }
public DateTime CurrentDate { get; set; }
IDialogService dialogService;
IFileService fileService;
private TaskCommand addTask;
private TaskCommand removeTask;
private TaskCommand saveCommand;
private TaskCommand openCommand;
private TaskCommand clearCommand;
public ObservableCollection<Task> Tasks { get; set; }
#region Command Properties
public TaskCommand AddCommand
{
get
{
return addTask ?? (addTask = new TaskCommand(
new Action<object>(obj =>
{
Tasks.Add((obj as Task) ?? new Task("Header", "smth", CurrentDate, TaskStates.None));
})
));
}
}
public TaskCommand RemoveCommand
{
get
{
return removeTask ?? (removeTask = new TaskCommand(
new Action<object>(obj =>
{
if (CurrentTask != null)
Tasks.Remove(CurrentTask);
}), new Func<object, bool>(obj => Tasks.Count > 0)
));
}
}
public TaskCommand SaveCommand
{
get
{
return saveCommand ?? (saveCommand = new TaskCommand(
new Action<object>(obj =>
{
try
{
if (dialogService.SaveFileDialog() == true)
{
fileService.Save(Tasks.ToList(), dialogService.FilePath);
dialogService.ShowMessage("File Saved");
}
}
catch (Exception ex)
{
dialogService.ShowMessage(ex.Message);
}
}),
new Func<object, bool>(obj => Tasks.Count > 0)));
}
}
public TaskCommand OpenCommand
{
get
{
return openCommand ?? (openCommand = new TaskCommand(
new Action<object>(obj =>
{
try
{
if (dialogService.OpenFileDialog())
{
var newTasks = fileService.Open(dialogService.FilePath);
if (newTasks.Count > 0)
{
Tasks.Clear();
foreach (var item in newTasks)
{
Tasks.Add(item);
}
}
}
}
catch (Exception ex)
{
dialogService.ShowMessage(ex.Message);
}
}
)));
}
}
public TaskCommand ClearCommand
{
get
{
return clearCommand ?? (clearCommand = new TaskCommand
(
new Action<object>(obj =>
{
var res = MessageBox.Show("Are you Sure?", "Caution", MessageBoxButton.YesNo, MessageBoxImage.Warning);
if (res == MessageBoxResult.Yes)
Tasks.Clear();
}),
new Func<object, bool>(obj => Tasks.Count > 0)
));
}
}
#endregion
public TaskViewModel()
{
CurrentDate = DateTime.Now;
fileService = new JsonFileService();
dialogService = new DefaultDialogService();
Tasks = new ObservableCollection<Task>()
{
new Task("Important", "Test",CurrentDate,TaskStates.Important),
new Task("Regular", "Test",CurrentDate,TaskStates.None),
};
}
protected void OnPropertyChanged([CallerMemberName] string prop = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
public event PropertyChangedEventHandler PropertyChanged;
}
Use a ValueConverter to accomplish this.
public class TaskStateCountConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
=> $"{parameter} - {((IEnumerable<Task>)value).Count(task => task.TaskState == (TaskStates)parameter)}";
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
=> throw new NotImplementedException();
}
XAML (you said you wanted a ListBox):
<Window.Resources>
<local:TaskStateCountConverter x:Key="TaskStateCountConverter"/>
</Window.Resources>
<Grid>
<ListBox>
<ListBoxItem Content="{Binding Tasks.Count}"/>
<ListBoxItem Content="{Binding Tasks,
Converter={StaticResource TaskStateCountConverter},
ConverterParameter={x:Static local:TaskStates.None}}"/>
<ListBoxItem Content="{Binding Tasks,
Converter={StaticResource TaskStateCountConverter},
ConverterParameter={x:Static local:TaskStates.Regular}}"/>
<ListBoxItem Content="{Binding Tasks,
Converter={StaticResource TaskStateCountConverter},
ConverterParameter={x:Static local:TaskStates.Important}}"/>
</ListBox>
</Grid>
ViewModel:
public class TaskViewModel : INotifyPropertyChanged
{
// unchanged parts skipped
public TaskViewModel()
{
Tasks.CollectionChanged += OnTasksChanged;
}
private void OnTasksChanged(object sender, EventArgs e)
=> OnPropertyChanged(nameof(Tasks));
It was a bit trickier than I first thought because the converted values are not updated without the event handler (Tasks.Count is).
And while you're about it, you could also create a ValueConverter for your coloring logic.
Edit
To update TaskViewModel from within CurrentTask:
public class TaskViewModel : INotifyPropertyChanged
{
// unchanged parts skipped
private Task _currentTask;
public Task CurrentTask
{
get => _currentTask;
set
{
if (value != _currentTask)
{
if (_currentTask != null)
{
_currentTask.PropertyChanged -= OnCurrentTaskChanged;
}
_currentTask = value;
_currentTask.PropertyChanged += OnCurrentTaskChanged;
}
}
}
private void OnCurrentTaskChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(Task.TaskState))
{
OnPropertyChanged(nameof(Tasks));
}
}
}
I am stuck with a problem where I am checking network connectivity in an iOS app and trying to binding a boolean hasNetworkConnection in my view controller for it's view model.
View controller UpdateContentView.cs
// This file has been autogenerated from a class added in the UI designer.
using System;
using MvvmCross.iOS.Views;
using MvvmCross.Binding.BindingContext;
using Training.Core;
namespace EdwardsTraining.IOS
{
public partial class UpdateContentView : MvxViewController
{
public UpdateContentView(IntPtr handle) : base(handle)
{
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
var bindingSet = this.CreateBindingSet<UpdateContentView, UpdateContentViewModel>();
bindingSet.Bind(NoConnectionView).For(x => x.Hidden).To(vm => vm.HasConnection).WithConversion("ReverseBoolean");
bindingSet.Bind(UpdateInProgressView).For(x => x.Hidden).To(vm => vm.InProgress).WithConversion("ReverseBoolean");
bindingSet.Bind(UpdateAvailableView).For(x => x.Hidden).To(vm => vm.HasContentUpdate).WithConversion("ReverseBoolean");
bindingSet.Bind(CancelButton).For(x => x.Hidden).To(vm => vm.CancelVisible).WithConversion("ReverseBoolean");
bindingSet.Bind(RetryButton).To(vm => vm.DoRetryUpdate);
bindingSet.Bind(ConfirmButton).To(vm => vm.DoUpdate);
//bindingSet.Bind(iOSNetworkConnectivitiy).For(x => x.HasNetworkConnection).To(vm => vm.NetworkConnectivitiy).TwoWay()
//.For(vm => vm.HasNetworkConnection);
bindingSet.Bind(iOSNetworkConnectivitiy).To(vm => vm.NetworkConnectivitiy).TwoWay();
bindingSet.Apply();
_iOSnetworkConnectivity = new NetworkConnectivity()
{
HasNetworkConnection = Reachability.IsNetworkAvailable()
};
}
private NetworkConnectivity _iOSnetworkConnectivity { get; set; }
public NetworkConnectivity iOSNetworkConnectivitiy {
get{return _iOSnetworkConnectivity;}
set { _iOSnetworkConnectivity = value;
}
}
}
}
I would like to check for connectivity using my iOS specific code and bind the boolean returned to a a public view model property.
View Model
using System;
using System.Threading.Tasks;
using EdwardsTraining.BusinessLayer.Interfaces.Services;
using MvvmCross.Core.ViewModels;
using MvvmCross.Platform;
namespace Training.Core
{
public class UpdateContentViewModel : BaseViewModel
{
private IApplicationContentService _applicationContentService;
private ITrainingContentService _trainingContentService;
public bool _isNetworkAvailable { get; set; }
public UpdateContentViewModel(IApplicationContentService applicationContentService, ITrainingContentService trainingContentService)
{
_applicationContentService = applicationContentService ?? Mvx.Resolve<IApplicationContentService>();
_trainingContentService = trainingContentService ?? Mvx.Resolve<ITrainingContentService>();
IntialSetup();
}
protected void IntialSetup()
{
_cancelVisible = false;
_hasContentUpdate = true;
_inProgress = false;
}
public void SetNoConnection()
{
_cancelVisible = true;
_hasContentUpdate = false;
_inProgress = false;
}
public void SetInProgress()
{
_cancelVisible = false;
HasContentUpdate = false;
InProgress = true;
}
public void SetProgessComplete()
{
InProgress = false;
Task.Run(async () => await FinishedUpdating());
}
public async Task UpdateContent()
{
if (_networkConnectivity.HasNetworkConnection)
{
SetInProgress();
await _trainingContentService.UpdateTrainingContentAsync();
await _applicationContentService.UpdateContent();
SetProgessComplete();
await FinishedUpdating();
}
return;
}
public async Task FinishedUpdating()
{
Close(this);
}
public MvxCommand DoUpdate
{
get { return new MvxCommand(async () => await UpdateContent()); }
}
public MvxCommand DoRetryUpdate
{
get { return new MvxCommand(async () => await UpdateContent()); }
}
public MvxCommand CancelUpdate
{
get { return new MvxCommand(async () => await FinishedUpdating()); }
}
private bool _hasContentUpdate;
public bool HasContentUpdate
{
get { return _hasContentUpdate; }
set
{
_hasContentUpdate = value;
RaisePropertyChanged(() => HasContentUpdate);
}
}
private bool _hasConnection;
public bool HasConnection
{
get { return _hasConnection; }
set
{
_hasConnection = value;
RaisePropertyChanged(() => HasConnection);
}
}
private bool _inProgress;
public bool InProgress
{
get { return _inProgress; }
set
{
_inProgress = value;
RaisePropertyChanged(() => InProgress);
}
}
private bool _cancelVisible;
public bool CancelVisible
{
get { return _cancelVisible; }
set
{
_cancelVisible = value;
RaisePropertyChanged(() => CancelVisible);
}
}
private NetworkConnectivity _networkConnectivity { get; set; }
public NetworkConnectivity NetworkConnectivitiy
{
get { return _networkConnectivity; }
set {
_networkConnectivity = value;
RaisePropertyChanged(() => NetworkConnectivitiy);
}
}
}
public class NetworkConnectivity
{
public bool HasNetworkConnection { get; set; }
}
}
I have a problem with this line of code:
public async Task UpdateContent()
{
if (_networkConnectivity.HasNetworkConnection)
{
SetInProgress();
await _trainingContentService.UpdateTrainingContentAsync();
await _applicationContentService.UpdateContent();
SetProgessComplete();
await FinishedUpdating();
}
return;
}
if (_networkConnectivity.HasNetworkConnection) is already null even though I set two way binding. I'm new to MVVM cross for this reason I don't know if my approach is correct.
Is there anyone who could provide some help?
Nick
You need to explicitly tell the binding what property you want to bind on your NetworkConnectivity like:
bindingSet.Bind(iOSNetworkConnectivitiy).For(v => v.HasNetworkConnection).To(vm => vm.NetworkConnectivitiy).TwoWay();
However, the binding does not have any way to get notified that your NetworkConnectivity class has gotten any of its values updated. Hence you would have to extend that class to have some kind of event where it can get notified.
Then, you would have to write and register a Target Binding class.
Lets say you simply implement INotifyPropertyChanged in your NetworkConnectivity class:
public class NetworkConnectivity : MvxNotifyPropertyChanged
{
private bool _hasNetworkConnection;
public bool HasNetworkConnection {
get { return _hasNetworkConnection; }
set {
_hasNetworkConnection = value;
RaisePropertyChanged();
}
}
}
Then you create the following class in your iOS project:
public class NetworkConnectivityTargetBinding
: MvxPropertyInfoTargetBinding<NetworkConnectivity>
{
public NetworkConnectivityTargetBinding(object target, PropertyInfo targetPropertyInfo)
: base(target, targetPropertyInfo)
{
var view = View;
if (view == null)
{
MvxBindingTrace.Trace(MvxTraceLevel.Error,
"NetworkConnectivity is null in NetworkConnectivityTargetBinding");
}
else
{
view.PropertyChanged += HandleValueChanged;
}
}
private void HandleValueChanged(object sender, System.EventArgs e)
{
var view = View;
if (view == null)
return;
FireValueChanged(view.HasNetworkConnection);
}
public override MvxBindingMode DefaultMode => MvxBindingMode.TwoWay;
protected override void Dispose(bool isDisposing)
{
if (isDisposing)
{
var view = View;
if (view != null)
{
view.PropertyChanged -= HandleValueChanged;
}
}
base.Dispose(isDisposing);
}
}
Then in Setup.cs override FillTargetFactories:
protected override void FillTargetFactories(IMvxTargetBindingFactoryRegistry registry)
{
registry.RegisterPropertyInfoBindingFactory(typeof(NetworkConnectivityTargetBinding),
typeof(NetworkConnectivity), "HasNetworkConnection");
base.FillTargetFactories(registry);
}
Now the TwoWay binding should work. You should also be able to remove the .For(v => v.HasNetworkConnection) from you binding expression.
I populate a data grid with a list of objects that come from a repository like this:
public static IEnumerable<Order> GetOrdersForDataGrid()
{
IEnumerable<Order> query;
using (RSDContext = new RSDContext())
{
query = context.Orders.Include(o=>o.OrderDetails).ToList();
}
return query;
}
When I want to edit an order I pass the selected row to a new window like this:
OrderEditWindow orderEdit = new OrderEditWindow();
orderEdit.SelectedOrder = SelectedOrder;
orderEdit.ShowDialog();
Here I set the DataContext of the Window to:
DataContext = SelectedOrder;
In this window I have another data grid that binds to OrderDetails collection property of Order. The problem is on CRUD operations on OrderDetails. For example, after I add a new orderDetail like this:
private void AddProductDetailButton_OnClick(object sender, RoutedEventArgs e)
{
if (!ValidateProductDetail())
return;
var _selectedProduct = ProductAutoCompleteBox.SelectedItem as Product;
var selectedProduct = ProductsRepository.GetProductById(_selectedProduct.ProductId);
OrderDetail orderDetail = new OrderDetail();
orderDetail.Price = selectedProduct.Price;
orderDetail.ProductCode = selectedProduct.Code;
orderDetail.ProductName = selectedProduct.Name;
orderDetail.Quantity = int.Parse(QuantityNumericUpDown.Value.ToString());
orderDetail.Um = selectedProduct.Um;
orderDetail.Total = selectedProduct.Price * int.Parse(QuantityNumericUpDown.Value.ToString());
orderDetail.Group = selectedProduct.Subgroup.Group.Name;
orderDetail.Subgroup = selectedProduct.Subgroup.Name;
orderDetail.SupplierName = selectedProduct.Supplier.Name;
//orderDetail.Order=SelectedOrder;
//orderDetail.OrderId = SelectedOrder.OrderId;
SelectedOrder.OrderDetails.Add(orderDetail);
ProductAutoCompleteBox.Text = string.Empty;
QuantityNumericUpDown.Value = 1;
ProductAutoCompleteBox.Focus();
}
and then I call the update method from repository:
public static void UpdateOrder(Order order)
{
using (RSDContext context = new RSDContext())
{
context.Orders.Attach(order);
context.Entry(order).State = EntityState.Modified;
context.SaveChanges();
}
}
I get an error about OrderId. If i set manualy the navigation property and the id I don't get an error but changes dont get saved into db.
My Order model look like this:
public class Order : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public Order()
{
_OrderDetails = new ObservableCollection<OrderDetail>();
_OrderDetails.CollectionChanged += _OrderDetails_CollectionChanged;
}
void _OrderDetails_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
AttachProductChangedEventHandler(e.NewItems.Cast<OrderDetail>());
if (e.OldItems != null)
CalcualteTotals();
}
[NotMapped]
public decimal CalculatedTotal
{
get
{
return OrderDetails.Sum(x => x.Total);
}
}
public int OrderId { get; set; }
private int _Number;
public int Number
{
get { return _Number; }
set
{
_Number = value;
NotifyPropertyChanged("Number");
}
}
private DateTime _Date;
public DateTime Date
{
get { return _Date; }
set
{
_Date = value;
NotifyPropertyChanged("Date");
}
}
private bool _Canceled;
public bool Canceled
{
get { return _Canceled; }
set
{
_Canceled = value;
NotifyPropertyChanged("Canceled");
}
}
private string _ClientName;
public string ClientName
{
get { return _ClientName; }
set
{
_ClientName = value;
NotifyPropertyChanged("ClientName");
}
}
private string _ClientPhone;
public string ClientPhone
{
get { return _ClientPhone; }
set
{
_ClientPhone = value;
NotifyPropertyChanged("ClientPhone");
}
}
private string _DeliveryAddress;
public string DeliveryAddress
{
get { return _DeliveryAddress; }
set
{
_DeliveryAddress = value;
NotifyPropertyChanged("DeliveryAddress");
}
}
private decimal _Transport;
public decimal Transport
{
get { return _Transport; }
set
{
_Transport = value;
NotifyPropertyChanged("Transport");
}
}
private decimal _Total;
public decimal Total
{
get { return _Total; }
set
{
_Total = value;
NotifyPropertyChanged("Total");
}
}
private ObservableCollection<OrderDetail> _OrderDetails;
public virtual ObservableCollection<OrderDetail> OrderDetails
{
//get { return _OrderDetails ?? (_OrderDetails = new ObservableCollection<OrderDetail>()); }
get
{
return _OrderDetails;
}
set
{
_OrderDetails = value;
NotifyPropertyChanged("OrderDetails");
}
}
private void AttachProductChangedEventHandler(IEnumerable<OrderDetail> orderDetails)
{
foreach (var p in orderDetails)
{
p.PropertyChanged += (sender, e) =>
{
switch (e.PropertyName)
{
case "Quantity":
case "Price":
case "Total":
CalcualteTotals();
break;
}
};
}
CalcualteTotals();
}
public void CalcualteTotals()
{
NotifyPropertyChanged("CalculatedTotal");
}
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this,
new PropertyChangedEventArgs(propertyName));
}
}
}
And my OrderDetail model look like this:
public class OrderDetail : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public int OrderDetailId { get; set; }
public int OrderId { get; set; }
public Order Order { get; set; }
private int _ProductCode;
public int ProductCode
{
get { return _ProductCode; }
set
{
_ProductCode = value;
NotifyPropertyChanged("ProductCode");
}
}
private string _ProductName;
public string ProductName
{
get { return _ProductName; }
set
{
_ProductName = value;
NotifyPropertyChanged("ProductName");
}
}
private string _Um;
public string Um
{
get { return _Um; }
set
{
_Um = value;
NotifyPropertyChanged("Um");
}
}
private decimal _Price;
public decimal Price
{
get { return _Price; }
set
{
_Price = value;
NotifyPropertyChanged("Price");
NotifyPropertyChanged("Total");
}
}
private int _Quantity;
public int Quantity
{
get { return _Quantity; }
set
{
_Quantity = value;
NotifyPropertyChanged("Quantity");
NotifyPropertyChanged("Total");
}
}
private string _SupplierName;
public string SupplierName
{
get { return _SupplierName; }
set
{
_SupplierName = value;
NotifyPropertyChanged("SupplierName");
}
}
private string _Subgroup;
public string Subgroup
{
get { return _Subgroup; }
set
{
_Subgroup = value;
NotifyPropertyChanged("Subgroup");
}
}
private string _Group;
public string Group
{
get { return _Group; }
set
{
_Group = value;
NotifyPropertyChanged("Group");
}
}
public decimal _Total;
public decimal Total
{
get { return Quantity * Price; }
set
{
_Total = value;
NotifyPropertyChanged("Total");
}
}
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this,
new PropertyChangedEventArgs(propertyName));
}
}
}
I'm really trying to use some sort of unit of work and I don't understand how i'm supposed to apply CRUD on objects with child collections and keep the UI updated in the same time (by working in a ObservableCollection and using Binding ClientPhone, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged my parent window is updated as I type)
A final working solution:
using (RSDContext context = new RSDContext())
{
var details = order.OrderDetails;
order.OrderDetails = null;
List<int> OriginalOrderDetailsIds =
context.OrderDetails.Where(o => o.OrderId == order.OrderId).Select(o => o.OrderDetailId).ToList();
List<int> CurrentOrderDetailsIds = details.Select(o => o.OrderDetailId).ToList();
List<int> DeletedOrderDetailsIds = OriginalOrderDetailsIds.Except(CurrentOrderDetailsIds).ToList();
context.Entry(order).State = EntityState.Modified;
foreach (var deletedOrderDetailId in DeletedOrderDetailsIds)
{
context.Entry(context.OrderDetails.Single(o => o.OrderDetailId == deletedOrderDetailId)).State = EntityState.Deleted;
}
foreach (OrderDetail detail in details)
{
// Add.
if (detail.OrderDetailId == 0)
{
detail.OrderId = order.OrderId;
context.Entry(detail).State = EntityState.Added;
}
// Update.
else
{
context.Entry(detail).State = EntityState.Modified;
}
}
context.SaveChanges();
}
You could do this way for adding and updating the child, but not sure about deleted order details in the ui. If you don't want to get the order from entity, you need some kind of marking in the OrderDetail for deleted OrderDetail.
using (RSDContext context = new RSDContext())
{
var details = order.OrderDetails;
order.OrderDetails = null;
context.Entry(order).State = EntityState.Modified;
foreach (var detail in details)
{
if (detail.Id == 0)
{
// Adds.
detail.OrderId = order.Id;
context.Entry(detail).State = EntityState.Added;
}
else if (detail.IsDeleted)
// Adds new property called 'IsDeleted'
// and add [NotMapped] attribute
// then mark this property as true from the UI for deleted items.
{
// Deletes.
context.Entry(detail).State = EntityState.Deleted;
}
else
{
// Updates.
context.Entry(detail).State = EntityState.Modified;
}
}
order.OrderDetails = details;
context.SaveChanges();
}
I am using IsolatedStorageSettings to store a contact list for a key in my app. But the app only stores the list till the app is active(i.e. like navigating from one page to another). If I exit the app and again relaunch it, the stored key/contact list is not found. How do i permanently save a list for an app, till the app is installed?
Here is the code of my viewmodel, I am using:
public class ContactsViewModel : ViewModelBase
{
private static IsolatedStorageSettings appSettings = IsolatedStorageSettings.ApplicationSettings;
private List<SavedContact> _SavedContactsList;
public ContactsViewModel()
{
Contacts cons = new Contacts();
cons.SearchAsync(String.Empty, FilterKind.None, null);
cons.SearchCompleted += new EventHandler<ContactsSearchEventArgs>(OnContactSearchCompleted);
SaveSavedContactsCommand = new RelayCommand(OnSaveSavedContacts);
}
private List<SavedContact> GetSavedContacts()
{
if (appSettings.Contains("SavedContacts"))
{
var savedContacts = (List<SavedContact>)appSettings["SavedContacts"];
return savedContacts;
}
else
{
return new List<SavedContact>();
}
}
public RelayCommand SaveSavedContactsCommand { get; set; }
private void OnSaveSavedContacts()
{
if (!SavedContactsList.Any(x => x.IsSelected == true))
{
MessageBox.Show("Please select some contacts and then click on Save button.");
}
else
{
var selectedSavedContacts = SavedContactsList.Where(x => x.IsSelected == true).ToList();
SavedContacts = selectedSavedContacts;
MessageBox.Show("Emergency contact list added successfully.");
App.RootFrame.GoBack();
}
}
void OnContactSearchCompleted(object sender, ContactsSearchEventArgs e)
{
try
{
SavedContactsList = new List<SavedContact>();
var allContacts = new List<Contact>(e.Results.Where(x => x.PhoneNumbers.Count() > 0).OrderBy(c => c.DisplayName));
// var savedContacts = GetSavedContacts();
var savedContacts = SavedContacts;
foreach (Contact contact in allContacts)
{
SavedContact SavedContact = new SavedContact() { Contact = contact };
if (savedContacts.Any(x => x.Contact.PhoneNumbers.ElementAt(0).PhoneNumber == contact.PhoneNumbers.ElementAt(0).PhoneNumber))
{
SavedContact.IsSelected = true;
}
else
{
SavedContact.IsSelected = false;
}
SavedContactsList.Add(SavedContact);
}
}
catch (System.Exception ex)
{
MessageBox.Show("Error in retrieving contacts : " + ex.Message);
}
}
[DataMember]
public List<SavedContact> SavedContactsList
{
get { return _SavedContactsList; }
set
{
_SavedContactsList = value;
RaisePropertyChanged("SavedContactsList");
}
}
private List<SavedContact> SavedContacts
{
get
{
if (appSettings.Contains("SavedContacts"))
{
var savedContacts = (List<SavedContact>)appSettings["SavedContacts"];
return savedContacts;
}
else
{
return new List<SavedContact>();
}
}
set
{
appSettings["SavedContacts"] = value;
}
}
}
And the class SavedContact is followings:
[DataContract]
public class SavedContact : INotifyPropertyChanged
{
public SavedContact() { }
private bool _isSelected;
private Contact _contact;
[DataMember]
public bool IsSelected
{
get
{
return _isSelected;
}
set
{
if (_isSelected != value)
{
_isSelected = value;
OnPropertyChanged("IsSelected");
}
}
}
[DataMember]
public Contact Contact
{
get
{
return _contact;
}
set
{
if (_contact != value)
{
_contact = value;
OnPropertyChanged("Contact");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Where the view model is bound to a <toolKit: LongListMultiSelector />. The functionality is like, I select some contacts from my longlist multiselector and save it in storage to reuse it later. But if I exit the app and restarts it , the savedContacts returns null. While I navigate other pages in my app, the savedContacts is getting printed.
If I save a list first time , on app relaunch GetSavedContacts() returns a new list.
The problem isn't related to IsolatedStorageSettings or your RelayCommand. Looking in more detail the problem is with serialization and de-serialization of the Contact object. If you update your implementation to something like the example below, you should be fine.
public class ContactsViewModel : ViewModelBase
{
private static IsolatedStorageSettings appSettings = IsolatedStorageSettings.ApplicationSettings;
private List<UserContact> _contactList;
public ContactsViewModel()
{
Contacts cons = new Contacts();
cons.SearchAsync(String.Empty, FilterKind.None, null);
cons.SearchCompleted += new EventHandler<ContactsSearchEventArgs>(OnContactSearchCompleted);
SaveSavedContactsCommand = new RelayCommand(OnSaveSavedContacts);
}
public RelayCommand SaveSavedContactsCommand { get; private set; }
private void OnSaveSavedContacts()
{
if (!Contacts.Any(x => x.IsSelected == true))
{
MessageBox.Show("Please select some contacts and then click on Save button.");
}
else
{
var selectedSavedContacts = Contacts.Where(x => x.IsSelected == true).Select(x => new SavedContact{ Name = x.Contact.DisplayName, PhoneNumber = x.Contact.PhoneNumbers.ElementAt(0).PhoneNumber}).ToList();
SavedContacts = selectedSavedContacts;
MessageBox.Show("Emergency contact list added successfully.");
App.RootFrame.GoBack();
}
}
void OnContactSearchCompleted(object sender, ContactsSearchEventArgs e)
{
try
{
Contacts = new List<UserContact>();
var allContacts = new List<Contact>(e.Results.Where(x => x.PhoneNumbers.Count() > 0).OrderBy(c => c.DisplayName));
foreach (Contact contact in allContacts)
{
UserContact SavedContact = new UserContact() { Contact = contact };
if (SavedContacts.Any(x => x.PhoneNumber == contact.PhoneNumbers.ElementAt(0).PhoneNumber))
{
SavedContact.IsSelected = true;
}
else
{
SavedContact.IsSelected = false;
}
Contacts.Add(SavedContact);
}
}
catch (System.Exception ex)
{
MessageBox.Show("Error in retrieving contacts : " + ex.Message);
}
}
[DataMember]
public List<UserContact> Contacts
{
get { return _contactList; }
set
{
_contactList = value;
RaisePropertyChanged("Contacts");
}
}
public List<SavedContact> SavedContacts
{
get
{
if (appSettings.Contains("SavedContacts"))
{
var savedContacts = (List<SavedContact>)IsolatedStorageSettings.ApplicationSettings["SavedContacts"];
return savedContacts;
}
else
{
return new List<SavedContact>();
}
}
set
{
if (value != null)
{
IsolatedStorageSettings.ApplicationSettings["SavedContacts"] = value;
IsolatedStorageSettings.ApplicationSettings.Save();
}
}
}
}
public class SavedContact
{
public string Name { get; set; }
public string PhoneNumber { get; set; }
}
[DataContract]
public class UserContact : INotifyPropertyChanged
{
public UserContact() { }
private bool _isSelected;
private Contact _contact;
[DataMember]
public bool IsSelected
{
get
{
return _isSelected;
}
set
{
if (_isSelected != value)
{
_isSelected = value;
OnPropertyChanged("IsSelected");
}
}
}
[DataMember]
public Contact Contact
{
get
{
return _contact;
}
set
{
if (_contact != value)
{
_contact = value;
OnPropertyChanged("Contact");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Key/Value pairs added to the applications IsolatedStorageSettings are persisted until the application is un-installed or the item is removed from the dictionary. This is true for between application restarts and when the device is powered on and off.
Your SaveList method could be simplified to the example below as it's only necessary to check .Contains when retrieving a value from the settings dictionary as it will throw an exception if the entry cannot be found.
private void SaveList()
{
appSettings["SavedContacts"] = urls;
}
Alternatively you could use a property to handle all access to IsolatedStorageSettings using something like the example below:
private IList<Contact> Contacts
{
get
{
return appSettings.Contains("SavedContacts") ? (List<Contact>)appSettings["SavedContacts"] : null;
}
set
{
appSettings["SavedContacts"] = value;
}
}
Im writing a simple wpf application, but Im stuck. I'd like to achieve, that I have a filter class, and If the id has been changed in the filter class by a user input, a list should refresh applying the filter. All the initial bindings are working. The list is displayed properly along with the CompanyId.
the databinding in xaml:
<ListBox Height="212" HorizontalAlignment="Left" Margin="211,31,0,0" Name="listBoxProducts" VerticalAlignment="Top" Width="267" ItemsSource="{Binding ElementName=this, Path=Products}" DisplayMemberPath="CompanyId" />
<TextBox Height="28" HorizontalAlignment="Left" Margin="12,31,0,0" Name="textBoxCompanyId" VerticalAlignment="Top" Width="170" Text="{Binding ElementName=this, Path=Company.Id}" />
The code-behind for the xaml:
private TestFactory _testFactory = new TestFactory();
private Company _company;
public Company Company
{
get { return _company; }
}
private IProductList _products;
public IProductList Products
{
get { return _products; }
}
public MainWindow()
{
_company = _testFactory.Company;
_products = _testFactory.Products;
InitializeComponent();
_company.FilterChanged += _testFactory.FilterChanging;
}
The (dummy)factory class:
private IProductList _products;
public IProductList Products
{
get { return _products; }
}
private Company _company = new Company();
public Company Company
{
get { return _company; }
}
public TestFactory()
{
_company = new Company() { Id = 2, Name = "Test Company" };
GetProducts();
}
public void GetProducts()
{
var products = new List<Product>();
products.Add(new Product() { ProductNumber = 1, CompanyId = 1, Name = "test product 1" });
products.Add(new Product() { ProductNumber = 2, CompanyId = 1, Name = "test product 2" });
products.Add(new Product() { ProductNumber = 3, CompanyId = 2, Name = "test product 3" });
if (Company.Id != 2)
{
products = products.Where(p => p.CompanyId == Company.Id).ToList();
}
_products = new ProductList(products);
}
public void FilterChanging(object sender, EventArgs e)
{
GetProducts();
}
The ProductList interface:
public interface IProductList : IList<Product>, INotifyCollectionChanged {}
The productlist class:
public class ProductList : IProductList
{
private readonly IList<Product> _products;
public ProductList() { }
public ProductList(IList<Product> products)
{
_products = products;
}
public IEnumerator<Product> GetEnumerator()
{
return _products.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public void Add(Product item)
{
_products.Add(item);
notifyCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item));
}
public void Clear()
{
_products.Clear();
notifyCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
public bool Contains(Product item)
{
return _products.Contains(item);
}
public void CopyTo(Product[] array, int arrayIndex)
{
_products.CopyTo(array, arrayIndex);
}
public bool Remove(Product item)
{
var removed = _products.Remove(item);
if (removed)
{
notifyCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item));
}
return removed;
}
public int Count
{
get { return _products.Count; }
}
public bool IsReadOnly
{
get { return _products.IsReadOnly; }
}
public int IndexOf(Product item)
{
return _products.IndexOf(item);
}
public void Insert(int index, Product item)
{
_products.Insert(index, item);
notifyCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
public void RemoveAt(int index)
{
_products.RemoveAt(index);
notifyCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
public Product this[int index]
{
get { return _products[index]; }
set
{
_products[index] = value;
notifyCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, _products[index]));
}
}
public event NotifyCollectionChangedEventHandler CollectionChanged;
private void notifyCollectionChanged(NotifyCollectionChangedEventArgs args)
{
if (CollectionChanged != null)
{
CollectionChanged(this, args);
}
}
}
The Company class (filter class):
public class Company : INotifyPropertyChanged
{
private int _id;
public int Id
{
get { return _id; }
set
{
if (_id == value)
return;
_id = value;
OnPropertyChanged("Id");
OnFilterChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
private string _name;
public string Name
{
get { return _name; }
set
{
if (_name == value)
return;
_name = value;
OnPropertyChanged("Name");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public event EventHandler FilterChanged;
private void OnPropertyChanged(string name)
{
if (PropertyChanged == null)
return;
var eventArgs = new PropertyChangedEventArgs(name);
PropertyChanged(this, eventArgs);
}
private void OnFilterChanged(NotifyCollectionChangedEventArgs e)
{
if (FilterChanged == null)
return;
FilterChanged(this, e);
}
}
The list is refreshed in factory, but there is no change in the view. I probably do something wrong, maybe my whole approach is not best. Maybe I have to use ObservableCollection type with valueconverter? Any help would be greatly appreciated. Cheers!
Use an ObservableCollection<Product> instead of creating your own list based on IList
The purpose of an ObservableCollection is to track changes to the collection, and it will automatically update the UI when the collection changes.
You might also consider using ICollectionView for this use-case.
Refer this post.