I need to put my table data into ListView (MVVM) - c#

Database connection and binding context are okay. Data can insert into a table without a problem. I need to add last of my table data row to a list view
<ListView ItemsSource="{Binding YourList}"
verticalOptions="FillAndExpand" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal">
<Label Text="{Binding Catagoryview}"/>
<Label Text="{Binding Date}"/>
<Label Text="{Binding Expense}"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
this is my XAML file. I create every property for in binding here. with backing fields.
in my ViewModel
private void add()
{
database = new Database();
var expensedata = database.GetExpenses(id);
if (expensedata != null)
{
Catagoryview = expensedata.Catagory;
Date= expensedata.Date;
Expense = expensedata.Expense;
}
}
I know this code is not perfect. I really need to get the last 10 rows of my data into a list view.
also, here my get expenses method I used
public AddExpenses GetExpenses(int expense)
{
return Conn.Table<AddExpenses>().FirstOrDefault(t => t.Id == expense);
}

To me it seems as if your add method does not actually add anything. In your viewmodel you'll need to have a public property YourList, which is propagated with add. Furthermore you are getting only one item from your expenses database, which won't work for adding 10 items.
The core question as far as I understood is
How can I add the 10 last items from an SQLite database to a Xamarin.Forms ListView using an MVVM approach.
Disregarding the fact that I do not know how and when you are calling add, I'll try to give an answer on how to get and add these elements.
First of all we need a property that will hold your items in your viewmodel. Depending on your requirements, either an IEnumerable<AddExpenses> or an ObservableCollection<AddExpenses> will do. I will demonstrate the ObservableCollection approach
ObservableCollection<AddExpenses> _expenses;
public ObservableCollection<AddExpenses> Expenses
{
get
{
if(_expenses == null)
{
_expenses = new ObserrvableCollection<AddExpenses>();
}
return _expenses;
}
}
You can bind to that property from your XAML
<ListView ItemsSource="{Binding Expenses}"
VerticalOptions="FillAndExpand">
<!-- What ever -->
</ListView>
And then add items to that collection from your AddExpenses method (I felt free to give it a more meaningful name):
private void AddExpenses()
{
var database = new Database();
var expenses = database.GetLastExpenses(numberOfExpenses: 10);
foreach(var expense in expenses)
{
Expenses.Add(expense);
}
}
Since we are now having objects of type AddExpenses in your collection, we'll have to change the binding in the ListView from Categoryview to Category:
<ListView ItemsSource="{Binding YourList}"
VerticalOptions="FillAndExpand">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal">
<Label Text="{Binding Category}"/>
<Label Text="{Binding Date}"/>
<Label Text="{Binding Expense}"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Last but not least we'll have to implement GetLastExpenses (this should work, while it's possible that there are better options)
public AddExpenses[] GetLastExpenses(int numberOfExpenses)
{
return Conn.Table<AddExpenses>()
.OrderByDescending(expenses => expenses.Date)
.Take(numberOfExpenses)
.OrderBy(expenses => expenses.Date) // If you need them ordered ascending
.ToArray()
}

Related

New Xamarin.Forms CollectionView doesn't allow multi pre-selection

I have a CollectionView in my Xamarin.Forms project:
<CollectionView ItemsSource="{Binding Categories}" ItemSizingStrategy="MeasureFirstItem" x:Name="CategoryColView"
SelectionMode="Multiple" SelectionChangedCommand="{Binding SelectionChangedCommand}"
SelectionChangedCommandParameter="{Binding Source={x:Reference CategoryColView}, Path=SelectedItems}"
SelectedItems="{Binding SelectedCategoryItems}">
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout ...>
<BoxView .../>
<StackLayout ...>
<Label .../>
<Image .../>
</StackLayout>
<BoxView/>
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
I included the entire XAML element, but the only important part is the SelectedItems property. It is bound to the following viewmodel implementation:
class ViewModel {
private ObservableCollection<object> selectedCategories { get; set; }
public ObservableCollection<object> SelectedCategories {
get => selectedCategories;
set {
selectedCategories = value;
OnPropertyChanged();
}
//...
ctor() {
//...
var alreadySelectedCategoryItems = alreadySelectedCategories.Select(pc => new CategoryItem { PlantCategory = pc, IsSelected = true }).Cast<object>();
SelectedCategoryItems = new ObservableCollection<object>(alreadySelectedCategoryItems);
//...
}
}
The rest of the implementation should be irrelevant. My aim is to have pre-selected values.
First: I noticed that if the T in ObservableCollection<T> is not object, everything is ignored. Just like in Microsoft's example here. If the T is e.g. of type CategoryItem, literally nothing happens, as if the ObserveableCollection were completely ignored.
Second: alreadySelectedCategoryItem contains 2 elements in debugger mode, but then the last line in the constructor throws a:
System.ArgumentOutOfRangeException
Index was out of range. Must be non-negative and less than the size of the collection.
Parameter name: index
Of course, since this is Xamarin.Forms and VS for Mac, the error is thrown on the Main function, not at its actual location...
Am I doing something wrong, or is CollectionView just still buggy?
The issue was that I was creating new CategoryItem instances as the pre-selected ones, which is invalid, as they weren't by default the same instances that were in the CollectionView.ItemsSource property. I should have filtered the ItemsSource instances and put them as the pre-selected ones. Like this:
var alreadySelectedCategoryItems = alreadySelectedCategories.Select(pc => new CategoryItem { PlantCategory = pc, IsSelected = true }).Cast<object>();
SelectedCategoryItems = Categories
.Where(sci =>
alreadySelectedCategoryItems.Any(alreadySelected =>
alreadySelected.PlantCategory.Id == sci.PlantCategory.Id);
So the items are selected off the ItemsSource itself and not created as new.
Although the error message was not as desired, so Xamarin.Forms team is going to fix that.
SelectedItems is read-only,you could not use like SelectedItems="{Binding SelectedCategoryItems}" in xaml
you could try to change in your behind code like:
CategoryColView.SelectedItems.Add(your selectItem 1);
CategoryColView.SelectedItems.Add(your selectItem 2);
CategoryColView.SelectedItems.Add(your selectItem 3);

How to use the ".ScrollTo" list view method in C#/Xamarin

I am trying to figure out how to use the ScrollTo method that is available with list views. I believe I have the last two arguments correct, but am not quite sure if I have the first argument correct.
Here is the xaml with my list view for my view...
<ListView
x:Name="MyList"
Grid.Row="0"
Grid.RowSpan="4"
Grid.Column="0"
Grid.ColumnSpan="8"
ItemsSource="{Binding History}"
ItemTapped="OnItemTapped"
RowHeight="60">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Margin="8">
<Label
Text="{Binding MessageTitle}"
FontAttributes="Bold" />
<Label
Text="{Binding MessageContents}" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
And here is part of the code behind for that view where I am trying to use .ScrollTo
private void ScrollToBottomClicked(object sender, EventArgs e)
{
List.ScrollTo(MyList.ItemsSource, ScrollToPosition.End, true);
}
Here is the ItemSource, which is an observable collection in my view model
public ObservableCollection<HistoryMessage> History
{
get
{
return History;
}
set
{
History = value;
}
}
When the ScrollToBottom button is clicked I am wanting the list view display to jump the the most recent item added. Any help would be greatly appreciated!
For example:
In my code behind I have the following method, in my case is void because I only call when I load all the list
public void ScrollDown(HistoryMessage historyMessage)
{
List.ScrollTo(historyMessage, ScrollToPosition.End, false);
}
So, when all I get all the list I call this method
ScrollDown(History.LastOrDefault());
If you have a button and you want me to show you the closest item of the list, you should make a query to your API to see if there is any element added and later call this method and sent the lastOrDefault element.
If what you want is the first element you need change List.ScrollTo(historyMessage, ScrollToPosition.End, false) to List.ScrollTo(historyMessage, ScrollToPosition.Start, false) and sent de first element in your observable collection

Binding listview group header

So, I attempting to, very simply, display items in a Windows 10 listview, and then seperate them by group. Everything is working fine, except that I can't seem to bind the title of the group.
Here is my current xaml:
<ListView ItemsSource="{Binding Source={StaticResource cvsEpisodes}}"/>
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding EpisodeNB}"/>
<TextBlock Text="{Binding EpisodeTT}"/>
<TextBlock Text="{Binding EpisodeDESC}"/>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding SEASONNB}"/>
</Grid>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListView.GroupStyle>
</ListView>
<Page.Ressources>
<CollectionViewSource x:Name="cvsEpisodes" IsSourceGrouped="True"/>
</Page.Ressources>
And the C#, that is executed by the OnNavigatedTo event:
List < EPISODELST > Episodes = new List < EPISODELST > ();
var episodes = root.episodes.Select(m = >new EPISODELST {EpisodeTT = m.title, EpisodeNB = m.episode.ToString(), EpisodeDESC = m.overview, SEASONNB = m.season.ToString()}).ToList();
foreach(EPISODELST s in episodes)
{
Episodes.Add(new EPISODELST {EpisodeTT = s.EpisodeTT, EpisodeDESC = s.EpisodeDESC, EpisodeNB = "EPISODE " + s.EpisodeNB, SEASONNB = s.SEASONNB });
}
var result = from EPISODELST in Episodes group EPISODELST by EPISODELST.SEASONNB into grp orderby grp.Key select grp;
cvsEpisodes.Source = result;
(EPISODELST and episodes are two classes, but it isn't necessary to paste them here)
I have seen various other implementations of grouped listviews online, but they are all way more complex than this, and I'm guessing that this should work, because I can tell the code can succesfuly sort all the data correctly.
The problem probably just has to do with the TextBlock's binding, but I have tried various other things I found online, such as {Binding=Name}, or {Binding Key.Name}, but nothing seems to work.
So, In the end, this was really simple. I found the awnser burried deep down Microsoft's UWP Github sample page.
It has to be binded to {Binding Key}
Inside GroupStyle:
<TextBlock Text="{Binding Key}"/>

DataTemplate for ListBox element is not binding at all

I have a ListBox element,
which purpose is to show the users the activities,
that are registered on the Database
so that they can choose from them to modify or delete them.
After consulting two very useful answers about using DataContext and DataTemplates,
I decided to implement that knowledge in my project,
unfortunately, it's not working.
When I run it and I select the text on the ListBox,
I only see: DataTemplate templ = new DataTemplate(typeof(Activities));
as its content, and I didn't mark it up as code,
because I want to stress the fact that it appears as a string,
if you will.
I get that there could be more than one workaround for what I'm trying to achieve.
however I really want to understand this, as it appears to be very useful.
Here's the code:
//This is the connection instance to the database
Connection c = new Connection();
DataTemplate templ = new DataTemplate(
typeof(Activities)
);
//The ListActivities method returns
//an ObservableCollection<Activities> list
libTest.DataContext = c.ListActivities(
objSem.getId()
);
libTest.SetBinding(
ItemsControl.ItemsSourceProperty, new Binding()
);
FrameworkElementFactory sp = new FrameworkElementFactory(
typeof(StackPanel)
);
sp.SetValue(StackPanel.OrientationProperty, Orientation.Horizontal);
sp.Name = "myTemplate";
FrameworkElementFactory date = new FrameworkElementFactory(
typeof(Label)
);
date.SetBinding(Label.ContentProperty, new Binding("date"));
sp.AppendChild(date);
FrameworkElementFactory nameAct = new FrameworkElementFactory(
typeof(Label)
);
nameAct.SetBinding(Label.ContentProperty, new Binding("nameAct"));
sp.AppendChild(nameAct);
FrameworkElementFactory descr = new FrameworkElementFactory(
typeof(Label)
);
descr.SetBinding(Label.ContentProperty, new Binding("descr"));
sp.AppendChild(descr);
FrameworkElementFactory quantity = new FrameworkElementFactory(typeof(Label));
quantity.SetBinding(Label.ContentProperty, new Binding("quantity"));
sp.AppendChild(quantity);
templ.VisualTree = sp;
libTest.ItemTemplate = templ;
i dont like code definition for such thing so here is the xaml one
<DataTemplate DataType="{x:Type local:Activities}">
<StackPanel Orientation="Horizontal">
<Label Content="{Binding date}"/>
<Label Content="{Binding nameAct}"/>
<Label Content="{Binding descr}"/>
<Label Content="{Binding quantity}"/>
</StackPanel>
</DataTemplate>
just put this into your resources and all Activities will render like this
pls read something more about binding in WPF, maybe MVVM stuff too. so you would better understand what you need when you do binding with WPF.
a little example
create a class which will be your DataContext and put a public property for your List in it.
public class SampleViewModel
{
public ObservableCollection<Activities> MyActivities {get;set;}
}
xaml.cs: set the DataContext for your View to your Viewmodel class
public partial class SampleWindow : Window
{
private SampleViewModel _data;
public SampleWindow()
{
_data = new SampleViewModel();
InitializeComponent();
this.DataContext = _data;
}
}
xaml: define your Bindings for your controls
<ListBox ItemsSource="{Binding MyActivities}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label Content="{Binding date}"/>
<Label Content="{Binding nameAct}"/>
<Label Content="{Binding descr}"/>
<Label Content="{Binding quantity}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
or
<ListBox ItemsSource="{Binding MyActivities}">
<ListBox.Resources>
<DataTemplate DataType="{x:Type local:Activities}">
<StackPanel Orientation="Horizontal">
<Label Content="{Binding date}"/>
<Label Content="{Binding nameAct}"/>
<Label Content="{Binding descr}"/>
<Label Content="{Binding quantity}"/>
</StackPanel>
</DataTemplate>
</ListBox.Resources>
</ListBox>

binding data - inotifypropertychanged does not work

I have a listBox1 in which data are binding from the list. Then I want to when I select any item from listBox1 in listBox2 will binding data from another list.
private void listBox1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
Teams teams = (Teams)listBox1.SelectedItems[0];
getH2hResults("//td[#class='hell']", teams.Team1, teams.Team2); // add elements to list
getH2hResults("//td[#class='dunkel']", teams.Team1, teams.Team2); // and here also
listBox2.ItemsSource = lists.h2hList;
}
On the first time this work, but for the twice time listBox2 doesn't displays new data.
public class Lists : BindableBase
{
public Lists()
{
_teamsList = new List<Teams>();
_h2hList = new List<H2H>();
}
private List<Teams> _teamsList;
public List<Teams> teamsList
{
get
{
return _teamsList;
}
set
{
if (value != _teamsList)
{
_teamsList = value;
RaisePropertyChanged("teamsList");
}
}
}
private List<H2H> _h2hList;
public List<H2H> h2hList
{
get
{
return _h2hList;
}
set
{
if (value != _h2hList)
{
_h2hList = value;
RaisePropertyChanged("h2hList");
}
}
}
}
And XAML
<ListBox Name="listBox1" Width="300" Height="300"
VerticalAlignment="Top"
HorizontalAlignment="Left"
ItemsSource="{Binding teamsList}" SelectionChanged="listBox1_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Foreground="#FF4273CD" Text="{Binding Team1, Mode=TwoWay}"></TextBlock>
<TextBlock Text=" vs " FontWeight="Bold"></TextBlock>
<TextBlock Foreground="#FF4273CD" Text="{Binding Team2, Mode=TwoWay}"></TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ListBox Name="listBox2" Grid.Column="1" Width="300" Height="300"
VerticalAlignment="Top"
HorizontalAlignment="Left"
ItemsSource="{Binding h2hList}" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding date, Mode=TwoWay}"></TextBlock>
<TextBlock Text="{Binding result, Mode=TwoWay}"></TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
RaisePropertyChanged("teamList");
is Wrong your propery is named 'teamsList' with an S,
change to:
RaisePropertyChanged("teamsList");
It is the public property you bind to and notify changes of,
edit:
also change your binding:
ItemsSource="{Binding teamList}"
to
ItemsSource="{Binding teamsList}"
Edit 2:
listBox2.DataContext = xxx
Not itemsource = xxx
With the line (in listBox1_SelectionChanged)
listBox2.ItemsSource = lists.h2hList;
you are effectively removing the binding from the ItemsSource property of listBox2.
Instead, you should only update the h2hList property in your Lists class (which presumably happens in getH2hResults) and remove the above line from your code.
Note however that it is not sufficient to clear and re-fill that list. You need to set the h2hList property in order to get a property change notification raised:
var newList = new List<H2H>();
// fill newList before assigning to h2hList property
lists.h2hList = newList;
If you want to keep the list and just change its elements, you would need to use ObservableCollection<H2H> instead of List<H2H> as collection type. This would be the better approach anyway, as you would not have to care for when exactly you add elements to a newly created collection.

Categories

Resources