How to code a UWP MVVM ComboBox within an ItemTemplate? - mvvm

I'm trying to figure out how to display a category for a selected item in a ComboBox which is itself within a GridView.ItemsTemplate. Nothing that I have tried so far has worked. I've tried using SelectedValue and SelectedValuePath.
I can display the Item's Category in a textbox from within the GridView.ItemsTemplate, but not the collection in a ComboBox.
// FiledNotesPageViewModel ViewModel
public class FiledNotesPageViewModel : ViewModelBase
{
public ObservableCollection<MemoryItem> NoteItems { get; set; } = new ObservableCollection<MemoryItem>();
}
// Public Properties
public int FiledNotesId
{
get
{
return _filedNotesId;
}
set
{
_filedNotesId = value;
OnPropertyChanged("FiledNotesId");
}
}
public string Name
{
get
{
return _name;
}
set
{
_name = value;
OnPropertyChanged("Name");
}
}
public string Note
{
get
{
return _note;
}
set
{
_note = value;
OnPropertyChanged("Note");
}
}
public string Category
{
get
{
return _category;
}
set
{
_category = value;
OnPropertyChanged("Category");
}
}
// MemoryItem Model
public int FiledNotesId { get; set; }
public string Note { get; set; }
public string Name { get; set; }
public string Category { get; set; }
FiledNotesPage View
<GridView x:Name="FiledNotesGrid"
RelativePanel.Below="FilterNoteGrid"
ItemsSource="{x:Bind ViewModel.NoteItems}">
<GridView.ItemsPanel>
<ItemsPanelTemplate>
<ItemsWrapGrid Orientation="Horizontal"
Margin="5"/>
</ItemsPanelTemplate>
</GridView.ItemsPanel>
<GridView.ItemTemplate>
<DataTemplate x:DataType="data:MemoryItem">
<StackPanel Orientation="Vertical"
BorderBrush="{ThemeResource AppBarBorderThemeBrush}"
BorderThickness="2,2,2,4"
Margin="20"
Background="#FFF7F713" >
<TextBox x:Name="NoteNameTextBox"
Text="{x:Bind Name, Mode=TwoWay}"/>
<TextBox x:Name="NoteTextBox"
TextWrapping="Wrap"
AcceptsReturn="True"
Text="{x:Bind Note, Mode=TwoWay}"/>
<StackPanel Orientation="Horizontal">
<TextBlock Margin="5,0">Category:</TextBlock>
<ComboBox x:Name="NoteCategoryComboBox"
SelectedItem="{Binding FiledNotesId, Mode=TwoWay}"
MinWidth="125">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Category}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
</StackPanel>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>

Your combobox doesn't have an ItemsSource
You need to do something like this...
In your CS File create an ObservableCollection property to bind to.
public ObservableCollection<int> FieldNoteIds {get; set;}
In your view, bind to your collection
<ComboBox ItemsSource="{Binding FieldNoteIds}">
Right now you're providing a template for how to display each item that is populated into the combobox but you're not giving it anything to populate.
MSDN Tutorial on ComboBoxes and Binding
https://code.msdn.microsoft.com/windowsdesktop/Best-ComboBox-Tutorial-5cc27f82#content

Related

Xamarin forms List in List (navigating using prism)

The issue is that i cant seem to pass through the NavigationService to ModulesModel to use to Navigate to AnotherView items that i can only pass it to SectionModel. but i can only pass the NavigationService to the Parent ListView(Which i dont want) it needs to pass through the child ListView. HELP!!!
UI - CODE
<ListView
SeparatorColor="Transparent"
VerticalOptions="FillAndExpand"
HorizontalOptions="FillAndExpand"
ItemsSource="{Binding Data}"
HasUnevenRows="True">
<ListView.Header>
<local:ModuleCourseHeaderViewControl />
</ListView.Header>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Frame
HasShadow="False"
BackgroundColor="{StaticResource BiancaBackgroundColour}">
<Frame.CornerRadius>
<OnIdiom
x:TypeArguments="x:Single"
Phone="0"
Tablet="15" />
</Frame.CornerRadius>
<Frame.Margin>
<OnIdiom
x:TypeArguments="Thickness"
Phone="0,10"
Tablet="20,10" />
</Frame.Margin>
<StackLayout>
<Label
FontFamily="{StaticResource LatoRegularFont}"
TextColor="{StaticResource MediumGold}"
Text="{Binding Title}">
<Label.FontSize>
<OnIdiom
x:TypeArguments="x:Double"
Phone="15"
Tablet="17" />
</Label.FontSize>
</Label>
<flv:FlowListView
BackgroundColor="Transparent"
SeparatorVisibility="None"
HasUnevenRows="False"
FlowColumnCount="{DynamicResource ModuleFlowColumnCount}"
RowHeight="{DynamicResource RiseCardHeight}"
FlowItemsSource="{Binding Modules}">
<flv:FlowListView.FlowColumnTemplate>
<OnIdiom
x:TypeArguments="DataTemplate">
<OnIdiom.Phone>
<DataTemplate>
<local:ModuleCourseCardPhoneControl>
<local:ModuleCourseCardPhoneControl.GestureRecognizers>
<TapGestureRecognizer
CommandParameter="{Binding}"
Command="{Binding ItemSelectedCommand}" />
</local:ModuleCourseCardPhoneControl.GestureRecognizers>
</local:ModuleCourseCardPhoneControl>
</DataTemplate>
</OnIdiom.Phone>
<OnIdiom.Tablet>
<DataTemplate>
<local:ModuleCourseCardControl>
<local:ModuleCourseCardControl.GestureRecognizers>
<TapGestureRecognizer
CommandParameter="{Binding}"
Command="{Binding ItemSelectedCommand}" />
</local:ModuleCourseCardControl.GestureRecognizers>
</local:ModuleCourseCardControl>
</DataTemplate>
</OnIdiom.Tablet>
</OnIdiom>
</flv:FlowListView.FlowColumnTemplate>
</flv:FlowListView>
</StackLayout>
</Frame>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Models - CODE
public class NewModuleModel
{
public IList<SectionModel> Sections { get; set; }
public int ActiviyId { get; set; }
public string CourseTitle { get; set; }
}
public class SectionModel
{
public string Title { get; set; }
public IList<ModulesModel> Modules { get; set; }
public INavigationService NavigationService { get; set; }
}
public class ModulesModel
{
public int ModuleId { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string ModuleImageUrl { get; set; }
public double Progress { get; set; }
public double RoundedProgress
{
get
{
var p = Math.Round(Progress * 100, 0);
return p;
}
}
public DateTime? LastActivityDTUtc { get; set; }
public DateTime? CompletedDate { get; set; }
public string EstimatedDuration { get; set; }
public int AssetCount { get; set; }
public int? NavigationAssetID { get; set; }
public int ActiviyId { get; set; }
public string CourseTitle { get; set; }
public int? NavigationAssetContentType { get; set; }
public string Status
{
get
{
var result = string.Empty;
if (CompletedDate != null)
{
var c = String.Format("{0:d MMMM yyyy}", CompletedDate);
result = "Completed on " + c;
}
else if (LastActivityDTUtc != null)
{
var l = String.Format("{0:d MMMM yyyy}", LastActivityDTUtc);
result = "Last Activity on " + l;
}
else
{
result = "New";
}
return result;
}
}
public ModulesModel()
{
ItemSelectedCommand = new DelegateCommand<ModulesModel>(this.OnCardItemSelectedCommand);
}
private void OnCardItemSelectedCommand(ModulesModel module)
{
if (module.NavigationAssetContentType == 28 && module.AssetCount == 1)
{
var navigationParams = new NavigationParameters();
navigationParams.Add("AssetContentId", module.NavigationAssetID);
navigationParams.Add("AssetActivityId", module.ActiviyId);
navigationParams.Add("AssetModuleId", module.ModuleId);
navigationParams.Add("ParamCourseTitle", module.CourseTitle);
navigationParams.Add("ParamModuleTitle", module.Title);
Preferences.Set("NavFromModulePage", true);
NavigationService.NavigateAsync("AssetWebViewPage", navigationParams, true, false).Forget();
}
else
{
var navigationParams = new NavigationParameters();
navigationParams.Add("ParamModuleId", module.ModuleId);
navigationParams.Add("ParamActivityId", module.ActiviyId);
navigationParams.Add("ParamCourseTitle", module.CourseTitle);
Preferences.Set("NavFromModulePage", false);
//NavigationService.NavigateAsync("ModuleAssetPage", navigationParams, true, false);
}
}
public DelegateCommand<ModulesModel> ItemSelectedCommand { get; set; }
}
}
ViewModel - CODE
private async void LoadData()
{
this.ExecuteAsyncTask(async () =>
{
var result = await this.ModuleService.GetNewModuleAsync(ActivityID);
if (result != null)
{
CourseTitle = result.CourseTitle;
CourseDescription = result.CourseDescription;
CourseImageUrl = result.CourseImageUrl;
DurationInSeconds = result.DurationInSeconds;
DurationAsReadableString = result.DurationAsReadableString;
DisplayDescription = result.DisplayDescription;
Progress = Math.Round(result.Progress * 100, 0);
StartDateUTC = result.StartDateUTC;
EnrolmentDate = result.EnrolmentDate;
LastActivityDate = result.LastActivityDate;
foreach (var item in result.Sections)
{
var itemToAdd = new SectionModel
{
Title = item.Title,
Modules = item.Modules,
NavigationService = navigationService
};
Device.BeginInvokeOnMainThread(() =>
{
this.Data.Add(itemToAdd);
});
}
}
else
{
var exception = new Exception($"Api Error");
}
});
}
}
Remember that MVVM is Model-View-ViewModel. Very often we see people confusing Models and ViewModel's. I'll more indirectly answer your question by showing you the proper architecture. To start we'll look at a ToDoItem model.
public class ToDoItem
{
public string Name { get; set; }
public DateTime Created { get; set; }
public DateTime Due { get; set; }
}
With this note that we can make this easier to Bind by having our model inherit from something like Prism's BindableBase or ReactiveUI's ReactiveObject. I will make the assumption here that you already understand how to make those properties implement INotifyPropertyChanged.
The next thing we need is our ViewModel, we'll say that this is the ToDoItemsPageViewModel
public class ToDoItemsPageViewModel : BindableBase
{
public ObservableCollection<ToDoItem> Items { get; set; }
public DelegateCommand<ToDoItem> ItemSelectedCommand { get; }
private async void ItemSelectedCommandExecuted(ToDoItem item)
{
// do your navigation here..
}
}
Finally we have our ListView or CollectionView (which is what you should use as ListView is being deprecated). For simplicity let's look at the following.
<ListView ItemsSource="{Binding Items}">
<ListView.ItemTemplate>
<DataTemplate>
<StackLayout>
<Label Text="{Binding Name}" />
<Button Text="Show"
Command="{Binding ItemSelectedCommand}"
CommandParameter="{Binding .}" />
</StackLayout>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The core idea to understand here is that the ListView shares the BindingContext of its parent Page. Within our DataTemplate our BindingContext is a ToDoItem and not the ToDoItemsPageViewModel. As a result our binding for the value of Name will return the name of the individual ToDoItem. While our Binding for the CommandParameter will pass in the entire ToDoItem, our Binding for the Command will not work because the ToDoItem has no such command.
Now let's look at how we can access the Command from our ViewModel.
<ListView ItemsSource="{Binding Items}" x:Name="list">
<ListView.ItemTemplate>
<DataTemplate>
<StackLayout>
<Label Text="{Binding Name}" />
<Button Text="Show"
Command="{Binding BindingContext.ItemSelectedCommand,Source={x:Reference list}}"
CommandParameter="{Binding .}" />
</StackLayout>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
What we've done here are three small changes.
We've added an x:Name to the ListView. This could also be put on the parent Page.
We've changed the Command Binding from ItemSelectedCommand to BindingContext.ItemSelectedCommand
We've changed the Command Binding to include a Source which references the parent item (the list in this case)
What this accomplishes is to update that one specific binding to look at the parent. This effectively sets the BindingContext (for this single property) to the actual parent. In this case you can think of it as the BindingContext being set to the instance of the ListView. This means that our Binding must reference the BindingContext, and then we can dot into access a property of our BindingContext on the ListView.

How to create a footer template in a Grouped ListView in Xamarin Form

I'm working with Xamarin Form and C#. So, I have a listview in a Xamarin project which I enable the property IsGroupingEnabled. The items and header of items work perfectly but I cannot set a footer template.
I need to create a listview, items must be composed of:
Header: User information.
Items: List of subitems.
Footer: Actions buttons (shared, comments, etc).
This is part of my code:
Model:
public class Post
{
public long PostID { get; set; }
public string Name { get; set; }
public List<OptionDefault> OptionsDefault { get; set; }
}
public class OptionDefault
{
public long OptionTypeID { get; set; }
public string SubItemName { get; set; }
}
ViewModel
public class PostsViewModel
{
public InfiniteScrollCollection<Grouping<Post, OptionDefault>> Items { get; set; } = new InfiniteScrollCollection<Grouping<Post, OptionDefault>>();
public IPostsService repository;
public PostsViewModel(INavigation navigation)
{
repository = new PostsService();
Items = new InfiniteScrollCollection<Grouping<Post, OptionDefault>>
{
OnLoadMore = async () => await GetItems()
};
Items.LoadMoreAsync();
}
async Task<List<Grouping<Post, OptionDefault>>> GetItems()
{
IsWorking = true;
List<Grouping<Post, OptionDefault>> items = new List<Grouping<Post, OptionDefault>>();
try
{
// load the next page
var lists = await repository.GetList(Items.Count);
foreach (var item in lists)
{
for (int i = 0; i < item.OptionsDefault.Count; i++)
{
if ((i + 1) == item.OptionsDefault.Count)
item.OptionsDefault[i].LastItem = true;
}
var group = new Grouping<Post, OptionDefault>(item, item.OptionsDefault);
items.Add(group);
}
}
catch (Exception ex)
{
ErrorHelper.RegisterError(ex);
}
finally
{
IsWorking = false;
}
return items;
}
.xaml:
<ListView ItemsSource="{Binding Items}"
IsGroupingEnabled="True">
<ListView.GroupHeaderTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Margin="5,15,5,0">
<Label Text="{Binding Key.User.UserName}" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.GroupHeaderTemplate>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Margin="5,0,5,5">
<Label Text="{Binding SubItemName}" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I've tried using footertemplate, but it doesn't work:
<ListView.FooterTemplate>
<DataTemplate>
<StackLayout Margin="5,0,5,5">
<Label Text="This is my footer" />
</StackLayout>
</DataTemplate>
</ListView.FooterTemplate>
Whether it's <ListView.Footer> or <ListView.FooterTemplate>, the display position is the same, right at the bottom of the ListView.
Currently, ListView of xamarin form does not have this property to make each items have a footer.
Maybe you need to rethink the requirements and UI design of your app.
CollectionView is now available in Xamarin.Forms. It have both GroupHeaderTemplate and GroupFooterTemplate. You can use it to show the Group of data. The code to do so is identical with ListView
It is not <ListView.FooterTemplate> but <ListView.Footer>
<ListView.Footer>
<StackLayout Margin="5,0,5,5">
<Label Text="This is my footer" />
</StackLayout>
</ListView.Footer>
For more info, see:
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/listview/customizing-list-appearance#headers-and-footers

Changing Property of ObservableCollection does nothing

In my Xamarin project I have a ListView, which gets populated by an ObservableCollection, which holds "Item" objects with some properties. If I add items to the collection the UI gets updated, but if I change only a property, it won't it does nothing. Even after an UI update through adding an item does nothing, although the property gets correctly changed. How can I make the UI refresh if a property gets changed?
BindableBase is a class from PRISM that implements INotifyPropertyChanged and DelegateCommand implements ICommand, btw.
Here's my XAML:
<ListView ItemsSource="{Binding ListItems}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell x:Name="viewCell">
<ContentView Padding="0,0,0,5"
HeightRequest="50">
<ContentView.GestureRecognizers>
<TapGestureRecognizer BindingContext="{Binding Source={x:Reference listView}, Path=BindingContext}"
Command="{Binding ItemTappedCommand}"
CommandParameter="{Binding Source={x:Reference viewCell}, Path=BindingContext}" />
</ContentView.GestureRecognizers>
<Frame OutlineColor="{Binding Color}" Padding="8">
<StackLayout Orientation="Horizontal" >
<Image x:Name="checkedImage"
HeightRequest="30"
WidthRequest="30"
BackgroundColor="{Binding Color}"
/>
<Label Text="{Binding Text}"
TextColor="{Binding Color}"
Margin="20,0,0,0"
VerticalTextAlignment="Center"
HorizontalOptions="FillAndExpand"/>
<Image Source="{Binding Image}" />
</StackLayout>
</Frame>
</ContentView>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Here's my ViewModel:
public class DetailPageViewModel : BindableBase
{
public DetailPageViewModel()
{
_listItems.Add(new ViewModels.Item("#123456", "Test1", "XamarinForms.Assets.Yellow.png"));
_listItems.Add(new ViewModels.Item("#654321", "Test3", "XamarinForms.Assets.close.png"));
}
private ObservableCollection<Item> _listItems = new ObservableCollection<Item>();
public ObservableCollection<Item> ListItems
{
get { return _listItems; }
set { SetProperty(ref _listItems, value); }
}
public DelegateCommand<Item> ItemTappedCommand => new DelegateCommand<Item>(ItemTapped);
private void ItemTapped(Item listItem)
{
// Adding an item refreshes the UI.
_listItems.Add(new ViewModels.Item("#654321", "Test3", "XamarinForms.Assets.close.png"));
// But changing the color of an item does nothing. Not even after an UI refresh.
_listItems.ElementAt(_listItems.IndexOf(listItem)).Color="#987654";
}
}
And my Item class:
public class Item
{
public string Color { set; get; }
public ImageSource Check { set; get; }
public string Text { private set; get; }
public ImageSource Image { private set; get; }
public Item(string color, string text, string imageSource)
{
Check = ImageSource.FromResource("XamarinForms.Assets.checkmark-outlined-verification-sign.png");
Color = color;
Text = text;
Image = ImageSource.FromResource(imageSource);
}
}
This is because also your item class needs to implement INotifyPropertyChanged. In your case as you are using Prism you just need to make your item class extend BindableBase (Prism base class which already implements INotifyPropertyChanged for you).
Link: https://github.com/PrismLibrary/Prism/blob/a60d38013c02b60807e9287db9ba7f7506af0e84/Source/Prism/Mvvm/BindableBase.cs
That should make it work.
Also I see in you are doing this:
public ObservableCollection<Item> ListItems
{
get { return _listItems; }
set { SetProperty(ref _listItems, value); }
}
With ObservableCollections you don't need to raise the event manually as they already do it internally. They can be defined as regular properties.
public ObservableCollection<Item> ListItems {get; set;}

ViewCell not binding

I have the following listView with binding on my xaml page. I am binding data to it from my viewModel. But the values are not binding to the cell.
<ListView x:Name="historyList" ItemsSource="{Binding History}" HasUnevenRows="true">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.View>
<StackLayout Orientation="Horizontal" Padding="10, 5, 10, 5" Spacing="0" HorizontalOptions="FillAndExpand" >
<Label Text="{Binding History.DayNumber}" HorizontalOptions="Center" FontSize="15" TextColor="Red" VerticalOptions="Center"></Label>
</StackLayout>
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
My History object has the property DayNumber, but the value is not binding to the label?
Any suggestion? or am i missing something?
Thank you.
Edit:
Here is how I get my History List:
void GetAllHistory()
{
foreach(CheckInTimeHistory h in App.Database.GetHistoryCheckins(null))
{
CheckInHistoryItem hi = new CheckInHistoryItem();
hi.CheckedIn = (h.AutoCheckIn ? false : true);
hi.CheckInTime = h.CheckInTime.AddSeconds(-h.CheckInTime.Second).ToString("HH:mm-ss tt");
hi.DayNumber = h.CheckInTime.Day.ToString();
hi.DayText = h.CheckInTime.DayOfWeek.ToString().Substring(0, 2);
History.Add(hi);
}
}
public class CheckInHistoryItem
{
public String DayText, DayNumber, CheckInTime;
public Boolean CheckedIn;
}
You will see warnings like this in your debug output:
Binding: 'DayNumber' property not found on '....CheckInHistoryItem', target property: 'Xamarin.Forms.Label.Text'
The properties have to be C# properties. Just change your class to
public class CheckInHistoryItem
{
public String DayText { get; set; }
public String DayNumber { get; set; }
public String CheckInTime { get; set; }
public Boolean CheckedIn { get; set; }
}
and use DayNumber instead of History.DayNumber
<Label Text="{Binding DayNumber}" HorizontalOptions="Center" FontSize="15" TextColor="Red" VerticalOptions="Center"></Label>
Assuming History is a static var:
ItemsSource="{x:Static local:History}">
And change:
{Binding History.DayNumber}
to:
{Binding DayNumber}
Binding Basics:
https://developer.xamarin.com/guides/xamarin-forms/user-interface/xaml-basics/data_binding_basics/#Bindings_and_Collections

mvvm datagrid cannot update model from view

I'm new to WPF, I am using MVVM via MVVMLight, I have a datagrid bound to a ObservableCollection of view models, When I manually update modelView, the view is changed as expected,
the problem is that when I change view, the viewModel's value is not changed,
this is model:
public class Student
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
this is viewModel:
public class StudentViewModel : ViewModelBase
{
private Student _student;
public StudentViewModel(Student student)
{
_student = student;
}
public string FirstName
{
get { return _student.FirstName; }
set
{
if(_student.FirstName==value)return;
_student.FirstName = value;
RaisePropertyChanged("FirstName");
}
}
public string LastName
{
get { return _student.LastName; }
set
{
if(_student.LastName==value)return;
_student.LastName = value;
RaisePropertyChanged("LastName");
}
}
}
this is Test.xaml:
<DataGrid Name="DgrdColumns" Grid.Row="1" Grid.Column="0" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTemplateColumn Header="FirstName">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding FirstName, Mode=TwoWay}"></TextBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="LastName" Width="150">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding LastName, Mode=TwoWay}"></TextBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
this is bind data code:
void BindData()
{
var students = new List<Student>()
{
new Student()
{
FirstName = "aa",
LastName = "AA"
},
new Student()
{
FirstName = "bb",
LastName = "BB"
}
};
var viewModels=new ObservableCollection<StudentViewModel>(students.Select(x=>new StudentViewModel(x)));
DgrdColumns.ItemsSource = viewModels;
}
Please try to set UpdateSourceTrigger in your bindings:
<TextBox Text="{Binding FirstName, Mode=TwoWay, UpdateSourceTrigger=LostFocus}" />
Let me know if it still doesn't work.