I have an UWP project c# where I declare an ObservableCollection of items, on the page there is a TreeView bound to the collection. When I load it everything is fine and the TreeView is populated.
I've then added a button to sort ascending and descending BUT when I sort it the TreeView is not changed; I've read a lot of similar questions but was not able to find a solution.
In one of them the solution was:
public ObservableCollection<MTreeViewPaz> Patients { get; private set; } = new ObservableCollection<MTreeViewPaz>();
... in the AppBar code behind on the AppBarToggleButton_Checked:
Patients = new ObservableCollection<MTreeViewPaz>(
from i in Patients orderby i.Data descending, i.Cognome, i.Nome select i);
The View is bind to Patients:
<winui:TreeView
x:Name="treeView"
Grid.Row="1"
Expanding="treeView_Expanding"
ItemInvoked="OnItemInvoked"
ItemTemplate="{StaticResource ItemTemplate}"
ItemsSource="{x:Bind Patients}"
SelectionMode="Single" />
But nothing happens.
I've checked with the debugger and the Patients items are different before and after the sorting. So that part works fine, only is not shown.
Even it should be useless as ObservableCollection should rise events I've even tried to do this:
private ObservableCollection<MTreeViewPaz> _Patients;
public ObservableCollection<MTreeViewPaz> Patients
{
get { return _Patients; }
private set { Set(ref _Patients, value); }
}
private void Set<T>(ref T storage, T value, [CallerMemberName]string propertyName = null)
{
if (Equals(storage, value))
{
return;
}
storage = value;
OnPropertyChanged(propertyName);
}
private void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
But it does not work
Sorting of ObservableCollection does not change TreeView
For checking above code, I found you used default x:Bind mode(one time). And it will not response the Patients reset. please edit above to OneWay Mode.
ItemsSource="{x:Bind DataSource, Mode=OneWay}"
Related
I would like to ask about bindings. What is the best approach to bind some actions in listview items in ios and android using xamarin in mvvm world. As I understand, we have few approaches.
1.
For every list item we have some Model, and to this model we have to add some Commands.
For example:
public class ItemModel
{
public string MyName { get; set; }
public ICommand RemoveCommand { get; set; }
}
Where in ViewModel we have SomeInitMethod
public class ViewModel
{
public ObservableCollection<ItemModel> Items {get;set;}
public async Task SomeInitMethod
{
Items = new ObservableCollection(await _myApiService.FetchItemsAsync());
foreach(var item in Items)
{
item.Command = new RelayCommand(RemoveItem);
}
}
public void RemoveItem(ItemModel item)
{
Items.Remove(item);
}
}
But I see a drawback in SomeInitMethod where we should set RemoveCommand. What if we should to set 2 or even more commands than we duplicate code in ListItemView(somehow we need to bind all these commands)?
Next approach is somehow handle events of remove/toggle buttons and others in Listview and then delegate this commands directly to ViewModel.
Example:
ContactsListView.ItemRemoveClicked += (ItemModel model) => ViewModel.RemoveItem
Advantages is: we no longer need to handle commands in ViewModel
Drawback is: we need every time to write custom ListView and support event handling in code-behind.
The last approach is to send ViewModel to ListItem to set Commands.
Example
somewhere we have method CreateListViewItem on the view, let's say on iOS.
private void InitTableView() {
TableView.RegisterNibForCellReuse(ItemViewCell.Nib, ItemViewCell.Key);
var source = new ObservableTableViewSource <ItemModel>
{
DataSource = ViewModel.Items,
BindCellDelegate = (cell, viewModel, index) =>
{
if (cell is ItemModel memberCell)
{
memberCell.BindViewModel(viewModel);
memberCell.RemoveItem = (item) => ViewModel.RemoveItem;
}
}
};
TableView.Source = source;
}
Advantages: we no longer need to have Commands in Model, and we don't need to setup this Commands in ViewModel.
Possibly, drawback is that we somehow need to have ViewModel reference.
In WPF or UWP you have DataContext, you can binding directly to ViewModel.
Which approach you use, maybe I miss something, and it would be perfect if you provide some examples or thoughts.
Thanks.
Is is possible to get an event when an item in the collection bound to a Xamarin Forms listview changes?
For example my object has a Date field which is bound to a label in a ViewCell. I would like an event fired when the Date is changed. Our object implements INotifyPropertyChanged so the listview updates properly.
I can manually subscribe to the OnPropertyChanged event of each item but I'm hoping their is an easier way.
Thanks.
There are triggers in Xamarin.Forms. It seems like an event trigger will do what you need. For example:
<EventTrigger Event="TextChanged">
<local:NumericValidationTriggerAction />
</EventTrigger>
public class NumericValidationTriggerAction : TriggerAction<Entry>
{
protected override void Invoke (Entry entry)
{
double result;
bool isValid = Double.TryParse (entry.Text, out result);
entry.TextColor = isValid ? Color.Default : Color.Red;
}
}
You can find more information about triggers here
See this example for deleting an object when it is selected from a list view.
private MyItemsViewModel _myItemsViewModel;
private void MyItemsListView_ItemSelected(object sender, SelectedItemChangedEventArgs e)
{
MyItem item = (MyItem)e.SelectedItem;
if (item == null)
return;
// remove the item from the ObservableCollection
_myItemsViewModel.Items.Remove(item);
}
I'm new to Xamarin and C#, so apologies in advance for any mistakes I make.
In my app, I have a list of plants. When a plant is selected, I have a detail view of info about the plant. In the detail view, I have a button that adds or removes the plant from a shopping list.
To implement this, I have a class named MyPlant, with a field called InCart, and a method ToggleInCart that the button calls.
(note that I didn't paste in some code to simplify this question as much as possible)
public class MyPlant : INotifyPropertyChanged
{
string name;
bool inCart;
...
public bool InCart
{
set
{
if (inCart != value)
{
inCart = value;
OnPropertyChanged("InCart");
}
}
get { return inCart; }
}
public ICommand ToggleCartStatus
{
get
{
if (_toggleCartStatus == null)
{
_toggleCartStatus = new Command(() => InCart = !InCart);
}
return _toggleCartStatus;
}
I have another class called PlantList, which has a method PlantsInCart that uses LINQ to return an ObservableCollection of MyPlant where InCart is true.
public class PlantList : INotifyPropertyChanged
{
public ObservableCollection PlantsInCart
{
private set { }
get
{
ObservableCollection list = new ObservableCollection(myPlants.Where(i => i.InCart));
return list;
}
}
In my XAML, I have a ListView bound to PlantsInCart.
Everything works as I want EXCEPT when I remove the selected plant, the list doesn't update to show the plant is missing even though the data underneath it is correctly updated. If I refresh the list by going to a different page and coming back, then the list shows the right plants.
I suspect this doesn't work because the change in the InCart field isn't bubbling up high enough to that the ListView hears that it is supposed to update.
Can anybody advise me on the proper way to implement this kind of feature? In other words, how should you implement a scenario where you have a list that should update when a property of an item in the list changes?
need some help, when i click the tap_event I get a message box delete or cancel which works and the price is taken off the total but it does'nt update the shopping cart after, it crashes on "ListBoxCart.Items.Remove(curr), thanks in advance!
private void listBoxCart_Tap(object sender, GestureEventArgs e)
{
if (MessageBox.Show("Are you sure!", "Delete", MessageBoxButton.OKCancel)
== MessageBoxResult.OK)
{
foreach (Dvd curr in thisapp.ShoppingCart)
{
if (curr.Equals(listBoxCart.SelectedItem))
{
listBoxCart.Items.Remove(curr);
listBoxCart.SelectedIndex = -1;
total -= Convert.ToDecimal(curr.price);
NavigationService.Navigate(new Uri("/ShoppingCart.xaml", UriKind.RelativeOrAbsolute));
}
}
txtBoxTotal.Text = total.ToString();
listBoxCart.ItemsSource = thisapp.ShoppingCart;
}
else
{
NavigationService.Navigate(new Uri("/ShoppingCart.xaml", UriKind.RelativeOrAbsolute));
}
}
I have wrote an artile (sorry in french but you can read the XAML) : http://www.peug.net/2012/05/17/contextmenu-dans-listbox-datatemplate/
and in the code-behind : an example :
private void MenuItem_Click(object sender, RoutedEventArgs e)
{
var menuItem = sender as MenuItem;
var fe = VisualTreeHelper.GetParent(menuItem) as FrameworkElement;
Dvd _fig = fe.DataContext as Dvd;
thisapp.ShoppingCart.Remove(_fig);
reloading();
}
When you set the ItemsSource property for the ListBox, it generates a read-only collection and displays them. What you're trying to do is access this read-only collection and modify it but because it's read-only, you can't do that.
Instead you can either have your collection implement the INotifyCollectionChanged interface and raise a collection changed event when the user has deleted the item or use an ObservableCollection instead to store your items. ObservableCollection implements the INotifyCollectionChanged interface for you so you can remove items from the ObservableCollection and the changes will reflect in the Listbox automatically.
ObservableCollection also implements INotifyPropertyChanged so any property updates will also be updated in the ListBox.
learning wpf with mvvm (using EF as ORM).
In my view model i have the property:
//---------------ClientNew
public const string ClientNewConst = "ClientNew";
private TBL_CLIENT _clientNew = new TBL_CLIENT();
public TBL_CLIENT ClientNew
{
get
{
return _clientNew;
}
set
{
if (_clientNew == value)
{
return;
}
var oldValue = _clientNew;
_clientNew = value;
// Update bindings, no broadcast
RaisePropertyChanged(ClientNewConst);
}
}
where TBL_CLIENT - is an entittyobject that mirrors TBL_CLIENT table in DB
now, in my VIEW i bind bunch of textboxes like this(example only for client's first name):
<TextBox Style="{StaticResource ResourceKey=entryFormTextBox}"
Text="{Binding ClientNew.CLIENT_FIRST_NAME,
ValidatesOnDataErrors=True,
NotifyOnValidationError=true,
ValidatesOnExceptions=True,
UpdateSourceTrigger=LostFocus}"
Grid.Column="1"
Grid.Row="1" />
I tried to use different triggers for updatesource.. still teh validation does not work.
oh, i do have implemented idataerrorinfo interface in my viewmodel (but it never hits it..)
#region IDataErrorInfo Members
string IDataErrorInfo.Error
{
get { throw new NotImplementedException(); }
}
string IDataErrorInfo.this[string columnName]
{
get
{
if (string.IsNullOrEmpty("ClientNew.CLIENT_FIRST_NAME"))
{
return "Client Name is required";
}
return null;
}
}
#endregion
so, question.. how can i implement the simple as possible validation using idataerrorinfo for my case, where i don't have the separate property defined in ModelView for each of the entity object, but the property takes the whole entity object?
thanks in advance,
Alex
You might have a look at the BookLibrary sample application of the WPF Application Framework (WAF). It defines the validation rules directly at the entity. Please have a look at "BookLibrary.Domain / Book.cs".