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.
Related
I am working on an application that utilizes the NHL API. I have a page that displays the schedule. By default, it loads the current day's games. I have a DatePicker in the 'CollectionView.Header', then a Grid within the ScheduleTemplate to display the data. If I select a new date in the DatePicker, it fires off the Property "get" process and loads the latest data into my "ObservableGames" List as expected, but the Grid clears completely and I just have a DatePicker left with an empty screen.
SchedulePage.xaml View:
<RefreshView Command="{Binding UpdateScheduleCommand}" IsRefreshing="{Binding IsRefreshing}">
<CollectionView Margin="20"
ItemsSource="{Binding ObservableGames}"
ItemTemplate="{StaticResource ScheduleTemplate}"
SelectionMode="Single">
<CollectionView.Header>
<Grid>
<DatePicker Grid.ColumnSpan="4" HorizontalOptions="Center" Date="{Binding SelectedDatePickerDate, Mode=TwoWay}" />
</Grid>
</CollectionView.Header>
</CollectionView>
</RefreshView>
App.xaml DataTemplate:
<DataTemplate x:Key="ScheduleTemplate">
<Grid Padding="8" RowDefinitions="Auto,Auto" ColumnDefinitions=".1*,.5*,.2*,.2*">
<Image Grid.Row="0" Grid.Column="0" Source="{Binding AwayTeamLogo, Mode=TwoWay}" HeightRequest="20" WidthRequest="20" HorizontalOptions="Center" VerticalOptions="Center" />
<Label Grid.Row="0" Grid.Column="1" Text="{Binding AwayAbbreviatedName, Mode=TwoWay}" HorizontalOptions="Start" />
<Label Grid.Row="0" Grid.Column="2" Text="{Binding AwayGoals, Mode=TwoWay}" HorizontalOptions="End" />
<Label Grid.Row="0" Grid.Column="3" Text="{Binding GameStatusString, Mode=TwoWay}" HorizontalOptions="End" />
<Image Grid.Row="1" Grid.Column="0" Source="{Binding HomeTeamLogo, Mode=TwoWay}" HeightRequest="20" WidthRequest="20" HorizontalOptions="Center" VerticalOptions="Center" />
<Label Grid.Row="1" Grid.Column="1" Text="{Binding HomeAbbreviatedName, Mode=TwoWay}" HorizontalOptions="Start" />
<Label Grid.Row="1" Grid.Column="2" Text="{Binding HomeGoals, Mode=TwoWay}" HorizontalOptions="End" />
<Grid.GestureRecognizers>
<TapGestureRecognizer
NumberOfTapsRequired="1"
Command="{Binding Source={RelativeSource AncestorType={x:Type viewModels:ScheduleViewModel}}, Path=GameTapped}"
CommandParameter="{Binding .}">
</TapGestureRecognizer>
</Grid.GestureRecognizers>
</Grid>
</DataTemplate>
ViewModel:
class ScheduleViewModel : INotifyPropertyChanged
{
public DateTime? _selectedDatePickerDate;
bool isRefreshing;
public ObservableCollection<Game> ObservableGames { get; private set; }
public DateTime DatePickerDate { get; set; }
public DateTime CurrentScheduleDate { get; set; }
public Command<Game> GameTapped { get; }
public Command<DateTime?> DatePickerTapped { get; }
public ICommand UpdateScheduleCommand => new Command(async () => await SetScheduleDataAsync());
public bool IsRefreshing
{
get
{
return isRefreshing;
}
set
{
isRefreshing = value;
OnPropertyChanged();
}
}
public DateTime? SelectedDatePickerDate
{
get => _selectedDatePickerDate;
set
{
SetProperty(ref _selectedDatePickerDate, value);
OnDatePickerSelected(value);
}
}
public ScheduleViewModel()
{
ObservableGames = new ObservableCollection<Game>();
GameTapped = new Command<Game>(OnGameSelected);
DatePickerTapped = new Command<DateTime?>(OnDatePickerSelected);
OnPropertyChanged(nameof(ObservableGames));
DatePickerDate = DateTime.Today;
SetScheduleDataAsync();
}
protected bool SetProperty<T>(ref T backingStore, T value, [CallerMemberName] string propertyName = "", Action onChanged = null)
{
if (EqualityComparer<T>.Default.Equals(backingStore, value))
return false;
backingStore = value;
onChanged?.Invoke();
OnPropertyChanged(propertyName);
return true;
}
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
async void OnGameSelected(Game game)
{
await Shell.Current.GoToAsync($"scheduledetails?gameLink={game.link}");
}
private void OnDatePickerSelected(DateTime? gameDate)
{
if (null != gameDate)
{
DatePickerDate = gameDate.Value;
SetScheduleDataAsync();
}
}
private async Task SetScheduleDataAsync()
{
IsRefreshing = true;
if (DatePickerDate != CurrentScheduleDate)
{
ObservableGames.Clear();
ScheduleData.LeagueSchedule = ScheduleData.GetSchedule(-1, DatePickerDate, null, string.Empty);
try
{
if ((null != ScheduleData.LeagueSchedule) && (null != ScheduleData.LeagueSchedule.dates) && (ScheduleData.LeagueSchedule.dates.Count > 0)
&& (null != ScheduleData.LeagueSchedule.dates[0]) && (ScheduleData.LeagueSchedule.dates[0].games.Count > 0))
{
ObservableGames = new ObservableCollection<Game>(ScheduleData.LeagueSchedule.dates[0].games);
for (int i = 0; i < ObservableGames.Count; i++)
{
if ((null != ObservableGames[i]) && (null != ObservableGames[i].status) && (!string.IsNullOrEmpty(ObservableGames[i].status.detailedState)))
{
switch (ObservableGames[i].status.detailedState.ToLower())
{
case "final":
ObservableGames[i].AwayGoals = ObservableGames[i].teams.away.score;
ObservableGames[i].HomeGoals = ObservableGames[i].teams.home.score;
ObservableGames[i].GameStatusString = ObservableGames[i].status.detailedState;
break;
case "in progress":
LiveGame liveGame = GameData.GetLiveGame(ObservableGames[i].link);
if (null != liveGame)
{
ObservableGames[i].AwayGoals = liveGame.liveData.linescore.teams.away.goals;
ObservableGames[i].HomeGoals = liveGame.liveData.linescore.teams.home.goals;
ObservableGames[i].GameStatusString = liveGame.liveData.linescore.currentPeriodTimeRemaining + " " + liveGame.liveData.linescore.currentPeriodOrdinal;
}
break;
case "postponed":
ObservableGames[i].GameStatusString = "Postponed";
break;
default:
ObservableGames[i].GameStatusString = ObservableGames[i].gameDate.ToLocalTime().ToShortTimeString();
break;
}
}
}
}
}
catch (Exception e)
{
}
finally
{
CurrentScheduleDate = DatePickerDate;
IsRefreshing = false;
}
}
}
}
I have tried using multiple Views, moved logic around and updated access modifiers. I have even debugged and the ObservableGames object does get updated with the new set of games based on the DatePicker Selection... . I just don't see it in the Grid.
For some reason my bindings in my program doesn't work for all my properties.
View
<StackPanel
Grid.Row="0"
Orientation="Horizontal" Margin="0,0,0,5" Grid.ColumnSpan="2">
<TextBlock
Margin="10"
VerticalAlignment="Center"
Text="Camera device: "
Foreground="White"/>
<ComboBox
Name="CameraDevices"
ItemsSource="{Binding ConnectedDevices}" <!-- This binds fine -->
SelectedItem="{Binding SelectedCamera}" <!-- This binds fine -->
Width="168"
VerticalAlignment="Center">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical" >
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" /> <!-- This binds fine -->
</StackPanel>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
<TextBlock Grid.Row="2" Grid.Column="1" x:Name="SelectedCamera_Name" Foreground="White"/> <!-- This binds fine -->
<StackPanel
Grid.Row="2"
HorizontalAlignment="Center"
Orientation="Horizontal" Width="100" Margin="58,0,59,0">
<Button
x:Name="ToggleButton"
Width="80"
Margin="10"
Height="25"
Content="Start" <!-- This binds to the function Start in my viewmodel (not shown)-->
/>
</StackPanel>
<Border
x:Name="devicecamContainer"
Grid.Column="0"
Grid.Row="3"
BorderBrush="Black"
BorderThickness="0">
<Image Source="{Binding Path=CameraImage, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Top" /> <!-- DOES NOT WORK -->
</Border>
</Grid>
ViewModel
public class UIViewModel : Screen
{
#region Properties
public BindableCollection<CameraModel> ConnectedDevices { get; set; }
private CameraModel _selectedCamera;
public CameraModel SelectedCamera
{
get { return _selectedCamera; }
set
{
_selectedCamera = value;
NotifyOfPropertyChange(() => SelectedCamera);
}
}
private ImageSource _cameraImage;
public ImageSource CameraImage
{
get { return _cameraImage; }
set { _cameraImage = value;
System.Diagnostics.Debug.WriteLine("Updated " + _cameraImage);
NotifyOfPropertyChange(() => CameraImage);
}
}
public UIViewModel()
{
ConnectedDevices = new BindableCollection<CameraModel>(CameraDevicesEnumerator.GetAllConnectedCameras());
}
public async void ToggleButton() //It's the same button for start/stop
{
if (_started == false)
{
_started = true;
var selectedCameraDeviceId = SelectedCamera.OpenCvId;
if ((_camera == null || _camera.CameraDeviceId != selectedCameraDeviceId))
{
_camera?.Dispose();
_camera = new CameraStreaming(cameraDeviceId: SelectedCamera.OpenCvId);
}
try
{
await _camera.StartCamera(true);
await _ultraSound.StartCamera(false);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
LoadingVisibility = false;
}
else
{
_started = false; // To be implemented
}
}
(Both have been edited down a bit, to only show relevant info)
The CameraImage is set in a helperclass, but It's set to the correct value, and updated as expected. But for some reason it doesn't show in my view.
I've tried binding it in a few different ways:
<Image Source="{Binding=CameraImage" />
<Image Source="{Binding=CameraImage, UpdateSourceTrigger=PropertyChanged}" />
<Image Source="{Binding Path=CameraImage}" />
<Image Source="{Binding Path=CameraImage, UpdateSourceTrigger=PropertyChanged}" />
<Image x:Name="CameraImage" />
CameraStreaming - *Helper class*
public sealed class CameraStreaming : IDisposable
{
private System.Drawing.Bitmap _lastFrame;
private Task _previewTask;
UIViewModel viewModel = new UIViewModel();
private CancellationTokenSource _cancellationTokenSource;
public int CameraDeviceId { get; private set; }
public CameraStreaming(int cameraDeviceId)
{
CameraDeviceId = cameraDeviceId;
}
public async Task StartCamera(bool crop)
{
if (_previewTask != null && !_previewTask.IsCompleted)
return;
var initializationSemaphore = new SemaphoreSlim(0, 1);
_cancellationTokenSource = new CancellationTokenSource();
_previewTask = Task.Run(async () =>
{
try
{
var videoCapture = new VideoCapture();
if (!videoCapture.Open(CameraDeviceId))
{
throw new ApplicationException("Cannot connect to camera");
}
using (var frame = new Mat())
{
while (!_cancellationTokenSource.IsCancellationRequested)
{
videoCapture.Read(frame);
if (!frame.Empty())
{
if (initializationSemaphore != null)
initializationSemaphore.Release();
_lastFrame = BitmapConverter.ToBitmap(frame);
var lastFrameBitmapSource = _lastFrame.ToBitmapSource();
lastFrameBitmapSource.Freeze();
viewModel.CameraImage = lastFrameBitmapSource;
}
await Task.Delay(10);
}
}
videoCapture?.Dispose();
}
finally
{
if (initializationSemaphore != null)
initializationSemaphore.Release();
}
}, _cancellationTokenSource.Token);
await initializationSemaphore.WaitAsync();
initializationSemaphore.Dispose();
initializationSemaphore = null;
if (_previewTask.IsFaulted)
{
// To let the exceptions exit
await _previewTask;
}
}
EDIT! Posted my CameraStreaming-class
None of them works. My other bindings, which are in the same ViewModel works just fine.
What am I doing wrong?
I'm working on a UWP App using the Template 10, and I don't manage to get the UI updated after a property is changed in the ViewModel. I tried to implement the Bindable base at the Model, but still doesn't work.
XAML:
<Page.DataContext>
<vm:RoomPageViewModel x:Name="ViewModel" />
</Page.DataContext>
<Grid x:Name="RoomProperties"
RelativePanel.Below="pageHeader"
RelativePanel.AlignLeftWithPanel="True">
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Image Grid.Column="0" Width="220" Height="220" Stretch="Fill" Source="{x:Bind ViewModel.Room.Image}"></Image>
<TextBlock Grid.Column="1" FontSize="16" Text="{x:Bind ViewModel.Room.Name}"></TextBlock>
<TextBlock Grid.Column="0" Grid.Row="1" FontSize="16" Text="Room Type: "></TextBlock>
<TextBlock Grid.Column="1" Grid.Row="1" FontSize="16" Text="{x:Bind ViewModel.Room.Type}"></TextBlock>
<TextBlock Grid.Column="0" Grid.Row="2" FontSize="16" Text="Room Number: "></TextBlock>
<TextBlock Grid.Column="1" Grid.Row="2" FontSize="16" Text="{x:Bind ViewModel.Room.Number}"></TextBlock>
</Grid>
<ListView x:Name="SensorListView"
ItemsSource="{x:Bind ViewModel.Room.Sensors}"
IsEnabled="False"
RelativePanel.Below="RoomProperties"
RelativePanel.AlignLeftWithPanel="True">
<ListView.ItemTemplate>
<DataTemplate x:DataType="data:Sensor">
<StackPanel HorizontalAlignment="Left">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" FontSize="16" Text="{x:Bind Name}"></TextBlock>
<TextBlock Grid.Column="1" FontSize="16" Text="{x:Bind SensorValues[0].Value, Mode=TwoWay}"></TextBlock>
<TextBlock Grid.Column="2" FontSize="16" Text="{x:Bind Units}"></TextBlock>
</Grid>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
ViewModel:
public class RoomPageViewModel : ViewModelBase
{
Template10.Services.SerializationService.ISerializationService _SerializationService;
private FileIOHelper.FileIOHelper fileIOHelper = new FileIOHelper.FileIOHelper();
private AppServiceConnection serialCommandService;
// This method is called by the Set accessor of each property.
// The CallerMemberName attribute that is applied to the optional propertyName
// parameter causes the property name of the caller to be substituted as an argument.
public RoomPageViewModel()
{
if (Windows.ApplicationModel.DesignMode.DesignModeEnabled)
{
Room = Room.CreateNewRoom();
}
}
private Room room = Room.CreateNewRoom();
public Room Room
{
get
{
return this.room;
}
set
{
Set(ref room, value);
}
}
public void UpdateRoom()
{
foreach (var sensor in Room.Sensors)
{
var sensorValue = new SensorValue();
sensorValue.Sensor = "R" + Room.Number + "D" + sensor.DeviceNumber + "S" + sensor.Type;
ObservableCollection<SensorValue> newList = fileIOHelper.ReadFromFile(sensorValue).ToObservableCollection();
SensorValue newSensorValue = newList.Last();
sensor.SensorValues = new ObservableCollection<SensorValue> { newSensorValue };
}
foreach (var actuator in Room.Actuators)
{
var actuatorValue = ActuatorValue.CreateNewActuatorValue();
actuatorValue.Actuator = "R" + Room.Number + "D" + actuator.DeviceNumber + "A" + actuator.Type;
ObservableCollection<ActuatorValue> newList = fileIOHelper.ReadFromFile(actuatorValue).ToObservableCollection();
ActuatorValue newActuatorValue = newList.Last();
actuator.ActuatorValues = new ObservableCollection<ActuatorValue> { newActuatorValue };
}
}
public async void RefreshButton_Click(object sender, object parameter)
{
Random rnd = new Random();
Room = Room.CreateNewRoom(rnd.Next(1, 9));
//UpdateRoom();
await Task.CompletedTask;
}
Model:
public class Room : BindableBase
{
private string name;
private string image;
private string type;
private int number;
public string Name
{
get
{
return name;
}
set
{
Set(ref name, value);
}
}
public string Image
{
get
{
return image;
}
set
{
Set(ref image, value);
}
}
public string Type
{
get
{
return type;
}
set
{
Set(ref type, value);
}
}
public int Number
{
get
{
return number;
}
set
{
Set(ref number, value);
}
}
private ObservableCollection<Sensor> sensors;
private ObservableCollection<Actuator> actuators;
public ObservableCollection<Sensor> Sensors
{
get
{
return sensors;
}
set
{
Set(ref sensors, value);
}
}
public ObservableCollection<Actuator> Actuators
{
get
{
return actuators;
}
set
{
Set(ref actuators, value);
}
}
public Room() {
Random rnd = new Random();
Name = "DefaultName";
Image = "DefaultImage";
Type = "DefaultType";
Number = rnd.Next(1,9);
Sensors = new ObservableCollection<Sensor>();
Actuators = new ObservableCollection<Actuator>();
}
public Room(int inputNumber)
{
Name = "DefaultName";
Image = "DefaultImage";
Type = "DefaultType";
Number = inputNumber;
Sensors = new ObservableCollection<Sensor>();
Actuators = new ObservableCollection<Actuator>();
}
public static Room CreateNewRoom(int inputNumber)
{
return new Room(inputNumber);
}
}
I used this guide for documentation for the implementation (https://github.com/Windows-XAML/Template10/wiki/MVVM). Any idea on why the UI is not getting updated? Thanks.
A mistake most people (including myself) often make when being used to the 'old' Binding syntax is that x:Bind has OneTime binding as default instead of OneWay binding.
Mode: Specifies the binding mode, as one of these strings: "OneTime", "OneWay", or "TwoWay". The default is "OneTime". Note that this differs from the default for {Binding}, which is "OneWay" in most cases.
Source: MSDN
What you need for your binding updates to work is:
Using INotifyPropertyChanged, this is handled by BindableBase.
Set the correct mode, e.g.
Text="{x:Bind ViewModel.Room.Number, Mode=OneWay}
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.
I'm new to WPF and am working on Databinding a Listbox from a xml file, everything loads correctly when the program starts, however I'm having trouble making the listbox update after I insert a new record. Everything that I've read is pointing to use a ObservableCollection which I am, but I can't figure out how to get the Listbox to refresh. I have tried calling a update to the ItemsSource but it still doesn't seem to work. Ideally I would like to just have a Refresh button that A user can click to update the listbox. Does anyone have any suggestions on a calling a update to the list box
Thanks Michael
public class ContactList
{
string contactFile = #"U:\Peridot\Users\" + Program.getUser.ToString() + ".xml";
public ContactList()
{
}
public ContactList(string contactFullName, string contactCellNumber,string contactBusinessNumber, string contactExtension, string contactEmail, string contactStatus,string contactAuralinkStatus, string contactAuralinkID)
{
this.ContactFullName = contactFullName;
this.ContactCellNumber = contactCellNumber;
this.ContactBusinessNumber = contactBusinessNumber;
this.ContactExtension = contactExtension;
this.ContactEmail = contactEmail;
this.ContactStatus = contactStatus;
this.ContactAuralinkStatus = contactAuralinkStatus;
this.ContactAuralinkID = contactAuralinkID;
}
private string ContactFullName;
public string PropContactFullName
{
get { return ContactFullName; }
set { ContactFullName = value; }
}
private string ContactCellNumber;
public string PropContactCellNumber
{
get { return ContactCellNumber; }
set { ContactCellNumber = value; }
}
private string ContactBusinessNumber;
public string PropContactBusinessNumber
{
get { return ContactBusinessNumber; }
set { ContactBusinessNumber = value; }
}
private string ContactEmail;
public string PropContactEmail
{
get { return ContactEmail; }
set { ContactEmail = value; }
}
private string ContactStatus;
public string PropContactStatus
{
get { return ContactStatus; }
set { ContactStatus = value; }
}
private string ContactAuralinkStatus;
public string PropContactAuralinkStatus
{
get { return ContactAuralinkStatus; }
set { ContactAuralinkStatus = value; }
}
public string ContactAuralinkID;
public string PropContactAuralinkID
{
get { return ContactAuralinkID; }
set { ContactAuralinkID = value; }
}
private string ContactExtension;
public string PropContactExtension
{
get { return ContactExtension; }
set { ContactExtension = value; }
}
}
public class Contacts : System.Collections.ObjectModel.ObservableCollection<ContactList>
{
string contactFile = #"U:\Peridot\Users\" + Program.getUser.ToString() + ".xml";
//Added this
public event NotifyCollectionChangedEventHandler CollectionChanged;
protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (CollectionChanged != null)
{
CollectionChanged(this, e);
}
}
public Contacts(): base()
{
getContactFile();
XDocument doc = XDocument.Load(contactFile);
var contacts = from r in doc.Descendants("Contact")
select new
{
FullName = r.Element("FullName").Value,
CellNumber = r.Element("CellNumber").Value,
BusinessNumber = r.Element("BusinessNumber").Value,
Extension = r.Element("Extension").Value,
Email = r.Element("Email").Value,
AuralinkID = r.Element("AuralinkID").Value
};
foreach (var r in contacts)
{
Add(new ContactList(r.FullName,r.CellNumber , r.BusinessNumber,r.Extension, r.Email, "", "",r.AuralinkID));
}
}
private void getContactFile()
{
if (!File.Exists(contactFile))
{
new XDocument(
new XElement("Contacts"
)
)
.Save(contactFile);
}
}
}
private void addContactICON_MouseDown(object sender, MouseButtonEventArgs e)
{
if (!doesContactExist())
{
try
{
XDocument doc = XDocument.Load(#"U:\Peridot\Users\" + Program.getUser.ToString() + ".xml");
XElement contact = new XElement("Contact");
contact.Add(new XElement("ContactID", contactID.ToString()));
contact.Add(new XElement("FullName", contactNameLBL.Content.ToString()));
contact.Add(new XElement("CellNumber", c1.Content.ToString()));
contact.Add(new XElement("BusinessNumber", businessPhoneIcon.ToolTip.ToString()));
contact.Add(new XElement("Extension", c3.Content.ToString()));
contact.Add(new XElement("Email", emailIcon.ToolTip.ToString()));
contact.Add(new XElement("AuralinkID", videoIcon.ToolTip.ToString()));
doc.Element("Contacts").Add(contact);
doc.Save(#"U:\Peridot\Users\" + Program.getUser.ToString() + ".xml");
MessageBox.Show(contactNameLBL.Content.ToString() + " has been added to your contacts.");
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
else
MessageBox.Show("Contact Already Exists");
}
XAML
<StackPanel>
<StackPanel.Resources>
<local:Contacts x:Key="contactListobj"></local:Contacts>
</StackPanel.Resources>
<ListBox x:Name="contactList" Width="305" Margin="5,3,0,0" VerticalAlignment="Top" HorizontalAlignment="Left" ItemsSource="{Binding Source={StaticResource contactListobj}}" Height="450" IsSynchronizedWithCurrentItem="True">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" >
<TextBlock Text="{Binding PropContactFullName}" ToolTip="{Binding PropContactFullName}" Height="35" Width="175" FontSize="12"/>
<TextBlock x:Name="contactEmailLBL" Text="{Binding PropContactEmail}" ToolTip="{Binding PropContactEmail}" Cursor="Hand" Width="30" Height="35" MouseLeftButtonUp="contactEmailLBL_MouseLeftButtonUp" Foreground="{x:Null}" FontSize="1">
<TextBlock.Background>
<ImageBrush Stretch="Uniform" ImageSource="Images/emailICON.png"/>
</TextBlock.Background>
</TextBlock>
<TextBlock x:Name="cellNumberLBL" Text="{Binding PropContactCellNumber}" ToolTip="{Binding PropContactCellNumber}" Cursor="Hand" MouseLeftButtonUp="cellNumberLBL_MouseLeftButtonUp" Width="30" Height="35" Foreground="{x:Null}" FontSize="1">
<TextBlock.Background>
<ImageBrush Stretch="Uniform" ImageSource="Images/mobilePhoneICON.png"/>
</TextBlock.Background>
</TextBlock>
<TextBlock x:Name="businessNumberLBL" Text="{Binding PropContactBusinessNumber}" ToolTip="{Binding PropContactBusinessNumber}" Cursor="Hand" Width="30" Height="35" MouseLeftButtonUp="businessNumberLBL_MouseLeftButtonUp" Foreground="{x:Null}" FontSize="1">
<TextBlock.Background>
<ImageBrush Stretch="Uniform" ImageSource="Images/BusinessPhoneICON.png"/>
</TextBlock.Background>
</TextBlock>
<TextBlock x:Name="auralinkLBL" Text="{Binding PropContactAuralinkID}" ToolTip="{Binding PropContactAuralinkID}" Cursor="Hand" Width="30" Height="35" Foreground="{x:Null}" FontSize="1" MouseLeftButtonUp="auralinkLBL_MouseLeftButtonUp">
<TextBlock.Background>
<ImageBrush Stretch="Uniform" ImageSource="Images/VideoICON.png"/>
</TextBlock.Background>
</TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
From what I can tell based on the source code for ObservableCollection, the problem is most likely that the Add method you are using to add ContactList objects to your ObservableCollection is part of the Collection class that ObservableCollection inherits from. This does not fire the CollectionChanged event on the ObservableCollection so your binding is never notified that the collection has changed. Try calling the OnCollectionChanged protected method after you add each item to the collection.