Xamarin.Forms ListView Memory Leak - c#

I am developing an enterprise application using xamarin.forms. It has been few days ListView's Memory leak issue become a nightmare for me. For the sake of simplicity I'll try to explain with sample code.
XAML Page Code - Page with ListView and two Button(Add & Remove)
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:ListViewTest"
x:Class="ListViewTest.MainPage">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="10*" />
<RowDefinition Height="1*"/>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<ListView Grid.Row="0" BackgroundColor="White" x:Name ="ItemsListView">
<ListView.ItemTemplate>
<DataTemplate >
<TextCell TextColor="Black" Text="{Binding ItemText}"
DetailColor="Black" Detail="{Binding ItemDetail}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button Grid.Row="1" Text="Add" Clicked="AddItemClicked"/>
<Button Grid.Row="2" Text="Remove" Clicked="RemoveItemClicked"/>
</Grid>
</ContentPage>
C# Code Behind - Just adding and removing objects from collection.
public partial class MainPage : ContentPage
{
ObservableCollection<SampleData> itemsListCollection;
public MainPage()
{
InitializeComponent();
itemsListCollection = new ObservableCollection<SampleData>();
ItemsListView.ItemsSource = itemsListCollection;
}
void AddItemClicked(object sender, EventArgs e)
{
SampleData data = new SampleData();
data.ItemText = "An Item";
data.ItemDetail = "Item - " + (itemsListCollection.Count + 1).ToString();
itemsListCollection.Add(data);
}
void RemoveItemClicked(object sender, EventArgs e)
{
SampleData item = (SampleData)ItemsListView.SelectedItem;
if (item != null)
{
itemsListCollection.Remove(item);
}
}
}
Data class - Just two properties
class SampleData
{
public event PropertyChangedEventHandler PropertyChanged;
private string itemText;
public string ItemText
{
get
{
return itemText;
}
set
{
itemText = value;
NotifyPropertyChanged("ItemText");
}
}
private string itemDetail;
public string ItemDetail
{
get
{
return itemDetail;
}
set
{
itemDetail = value;
NotifyPropertyChanged("ItemDetail");
}
}
private void NotifyPropertyChanged(string propName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propName));
}
}
}
Add Button - Adds item to ListView
Remove Button - Removes item from ListView
Problem -
Add some items to list.
Remove few or all.
All previously added SampleData objects remain in memory even after all the items have
been removed using Remove button.
Image - Memory Snapshot of original application
Image - Detailed Memory Snapshot of sample application

Related

ListView not refreshing after adding value in Xamarin Forms

What i am doing is passing data through more than 2 pages. I assign viewmodel to next page while i am navigating. In second page i have a listview that is not refreshing/updating after adding a value.
Help me please!!
Here is my code
MyViewModel
public class MyViewModel : INotifyPropertyChanged
{
public string _userName { get; set; }
public List<family> familyList;
public List<family> FamilyList
{
get { return familyList; }
set
{
familyList = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
public MyViewModel()
{
_userName = "Mak";
familyList = new List<family>();
}
public void AddMember(string memberName)
{
FamilyList.Add(new family
{
name = memberName,
id = Guid.NewGuid().ToString(),
username=_userName
});
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
userdetails.xaml
<cl:BasePage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="familyinfo.userdetails" xmlns:cl="clr-namespace:familyinfo;assembly=familyinfo">
<Label Font="Roboto-Medium" FontSize="14" Text="{Bindinbg _userName}" />
<Button Clicked="Next_Step" HeightRequest="30" HorizontalOptions="FillAndExpand" BorderRadius="12" Text="NEXT" />
</cl:BasePage>
userdetails.xaml.cs
public partial class userdetails : BasePage
{
public MyViewModel _myViewModel { get; set; }
public userdetails()
{
InitializeComponent();
BindingContext = new MyViewModel();
}
void Next_Step(object sender, System.EventArgs e)
{
_myViewModel =(MyViewModel) this.BindingContext;
var familyMember = new FamilyMember();
familyMember.BindingContext = _myViewModel;
Application.Current.MainPage = new NavPage(registerCar);
}
}
FamilyMember.xaml
<cl:BasePage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="familyinfo.FamilyMember" xmlns:cl="clr-namespace:familyinfo;assembly=familyinfo">
<Label Font="Roboto-Medium" FontSize="14" Text="{Bindinbg _userName}" />
<cl:CustomEntry x:Name="txtMemberName" Placeholder="Member Name" FontSize="12" />
<Button Clicked="AddMember" HeightRequest="30" HorizontalOptions="FillAndExpand" BorderRadius="12" Text="Add" />
<ListView ItemsSource="{Binding FamilyList}" VerticalOptions="FillAndExpand" BackgroundColor="Transparent">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.View>
<Grid Padding="20,10,0,0" ColumnSpacing="12" RowSpacing="0" BackgroundColor="Transparent">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto">
</ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto">
</RowDefinition>
</Grid.RowDefinitions>
<Label Grid.Row="0" Text="{Binding name}" Grid.Column="0" Font="Roboto-Medium" FontSize="14" TextColor="#000000" />
</Grid>
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</cl:BasePage>
FamilyMember.xaml.cs
public partial class FamilyMember : BasePage
{
public MyViewModel _myViewModel { get; set; }
public userdetails()
{
InitializeComponent();
}
void AddMember(object sender, System.EventArgs e)
{
_myViewModel = (MyViewModel)this.BindingContext;
_myViewModel.AddMember(txtMemberName.Text);
}
}
I agree with Atul: Using an ObservableCollection is the right way to do it.
A workaround - if you don't have a chance to change that - is to set the ListView's ItemSource to null and back to the list, whenever data changed and the UI needs to update:
void UpdateListView(ListView listView)
{
var itemsSource = listView.ItemsSource;
listView.ItemsSource = null;
listView.ItemsSource = itemsSource;
}
In fact, you must use a collection that implements INotifyCollectionChanged interface (instead of the well known INofifyPropertyChanged). And that's exactly what does ObservableCollection<T> for you. This is why it works like "magic".
I just used ObservableCollection instead of List and it works!!

How to set the item for a CarouselView from code behind?

I have a CarouselView which is bound to an ItemsSource of images.
But I want to change the current Image shown by changing the index of the CarouselView.
I have tried using CarouselView.Position to the index of the element that has to be selected. But unfortunately this does not work.
How can I achieve this?
Thanks
I have tried using CarouselView.Position to the index of the element that has to be selected. But unfortunately this does not work.
Since you're using data binding for ItemsSource of CarouselView, you can implement the INotifyPropertyChanged interface for your image model.
For example:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:cv="clr-namespace:Xamarin.Forms;assembly=Xamarin.Forms.CarouselView"
x:Class="FormsIssue2.Page1">
<Grid>
<cv:CarouselView ItemsSource="{Binding Zoos, Mode=OneWay}" x:Name="CarouselZoos">
<cv:CarouselView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Image Grid.RowSpan="2" Aspect="AspectFill" Source="{Binding ImageUrl, Mode=OneWay}" />
<StackLayout Grid.Row="1" BackgroundColor="#80000000" Padding="12">
<Label TextColor="White" Text="{Binding Name, Mode=OneWay}" FontSize="16" HorizontalOptions="Center" VerticalOptions="CenterAndExpand" />
</StackLayout>
</Grid>
</DataTemplate>
</cv:CarouselView.ItemTemplate>
</cv:CarouselView>
</Grid>
</ContentPage>
Code behind:
public partial class Page1 : ContentPage
{
public Page1()
{
InitializeComponent();
Zoos = new ObservableCollection<Zoo>
{
new Zoo
{
ImageUrl = "http://wallpaper-gallery.net/images/image/image-13.jpg",
Name = "Woodland Park Zoo"
},
new Zoo
{
ImageUrl = "https://s3-us-west-1.amazonaws.com/powr/defaults/image-slider2.jpg",
Name = "Cleveland Zoo"
},
new Zoo
{
ImageUrl = "http://i.stack.imgur.com/WCveg.jpg",
Name = "Phoenix Zoo"
}
};
//CarouselZoos.ItemsSource = Zoos;
this.BindingContext = this;
CarouselZoos.ItemSelected += CarouselZoos_ItemSelected;
}
private void CarouselZoos_ItemSelected(object sender, SelectedItemChangedEventArgs e)
{
var item = e.SelectedItem as Zoo;
if (item == null)
return;
item.ImageUrl = "https://3.bp.blogspot.com/-W__wiaHUjwI/Vt3Grd8df0I/AAAAAAAAA78/7xqUNj8ujtY/s1600/image02.png";
}
public ObservableCollection<Zoo> Zoos { get; set; }
}
public class Zoo : INotifyPropertyChanged
{
private string _ImageUrl;
public string ImageUrl
{
get { return _ImageUrl; }
set
{
if (value != _ImageUrl)
{
_ImageUrl = value;
OnPropertyChanged("ImageUrl");
}
}
}
private string _Name;
public string Name
{
get { return _Name; }
set
{
if (value != _Name)
{
_Name = value;
OnPropertyChanged("Name");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Once an item is selected, you can find this item's instance in SelectedItemChangedEventArgs, then you can change the image source of this item.
Update:
Based on our discussion, I think the item sources for your CarouselView for thumbnails and the CarouselView for your larger images are in the same sequential order, then when you select the item in your thumbnails, you can get the position of thumbnails and scroll the CarouselView for larger images like this:
var postion = CarouselThunbnails.Position;
CarouselImages.Position = postion;

How to get ComboBox content value?

I would like to get content from my combobox. I already tried some ways to do that, but It doesn't work correctly.
This is example of my combobox:
<ComboBox x:Name="cmbSomething" Grid.Column="1" Grid.Row="5" HorizontalAlignment="Center" Margin="0 100 0 0" PlaceholderText="NothingToShow">
<ComboBoxItem>First item</ComboBoxItem>
<ComboBoxItem>Second item</ComboBoxItem>
</ComboBox>
After I click the button, I want to display combobox selected item value.
string selectedcmb= cmbSomething.Items[cmbSomething.SelectedIndex].ToString();
await new Windows.UI.Popups.MessageDialog(selectedcmb, "Result").ShowAsync();
Why this code does not work?
My result instead of showing combobox content, it shows this text:
Windows.UI.Xaml.Controls.ComboBoxItem
You need the Content property of ComboBoxItem. So this should be what you want:
var comboBoxItem = cmbSomething.Items[cmbSomething.SelectedIndex] as ComboBoxItem;
if (comboBoxItem != null)
{
string selectedcmb = comboBoxItem.Content.ToString();
}
I have expanded on my suggestion regarding using models instead of direct UI code-behind access. These are the required parts:
BaseViewModel.cs
I use this in a lot of the view models in my work project. You could technically implement it directly in a view model, but I like it being centralized for re-use.
public abstract class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private Hashtable values = new Hashtable();
protected void SetValue(string name, object value)
{
this.values[name] = value;
OnPropertyChanged(name);
}
protected object GetValue(string name)
{
return this.values[name];
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
ComboViewModel.cs
This what you'll bind to make it easy to get values. I called it ComboViewModel because I'm only dealing with your ComboBox. You'll want a much bigger view model with a better name to handle all of your data binding.
public class ComboViewModel : BaseViewModel
{
public ComboViewModel()
{
Index = -1;
Value = string.Empty;
Items = null;
}
public int Index
{
get { return (int)GetValue("Index"); }
set { SetValue("Index", value); }
}
public string Value
{
get { return (string)GetValue("Value"); }
set { SetValue("Value", value); }
}
public List<string> Items
{
get { return (List<string>)GetValue("Items"); }
set { SetValue("Items",value); }
}
}
Window1.xaml
This is just something I made up to demonstrate/test it. Notice the various bindings.
<Window x:Class="SO37147147.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ComboBox x:Name="cmbSomething" Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="0" HorizontalAlignment="Center" MinWidth="80"
ItemsSource="{Binding Path=Items}" SelectedIndex="{Binding Path=Index}" SelectedValue="{Binding Path=Value}"></ComboBox>
<TextBox x:Name="selectedItem" MinWidth="80" Grid.Row="2" Grid.Column="0" Text="{Binding Path=Value}" />
<Button x:Name="displaySelected" MinWidth="40" Grid.Row="2" Grid.Column="1" Content="Display" Click="displaySelected_Click" />
</Grid>
</Window>
Window1.xaml.cs
Here's the code-behind. Not much to it! Everything is accessed through the dataContext instance. There's no need to know control names, etc.
public partial class Window1 : Window
{
ComboViewModel dataContext = new ComboViewModel();
public Window1()
{
InitializeComponent();
dataContext.Items=new List<string>(new string[]{"First Item","Second Item"});
this.DataContext = dataContext;
}
private void displaySelected_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(String.Format("Selected item:\n\nIndex: {0}\nValue: {1}", dataContext.Index, dataContext.Value));
}
}
You can add business logic for populating models from a database, saving changes to a database, etc. When you alter the properties of the view model, the UI will automatically be updated.

ViewModels Property after PropertyChanged event is always Null

This is my first experience with C#, as well as .net in general. I'm trying to get an understanding of MVVM and how it can work with a screen I want to build.
My button text is not updating when I am changing it after a different button is clicked. The debugger is showing that even though the OnPropertyChanged function is getting called, the PropertyChanged is always null.
I've looked at some documentation and taken a look at other posts of people asking similar questions but none of those solutions have worked for me. I'm also looking for an explanation so I can understand what i'm doing wrong.
Code:
MonthViewPage.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"
x:Class="Mobile_Release_POC_4.MonthViewPage"
xmlns:local="clr-namespace:Mobile_Release_POC_4;assembly=Mobile_Release_POC_4"
xmlns:telerikInput="clr-namespace:Telerik.XamarinForms.Input;assembly=Telerik.XamarinForms.Input"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<ContentPage.BindingContext>
<local:WeekViewDatesViewModel MiddleWeekViewDate='{x:Static sys:DateTime.Now}' />
</ContentPage.BindingContext>
<StackLayout>
<Label Text="Monday, April 27, 2015"
HorizontalOptions="Center"></Label>
<Grid Padding="0" ColumnSpacing="-2">
<Grid.RowDefinitions>
<RowDefinition Height="75" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions >
<ColumnDefinition Width="50" />
</Grid.ColumnDefinitions>
<Button x:Name="monthViewDateButton1"
Text="{Binding MiddleWeekViewDate}"
FontSize="10"
Grid.Row="0"
Grid.Column="0"
/>
<Button x:Name="monthViewDateButton2"
Text="Tue 4/28"
FontSize="10"
Grid.Row="0"
Grid.Column="1"
Clicked="OnButtonClicked"
/>
</Grid>
</StackLayout>
MonthViewPage.xaml.cs
using Xamarin.Forms;
namespace Mobile_Release_POC_4
{
public partial class MonthViewPage : ContentPage
{
public MonthViewPage ()
{
InitializeComponent();
}
void OnButtonClicked(object sender , EventArgs args){
WeekViewDatesViewModel dc = new WeekViewDatesViewModel ();
dc.MiddleWeekViewDate = new DateTime (2014, 2, 2);
}
}
WeekViewDatesViewModel.cs
namespace Mobile_Release_POC_4
{
public class WeekViewDatesViewModel : INotifyPropertyChanged
{
DateTime middleWeekViewDate;
public event PropertyChangedEventHandler PropertyChanged;
public DateTime MiddleWeekViewDate
{
set
{
if (middleWeekViewDate != value)
{
middleWeekViewDate = value;
OnPropertyChanged("MiddleWeekViewDate");
}
}
get
{
return middleWeekViewDate;
}
}
protected void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (PropertyChanged != null)
{
PropertyChanged(this,
new PropertyChangedEventArgs(propertyName));
}
}
}
}
Thank you
Only create one ViewModel, its scope is the life of the page (at least).
Change your code to this:
public partial class MonthViewPage : ContentPage
{
public WeekViewDatesViewModel VM { get; set; }
public MonthViewPage ()
{
DataContext = VM = new WeekViewDatesViewModel();
InitializeComponent();
}
void OnButtonClicked(object sender , EventArgs args){
VM.MiddleWeekViewDate = new DateTime (2014, 2, 2);
}
}

How to share Database within pages in Windows Phone 8?

I am creating a to do list application. At the moment I want to add a new to do list from todolistPage.xaml and after adding, I want to take the data to be able to view in the MainPage.xaml . I am able to view it from the todolistPage but not sure how to bring it to to another page. Hope to have some help. Thanks.
Below are my codes
MainPage.xaml.cs
namespace PivotApp3
{
public partial class MainPage : PhoneApplicationPage
{
// Constructor
public MainPage()
{
InitializeComponent();
// Set the data context of the listbox control to the sample data
DataContext = App.ViewModel;
// Sample code to localize the ApplicationBar
//BuildLocalizedApplicationBar();
}
// Load data for the ViewModel Items
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (!App.ViewModel.IsDataLoaded)
{
App.ViewModel.LoadData();
}
}
private void LongListSelector_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var si = mLongListSelector.SelectedItem as PivotApp3.ViewModels.ItemViewModel;
if (mLongListSelector.SelectedItem == null)
return;
if (si.LineOne.Equals("+ To Do List"))
NavigationService.Navigate(new Uri("/todolistPage.xaml", UriKind.Relative));
else if (si.LineOne.Equals("+ Reminder"))
NavigationService.Navigate(new Uri("/reminderPage.xaml", UriKind.Relative));
// Reset selected item to null (no selection)
mLongListSelector.SelectedItem = null;
}
}
MainPage.xaml
<!--LayoutRoot is the root grid where all page content is placed-->
<Grid x:Name="LayoutRoot" Background="Transparent">
<!-- LOCALIZATION NOTE:
To localize the displayed strings copy their values to appropriately named
keys in the app's neutral language resource file (AppResources.resx) then
replace the hard-coded text value between the attributes' quotation marks
with the binding clause whose path points to that string name.
For example:
Text="{Binding Path=LocalizedResources.ApplicationTitle, Source={StaticResource LocalizedStrings}}"
This binding points to the template's string resource named "ApplicationTitle".
Adding supported languages in the Project Properties tab will create a
new resx file per language that can carry the translated values of your
UI strings. The binding in these examples will cause the value of the
attributes to be drawn from the .resx file that matches the
CurrentUICulture of the app at run time.
-->
<!--Pivot Control-->
<phone:Pivot Title="DAILY ROUTINE">
<!--Pivot item one-->
<phone:PivotItem Header="activity">
<!--Double line list with text wrapping-->
<phone:LongListSelector x:Name="mLongListSelector" Margin="0,0,-12,0" ItemsSource="{Binding Items}" SelectionChanged="LongListSelector_SelectionChanged">
<phone:LongListSelector.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,17">
<TextBlock Text="{Binding LineOne}" TextWrapping="Wrap" Style="{StaticResource PhoneTextExtraLargeStyle}"/>
</StackPanel>
</DataTemplate>
</phone:LongListSelector.ItemTemplate>
</phone:LongListSelector>
</phone:PivotItem>
<!--Pivot item two-->
<phone:PivotItem Header="today">
</phone:PivotItem>
</phone:Pivot>
<!--Uncomment to see an alignment grid to help ensure your controls are
aligned on common boundaries. The image has a top margin of -32px to
account for the System Tray. Set this to 0 (or remove the margin altogether)
if the System Tray is hidden.
Before shipping remove this XAML and the image itself.-->
<!--<Image Source="/Assets/AlignmentGrid.png" VerticalAlignment="Top" Height="800" Width="480" Margin="0,-32,0,0" Grid.Row="0" IsHitTestVisible="False" />-->
</Grid>
todolistPage.xaml.cs
namespace PivotApp3
{
public partial class todolistPage : PhoneApplicationPage, INotifyPropertyChanged
{
// Data context for the local database
private ToDoDataContext toDoDB;
// Define an observable collection property that controls can bind to.
private ObservableCollection<ToDoItem> _toDoItems;
public ObservableCollection<ToDoItem> ToDoItems
{
get
{
return _toDoItems;
}
set
{
if (_toDoItems != value)
{
_toDoItems = value;
NotifyPropertyChanged("ToDoItems");
}
}
}
//constructor
public todolistPage()
{
InitializeComponent();
// Connect to the database and instantiate data context.
toDoDB = new ToDoDataContext(ToDoDataContext.DBConnectionString);
// Data context and observable collection are children of the main page.
this.DataContext = this;
}
private void deleteTaskButton_Click(object sender, RoutedEventArgs e)
{
// Cast parameter as a button.
var button = sender as Button;
if (button != null)
{
// Get a handle for the to-do item bound to the button.
ToDoItem toDoForDelete = button.DataContext as ToDoItem;
// Remove the to-do item from the observable collection.
ToDoItems.Remove(toDoForDelete);
// Remove the to-do item from the local database.
toDoDB.ToDoItems.DeleteOnSubmit(toDoForDelete);
// Save changes to the database.
toDoDB.SubmitChanges();
// Put the focus back to the main page.
this.Focus();
}
}
private void newToDoTextBox_GotFocus(object sender, RoutedEventArgs e)
{
// Clear the text box when it gets focus.
newToDoTextBox.Text = String.Empty;
}
private void newToDoAddButton_Click(object sender, RoutedEventArgs e)
{
// Create a new to-do item based on the text box.
ToDoItem newToDo = new ToDoItem { ItemName = newToDoTextBox.Text };
// Add a to-do item to the observable collection.
ToDoItems.Add(newToDo);
// Add a to-do item to the local database.
toDoDB.ToDoItems.InsertOnSubmit(newToDo);
}
protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
{
// Call the base method.
base.OnNavigatedFrom(e);
// Save changes to the database.
toDoDB.SubmitChanges();
}
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
// Define the query to gather all of the to-do items.
var toDoItemsInDB = from ToDoItem todo in toDoDB.ToDoItems
select todo;
// Execute the query and place the results into a collection.
ToDoItems = new ObservableCollection<ToDoItem>(toDoItemsInDB);
// Call the base method.
base.OnNavigatedTo(e);
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
// Used to notify the app that a property has changed.
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
public class ToDoDataContext : DataContext
{
// Specify the connection string as a static, used in main page and app.xaml.
public static string DBConnectionString = "Data Source=isostore:/ToDo.sdf";
// Pass the connection string to the base class.
public ToDoDataContext(string connectionString)
: base(connectionString)
{ }
// Specify a single table for the to-do items.
public Table<ToDoItem> ToDoItems;
}
[Table]
public class ToDoItem : INotifyPropertyChanged, INotifyPropertyChanging
{
// Define ID: private field, public property and database column.
private int _toDoItemId;
[Column(IsPrimaryKey = true, IsDbGenerated = true, DbType = "INT NOT NULL Identity", CanBeNull = false, AutoSync = AutoSync.OnInsert)]
public int ToDoItemId
{
get
{
return _toDoItemId;
}
set
{
if (_toDoItemId != value)
{
NotifyPropertyChanging("ToDoItemId");
_toDoItemId = value;
NotifyPropertyChanged("ToDoItemId");
}
}
}
// Define item name: private field, public property and database column.
private string _itemName;
[Column]
public string ItemName
{
get
{
return _itemName;
}
set
{
if (_itemName != value)
{
NotifyPropertyChanging("ItemName");
_itemName = value;
NotifyPropertyChanged("ItemName");
}
}
}
// Define completion value: private field, public property and database column.
private bool _isComplete;
[Column]
public bool IsComplete
{
get
{
return _isComplete;
}
set
{
if (_isComplete != value)
{
NotifyPropertyChanging("IsComplete");
_isComplete = value;
NotifyPropertyChanged("IsComplete");
}
}
}
// Version column aids update performance.
[Column(IsVersion = true)]
private Binary _version;
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
// Used to notify the page that a data context property changed
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
#region INotifyPropertyChanging Members
public event PropertyChangingEventHandler PropertyChanging;
// Used to notify the data context that a data context property is about to change
private void NotifyPropertyChanging(string propertyName)
{
if (PropertyChanging != null)
{
PropertyChanging(this, new PropertyChangingEventArgs(propertyName));
}
}
#endregion
}
todolistPage.xaml
<!--LayoutRoot is the root grid where all page content is placed-->
<Grid x:Name="LayoutRoot" Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!--TitlePanel contains the name of the application and page title-->
<StackPanel Grid.Row="0" Margin="12,17,0,28">
<TextBlock Text="TO DO LIST" Style="{StaticResource PhoneTextNormalStyle}"/>
<TextBlock Text="add" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
</StackPanel>
<!-- Bind the list box to the observable collection. -->
<ListBox x:Name="toDoItemsListBox" ItemsSource="{Binding ToDoItems}"
Grid.Row="1" Margin="12,0,28,210" Width="440">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid HorizontalAlignment="Stretch" Width="440">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<CheckBox
IsChecked="{Binding IsComplete, Mode=TwoWay}"
Grid.Column="0"
VerticalAlignment="Center"/>
<TextBlock
Text="{Binding ItemName}"
FontSize="{StaticResource PhoneFontSizeLarge}"
Grid.Column="1"
VerticalAlignment="Center"/>
<Button
Grid.Column="2"
x:Name="deleteTaskButton"
BorderThickness="0"
Margin="0"
Click="deleteTaskButton_Click">
<Image Source="appbar.delete.rest.png"/>
</Button>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<!--ContentPanel - place additional content here-->
<Grid x:Name="ContentPanel" Grid.Row="2" Margin="12,465,12,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="10*"/>
<ColumnDefinition Width="9*"/>
</Grid.ColumnDefinitions>
<TextBox
x:Name="newToDoTextBox"
Grid.Column="0"
Text="add new task"
FontFamily="{StaticResource PhoneFontFamilyLight}"
GotFocus="newToDoTextBox_GotFocus" Margin="0,-65,0,104" Grid.ColumnSpan="2"/>
<Button
Content="add"
x:Name="newToDoAddButton"
Click="newToDoAddButton_Click" Margin="150,43,130,10" Grid.ColumnSpan="2"/>
</Grid>
</Grid>
Database Created
using (ToDoDataContext db = new ToDoDataContext(ToDoDataContext.DBConnectionString))
{
if (db.DatabaseExists() == false)
{
//Create the database
db.CreateDatabase();
}
}
Put your database into App.xaml.cs:
// Data context for the local database
public ToDoDataContext toDoDB;
add this code to App.xaml.cs:
public new static App Current
{
get
{
return (App)Application.Current;
}
}
Then you can access your database everywhere by using:
App.Current.toDoDB...
You can create class to manipulate data in sql and make it avaiable from App.xaml.cs
Here is sample code
ToDoDataViewModel class:
public class ToDoDataViewModel : INotifyPropertyChanged
{
ToDoDataContext db;
public ToDoDataViewModel(string connectionString)
{
db = new ToDoDataContext(connectionString);
}
private ObservableCollection<ToDoItem> _toDoItems;
public ObservableCollection<ToDoItem> ToDoItems
{
get { return this._toDoItems; }
set
{
this._toDoItems = value;
NotifyPropertyChanged("ToDoItems");
}
}
public void LoadCollectionsFromDatabase()
{
var toDos = from todo in db.ToDoItems
select todo;
_toDoItems = new ObservableCollection<ToDoItem>(toDos);
}
public void InsertToDoItem(ToDoItem item)
{
db.ToDoItems.InsertOnSubmit(item);
_toDoItems.Add(item);
db.SubmitChanges();
}
public void DeleteToDoItem(ToDoItem item)
{
db.ToDoItems.DeleteOnSubmit(item);
_toDoItems.Remove(item);
db.SubmitChanges();
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
In App.xaml.cs:
public partial class App : Application
{
private static ToDoDataViewModel _viewModel;
public static ToDoDataViewModel ViewModel
{
get { return _viewModel; }
}
//other methods of App
public App()
{
//place this code at the and of the contructor
CreateDb();
}
private void CreateDb()
{
using(var db=new ToDoDataContext(ToDoDataContext.DBConnectionString))
{
if(!db.DatabaseExists())
{
db.CreateDatabase();
}
}
_viewModel=new ToDoDataViewModel(ToDoDataContext.DBConnectionString);
_viewModel.LoadCollectionsFromDatabase();
}
}
And place this code in the constructors of your pages:
this.DataContext=App.ViewModel;
This way you separated your database logic from application logic
Now you can update your newToDoAddButton_Click method as following:
private void newToDoAddButton_Click(object sender, RoutedEventArgs e)
{
// Create a new to-do item based on the text box.
ToDoItem newToDo = new ToDoItem { ItemName = newToDoTextBox.Text };
//Add to-do item to the local database
App.ViewModel.InsertToDoItem(newToDo);
this.Focus();
}
And your deleteTaskButton_Click method:
private void deleteTaskButton_Click(object sender, RoutedEventArgs e)
{
var button = sender as Button;
if (button != null)
{
// Get a handle for the to-do item bound to the button.
ToDoItem toDoForDelete = button.DataContext as ToDoItem;
// Remove the to-do item from the local database.
App.ViewModel.DeleteToDoItem(toDoForDelete);
this.Focus();
}
}

Categories

Resources