I'm working on my fist app with Xamarin Forms and I'm struggling with a piece of code for a while now without making it work. Would be thankful if someone could give me a helping hand.
I have two pickers, picker1 and picker2, which have their Items and SelectedIndex properties logically connected to each other. A change in picker2 SelectedIndex causes an update of the Items and the SelectedIndex properties of the picker1 view. I have created a custom picker class which holds a bindable property for the items source to make the view pickers Items properties update accordingly to the viewmodel.
class BindablePicker : Picker
{
public static BindableProperty ItemsSourceProperty =
BindableProperty.Create<BindablePicker, IEnumerable>(o => o.ItemsSource, default(IEnumerable), propertyChanged: OnItemsSourceChanged);
public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
private static void OnItemsSourceChanged(BindableObject bindable, IEnumerable oldvalue, IEnumerable newvalue)
{
var picker = bindable as BindablePicker;
picker.Items.Clear();
if (newvalue != null)
{
foreach (var item in newvalue)
{
picker.Items.Add(item.ToString());
}
}
}
}
The creation and binding of the view objects in the content page:
BindablePicker Level1Picker = new BindablePicker { Title = "Select" };
Level1Layout.Children.Add(Level1Picker);
Level1Picker.SetBinding(BindablePicker.ItemsSourceProperty, "Level1Items");
Level1Picker.SetBinding(BindablePicker.SelectedIndexProperty, "Level1Index");
BindablePicker Level2Picker = new BindablePicker { Title = "Select" };
Level2Layout.Children.Add(Level2Picker);
Level2Picker.SetBinding(BindablePicker.ItemsSourceProperty, "Level2Items");
Level2Picker.SetBinding(BindablePicker.SelectedIndexProperty, "Level2Index");
and finally the view model:
class EducationLevelViewModel1 : BaseViewModel
{
public EducationLevelViewModel1(Criterion educationLevel)
{
this.EducationLevel = educationLevel;
}
Criterion EducationLevel { get; set; }
string level1Choice;
string Level1Choice
{
get { return level1Choice ?? (EducationLevel._Filters[0]._RelevanceType.ToString()); }
set { level1Choice = value; }
}
List<string> level1Items;
public List<string> Level1Items
{
get { return level1Items ?? (level1Items = GetLevel1List()); }
set
{
level1Items = value;
OnPropertyChanged();
}
}
int level1Index = 0;
public int Level1Index
{
get { return level1Index; }
set
{
level1Index = value;
OnPropertyChanged();
}
}
void UpdateLevel1List()
{
Level1Choice = Level1Items[Level1Index];
Level1Items = GetLevel1List();
Level1Index =Level1Items.FindIndex(x => x == Level1Choice);
}
List<string> GetLevel1List()
{
if (Level2Items[(int)Level2Index] == Criterion.Filter.RelevanceType.Inadequacy.ToString())
{
return InadequacyItems;
}
else {
return AllPickerItems;
}
}
string level2Choice;
public string Level2Choice
{
get { return level2Choice ?? (EducationLevel._Filters[1]._RelevanceType.ToString()); }
set { level2Choice = value; }
}
List<string> level2Items;
public List<string> Level2Items
{
get { return level2Items ?? (level2Items = GetLevel2List()); }
set
{
level2Items = value;
OnPropertyChanged();
}
}
int level2Index = 0;
public int Level2Index
{
get { return level2Index; }
set
{
level2Index = value;
UpdateLevel1List();
OnPropertyChanged();
}
}
}
}
As you see, my viewmodel implements the BaseViewModel class which in turn implements the INotifyPropertyChanged. The logic in the vm is that a certain selectedindex in picker2 is triggering an update of the picker1 itemlist and selectedIndex. I'm doing this in the UpdateLevel1List(). Note the saving of the string selection so I can set the same selection of picker1 in the new list, otherwise I set it to index 0.
void UpdateLevel1List()
{
Level1Choice = Level1Items[Level1Index];
Level1Items = GetLevel1List();
Level1Index = Level1Items.FindIndex(x => x == Level1Choice) == -1 ? 0 : Level1Items.FindIndex(x => x == Level1Choice);
}
The problem:
So the actual update of the picker1 itemlist updated in the view accordingly the vm logic. But the new index is NOT, it is set to the default(-1). The app later crashes when i change the picker2 back to it's default selection.
I hope I made my self clear on what I'm trying to achieve here.
Related
I am trying to update my treeview in primary viewmodel everytime I add an object to my database in my usercontrol viewmodel.
This is the code in my primary viewmodel
public class RechtbankenRechtersViewModel : Basis
{
IUnitOfWork uow = new UnitOfWork(new RechtContext());
private ObservableCollection<Rechtbank> _rechtbanken;
private IntroRechtbankenEnRechters intro = new IntroRechtbankenEnRechters();
private UserControl _control;
private ObservableCollection<TreeViewItem> _tree;
private TreeViewItem _treeItem;
public TreeViewItem TreeItem
{
get
{
return _treeItem;
}
set
{
_treeItem = value;
NotifyPropertyChanged();
}
}
public string Title { get; set; }
public UserControl Control
{
get
{
return _control;
}
set
{
_control = value;
NotifyPropertyChanged();
}
}
public ObservableCollection<TreeViewItem> Tree
{
get
{
return _tree;
}
set
{
_tree = value;
NotifyPropertyChanged();
}
}
public ObservableCollection<Rechtbank> Rechtbanken
{
get
{
return _rechtbanken;
}
set
{
_rechtbanken = value;
BouwBoom();
NotifyPropertyChanged();
}
}
public override string this[string columnName] => throw new NotImplementedException();
public RechtbankenRechtersViewModel()
{
Title = "Rechtbanken en rechters";
Tree = new ObservableCollection<TreeViewItem>();
TreeItem = new TreeViewItem();
Rechtbanken = new ObservableCollection<Rechtbank>(uow.RechtbankRepo.Ophalen(x => x.Rechters));
IntroRechtbankenEnRechters intro = new IntroRechtbankenEnRechters();
Control = intro;
}
//gaat de lijst van Tree opvullen met treeviewitems
public void BouwBoom()
{
foreach (var rechtbank in Rechtbanken)
{
TreeViewItem parent = new TreeViewItem() { Header = rechtbank.Naam, Tag = rechtbank.RechtbankID, Name="Rechtbank"};
foreach (var rechter in rechtbank.Rechters)
{
parent.Items.Add(new TreeViewItem() { Header = "Rechter - " + rechter.Voornaam + " " + rechter.Achternaam, Tag = rechter.RechterID, Name = "Rechter" });
}
Tree.Add(parent);
}
}
The Method BouwBoom is what fills my treeview since I struggled with it in the xaml(not much of a designer)
when opening the usercontrol i pass through the tag so that i can load the correct data into an object
my usercontrol viewmodel looks like this
public class OperatiesRechterViewModel : Basis
{
private RechtersRechtbanken context = (RechtersRechtbanken)Application.Current.Windows[1];
private Rechtbank _selectedRechtbank;
private ObservableCollection<Rechtbank> _rechtbanken;
private Rechter _rechter;
IUnitOfWork uow = new UnitOfWork(new RechtContext());
public override string this[string columnName] => throw new NotImplementedException();
public Rechter Rechter
{
get
{
return _rechter;
}
set
{
_rechter = value;
NotifyPropertyChanged();
}
}
public Rechtbank SelectedRechtbank
{
get
{
return _selectedRechtbank;
}
set
{
_selectedRechtbank = value;
NotifyPropertyChanged();
}
}
public ObservableCollection<Rechtbank> Rechtbanken
{
get
{
return _rechtbanken;
}
set
{
_rechtbanken = value;
}
}
public OperatiesRechterViewModel()
{
Rechter = new Rechter();
Rechtbanken = new ObservableCollection<Rechtbank>(uow.RechtbankRepo.Ophalen());
}
public OperatiesRechterViewModel(int id)
{
Rechter = uow.RechterRepo.ZoekOpPK(id);
Rechtbanken = new ObservableCollection<Rechtbank>(uow.RechtbankRepo.Ophalen());
SelectedRechtbank = uow.RechtbankRepo.Ophalen(x => x.RechtbankID == Rechter.RechtbankID).SingleOrDefault();
}
public override bool CanExecute(object parameter)
{
switch (parameter.ToString())
{
case "Toevoegen":
if (Rechter.RechterID <= 0)
{
return true;
}
return false;
case "Wijzigen":
if (Rechter.RechterID > 0)
{
return true;
}
return false;
case "Verwijderen":
if (Rechter.RechterID > 0)
{
return true;
}
return false;
}
return false;
}
public string FoutmeldingInstellen()
{
string melding = "";
if (SelectedRechtbank == null)
{
}
return melding;
}
public void Toevoegen()
{
if (SelectedRechtbank != null)
{
Rechter.RechtbankID = SelectedRechtbank.RechtbankID;
if (Rechter.Voornaam != "")
{
if (Rechter.Achternaam != "")
{
uow.RechterRepo.Toevoegen(Rechter);
int ok = uow.Save();
if (ok > 0)
{
MessageBox.Show("Rechter is toegevoegd!");
///refresh view in principe
context.DataContext = new RechtbankenRechtersViewModel();
}
}
else
{
//
}
}
else
{
//foutmelding maken
}
}
else
{
//foutmelding maken
}
}
public override void Execute(object parameter)
{
switch (parameter.ToString())
{
case "Toevoegen":
Toevoegen();
break;
}
}
}
}
As you can see here, I use the application.current.windows method to get the activated window and then I update it's datacontext when toevoegen(add) is pressed.
However I don't know if this is allowed in mvvm.
Can somebody help me?
Solved it!
for those who want to do the same thing just pass on the function to the constructor of the usercontrol viewmodel as an action then inside the uc viewmodel you can invoke it
I've this combobox:
<ComboBox x:Name="notification_mode" ItemsSource="{Binding NotificationMode}"
DisplayMemberPath="Text"/>
in my model I've created a class that add also the value to comboboxitem:
public class ComboboxItem
{
public string Text { get; set; }
public object Value { get; set; }
public override string ToString()
{
return Text;
}
}
so in my viewmodel I've created an observableCollection that store all the items:
private ObservableCollection<Models.ComboboxItem> _notificationMode = new ObservableCollection<Models.ComboboxItem>();
public ObservableCollection<Models.ComboboxItem> NotificationMode
{
get
{
return _notificationMode;
}
set
{
Models.ComboboxItem item = new Models.ComboboxItem();
item.Text = "Con sonoro";
item.Value = 0;
_notificationMode.Add(item);
}
}
The problem's that in the combobox item isn't displayed nothing. Why happen this?
This is the standard Property declaration
public ObservableCollection<Models.ComboboxItem> NotificationMode
{
get
{
return _notificationMode;
}
set
{
_notificationMode = value;
OnPropertyChanged("NotificationMode");
}
}
You can initialize the above defined property in the ViewModel constructor
public YourViewModel()
{
Models.ComboboxItem item = new Models.ComboboxItem();
item.Text = "Con sonoro";
item.Value = 0;
_notificationMode.Add(item);
}
I am trying to understand how to trigger all the properties update when new data available.
For example I have two properties:
public string PropertyOne
{
get
{
return _propertyOne
}
set
{
_propertyOne= value;
this.OnPropertyChanged();
}
}
public string PropertyTwo
{
get
{
return _propertyTwo;
}
set
{
_propertyTwo = value;
this.OnPropertyChanged();
}
}
When I receive notification about new data I assign my properties:
Mediator<ViewModelMessages>.Instance.Register(ViewModelMessages.OnNewData, this.OnNewData);
private void OnNewData(object obj)
{
PropertyOne = (MyClass)obj.propertyOne;
PropertyTwo = (MyClass)obj.propertyTwo;
}
What I want to have is something like this:
private MyClass _myClass;
private void OnNewData(object obj)
{
_myClass = (MyClass)obj;
}
public string PropertyOne
{
get
{
return _myClass.PropertyOne;
}
set
{
_myClass.PropertyOne = value;
this.OnPropertyChanged();
}
}
public string PropertyTwo
{
get
{
return _myClass.propertyTwo;
}
set
{
_myClass.propertyTwo = value;
this.OnPropertyChanged();
}
}
So when new data arrived, my properties are automatically updated.
You can achieve that by passing an Empty string or null to your OnPropertyChanged rather than a property name, but note that property changed will get raised for all properties in this case.
Inner Collection
public class ItemsDetails
{
//Constructor
public ItemsDetails(int iID,
string sItemName,
Decimal iPrice,
int iQuantity,
int iPostalCode,
bool bisDB,
string SImagePath)
{
this.iID = iID;
this.sItemName = sItemName;
this.iPrice = iPrice;
this.iquantity = iQuantity;
this.iPostalCode = iPostalCode;
this.bisDB = bisDB;
this.sImagePath = SImagePath;
}
public ItemsDetails()
{
}
#region PrivateProperties
private int iID;
private string sItemName;
private Decimal iPrice;
private int iquantity;
private int iPostalCode;
private string sImagePath;
private int iMasterID;
#endregion
//static List<ItemsDetails> itemsDetails = new List<ItemsDetails>();
#region public Properties
public bool bisChanged = false;
public bool bisDB = true;
#endregion
public int IMasterID
{
get
{
return this.iMasterID;
}
set
{
if (this.iMasterID != value)
bisChanged = true;
this.iMasterID = value;
}
}
public bool BisChanged
{
get
{
return this.bisChanged;
}
set
{
this.bisChanged = value;
}
}
public int IPostalCode
{
get
{
return this.iPostalCode;
}
set
{
if (this.iPostalCode != value)
bisChanged = true;
this.iPostalCode = value;
}
}
public int Iquantity
{
get
{
return this.iquantity;
}
set
{
if (this.iquantity != value)
bisChanged = true;
this.iquantity = value;
}
}
public Decimal IPrice
{
get
{
return this.iPrice;
}
set
{
if (this.iPrice != value)
bisChanged = true;
this.iPrice = value;
}
}
public string SItemName
{
get
{
return this.sItemName;
}
set
{
if (this.sItemName != value)
bisChanged = true;
this.sItemName = value;
}
}
public string SImagePath
{
get
{
return this.sImagePath;
}
set
{
if (this.sImagePath != value)
bisChanged = true;
this.sImagePath = value;
}
}
public int IID // unique id
{
get
{
return this.iID;
}
set
{
if (this.iID != value)
bisChanged = true;
this.iID = value;
}
}
}
Main collection
public class ItemsMapping
{
private int itemMasterID;
private string sCatagoryName;
private string sImagePath;
private ICollection<ItemsDetails> iCollectionItemDetails;
public string SImagePath
{
get
{
return this.sImagePath;
}
set
{
this.sImagePath = value;
}
}
public string SCatagoryName
{
get
{
return this.sCatagoryName;
}
set
{
this.sCatagoryName = value;
}
}
public ICollection<ItemsDetails> ICollectionItemDetails
{
get
{
return this.iCollectionItemDetails;
}
set
{
this.iCollectionItemDetails = value;
}
}
public int ItemMasterID
{
get
{
return this.itemMasterID;
}
set
{
this.itemMasterID = value;
}
}
}
Object of collection
Collection<ItemsMapping> objItemCollection = getdata();// from db
Collection<ItemsDetails> objDeleteItems = itemsToDelete();// from selected items
Result i need to delete items from inner collection objItemCollection based on objDeleteItems collection.
I expect without using foreach/for loop. Trying to find solution on linq
Thanks in advance..
Linq (Language-Integrated Query)is for querying, not for modifying (i.e. updating, adding, deleting). Use foreach loop if you want to modify collection.
You can write query which returns set of items without those you want to delete, and then use results of this query instead of your original collection. If you have same instances of ItemsDetails objects in both collections (otherwise you will need to override Equals and GetHashCode of ItemsDetails class), and you don't need duplicates in result:
objItemCollection.ICollectionItemDetails =
objItemCollection.ICollectionItemDetails.Except(objDeleteItems);
But I would go without query. Simple loop will do the job (overriding of Equals and GetHashCode still required if you don't use same instances of ItemsDetails):
foreach(var item in objDeleteItems)
objItemCollection.ICollectionItemDetails.Remove(item);
You can move this logic to extension method
public static void RemoveAll<T>(
this ICollection<T> source, IEnumerable<T> itemsToRemove)
{
if (source == null)
throw new ArgumentNullException("source");
foreach(var item in itemsToRemove)
source.Remove(item);
}
Now code will look like:
objItemCollection.ICollectionItemDetails.RemoveAll(objDeleteItems);
How can override the type of View property to my custom type.
My CustomGroupListCollectionView type adds extra property to the Groups property.
During runtime when i observe the type of View property is ListCollectionView, i want to change this to CustomGroupListCollectionView.
public class CollectionViewSourceCustom : CollectionViewSource
{
public new CustomGroupListCollectionView View { get; set; }
}
public class CustomGroupListCollectionView : ListCollectionView
{
private readonly CustomGroup _allGroup;
public CustomGroupListCollectionView(IList list)
: base(list)
{
_allGroup = new CustomGroup("All");
foreach (var item in list)
{
_allGroup.AddItem(item);
}
}
public override ReadOnlyObservableCollection<object> Groups
{
get
{
var group = new ObservableCollection<object>(base.Groups.ToList());
group.Add(_allGroup);
return new ReadOnlyObservableCollection<object>(group);
}
}
}
public class CustomGroup : CollectionViewGroup
{
public CustomGroup(object name)
: base(name)
{
}
public void AddItem(object item)
{
ProtectedItems.Add(item);
}
public override bool IsBottomLevel
{
get { return true; }
}
bool _IsChecked;
public bool IsChecked
{
get { return _IsChecked; }
set { _IsChecked = value; }
}
Visibility _CheckBoxVisibility;
public Visibility CheckBoxVisibility
{
get { return _CheckBoxVisibility; }
set { _CheckBoxVisibility = value; }
}
bool _IsExpanded;
public bool IsExpanded
{
get { return _IsExpanded; }
set { _IsExpanded = value; }
}
Visibility _ExpanderVisibility;
public Visibility ExpanderVisibility
{
get { return _ExpanderVisibility; }
set { _ExpanderVisibility = value; }
}
Visibility _ImageVisibility = Visibility.Collapsed;
public Visibility ImageVisibility
{
get { return _ImageVisibility; }
set { _ImageVisibility = value; }
}
}
CollectionViewSource has a CollectionViewType property, which you can use to determine the type of CollectionView the CollectionViewSource returns, like
<CollectionViewSource x:Key="source" CollectionViewType="{x:Type my:CustomGroupListCollectionView}" Source="{Binding MyData}"/>
As you see, you don't even have to create a new CollectionViewSource class.
If you still persist on using your way I would suggest this code:
public class CollectionViewSourceCustom : CollectionViewSource
{
public CollectionViewSourceCustom()
: base()
{
((ISupportInitialize)this).BeginInit();
this.CollectionViewType = typeof(CustomGroupListCollectionView);
((ISupportInitialize)this).EndInit();
}
}
Hope it helps.