How do I use grouping using Collection View MVVM? - mvvm

I'm fairly new to MVVM, and I have recently started a project cleaning up my codebehind and bit by bit I am moving everything to Model and ViewModel.
My problem is, now, how do you use grouping using Collection View without any code behind? I thought I had figured it out, after reading answers to similar questions here on Stackoverflow, but I still can't get it to work. Probably a silly mistake, but I would be very grateful if somebody could have a look at my code and let me know what they think. All feedback is great feedback, I really want to become a good programmer :)
The list is btw of the type ObservableCollection in the Menu class.
<CollectionViewSource x:Key="foods" Source="{Binding Items}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Category"/>
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
<ListBox x:Name="selectedMenuItem" Foreground="White" Grid.Column="0" Grid.Row="1" ItemsSource="{Binding Source={StaticResource foods}}"
DisplayMemberPath="Name" Background="{x:Null}" BorderThickness="0">
<ListBox.GroupStyle>
<x:Static Member="GroupStyle.Default"/>
</ListBox.GroupStyle>
</ListBox>
private CollectionViewSource _items;
private Menu _menu = new Menu();
public ICollectionView Items
{
get
{
if (_items == null)
{
_items = new CollectionViewSource {Source = new ObservableCollection<MenuItem>(_menu.MyMenu)};
}
return _items.View;
}
}

I'm assuming your problem is that data doesn't show up in your ListBox? Try programmatically adding your groupings to _items and binding your ListBox.ItemsSource directly to Items:
public ICollectionView Items
{
get
{
if (_items == null)
{
_items = new CollectionViewSource {Source = new ObservableCollection<MenuItem>(_menu.MyMenu)};
_items.GroupDescriptions.Add(new PropertyGroupDescription("Category"));
}
return _items.View;
}
}
<ListBox x:Name="selectedMenuItem" Foreground="White" Grid.Column="0" Grid.Row="1" ItemsSource="{Binding Items}"
DisplayMemberPath="Name" Background="{x:Null}" BorderThickness="0">
<ListBox.GroupStyle>
<x:Static Member="GroupStyle.Default"/>
</ListBox.GroupStyle>
</ListBox>
You can then do away with the foods resource, assuming I haven't boffed my code.

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.

how to bind multiple selected items of syncfusion xamarin forms datagrid in 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.

Double-wrapping Windows8 grid view

I'm working on a Windows 8 application and i have to create a different-sized tiles view. An issue is that i need my view to be double-wrapped: first, it should be wrapped horizontally (ie, each column should be 2 tiles in width). Such a view is going to be a very tall column. Next, it should wrap vertically.
The sketch of a view i'm going to create is given below. Are there any Win8 controls able to provide the described behavior ?
I've tried to use VariableSizedWrapGrid, but it seems it's unable to double-wrap it.
Ok, the time has passed and i've got no better solution than creating a custom columned model and using a grid with few VariableGridViews (one VariableGridViews per column).
Maybe, my XAML will help somebody.
<controls:VariableGridView ItemsSource="{Binding Columns}">
<controls:VariableGridView.ItemTemplate>
<DataTemplate>
<controls:VariableGridView ItemsSource="{Binding Tiles}">
<controls:VariableGridView.ItemTemplate>
<DataTemplate>
<Grid VariableSizedWrapGrid.ColumnSpan="{Binding Width}">
<Rectangle Fill="Red" Width="{Binding WidthPixels}" Height="{Binding HeightPixels}"></Rectangle>
</Grid>
</DataTemplate>
</controls:VariableGridView.ItemTemplate>
<controls:VariableGridView.ItemsPanel>
<ItemsPanelTemplate>
<VariableSizedWrapGrid VerticalAlignment="Center"
ItemHeight="150"
ItemWidth="150"
MaximumRowsOrColumns="2"
Orientation="Horizontal" Height="768"
AllowDrop="True"
Tapped="VariableSizedWrapGrid_Tapped"/>
</ItemsPanelTemplate>
</controls:VariableGridView.ItemsPanel>
</controls:VariableGridView>
</DataTemplate>
</controls:VariableGridView.ItemTemplate>
</controls:VariableGridView>
Model source code looks like (truncated)
public class TilesCollection
{
public ObservableCollection<ITile> Tiles {get; private set;}
}
public class ColumnedListModel
{
ObservableCollection<TilesCollection> m_columns = new ObservableCollection<TilesCollection>();
public ObservableCollection<TilesCollection> Columns
{
get
{
return m_columns;
}
}
// ....
}

Binding a View to my main window only shows me the type

I'm trying to bind a View to my ContentControl. Currently, it just shows me the type (eg NameSpace.ViewModel.MainWindowViewModel)
Although I will point out, I'm not sure if I'm approaching this correctly.
My simple set up is I have a View (UserControl) which is empty other than a single control (which has been placed just for the visual).
My MainWindow.xaml
<Window x:Class="DelegateGoodExample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:viewModel="clr-namespace:DelegateGoodExample.ViewModel"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<viewModel:MainWindowViewModel x:Key="Vm" />
</Window.Resources>
<Grid>
<ContentControl Height="147" Margin="53,132,60,0"
VerticalAlignment="Top"
Content="{StaticResource Vm}" />
</Grid>
</Window>
(There is nothing in the code behind).
My MainWindowViewModel.cs
namespace DelegateGoodExample.ViewModel
{
public class MainWindowViewModel
{
private object _currentView;
public object CurrentView
{
get { return new View.QuickView(); }
set { _currentView = value; }
}
}
}
So, my question is,
Do I have to set a datacontext in this instance (and even if I do add it the results persist)?
What have I done wrong?
You are putting a viewmodel inside the ContentControl, not a view. Since your viewmodel class is not a UIElement and there is no DataTemplate to determine how it should be rendered, what gets displayed is simply its .ToString() representation.
An immediate fix would be:
<ContentControl Height="147" Margin="53,132,60,0"
VerticalAlignment="Top"
Content="{Binding Source={StaticResource Vm}, Path=View}" />
However, instead of doing things this way you should be putting your view inside the Grid directly, and the viewmodel should not have any knowledge of the view.

Silverlight DataForm+ChildWindows+MVVM: ComboBox's DataField dont get populated

i have my View Page with a GridView control. Items in the Grid are edited using a popup Childwindows with the following xaml:
<toolkit:DataForm.EditTemplate>
<DataTemplate>
<StackPanel>
<toolkit:DataField Label="Avisar a: ">
<ComboBox ItemsSource="{Binding Path=Sucursales}"/>
</toolkit:DataField>
<toolkit:DataField Label="Mensaje:">
<TextBox Text="{Binding mensaje, Mode=TwoWay}"/>
</toolkit:DataField>
<toolkit:DataField Label="Estado: ">
<ComboBox ItemsSource="{Binding Path=EstadosMensaje}"/>
</toolkit:DataField>
</StackPanel>
</DataTemplate>
</toolkit:DataForm.EditTemplate>
</toolkit:DataForm>
DataContext to this popup is injected view constructor from the parent view as follow:
AlertaForm frm = new AlertaForm(DataContext as AlertasViewModel);
frm.Show();
//ChildWindows constructor
public AlertaForm(AlertasViewModel viewModel){
InitializeComponent();
DataContext = viewModel;
}
As you can see, ChildWindows and parent view share the same ViewModel.
The problem is that ComboBox controls dont get populated. TextBox field are binded correctly,they display values from DataContext property, that is confusing because that prove that the DataForm recognize the ViewModel passed to the ChildWindows AlertaForm.
Obviously i'm missing something here but cannot figure out what is.
Thanks
I end up throwing away the User control with the DataForm together
sticking in ChildWindows with common controls. It seems that DataForm is not sweet to
complex scenarios