I am developing an windows phone applicaiton using sqlite database.I have stored all the values in history.xaml.cs but when I run the applicaiton it ends being error on debugger.break as "> SmartParking.DLL!SmartParking.App.RootFrame_NavigationFailed(object sender, System.Windows.Navigation.NavigationFailedEventArgs e) Line 133 C#"
Below are all my 3 classes that I am dealing with to store the values in the database and output it in the history.xaml
<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="Smart Parking" Style="{StaticResource PhoneTextNormalStyle}"/>
<TextBlock Text="History" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
</StackPanel>
<!--ContentPanel - place additional content here-->
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<ListBox x:Name="ListData">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock x:Name= "DateTxt" Text="{Binding Date}" TextWrapping="Wrap" />
<TextBlock x:Name= "TimeTxt" Text="{Binding Time}" TextWrapping="Wrap" />
<TextBlock x:Name= "ZoneTxt" Text="{Binding Zone}" TextWrapping="Wrap"/>
<TextBlock x:Name= "FloorTxt" Text="{Binding Floor}" TextWrapping="Wrap"/>
<TextBlock x:Name= "LatTxt" Text="{Binding location_latitude}" TextWrapping="Wrap" />
<TextBlock x:Name= "LongTxt" Text="{Binding location_longitude}" TextWrapping="Wrap" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Grid>
below is dbhelper that carries out all the main functions
public class DbHelper
{
SQLiteConnection dbConn;
public async Task<bool> onCreate(string DB_PATH)
{
try
{
if (!CheckFileExists(DB_PATH).Result)
{
using (dbConn = new SQLiteConnection(DB_PATH))
{
dbConn.CreateTable<historyTableSQlite>();
}
}
return true;
}
catch
{
return false;
}
}
private async Task<bool> CheckFileExists(string fileName)
{
try
{
var store = await Windows.Storage.ApplicationData.Current.LocalFolder.GetFileAsync(fileName);
return true;
}
catch
{
return false;
}
}
//retrieve all list from the database
public ObservableCollection<historyTableSQlite> ReadHistory()
{
using (var dbConn = new SQLiteConnection(App.DB_PATH))
{
List<historyTableSQlite> myCollection = dbConn.Table<historyTableSQlite>().ToList<historyTableSQlite>();
ObservableCollection<historyTableSQlite> HistoryList = new ObservableCollection<historyTableSQlite>(myCollection);
return HistoryList;
}
}
// Insert the new info in the histrorytablesqlite table.
public void Insert(historyTableSQlite newcontact)
{
using (var dbConn = new SQLiteConnection(App.DB_PATH))
{
dbConn.RunInTransaction(() =>
{
dbConn.Insert(newcontact);
});
}
}
}
here is the history.xaml.cs code file
public partial class History : PhoneApplicationPage
{
// string dbPath = Path.Combine(Windows.Storage.ApplicationData.Current.LocalFolder.Path, "db.sqlite");
ObservableCollection<historyTableSQlite> DB_HistoryList = new ObservableCollection<historyTableSQlite>();
public History()
{
InitializeComponent();
AddInfo();
ReadHistoryList_Loaded();
}
public void ReadHistoryList_Loaded()
{
ReadAllContactsList dbhistory = new ReadAllContactsList();
DB_HistoryList = dbhistory.GetAllHistory();//Get all DB contacts
ListData.ItemsSource = DB_HistoryList.OrderByDescending(i => i.Id).ToList();//Latest contact ID can Display first
}
public void AddInfo()
{
// ListData.Items.Add(new historyTableSQlite{
// Date = DateTime.Now.ToShortDateString(),
// Time = DateTime.Now.ToShortTimeString(),
// Zone = "PST",
// Floor = "10th Floor",
// latitude = 35.45112,
// longtitude = -115.42622
//});
DbHelper Db_helper = new DbHelper();
Db_helper.Insert((new historyTableSQlite {
Date = DateTime.Now.ToShortDateString(),
Time = DateTime.Now.ToShortTimeString(),
Zone = "PST",
Floor = "10th Floor",
latitude = 35.45112,
longtitude = -115.42622
}));
}
}
and the last is the class where I intialize all my values
ublic class historyTableSQlite : INotifyPropertyChanged
{
[SQLite.PrimaryKey, SQLite.AutoIncrement]
public int Id { get; set; }
private int idvalue;
private string dateValue = string.Empty;
public string Date {
get { return this.dateValue; }
set
{
if (value != this.dateValue)
{
this.dateValue = value;
NotifyPropertyChanged("Date");
}
}
}
private string timeValue = string.Empty;
public string Time
{
get { return this.timeValue; }
set
{
if (value != this.timeValue)
{
this.timeValue = value;
NotifyPropertyChanged("Time");
}
}
}
private string floorValue = string.Empty;
public string Floor
{
get { return this.floorValue; }
set
{
if (value != this.floorValue)
{
this.floorValue = value;
NotifyPropertyChanged("Floor");
}
}
}
public string zoneValue;
public string Zone
{
get { return this.zoneValue; }
set
{
if (value != this.zoneValue)
{
this.zoneValue = value;
NotifyPropertyChanged("Zone");
}
}
}
private double latValue;
public double latitude
{
get { return latValue; }
set
{
if (value != this.latValue)
{
this.latValue = value;
NotifyPropertyChanged("Latitude");
}
}
}
private double lonValue;
public double longtitude
{
get { return this.lonValue; }
set
{
if (value != this.lonValue)
{
this.lonValue = value;
NotifyPropertyChanged("Longitude");
}
}
}
// public string isMarkPoint { get; set; }
public historyTableSQlite()
{
}
public historyTableSQlite(string date,string time,string floor,string zone,double lat,double lng)
{
Date = date;
Time = time;
Floor = floor;
Zone = zone;
latitude = lat;
longtitude = lng;
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
Remove AddInfo() and ReadHistoryList_Loaded() from the History constructor and override OnNavigatedTo() like this.
protected override void OnNavigatedTo(NavigationEventArgs e)
{
AddInfo();
ReadHistoryList_Loaded();
}
If it the page constructor takes long than a few hundred millisecond to create the page windows will assume something has gone wrong. It is best not to put anything in the constructor for this reason.
Related
Button in a data template with a command in a list view of items not working. I have tried other data templates with stack panel.
The button to search for the initial items will recognize it's command and work.
Here is the page with the data template for the items:
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfFstApp1.Pages"
xmlns:ViewModels="clr-namespace:WpfFstApp1.ViewModels" x:Class="WpfFstApp1.Pages.CsvPage"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
Title="CsvPage">
<Page.DataContext>
<ViewModels:CsvViewModel/>
</Page.DataContext>
<Page.Resources>
<DataTemplate x:Key="CsvTemp">
<ListView>
<TextBlock Text="{Binding Dir}"/>
<TextBlock Text="{Binding FiPath}"/>
<Button Content="Click To Open" Command="{Binding OpenFiCommand, Mode=OneWay}" CommandParameter="{Binding SelResult.FiPath}"></Button>
<ListView ItemsSource="{Binding Refs}"/>
</ListView>
</DataTemplate>
</Page.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="55"/>
<RowDefinition/>
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<TextBlock Text="Project" HorizontalAlignment="Left" Margin="10,5,0,0" VerticalAlignment="Top" Height="16" Width="37"/>
<TextBlock Text="Sales Line" HorizontalAlignment="Left" Margin="65,5,0,0" VerticalAlignment="Top" Height="16" Width="52"/>
<TextBlock Text="Part Number" HorizontalAlignment="Left" Margin="155,5,0,0" VerticalAlignment="Top" Height="16" Width="68"/>
<TextBox x:Name="TxtBoxProj" HorizontalAlignment="Left" Height="23" Margin="10,26,0,0" VerticalAlignment="Top" Width="50"
TextAlignment="Right" MaxLines="1" Text="{Binding SearchVal1, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox x:Name="TxtBoxSline" HorizontalAlignment="Left" Height="23" Margin="65,26,0,0" VerticalAlignment="Top" Width="85" MaxLines="1" MaxLength="25"
TextAlignment="Right" CharacterCasing="Upper" Text="{Binding SearchVal2, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox x:Name="TxtBoxPart" HorizontalAlignment="Left" Height="23" Margin="155,26,0,0" VerticalAlignment="Top" Width="120"
TextAlignment="Right" MaxLines="1" CharacterCasing="Upper" Text="{Binding SearchVal3, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<Button x:Name="BtnSrch" Content="Search" HorizontalAlignment="Left" Margin="280,29,0,0" VerticalAlignment="Top" Width="40" Command="{Binding SearchCommand, Mode=OneWay}"/>
<ListView Margin="10" Grid.Row="1" ItemsSource="{Binding OcResults}" ItemTemplate="{Binding Mode=OneWay, Source={StaticResource CsvTemp}}" SelectedItem="{Binding SelResult}"/>
<StatusBar Grid.Row="2" FontSize="14" Margin="1,0,1,1" VerticalAlignment="Bottom">
<TextBlock x:Name="tsTxtBlck1" Text="{Binding CurStat1}" />
<TextBlock x:Name="tsTxtBlck2" Text="{Binding CurStat2}"/>
</StatusBar>
</Grid>
Here is the Model :
class SearchResult
{
public string Dir { get; set; }
public string FiName { get; set; }
public string FiPath { get; set; }
public List<string> Refs { get; set; }
}
Here is my ViewModel :
class CsvViewModel : ModelBase
{
#region [ Declarations ]
//
bool Init = false;
string CurDir = string.Empty;
//Count of files traversed and timer for diagnostic output
int fileCount = 0;
Stopwatch sw;
public List<SearchResult> ResList;
//
#endregion
#region [ Properties ]
//
public ObservableCollection<SearchResult> OcResults { get; set; }
//
private string _curStat1;
public string CurStat1
{
get { return _curStat1; }
set { _curStat1 = value; OnPropertyChanged(nameof(CurStat1)); }
}
private string _curStat2;
public string CurStat2
{
get { return _curStat2; }
set { _curStat2 = value; OnPropertyChanged(nameof(CurStat2)); }
}
//
private string _searchVal1 = string.Empty;
public string SearchVal1
{
get { return _searchVal1; }
set { _searchVal1 = value; OnPropertyChanged(nameof(SearchVal1)); }
}
private string _searchVal2 = string.Empty;
public string SearchVal2
{
get { return _searchVal2; }
set { _searchVal2 = value; OnPropertyChanged(nameof(SearchVal2)); }
}
private string _searchVal3 = string.Empty;
public string SearchVal3
{
get { return _searchVal3; }
set { _searchVal3 = value; OnPropertyChanged(nameof(SearchVal3)); }
}
//
public RelCom SearchCommand { get; private set; }
public RelCom OpenFiCommand { get; private set; }
//
private SearchResult selResult;
public SearchResult SelResult
{
get { return selResult; }
set { selResult = value; OnPropertyChanged(nameof(SelResult)); }
}
//
#endregion
public CsvViewModel()
{
ResList = new List<SearchResult>();
OcResults = new ObservableCollection<SearchResult>();
SearchCommand = new RelCom(Search_CanExecute, Search);
OpenFiCommand = new RelCom(OpenFi_CanExecute, OpenFile);
}
private bool Search_CanExecute(object obj)
{
return true;
}
private void Search(object obj)
{
CurDir = string.Empty;
//
if (!string.IsNullOrWhiteSpace(SearchVal1))
SearchVal2 = SearchVal1.Trim() + " " + SearchVal2.Trim();
//
if (!string.IsNullOrWhiteSpace(SearchVal3))
SearchVal3 = SearchVal3.Trim();
//
try
{
TraverseTreeParallelForEach(#"\\ast-sigmanest\Feedback\Feedback", (f) =>
{
// Exceptions are no-ops.
try
{
// Do nothing with the data except read it to find string...
FileInfo fi1 = new FileInfo(f);
bool addfi = false;
//
string[] Lines = File.ReadAllLines(f);
List<string> refLines = new List<string>();
foreach (string line in Lines)
{
if (line.Contains(SearchVal2) & line.Contains(SearchVal3))
{
addfi = true;
refLines.Add(line);
}
}
//
if (addfi)
{
SearchResult Sres = new SearchResult { Dir = fi1.Directory.ToString(), FiName = fi1.Name, FiPath = fi1.FullName, Refs = refLines };
ResList.Add(Sres);
}
//
//contents = null;
Lines = null;
fi1 = null;
//
}
catch (FileNotFoundException) { }
catch (IOException) { }
catch (UnauthorizedAccessException) { }
catch (SecurityException) { }
// Display the filename.
//Console.WriteLine(f);
});
}
catch (ArgumentException Ae)
{
MessageBox.Show(Ae.Message);
}
//
var resgrp = (from res in ResList group res by res.Dir into newres orderby newres.Key select newres);
foreach (var rgrp in resgrp)
{
foreach (var res in rgrp)
{
OcResults.Add(res);
}
}
//
}
private bool OpenFi_CanExecute(object obj)
{
return obj != null;
}
private void OpenFile(object obj)
{
Process.Start(new ProcessStartInfo(SelResult.FiPath));
}
public void TraverseTreeParallelForEach(string root, Action<string> action)
{
//Count of files traversed and timer for diagnostic output
fileCount = 0;
sw = Stopwatch.StartNew();
// Determine whether to parallelize file processing on each folder based on processor count.
int procCount = System.Environment.ProcessorCount;
// Data structure to hold names of subfolders to be examined for files.
Stack<string> dirs = new Stack<string>();
if (!Directory.Exists(root))
{
throw new ArgumentException();
}
dirs.Push(root);
while (dirs.Count > 0)
{
string currentDir = dirs.Pop();
string[] subDirs = { };
string[] files = { };
try
{
subDirs = Directory.GetDirectories(currentDir);
// Push the subdirectories onto the stack for traversal.
// This could also be done before handing the files.
foreach (string str in subDirs)
dirs.Push(str);
}
// Thrown if we do not have discovery permission on the directory.
catch (UnauthorizedAccessException e)
{
Console.WriteLine(e.Message);
continue;
}
// Thrown if another process has deleted the directory after we retrieved its name.
catch (DirectoryNotFoundException e)
{
Console.WriteLine(e.Message);
continue;
}
//
try
{
files = Directory.GetFiles(currentDir, "*.csv");
}
catch (UnauthorizedAccessException e)
{
Console.WriteLine(e.Message);
continue;
}
catch (DirectoryNotFoundException e)
{
Console.WriteLine(e.Message);
continue;
}
catch (IOException e)
{
Console.WriteLine(e.Message);
continue;
}
//
// Execute in parallel if there are enough files in the directory.
// Otherwise, execute sequentially.Files are opened and processed
// synchronously but this could be modified to perform async I/O.
try
{
if (files.Length < procCount)
{
foreach (var file in files)
{
action(file);
fileCount++;
}
}
else
{
Parallel.ForEach(files, () => 0, (file, loopState, localCount) =>
{
action(file);
return (int)++localCount;
},
(c) =>
{
Interlocked.Add(ref fileCount, c);
});
}
}
catch (AggregateException ae)
{
ae.Handle((ex) =>
{
if (ex is UnauthorizedAccessException)
{
// Here we just output a message and go on.
Console.WriteLine(ex.Message);
return true;
}
// Handle other exceptions here if necessary...
return false;
});
}
//
}
//
}
}
For some reason the button in the data template does not trigger the command, even though it shows to be bound to it.
Any help appreciated.
Thanks.
You need to let the Open command know that the CanExecute conditions have changed, do this in your SelResult setter:
((RelayCommand<object>)OpenFiCommand).RaiseCanExecuteChanged();
This is one of the reasons I don't like using CanExecute.
At first I made an application that downloads a file from the entered link and displays information about progress, speed, etc. When I decided to change the application to download several files at the same time, I ran into a problem. So, in the interface there is a listbox in which there are several objects. When you select one of the objects and enter the link to the file, it should start downloading. When selecting another object, information about the previous object should change to the information of the selected one. I can also enter a link to the file there and then track the downloads of the two files by switching between objects. However, when switching information does not change. How to implement it?
Model:
public class Model
{
public WebClient webClient = new WebClient();
public Stopwatch stopWatch = new Stopwatch();
public event Action<long> FileSizeChanged;
public event Action<long, TimeSpan> DownloadBytesChanged;
public event Action<double> ProgressPercentageChanged;
public event Action DownloadComplete;
public string Name { get; set; }
public void DownloadFile(string url, bool openAfterDownload)
{
if (webClient.IsBusy)
throw new Exception("The client is busy");
try
{
var startDownloading = DateTime.UtcNow;
webClient.Proxy = null;
if (!SelectFolder(Path.GetFileName(url)+Path.GetExtension(url), out var filePath))
throw DownloadingError();
webClient.DownloadProgressChanged += (o, args) =>
{
ProgressPercentageChanged?.Invoke(args.ProgressPercentage);
FileSizeChanged?.Invoke(args.TotalBytesToReceive);
DownloadBytesChanged?.Invoke(args.BytesReceived, DateTime.UtcNow - startDownloading);
if (args.ProgressPercentage >= 100 && openAfterDownload)
Process.Start(filePath);
};
webClient.DownloadFileCompleted += (o, args) => DownloadComplete?.Invoke();
stopWatch.Start();
webClient.DownloadFileAsync(new Uri(url), filePath);
}
catch (Exception e)
{
throw DownloadingError();
}
}
public void CancelDownloading()
{
webClient.CancelAsync();
webClient.Dispose();
DownloadComplete?.Invoke();
}
private static Exception DownloadingError()
=> new Exception("Downloading error!");
private static bool SelectFolder(string fileName, out string filePath)
{
var saveFileDialog = new SaveFileDialog
{
InitialDirectory = "c:\\",
FileName = fileName,
Filter = "All files (*.*)|*.*"
};
filePath = "";
if (saveFileDialog.ShowDialog() != true) return false;
filePath = saveFileDialog.FileName;
return true;
}
}
ViewModel:
class MainVM : INotifyPropertyChanged
{
private string url;
private RelayCommand downloadCommand;
private RelayCommand cancelCommand;
private double progressBarValue;
private string bytesReceived;
private string bytesTotal;
private string speed;
private string time;
private string error;
private long totalBytes;
private Model selectedGame;
public ObservableCollection<Model> Games { get; set; }
public MainVM()
{
Games = new ObservableCollection<Model>();
Model Game1 = new Model { Name = "Name1" };
Model Game2 = new Model { Name = "Name2" };
Game1.FileSizeChanged += bytes => BytesTotal = PrettyBytes(totalBytes = bytes);
Game1.DownloadBytesChanged += (bytes, time) =>
{
BytesReceived = PrettyBytes(bytes);
Speed = DownloadingSpeed(bytes, time);
Time = DownloadingTime(bytes, totalBytes, time);
};
Game1.ProgressPercentageChanged += percentage => ProgressBarValue = percentage;
Game1.DownloadComplete += () =>
{
BytesReceived = "";
BytesTotal = "";
Speed = "";
Time = "";
ProgressBarValue = 0;
};
Game2.FileSizeChanged += bytes => BytesTotal = PrettyBytes(totalBytes = bytes);
Game2.DownloadBytesChanged += (bytes, time) =>
{
BytesReceived = PrettyBytes(bytes);
Speed = DownloadingSpeed(bytes, time);
Time = DownloadingTime(bytes, totalBytes, time);
};
Game2.ProgressPercentageChanged += percentage => ProgressBarValue = percentage;
Game2.DownloadComplete += () =>
{
BytesReceived = "";
BytesTotal = "";
Speed = "";
Time = "";
ProgressBarValue = 0;
};
Games.Add(Game1);
Games.Add(Game2);
}
public Model SelectedGame
{
get => selectedGame;
set
{
if (value == selectedGame) return;
selectedGame = value;
OnPropertyChanged(nameof(SelectedGame));
}
}
public string Error
{
get => error;
private set
{
error = value;
OnPropertyChanged(nameof(Error));
}
}
public string URL
{
get => url;
set
{
url = value;
OnPropertyChanged(nameof(URL));
}
}
public bool OpenDownloadedFile { get; set; }
public double ProgressBarValue
{
get => progressBarValue;
set
{
progressBarValue = value;
OnPropertyChanged(nameof(ProgressBarValue));
}
}
public string BytesTotal
{
get => bytesTotal;
private set
{
bytesTotal = value;
OnPropertyChanged(nameof(BytesTotal));
}
}
public string BytesReceived
{
get => bytesReceived;
private set
{
bytesReceived = value;
OnPropertyChanged(nameof(BytesReceived));
}
}
public string Speed
{
get => speed;
private set
{
speed = value;
OnPropertyChanged(nameof(Speed));
}
}
public string Time
{
get => time;
private set
{
time = value;
OnPropertyChanged(nameof(Time));
}
}
public RelayCommand DownloadCommand =>
downloadCommand ??
(downloadCommand = new RelayCommand(DownloadButton_Click));
public RelayCommand CancelCommand =>
cancelCommand ??
(cancelCommand = new RelayCommand(CancelButton_Click));
private void DownloadButton_Click(object obj)
{
if (url == null && url == "") return;
try
{
SelectedGame.DownloadFile(url, OpenDownloadedFile);
}
catch (Exception e)
{
Error = e.Message;
}
}
private void CancelButton_Click(object obj)
{
if (url != null || url != "")
SelectedGame.CancelDownloading();
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string prop = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
}
private static string PrettyBytes(double bytes)
{
if (bytes < 1024)
return bytes + "Bytes";
if (bytes < Math.Pow(1024, 2))
return (bytes / 1024).ToString("F" + 2) + "Kilobytes";
if (bytes < Math.Pow(1024, 3))
return (bytes / Math.Pow(1024, 2)).ToString("F" + 2) + "Megabytes";
if (bytes < Math.Pow(1024, 4))
return (bytes / Math.Pow(1024, 5)).ToString("F" + 2) + "Gygabytes";
return (bytes / Math.Pow(1024, 4)).ToString("F" + 2) + "terabytes";
}
public static string DownloadingSpeed(long received, TimeSpan time)
{
return ((double)received / 1024 / 1024 / time.TotalSeconds).ToString("F" + 2) + " megabytes/sec";
}
public static string DownloadingTime(long received, long total, TimeSpan time)
{
var receivedD = (double) received;
var totalD = (double) total;
return ((totalD / (receivedD / time.TotalSeconds)) - time.TotalSeconds).ToString("F" + 1) + "sec";
}
}
View:
<Window x:Class="DownloadingFiles.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:DownloadingFiles"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<local:MainVM/>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<Canvas Grid.Column="1" Grid.ColumnSpan="3" Grid.RowSpan="4">
<TextBox Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="2" Text="{Binding URL, UpdateSourceTrigger=PropertyChanged}"
FontSize="40" Width="424"/>
<Button Grid.Row="0" Grid.Column="3" Content="DOWNLOAD" FontSize="30" FontFamily="./#Sochi2014" Command="{Binding DownloadCommand}" Canvas.Left="429" Canvas.Top="-2" Width="157"/>
<Label Grid.Row="1" Grid.Column="2" Content="{Binding Error, Mode=OneWay}" FontFamily="./#Sochi2014" Height="45" VerticalAlignment="Bottom" Canvas.Left="401" Canvas.Top="123" Width="184" />
<CheckBox Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2" FontSize="30" Content="Open after downloading"
IsChecked="{Binding OpenDownloadedFile, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" FontFamily="./#Sochi2014" Canvas.Left="15" Canvas.Top="80"/>
<Button Grid.Row="1" Grid.Column="3" Content="CANCEL" FontSize="30" FontFamily="./#Sochi2014" Command ="{Binding CancelCommand}" Canvas.Left="429" Canvas.Top="50" Width="157"/>
<Label Grid.Row="2" Grid.Column="1" Content="{Binding Time, Mode=OneWay}" FontSize="30" FontFamily="./#Sochi2014" Height="40" Width="69" Canvas.Left="310" Canvas.Top="277" RenderTransformOrigin="2.284,1.56"/>
<Label Grid.Row="2" Grid.Column="3" Content="{Binding Speed, Mode=OneWay}" FontSize="30" FontFamily="./#Sochi2014" Height="40" Width="193" Canvas.Left="15" Canvas.Top="277"/>
<ProgressBar Grid.Row="3" Grid.Column="1" Grid.ColumnSpan="2" Value="{Binding ProgressBarValue}" Foreground="#AAA1C8" Height="75" Width="424" Canvas.Left="15" Canvas.Top="335"/>
<Label Grid.Row="3" FontSize="30" FontFamily="./#Sochi2014" Content="{Binding ProgressBarValue}" Grid.ColumnSpan="2" Canvas.Left="230" Canvas.Top="339"/>
<Label Grid.Row="3" Grid.Column="3" Content="{Binding BytesReceived, Mode=OneWay}" FontSize="30" FontFamily="./#Sochi2014" Height="40" VerticalAlignment="Top" Canvas.Left="448" Canvas.Top="299" Width="137"/>
<Label Grid.Row="3" Grid.Column="3" Content="{Binding BytesTotal, Mode=OneWay}" FontSize="30" FontFamily="./#Sochi2014" Height="44" Canvas.Left="448" Canvas.Top="344" Width="137" />
<Label Content="{Binding Name}" Height="40" Width="186" Canvas.Left="22" Canvas.Top="202"/>
</Canvas>
<ListBox Grid.Row="0" Grid.Column="0" Grid.RowSpan="4" ItemsSource="{Binding Games}"
SelectedItem="{Binding SelectedGame, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" SelectedIndex="0" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock FontSize="20" Text="{Binding Name}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
RelayCommand:
public class RelayCommand : ICommand
{
private readonly Action<object> _execute;
private readonly Predicate<object> _canExecute;
public RelayCommand(Action<object> execute, Predicate<object> canExecute = null)
{
if (execute == null) throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExecute == null || _canExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
_execute(parameter ?? "<N/A>");
}
}
You have to bind to the SelectedGame property. But to fully enable the switching between the downloading items you would have to refactor your code and move download specific attributes (e.g. progress, speed) into a separate class for each download (because SelectedGame doesn't expose all required attributes). This way each game or download item has it's own exposes its own download related information to the view.
So I introduced a DownloadItem class that encapsulates donwnload related attributes or data. This class represents your game or download items that you can select in the ListView:
class DownloadItem : INotifyPropertyChanged
{
public DownloadItem()
{
this.DisplayBytesTotal = string.Empty;
this.Url = string.Empty;
this.DownloadSpeed = string.Empty;
this.ErrorMessage = string.Empty;
this.Name = string.Empty;
this.ProgressBytesRead = string.Empty;
}
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
private string name;
public string Name
{
get => this.name;
set
{
if (value == this.name) return;
this.name = value;
OnPropertyChanged();
}
}
private string url;
public string Url
{
get => this.url;
set
{
if (value == this.url) return;
this.url = value;
OnPropertyChanged();
}
}
private double progress;
public double Progress
{
get => this.progress;
set
{
this.progress = value;
OnPropertyChanged();
}
}
private bool isOpenAfterDownloadEnabled;
public bool IsOpenAfterDownloadEnabled
{
get => this.isOpenAfterDownloadEnabled;
set
{
this.isOpenAfterDownloadEnabled = value;
OnPropertyChanged();
}
}
private string progressBytesRead;
public string ProgressBytesRead
{
get => this.progressBytesRead;
set
{
if (value == this.progressBytesRead) return;
this.progressBytesRead = value;
OnPropertyChanged();
}
}
private long bytesTotal;
public long BytesTotal
{
get => this.bytesTotal;
set
{
if (value == this.bytesTotal) return;
this.bytesTotal = value;
OnPropertyChanged();
}
}
private string displayBytesTotal;
public string DisplayBytesTotal
{
get => this.displayBytesTotal;
set
{
if (value == this.displayBytesTotal) return;
this.displayBytesTotal = value;
OnPropertyChanged();
}
}
private string downloadSpeed;
public string DownloadSpeed
{
get => this.downloadSpeed;
set
{
if (value == this.downloadSpeed) return;
this.downloadSpeed = value;
OnPropertyChanged();
}
}
private string timeElapsed;
public string TimeElapsed
{
get => this.timeElapsed;
set
{
if (value == this.timeElapsed) return;
this.timeElapsed = value;
OnPropertyChanged();
}
}
private string errorMessage;
public string ErrorMessage
{
get => this.errorMessage;
set
{
if (value == this.errorMessage) return;
this.errorMessage = value;
OnPropertyChanged();
}
}
}
Then, to encapsulate the download behavior, I modified the your Model class and renamed it to Downloader. Each DownloadItem is associated with one Downloader. Therefore the Downloader now handles the progress of its associated DownloadItem by itself and updates the DownloadItem accordingly:
class Downloader
{
public DownloadItem CurrentDownloadItem { get; set; }
public WebClient webClient = new WebClient();
public Stopwatch stopWatch = new Stopwatch();
public event Action<long> FileSizeChanged;
public event Action<long, TimeSpan> DownloadBytesChanged;
public event Action<double> ProgressPercentageChanged;
public event Action DownloadComplete;
public void DownloadFile(DownloadItem gameToDownload)
{
this.CurrentDownloadItem = gameToDownload;
if (webClient.IsBusy)
throw new Exception("The client is busy");
var startDownloading = DateTime.UtcNow;
webClient.Proxy = null;
if (!SelectFolder(
Path.GetFileName(this.CurrentDownloadItem.Url) + Path.GetExtension(this.CurrentDownloadItem.Url),
out var filePath))
{
DownloadingError();
return;
}
webClient.DownloadProgressChanged += (o, args) =>
{
UpdateProgressPercentage(args.ProgressPercentage);
UpdateFileSize(args.TotalBytesToReceive);
UpdateProgressBytesRead(args.BytesReceived, DateTime.UtcNow - startDownloading);
if (args.ProgressPercentage >= 100 && this.CurrentDownloadItem.IsOpenAfterDownloadEnabled)
Process.Start(filePath);
};
webClient.DownloadFileCompleted += OnDownloadCompleted;
stopWatch.Start();
webClient.DownloadFileAsync(new Uri(this.CurrentDownloadItem.Url), filePath);
}
public void CancelDownloading()
{
webClient.CancelAsync();
webClient.Dispose();
DownloadComplete?.Invoke();
}
private string PrettyBytes(double bytes)
{
if (bytes < 1024)
return bytes + "Bytes";
if (bytes < Math.Pow(1024, 2))
return (bytes / 1024).ToString("F" + 2) + "Kilobytes";
if (bytes < Math.Pow(1024, 3))
return (bytes / Math.Pow(1024, 2)).ToString("F" + 2) + "Megabytes";
if (bytes < Math.Pow(1024, 4))
return (bytes / Math.Pow(1024, 5)).ToString("F" + 2) + "Gygabytes";
return (bytes / Math.Pow(1024, 4)).ToString("F" + 2) + "terabytes";
}
private string DownloadingSpeed(long received, TimeSpan time)
{
return ((double) received / 1024 / 1024 / time.TotalSeconds).ToString("F" + 2) + " megabytes/sec";
}
private string DownloadingTime(long received, long total, TimeSpan time)
{
var receivedD = (double) received;
var totalD = (double) total;
return ((totalD / (receivedD / time.TotalSeconds)) - time.TotalSeconds).ToString("F" + 1) + "sec";
}
private void OnDownloadCompleted(object sender, AsyncCompletedEventArgs asyncCompletedEventArgs)
{
}
private void UpdateProgressPercentage(double percentage)
{
this.CurrentDownloadItem.Progress = percentage;
}
private void UpdateProgressBytesRead(long bytes, TimeSpan time)
{
this.CurrentDownloadItem.ProgressBytesRead = PrettyBytes(bytes);
this.CurrentDownloadItem.DownloadSpeed = DownloadingSpeed(bytes, time);
this.CurrentDownloadItem.TimeElapsed = DownloadingTime(bytes, this.CurrentDownloadItem.BytesTotal, time);
}
protected virtual void UpdateFileSize(long bytes)
{
this.CurrentDownloadItem.DisplayBytesTotal = PrettyBytes(bytes);
}
private void DownloadingError()
=> this.CurrentDownloadItem.ErrorMessage = "Downloading Error";
private static bool SelectFolder(string fileName, out string filePath)
{
var saveFileDialog = new SaveFileDialog
{
InitialDirectory = #"C:\Users\MusicMonkey\Downloads",
FileName = fileName,
Filter = "All files (*.*)|*.*",
};
filePath = "";
if (saveFileDialog.ShowDialog() != true)
return false;
filePath = saveFileDialog.FileName;
return true;
}
}
I highly recommend to move the SaveFileDialog and the interaction to the view. This way you would eliminate view model dependencies to view related operations or logic.
The refactored view model would look as followed:
class TestViewModel : INotifyPropertyChanged
{
private RelayCommand downloadCommand;
private RelayCommand cancelCommand;
private DownloadItem selectedGame;
public ObservableCollection<DownloadItem> Games { get; set; }
private Dictionary<DownloadItem, Downloader> DownloaderMap { get; set; }
public TestViewModel()
{
this.Games = new ObservableCollection<DownloadItem>();
this.DownloaderMap = new Dictionary<DownloadItem, Downloader>();
var game1 = new DownloadItem() {Name = "Name1"};
this.Games.Add(game1);
this.DownloaderMap.Add(game1, new Downloader());
var game2 = new DownloadItem() {Name = "Name2"};
this.Games.Add(game2);
this.DownloaderMap.Add(game2, new Downloader());
}
public DownloadItem SelectedGame
{
get => selectedGame;
set
{
if (value == selectedGame)
return;
selectedGame = value;
OnPropertyChanged(nameof(SelectedGame));
}
}
public RelayCommand DownloadCommand =>
downloadCommand ??
(downloadCommand = new RelayCommand((param) => DownloadButton_Click(param), (param) => true));
public RelayCommand CancelCommand =>
cancelCommand ??
(cancelCommand = new RelayCommand((param) => CancelButton_Click(param), (param) => true));
private void DownloadButton_Click(object obj)
{
if (string.IsNullOrWhiteSpace(this.SelectedGame.Url))
return;
if (this.DownloaderMap.TryGetValue(this.SelectedGame, out Downloader downloader))
{
downloader.DownloadFile(this.SelectedGame);
}
}
private void CancelButton_Click(object obj)
{
if (!string.IsNullOrWhiteSpace(this.SelectedGame.Url) &&
this.DownloaderMap.TryGetValue(this.SelectedGame, out Downloader downloader))
{
downloader.CancelDownloading();
}
}
}
Last step I updated the view's bindings to the new properties:
<Grid>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<Canvas Grid.Column="1"
Grid.ColumnSpan="3"
Grid.RowSpan="4">
<TextBox Grid.Row="0"
Grid.Column="1"
Grid.ColumnSpan="2"
Text="{Binding SelectedGame.Url, UpdateSourceTrigger=PropertyChanged}"
FontSize="40"
Width="424" />
<Button Grid.Row="0"
Grid.Column="3"
Content="DOWNLOAD"
FontSize="30"
FontFamily="./#Sochi2014"
Command="{Binding DownloadCommand}"
Canvas.Left="429"
Canvas.Top="-2"
Width="157" />
<Label Grid.Row="1"
Grid.Column="2"
Content="{Binding SelectedGame.ErrorMessage, Mode=OneWay}"
FontFamily="./#Sochi2014"
Height="45"
VerticalAlignment="Bottom"
Canvas.Left="401"
Canvas.Top="123"
Width="184" />
<CheckBox Grid.Row="1"
Grid.Column="1"
Grid.ColumnSpan="2"
FontSize="30"
Content="Open after downloading"
IsChecked="{Binding SelectedGame.IsOpenAfterDownloadEnabled, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
FontFamily="./#Sochi2014"
Canvas.Left="15"
Canvas.Top="80" />
<Button Grid.Row="1"
Grid.Column="3"
Content="CANCEL"
FontSize="30"
FontFamily="./#Sochi2014"
Command="{Binding CancelCommand}"
Canvas.Left="429"
Canvas.Top="50"
Width="157" />
<Label Grid.Row="2"
Grid.Column="1"
Content="{Binding SelectedGame.TimeElapsed, Mode=OneWay}"
FontSize="30"
FontFamily="./#Sochi2014"
Height="40"
Width="69"
Canvas.Left="310"
Canvas.Top="277"
RenderTransformOrigin="2.284,1.56" />
<Label Grid.Row="2"
Grid.Column="3"
Content="{Binding SelectedGame.DownloadSpeed, Mode=OneWay}"
FontSize="30"
FontFamily="./#Sochi2014"
Height="40"
Width="193"
Canvas.Left="15"
Canvas.Top="277" />
<ProgressBar Grid.Row="3"
Grid.Column="1"
Grid.ColumnSpan="2"
Value="{Binding SelectedGame.Progress}"
Foreground="#AAA1C8"
Height="75"
Width="424"
Canvas.Left="15"
Canvas.Top="335" />
<Label Grid.Row="3"
FontSize="30"
FontFamily="./#Sochi2014"
Content="{Binding SelectedGame.Progress}"
Grid.ColumnSpan="2"
Canvas.Left="230"
Canvas.Top="339" />
<Label Grid.Row="3"
Grid.Column="3"
Content="{Binding SelectedGame.ProgressBytesRead, Mode=OneWay}"
FontSize="30"
FontFamily="./#Sochi2014"
Height="40"
VerticalAlignment="Top"
Canvas.Left="448"
Canvas.Top="299"
Width="137" />
<Label Grid.Row="3"
Grid.Column="3"
Content="{Binding SelectedGame.DisplayBytesTotal, Mode=OneWay}"
FontSize="30"
FontFamily="./#Sochi2014"
Height="44"
Canvas.Left="448"
Canvas.Top="344"
Width="137" />
<Label Content="{Binding SelectedGame.Name}"
Height="40"
Width="186"
Canvas.Left="22"
Canvas.Top="202" />
</Canvas>
<ListBox x:Name="ListBox" Grid.Row="0"
Grid.Column="0"
Grid.RowSpan="4"
ItemsSource="{Binding Games}"
SelectedItem="{Binding SelectedGame, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectedIndex="0">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock FontSize="20"
Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
To enhance the view you could consider about creating a collection with the current active downloads and bind it to an ItemsControl. When you now move the layout into a ItemTemplate you would be able to display the progress for each download simultaneously without any switching necessary.
To sum it up: your design doesn't allow you to achieve your goal or makes it too complicated. After splitting your code into responsibilities and encapsulating certain behavior and attributes your goal is much easier to achieve. This is just a raw example of how a improved design can help to be more flexible when implementing requirements.
My problem, developing a shopping system on a Raspberry Pi 3 Bodel B, is as following:
First of all some context:
Raspberry Pi with Barcode Scanner is used as self-service shop
Employees scans a drink and the price on the screen is added up the price of the drink
Now a extension should be provided: A list of all currently scanned products should be shown.
Until now I only achieved, that every scanned product appears as one ListViewItem in the ListView
This is my Model
public partial class Product : object, System.ComponentModel.INotifyPropertyChanged {
private string DescriptionField;
private System.DateTime ExpirationDateField;
private int IDField;
private string NameField;
private decimal PriceField;
[System.Runtime.Serialization.DataMemberAttribute()]
public string Description {
get {
return this.DescriptionField;
}
set {
if ((object.ReferenceEquals(this.DescriptionField, value) != true)) {
this.DescriptionField = value;
this.RaisePropertyChanged("Description");
}
}
}
[System.Runtime.Serialization.DataMemberAttribute()]
public System.DateTime ExpirationDate {
get {
return this.ExpirationDateField;
}
set {
if ((this.ExpirationDateField.Equals(value) != true)) {
this.ExpirationDateField = value;
this.RaisePropertyChanged("ExpirationDate");
}
}
}
[System.Runtime.Serialization.DataMemberAttribute()]
public int ID {
get {
return this.IDField;
}
set {
if ((this.IDField.Equals(value) != true)) {
this.IDField = value;
this.RaisePropertyChanged("ID");
}
}
}
[System.Runtime.Serialization.DataMemberAttribute()]
public string Name {
get {
return this.NameField;
}
set {
if ((object.ReferenceEquals(this.NameField, value) != true)) {
this.NameField = value;
this.RaisePropertyChanged("Name");
}
}
}
[System.Runtime.Serialization.DataMemberAttribute()]
public decimal Price {
get {
return this.PriceField;
}
set {
if ((this.PriceField.Equals(value) != true)) {
this.PriceField = value;
this.RaisePropertyChanged("Price");
}
}
}
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName) {
System.ComponentModel.PropertyChangedEventHandler propertyChanged = this.PropertyChanged;
if ((propertyChanged != null)) {
propertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
}
}
This model describes a product which can be bought in our shop.
My XAML Snippet
<ListView Name="ProductSumUp"
AllowDrop="False"
SelectionMode="None"
CanDrag="False"
CanReorderItems="False"
CanDragItems="False"
Grid.Column="2"
HorizontalAlignment="Left"
Height="290"
Margin="10,10,0,0"
Grid.Row="1"
VerticalAlignment="Top"
Width="180"
RenderTransformOrigin="1.682,0.59"
Foreground="White"
FontFamily="Assets/Fonts/Baskerville.ttf#Baskerville">
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock FontFamily="Assets/Fonts/Baskerville.ttf#Baskerville"
Foreground="White">
<Run Text="{Binding Name}"/>
<Run x:Name="{Binding Price}"/>
</TextBlock>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Now my question is:
Is it possible, if a product was scanned twice, not to add a new ListViewItem, instead, the existing ListViewItem should be manipulated by adding the same Price on the existing Item.
I tried many possibilities and also asked some developers at my work but nobody could figure out.
If you need more information just ask.
Thanks in advance and excuse me for my horrible English grammar :)
So, I worked out a solution by myself with nice support from #JustinXL and #user230910.
I'll just post my code here, so you can see that I came to a solution.
XAML
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock
FontFamily="Assets/Fonts/Baskerville.ttf#Baskerville"
Foreground="White">
<Run Text="{Binding Path=groupedProduct.Name}"/>
<Run Text="{Binding PriceSum,
Converter={StaticResource PriceConverter},
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"/>
<LineBreak/>
<Run Text="{Binding Path=Count,
Converter={StaticResource StringFormatter},
ConverterParameter='Anzahl: {0}',
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
C# - Modelcode
public class ProductGroup : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void onPropertyChanged(object sender, string propertyName)
{
if (this.PropertyChanged != null)
{
PropertyChanged(sender, new PropertyChangedEventArgs(propertyName));
}
}
private decimal _priceSum;
private int _count;
public Product groupedProduct { get; set; }
public int Count
{
get
{
return _count;
}
set
{
_count = value;
onPropertyChanged(this, "Count");
}
}
public decimal Price { get; set; }
public decimal PriceSum
{
get { return _priceSum; }
set
{
_priceSum = value;
onPropertyChanged(this, "PriceSum");
}
}
}
C# - CollectionFill
ProductSumUp.ItemsSource = _showProducts;
bool prodExists = false;
foreach (ProductGroup prodz in _showProducts)
{
if (prodz.groupedProduct.ID == prod.ID)
{
prodExists = true;
}
}
if (!prodExists)
{
ProductGroup prodGroup = new ProductGroup();
prodGroup.groupedProduct = prod;
prodGroup.Price = prod.Price;
prodGroup.Count = 1;
prodGroup.PriceSum += prod.Price;
_showProducts.Add(prodGroup);
}
else
{
ProductGroup pgroup = _showProducts.First(x => x.groupedProduct.ID == prod.ID);
if (pgroup != null)
{
pgroup.Count++;
pgroup.PriceSum += pgroup.Price;
}
}
Please don't judge my programming style, I solved the problem quick and dirty.
I hope that somebody could use my solution for his/her problems and thanks for your help, it saved me a lot of time.
Am trying to bind 2 items to a xaml page.One is an observable collection of services the other is an instance of expert item.Am getting an error A first chance exception of type 'System.ArgumentException' occurred in myapp.Client.DLL when I try to raise an INotifyProperty changed for the expert item.
SummaryPage.xaml
//some fields ommitted for brevity Expert
<TextBlock Text="Full Name" HorizontalAlignment="Left"/>
<TextBlock Text="{Binding Expert.name,Mode=TwoWay}" Grid.Column="1" HorizontalAlignment="Center"/>
<TextBlock Text="Age" Grid.Row="1" HorizontalAlignment="Left"/>
<TextBlock Text="{Binding Expert.age}" Grid.Row="1" Grid.Column="1" HorizontalAlignment="Left"/>
<TextBlock Text="National ID:" Grid.Row="2" HorizontalAlignment="Left"/>
//observable list services
<ListView ItemsSource="{Binding Services}"
IsItemClickEnabled="False"
ScrollViewer.VerticalScrollMode="Enabled"
ScrollViewer.VerticalScrollBarVisibility="Visible"
TabIndex="1" Header="Service Request">
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Name}" HorizontalAlignment="Left"/>
<TextBlock Text="{Binding Cost}" Grid.Column="1" HorizontalAlignment="Center"/>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
SummaryPageViewModel.cs
public class SummaryPageViewModel : ViewModel, ISummaryPageViewModel
{
public ICommand ConfirmCommand { get; private set; }
public ExpertItem Expert { get { return this.GetValue<ExpertItem>(); } set { this.SetValue(value); } }
public ObservableCollection<ServiceItem> Services { get { return this.GetValue<ObservableCollection<ServiceItem>>(); } set { this.SetValue(value); } }
public SummaryPageViewModel()
{
}
public override void Initialize(IViewModelHost host)
{
base.Initialize(host);
// setup...
this.Services = new ObservableCollection<ServiceItem>();
this.Expert = new ExpertItem();
this.ConfirmCommand = new DelegateCommand((args) => GetSelected(args as CommandExecutionContext));
}
public override async void Activated(object args)
{
base.Activated(args);
var conn = Myapp.GetSystemDatabase();
using (this.EnterBusy())
{
IEnumerable<ServiceItem> servs = await conn.Table<ServiceItem>().ToListAsync();
foreach (ServiceItem s in servs)
this.Services.Add(s);
IEnumerable<ExpertItem> exps = await ExpertItem.GetAllFromCacheAsync();
this.Expert = await conn.Table<ExpertItem>().Where(v => v.NativeId == 3).FirstOrDefaultAsync();
}
}
ModelItem.cs
public abstract class ModelItem : INotifyPropertyChanged
{
private Dictionary<string, object> Values { get; set; }
protected ModelItem()
{
this.Values = new Dictionary<string, object>();
}
public event PropertyChangedEventHandler PropertyChanged;
protected T Get
Value<T>([CallerMemberName] string name = null)
{
if (this.Values.ContainsKey(name))
return (T)this.Values[name];
else
return default(T);
}
protected void SetValue(object value, [CallerMemberName] string name = null)
{
// set...
this.Values[name] = value;
// notify...
this.OnPropertyChanged(new PropertyChangedEventArgs(name));
}
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
this.OnPropertyChanged(new PropertyChangedEventArgs(name));
}
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, e);//error raised here e- property name expert.Value does not fall in expected range
}
}
ExpertItem.cs
//code omitted for brevity
public class ExpertItem : ModelItem
{
public int Id { get; set; }
public int NativeId { get { return this.GetValue<int>(); } set { this.SetValue(value); } }
public string name { get { return this.GetValue<string>(); } set { this.SetValue(value); } }
public int age { get { return this.GetValue<int>(); } set { this.SetValue(value); } }
public string email { get { return this.GetValue<string>(); } set { this.SetValue(value); } }
}
stacktrace
at System.ComponentModel.PropertyChangedEventHandler.Invoke(Object sender, PropertyChangedEventArgs e)
at MFundi.Client.ModelItem.OnPropertyChanged(PropertyChangedEventArgs e)
at MFundi.Client.ModelItem.SetValue(Object value, String name)
at MFundi.Client.SummaryPageViewModel.set_Expert(ExpertItem value)
at MFundi.Client.SummaryPageViewModel.<Activated>d__b.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.AsyncMethodBuilderCore.<ThrowAsync>b__3(Object state)
I have the following XAML for a list of data items:
<phone:LongListSelector x:Name="Port_SummaryList" ItemsSource="{Binding PortList}" ItemTemplate="{StaticResource PortfolioDataTemplate}"/>
The template is defined as this:
<phone:PhoneApplicationPage.Resources>
<DataTemplate x:Key="PortfolioDataTemplate">
<Grid d:DesignHeight="91.5" d:DesignWidth="439.875" Height="82">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="31*"/>
<ColumnDefinition Width="19*"/>
<ColumnDefinition Width="19*"/>
<ColumnDefinition Width="19*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="15*"/>
<RowDefinition Height="15*"/>
<RowDefinition Height="15*"/>
</Grid.RowDefinitions>
<TextBlock x:Name="PortfolioName" HorizontalAlignment="Left" Height="92" TextWrapping="Wrap" Text="{Binding Name}" VerticalAlignment="Top" Width="155" Grid.RowSpan="2" Margin="0,0,0,-10"/>
<TextBlock x:Name="NAV" Grid.Row="1" Grid.Column="1" HorizontalAlignment="Left" Height="31" TextWrapping="Wrap" Text="{Binding NAV, StringFormat='{}{0:C}'}" VerticalAlignment="Top" Width="95" Margin="0,-1,0,0"/>
<TextBlock x:Name="CostBasis" Grid.Row="2" Grid.Column="1" HorizontalAlignment="Left" Height="30" TextWrapping="Wrap" Text="{Binding Cost,StringFormat='{}{0:C}'}" VerticalAlignment="Top" Width="95" />
</Grid>
</DataTemplate>
</phone:PhoneApplicationPage.Resources>
and in my ViewModel I have this:
private TrulyObservableCollection<PortfolioModel> _PortList;
public TrulyObservableCollection<PortfolioModel> PortList
{
get { return _PortList; }
set
{
_PortList = value;
_PortList.CollectionChanged += _PortList_CollectionChanged;
RaisePropertyChanged("PortList");
}
}
void _PortList_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
RaisePropertyChanged("PortList");
}
The class "TrulyObservableCollection<>" is from this SO post.
The class "PortfolioModel" is defined as this:
public class PortfolioModel : INotifyPropertyChanged
{
public string Name { get; set; }
public DateTime Created { get; set; }
public int Id { get; set; }
public TrulyObservableCollection<CashModel> Cashflow;
public TrulyObservableCollection<HoldingModel> Positions;
public float Cost
{
get
{
float total_cost = 0.0f;
foreach (HoldingModel holding in Positions)
{
total_cost += holding.Share * holding.CostBasis;
}
return total_cost;
}
private set { ;}
}
//Numbers that are calculated with other variables, listed here for databinding purposes
public float NAV
{
get
{
float acc = 0.0f;
foreach (HoldingModel hm in Positions)
{
acc += hm.CurrentPrice * hm.Share;
}
foreach (CashModel cm in Cashflow)
{
acc += cm.Amount;
}
return acc;
}
set { ;}
}
public float DailyPercent { get; set; }
public float OverallPercent { get; set; }
public PortfolioModel()
{
Cashflow = new TrulyObservableCollection<CashModel>();
Cashflow.CollectionChanged += Cashflow_CollectionChanged;
Positions = new TrulyObservableCollection<HoldingModel>();
Positions.CollectionChanged += Positions_CollectionChanged;
}
void Positions_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
NotifyPropertyChanged("Positions");
NotifyPropertyChanged("NAV");
}
void Cashflow_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
NotifyPropertyChanged("Cashflow");
NotifyPropertyChanged("NAV");
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
The class "HoldingModel" is defined as this:
public class HoldingModel : INotifyPropertyChanged
{
private string _Ticker;
public string Ticker
{
get { return _Ticker; }
set { if (value != _Ticker) { _Ticker = value; NotifyPropertyChanged("Ticker"); } }
}
private string _CompanyName;
public string CompanyName
{
get { return _CompanyName; }
set { if (value != _CompanyName) { _CompanyName = value; NotifyPropertyChanged("CompanyName"); } }
}
private int _Share;
public int Share
{
get { return _Share; }
set { if (value != _Share) { _Share = value; NotifyPropertyChanged("Share"); } }
} //negative means short
public string LongShort
{
get { if (Share > 0) return "LONG"; else return "SHORT"; }
}
private float _Value;
public float Value
{
get { return _Value; }
set { if (value != _Value) { _Value = value; NotifyPropertyChanged("Value"); } }
}
public float Cost
{
get { return Share * CostBasis; }
set { ;}
}
private float _CostBasis;
public float CostBasis
{
get { return _CostBasis; }
set { if (value != _CostBasis) { _CostBasis = value; NotifyPropertyChanged("CostBasis"); } }
}
private float _RealizedGain;
public float RealizedGain
{
get { return _RealizedGain; }
set { _RealizedGain = value; NotifyPropertyChanged("RealizedGain"); }
}
private float _CurrentPrice;
public float CurrentPrice
{
get { return _CurrentPrice; }
set
{
_CurrentPrice = value;
NotifyPropertyChanged("CurrentPrice");
}
}
private float _CurrentChange;
public float CurrentChange
{
get { return _CurrentChange; }
set { _CurrentChange = value; NotifyPropertyChanged("CurrentChange"); }
}
ObservableCollection<TradeModel> Trades;
public HoldingModel()
{
Trades = new ObservableCollection<TradeModel>();
}
public void AddTrade(TradeModel trade)
{
//Order can't change, since RealizedGain relies on CostBasis and Share, CostBasis relies on Share
UpdateRealizedGain(trade);
UpdateCostBasis(trade);
Share += trade.Share;
trade.PropertyChanged += PropertyChanged;
Trades.Add(trade);
}
private void UpdateCostBasis(TradeModel trade)
{
if (trade.Share + Share == 0)
{
CostBasis = 0;
return;
}
float cost = CostBasis * Share;
cost += trade.Price * trade.Share;
CostBasis = cost / (Share + trade.Share);
}
private void UpdateRealizedGain(TradeModel trade)
{
if (trade.Share * Share > 0) return; //No realized gain on add-on
if (Math.Abs(trade.Share) > Math.Abs(Share))
{
RealizedGain += Share * (trade.Price - CostBasis);
}
else
{
RealizedGain += trade.Share * (trade.Price - CostBasis);
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
//Debug.WriteLine("symbol_user got property {0} changed, bubbling up", propertyName);
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
What I wanted to do is that, every time I update the CurrentPrice property in HoldingModel, I want to see the NAV property in PortfolioModel change, and reflect that in the view. I tried all I can but still unable to achieve that. Is there anything that I'm missing? Any help is appreciated
I've also noticed some problems with LongListSelector and ObservableCollection. I've posted it here:
Long List Selector Observable Collection and Visual Tree - problems?
Please check in your project something like this: leave the Page with back button and the reenter the page with LLS - if it's correctly displayed that mean we have the same problems, and I think it's the problem with LLS and we have to wait for WP 8.1. I assume that LLS is not correctly Updated (VisualTree doesn't change), because when I use normal ListBox everything works perfect.
Try to use ListBox (as you don't have grouping):
<ListBox x:Name="Port_SummaryList" ItemsSource="{Binding PortList}" ItemTemplate="{StaticResource PortfolioDataTemplate}"/>
If you don't see changes you can try call (in my project that function didn't worj with LLS but with LisBox works fine):
Port_SummaryList.UpdateLayout();
Try explicitly specifying Mode=OneWay in the NAV binding.
Text="{Binding NAV, Mode=OneWay, StringFormat='{}{0:C}'}"
I just had a case where the Mode behaved like it was defaulting to the Mode=OneTime. After explicitly setting Mode=OneWay, my data changes started to display. The BindingMode Enumeration documentation here suggests Mode=OneWay is implied. Recent experience suggests that may not always be the case.