how to bind multiple selected items of syncfusion xamarin forms datagrid in mvvm? - mvvm

I am able to bind SelectedItem if Selection Mode is single but if it is set to multiple then how do you bind it?
Here is what I tried for Single Selection Mode
<sync:SfDataGrid Grid.Row="1" AutoGenerateColumns="False" AllowSorting="True"
AllowGroupExpandCollapse="True" AutoExpandGroups="True"
SelectionMode="Multiple" ColumnSizer="Star"
ItemsSource="{Binding LstItems}"
SelectedItem="{Binding Path=SelectedItem, Mode=TwoWay}"
>
<sync:SfDataGrid.Columns>
<sync:GridTextColumn HeaderText="Name" MappingName="Name" />
<sync:GridTextColumn HeaderText="MRP" MappingName="MRP"/>
<sync:GridTextColumn HeaderText="Category" MappingName="Category" Width="0"/>
</sync:SfDataGrid.Columns>
<sync:SfDataGrid.GroupColumnDescriptions>
<sync:GroupColumnDescription ColumnName="Category"/>
</sync:SfDataGrid.GroupColumnDescriptions>
</sync:SfDataGrid>
In the above xaml, selection mode is set to multiple but I am unable to get the SelectedItems in xaml as mentioned here
https://help.syncfusion.com/xamarin/sfdatagrid/selection

In SfDataGrid, it is not possible to bind the SfDataGrid.SelectedItems property to the view model as like SelectedItem property since we can only get the selected items in SfDataGrid. Hence, you will not be able to bind the values in XAML for SelectedItems property.
However, you can achieve your requirement by writing behavior for SfDataGrid which will not affect the MVVM pattern. Please refer the below code snippet.
<sfGrid:SfDataGrid x:Name="dataGrid"
AutoGenerateColumns="True"
ItemsSource="{Binding OrdersInfo}"
SelectionMode="Multiple">
<b:Interaction.Behaviors>
<b:BehaviorCollection>
<b:EventToCommand Command="{Binding SelectionCommand}"
CommandParameter="{x:Reference Name=dataGrid}"
EventName="SelectionChanged" />
</b:BehaviorCollection>
</b:Interaction.Behaviors>
</sfGrid:SfDataGrid>
// In ViewModel.cs
public ViewModel()
{
selectionCommand = new Command<SfDataGrid>(onSelectionChanged);
selectedItems = new ObservableCollection<object>();
}
private Command<SfDataGrid> selectionCommand;
public Command<SfDataGrid> SelectionCommand
{
get { return selectionCommand; }
set { selectionCommand = value; }
}
private ObservableCollection<object> selectedItems;
public ObservableCollection<object> SelectedItems
{
get { return selectedItems; }
set { selectedItems = value; }
}
private void onSelectionChanged(SfDataGrid obj)
{
//you can get the selected items in the datagrid
selectedItems = obj.SelectedItems;
}
Also, we have prepared a sample for your reference and you can download the same from the below link.
Sample link: http://www.syncfusion.com/downloads/support/directtrac/168321/ze/DataGridDemo352928928
Regards,
Divakar.

Related

Should ItemSource and BindingContext both be set when using MVVM (Xamarin.Froms ListView)?

Model:
public class Question : INotifyPropertyChanged
{
private float? _answer;
public float? Answer
{
get => _answer;
set
{
_answer = value;
NotifyPropertyChanged();
}
}
protected void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
View model:
public class QuestionViewModel
{
private ObservableCollection<Question> _questions;
public ObservableCollection<Question> Questions
{
get => _questions;
set
{
if (_questions != value)
{
_questions = value;
}
}
}
}
XAML:
<ListView x:Name="ListViewQuestions" SelectionMode="Single" HasUnevenRows="True" HeightRequest="250" VerticalOptions="FillAndExpand">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Entry x:Name="EntryAnswer" Text="{Binding Answer,Mode=TwoWay}" Keyboard="Numeric" FontSize="Medium" VerticalOptions="End"
HorizontalOptions="FillAndExpand" Grid.Row="0" Grid.Column="1" >
<Entry.Behaviors>
<behaviors:EntryMaxValueBehavior MaxValue="{Binding MaxVal}" BindingContext="{Binding BindingContext, Source={x:Reference EntryAnswer}}" />
<behaviors:EntryMinValueBehavior MinValue="{Binding MinVal}" BindingContext="{Binding BindingContext, Source={x:Reference EntryAnswer}}" />
</Entry.Behaviors>
</Entry>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
In my page OnAppearing method, I set the ListViewQuestions like this:
var questions = await DataStore.GetQuestions(_inspection.Id);
var questionsViewModel = new QuestionViewModel { Questions = new ObservableCollection<Question>(questions) };
ListViewQuestions.ItemsSource = null;
ListViewQuestions.ItemsSource = questionsViewModel.Questions;
However, when values are entered into EntryAnswer, the setter in the Question model is not called, as I would expect. I thought that maybe this was because the BindingContext for the ListView needed to be set, so I set it like this:
ListViewQuestions.BindingContext = questionsViewModel;
However, the setter in the Question model is still not called. I also tried implementing INotifyPropertyChanged in the QuestionViewModel, but still no joy. I checked that the ObservableCollection in the View Model is set correctly, with actual data, and it is. Can anyone spot what might be going wrong here?
Edit 1: I also tried not setting the ItemSource, but only setting the ListViewQuestions.BindingContext to the view model, but then the ListView was not being populated with any data.
Here is how this works together.
The BindingContext is the object that will be the scope for whatever bindings that are in the page or it's children, unless you specify a different context for a certain child object, but let's not overcomplicate things for now.
This means, that when you have set the BindingContext, all Bindings will now start looking into the object referenced in the BindingContext. In your case, you set the BindingContext to an instance of QuestionViewModel.
You want your ListView, to get its items from the QuestionViewModel.Questions property. So, you set a binding like this:
<ListView x:Name="ListViewQuestions" ItemsSource="{Binding Questions}" ...>.
Questions needs to be a public property in the BindingContext, in our case QuestionViewModel. You got this right already.
Now, whenever you assign something to Questions this should also propagate to your ListView because of the binding.
Inside your ListView you are using a ViewCell, now note, that the scope does change here. Each cell represents an instance of an object inside the ItemsSource. In our case, each cell will hold a Question. You are using this:
<Entry x:Name="EntryAnswer" Text="{Binding Answer,Mode=TwoWay}" ...>
This means Answer needs to be a public property inside Question. You got this right already.
When you implement it like this, basically the only thing you do is fill your view model and assign that to the BindingContext of your page. If you are using an MVVM framework, this might happen automatically.
At some point, you might run into some trouble that the UI doesn't update and you will have to implement the INotifyPropertyChanged interface. Have a close look at what object doesn't update on screen and implement the interface on that object along with the needed plumbing, but from what I can see in this code, this isn't needed right now. And besides, you have implemented it the right way in your Question right now.
I hope this makes sense! It's a bit hard to wrap your head around the first time, but once you get the swing of it, it is pretty easy!
In your Answer Setter try:
set
{
float? temp = null;
if(float.TryParse(value, out temp)
{
_answer = temp;
NotifyPropertyChanged("Answer");
}
}
It seems like for this to work though your setter would have to be called, and you indicate that it is not, so I think it must be the min, max binding where this is kicking out the error. For now perhaps get rid of that and see if the setter will get called.
In WPF using a converter is typical and I think will work with the Xamarin as well. See this for a good example of how to implement IValueConverter.

Xamarin Forms Controls values not visible

I have created a page that passes a value to a new page that will allow users to update the data. When the users selects the record to be updated the edit form opens but the data is not visible. If the value is changed and the edit button clicked it will update the value, but it is never visible. How can I show the data that is to be edited?
View Model
namespace QiApp.ViewModels
{
public class EditTodayCasesViewModel
{
private SxCaseDataService _sxCaseDataService = new SxCaseDataService();
public SxCase SelectedSxCase { get; set; }
public ICommand EditSxCaseCommand => new Command(async () =>
{
await _sxCaseDataService.PutSxCase(SelectedSxCase.SxCaseId, SelectedSxCase);
});
}
}
Edit Page xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:viewModels="clr-namespace:QiApp.ViewModels;assembly=QiApp.UWP"
x:Class="QiApp.Views.EditTodayCasePage">
<ContentPage.BindingContext>
<viewModels:EditTodayCasesViewModel/>
</ContentPage.BindingContext>
<StackLayout>
<Label Text="Surgery Case"/>
<Label Text="{Binding SelectedSxCase.SxCaseId}"/>
<Entry Text="{Binding SelectedSxCase.Record}"/>
<Switch IsToggled="{Binding SelectedSxCase.Complete}"/>
<Button Text="Edit Surgery Case"
Command="{Binding EditSxCaseCommand}"/>
</StackLayout>
</ContentPage>
Code behind
namespace QiApp.Views
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class EditTodayCasePage : ContentPage
{
public EditTodayCasePage(SxCase sxCase)
{
InitializeComponent();
var editTodayCasesViewModel = BindingContext as EditTodayCasesViewModel;
editTodayCasesViewModel.SelectedSxCase = sxCase;
}
}
}
Everything is alright except that your view gets bound to a view model which stays silent if properties are changed. Your view cannot get any information on when it should update itself and hence the UI as soon as the property SelectedSxCase gets changed.
Thankfully this can be done very easily by simply implementing the common interface INotifyPropertyChanged and extending your bound properties with a code line raising the event the interface provides.
Basically it goes like this ...
private SxCase _case;
public SxCase SelectedSxCase
{
get => _case;
set
{
_case = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedSxCase)));
}
}
... but there are several implementations to do that more elegant like using the CallerMemberName or even weaving the getter and setter automatically with Fody.

How do I get the values from the cells of a selected row in a datagrid?

I see where this question has been asked but most of the answers seem to center around code behind while I am using an MVVM pattern. For those answers that pertain to my situation, I have tried a number of different suggestions but I seem to be missing the big picture.
Basically what I am looking to do is add a delete button that would delete all selected rows in the datagrid from an SQL database or add a delete button to each row. I would prefer to have a single delete button but am open to a delete button in each row, which I have actually already done but still face the same problem in that I need to delete a specific record ID from the database as well as remove it from the collection.
XAML
<DataGrid x:Name="licenseGrid"
ItemsSource="{Binding LoggedUsers}"
SelectedItem="{Binding SelectedLicense, Mode=TwoWay}"
Style="{DynamicResource DataGridStyle}"
Grid.Row="2"
Grid.Column="1"
Grid.ColumnSpan="6"
Height="535"
VerticalAlignment="Top"
IsReadOnly="True"
AutoGenerateColumns="False"
HeadersVisibility="Column"
SelectionMode="Extended"
CanUserDeleteRows="True">
ViewModel
public ObservableCollection<MD_LoggedUsersModel> LoggedUsers
{
get { return _loggedUsers; }
set { _loggedUsers = value; NotifyPropertyChanged(nameof(LoggedUsers)); }
public string SelectedLicense
{
get
{
return _selectedLicense;
}
set
{
if (_selectedLicense != value)
{
_selectedLicense = value;
OnPropertyChanged(nameof(SelectedLicense));
RevokeSelected(_selectedLicense);
}
if (_selectedLicense == null)
{
LoadData();
}
}
}
The value that is being returned is the name of the LoggedUsers model.
How do I get the value of each cell?
You'll need to update your SelectedItem to be the same type as the items in your ObservableColleciton.
public MD_LoggedUsersModel SelectedLicense
{
get
{
return _selectedLicense;
}
set
{
if (_selectedLicense != value)
{
_selectedLicense = value;
}
}
}

ZKoss issue with selectedItem of listbox

that's my code:
<listbox id="boxFirma" multiple="true"
visible="#load(vm.opzioneSelezionata eq 'firma' ? 'true' : 'false')"
checkmark="true" width="400px" height="200px"
model="#bind(vm.opzioniFirma)"
selectedItems="#bind(vm.pickedItemSet)">
<template name="model" var="item"
status="s">
<listitem selected="#bind(item.preSelected)">
<listcell label="#bind(item.valore)" />
</listitem>
</template>
</listbox> <button label="Salva" style="margin-top:10px" disabled="#load(empty vm.pickedUser)"
onClick="#command('salvaPersonalizzazioneUtente')" />
The problem is when I push the button Salva, I get on the vm.pickedItemSet only the item that the user has just chosen, but nothing about the preselected items -> 'listitem selected="#bind(item.preSelected)" ' . So if there were 2 items preselected and one clicked by the user on the view model, I get just the one clicked, whereas I want all three. How do I fix this?
I think that your problem is behind the use of "preselected" property of your domain object. Without your View Model it's hard to understand what you are trying to achieve.
Hovewer, let me try to address you:
fill the set (pickedItemset) in the init method, and let zk handle that set.
remove " selected="#bind(item.preSelected)" " from you template. If you like
checkboxes, add "checkmark=true" as a listbox property
(http://books.zkoss.org/wiki/ZK_Component_Reference/Data/Listbox#Multiple_Selection).
As an example, try this View Model ( "SignOption" is a bean with a single member valore). The "Salva" button will print out the set of selected list items.
// a bunch of imports
public class MultiSelectionVM {
private String opzioneSelezionata = "firma";
private Set<SignOption> opzioniFirma = new HashSet<SignOption>();
private Set<SignOption> pickedItemSet = new HashSet<SignOption>();
private boolean pickedUser = true;
#Init
public void init(){
SignOption opt1 = new SignOption();
opt1.setValore("opt1");
SignOption opt2 = new SignOption();
opt2.setValore("opt2");
SignOption opt3 = new SignOption();
opt3.setValore("opt3");
//Init list model
opzioniFirma.add(opt1);
opzioniFirma.add(opt2);
opzioniFirma.add(opt3);
//Init selected Items
pickedItemSet.add(opt2);
}
#Command
public void salvaPersonalizzazioneUtente(){
System.out.println(pickedItemSet);
}
//Getters and setter for all members
}
Hope this helps!

MVVM Windows Phone 8 - adding a collection of pushpins to a map

Here is the XAML code:
<maps:Map x:Name="NearbyMap"
Center="{Binding MapCenter, Mode=TwoWay}"
ZoomLevel="{Binding ZoomLevel, Mode=TwoWay}"
>
<maptk:MapExtensions.Children>
<maptk:MapItemsControl Name="StoresMapItemsControl" ItemsSource="{Binding Treks}">
<maptk:MapItemsControl.ItemTemplate>
<DataTemplate>
<maptk:Pushpin x:Name="RouteDirectionsPushPin" GeoCoordinate="{Binding Location}" Visibility="Visible" Content="test"/>
</DataTemplate>
</maptk:MapItemsControl.ItemTemplate>
</maptk:MapItemsControl>
<maptk:UserLocationMarker x:Name="UserLocationMarker" Visibility="Visible" GeoCoordinate="{Binding MyLocation}"/>
</maptk:MapExtensions.Children>
</maps:Map>
xmlns:maps="clr-namespace:Microsoft.Phone.Maps.Controls;assembly=Microsoft.Phone.Maps"
xmlns:maptk="clr-namespace:Microsoft.Phone.Maps.Toolkit;assembly=Microsoft.Phone.Controls.Toolkit"
PushPinModel has an attribute Location which is a GeoCoordinate. Treks is an ObservableCollection<PushPinModel>. I run this code and only the UserLocationMarker is displayed, which is my current location.
I finally make it work by using dependency property. I added a new class:
public static class MapPushPinDependency
{
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.RegisterAttached(
"ItemsSource", typeof(IEnumerable), typeof(MapPushPinDependency),
new PropertyMetadata(OnPushPinPropertyChanged));
private static void OnPushPinPropertyChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
UIElement uie = (UIElement)d;
var pushpin = MapExtensions.GetChildren((Map)uie).OfType<MapItemsControl>().FirstOrDefault();
pushpin.ItemsSource = (IEnumerable)e.NewValue;
}
#region Getters and Setters
public static IEnumerable GetItemsSource(DependencyObject obj)
{
return (IEnumerable)obj.GetValue(ItemsSourceProperty);
}
public static void SetItemsSource(DependencyObject obj, IEnumerable value)
{
obj.SetValue(ItemsSourceProperty, value);
}
#endregion
}
And in the .xaml file I have added
xmlns:dp="clr-namespace:Treks.App.Util.DependencyProperties"
and now the .xaml file looks like this:
<maps:Map x:Name="NearbyMap"
Center="{Binding MapCenter, Mode=TwoWay}"
ZoomLevel="{Binding ZoomLevel, Mode=TwoWay}"
dp:MapPushPinDependency.ItemsSource="{Binding Path=Treks}"
>
<maptk:MapExtensions.Children>
<maptk:MapItemsControl Name="StoresMapItemsControl">
<maptk:MapItemsControl.ItemTemplate>
<DataTemplate>
<maptk:Pushpin x:Name="PushPins" GeoCoordinate="{Binding Location}" Visibility="Visible" Content="test"/>
</DataTemplate>
</maptk:MapItemsControl.ItemTemplate>
</maptk:MapItemsControl>
<maptk:UserLocationMarker x:Name="UserLocationMarker" Visibility="Visible" GeoCoordinate="{Binding MyLocation}"/>
</maptk:MapExtensions.Children>
</maps:Map>
Now all the pushpins are correctly rendered.
The MapItemsControl is currently not yet MVVM bindable ( what I am aware off ).
So best way is to set it's ItemsSource in the code behind of your view.
You can still use the collection defined in your ViewModel though!
Options are:
through mvvm messaging pass along the collection from the viewmodel to the code behind of the view
use the datacontext of the view to access the collection, something like this: this.StoresMapItemsControl.ItemsSource = ServiceLocator.Current.GetInstance<MainViewModel>().Locations;