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.
Related
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?
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.
I'm fairy new to Xamarin, and I trying to do a switching List. So when button is pressed, it switches to another list. I have CustomLists class which wraps all this lists and exposes ChosenList property, which gives access to list currenly being displayed. When entry in List is deleted Command property gets called
public ICommand DeleteTest
{
get { return new Command<TaskRecord>((s) => OnDelete(s)); }
}
void OnDelete(TaskRecord task)
{
List.Remove(task);
IsUnfinishedChanged_ = !task.IsFinished;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("ChosenList"));
System.Diagnostics.Debug.Print("Deleting..");
}
When I delete entry in a first list (those shown at the programm start) it works fine. But in a second, ListView doesn't update for some reason
Here's my XAML code
<ListView ItemsSource="{Binding ChosenList}" IsPullToRefreshEnabled="True"
Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="2" SeparatorColor="DimGray">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.ContextActions>
<MenuItem Text="Delete" IsDestructive="true"
Command="{Binding Source={x:Reference MainGrid},
Path=BindingContext.DeleteTest}"
CommandParameter="{Binding .}"/>
</ViewCell.ContextActions>
<StackLayout Padding="15,0" VerticalOptions="Center">
<Label Text="{Binding Path=Name}" FontSize="Large" Font="Arial"
FontAttributes="Bold" VerticalOptions="Center"/>
<Label Text="{Binding Path=ShortDescr}" FontSize="Micro" Font="Arial"
VerticalOptions="Center"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Edit: ChosenList is wrapper for ListView to bind to
public List<TaskRecord> ChosenList
{
get
{
return (IsAll_ ? List : Unfinished);
}
}
Edit 2: Putting the whole code up here
private List<TaskRecord> List { get; set; }
private List<TaskRecord> UnfinishedCache_;
private List<TaskRecord> Unfinished
{
get
{
if (IsUnfinishedChanged_)
{
UnfinishedCache_ = new List<TaskRecord>();
foreach (TaskRecord task in List)
{
if (!task.IsFinished) UnfinishedCache_.Add(task);
}
IsUnfinishedChanged_ = false;
}
return UnfinishedCache_;
}
set { UnfinishedCache_ = value; }
}
private bool IsUnfinishedChanged_=true;
private bool IsAll_;
public ICommand ListChangeCommand
{
get { return new Command<string>((s)=>OnListSwitch(s)); }
}
void OnListSwitch(string senderText)
{
if (senderText == "All" && !IsAll_)
{
IsAll_ = true;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("ChosenList"));
}
else if (senderText == "Unfinished" && IsAll_)
{
IsAll_ = false;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("ChosenList"));
}
}
public ICommand DeleteTest
{
get { return new Command<TaskRecord>((s) => OnDelete(s)); }
}
void OnDelete(TaskRecord task)
{
List.Remove(task);
IsUnfinishedChanged_ = !task.IsFinished;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("ChosenList"));
System.Diagnostics.Debug.Print("Deleting..");
}
public event PropertyChangedEventHandler PropertyChanged;
public List<TaskRecord> ChosenList
{
get
{
return (IsAll_ ? List : Unfinished);
}
}
I am trying to filter my ListView based on my TextBox but it's not firing for some reason. Running the debugger, I can see the input text to be changed but it's not reflected in the ListView.
My Main View Model:
projectCollection = new CollectionViewSource();
projectCollection.Source = Projects;
projectCollection.Filter += projectCollection_Filter;
}
public ICollectionView SourceCollection
{
get
{
return this.projectCollection.View;
}
}
public string FilterText
{
get
{
return filterText;
}
set
{
filterText = value;
this.projectCollection.View.Refresh();
RaisePropertyChanged("SearchProjectsText");
}
}
private void projectCollection_Filter(object sender, FilterEventArgs e)
{
if (string.IsNullOrEmpty(FilterText))
{
e.Accepted = true;
return;
}
Project project = e.Item as Project;
if (project.Name.ToUpper().Contains(FilterText.ToUpper()) || project.Path.ToUpper().Contains(FilterText.ToUpper()))
{
e.Accepted = true;
}
else
{
e.Accepted = false;
}
}
public void RaisePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
And my relevant XAML:
<TextBox x:Name="SearchProjectsBox" Grid.Column="5" Background="White" Grid.Row="1" Text="{Binding FilterText, UpdateSourceTrigger=PropertyChanged}"
Margin="47.333,0,0,654.333" VerticalContentAlignment="Center" Foreground="Black" Padding="6" FontSize="16" HorizontalAlignment="Left" Width="268"/>
<ListView x:Name="ProjectListView" Margin="0,0,10,0" ItemsSource="{Binding ElementName=Hierarchy, Path=SelectedItem.GetAllMembers, UpdateSourceTrigger=PropertyChanged}" FontSize="16" Foreground="Black">
Edit. Here is the XAML.
<TextBox x:Name="SearchProjectsBox" Grid.Column="5" Background="White" Grid.Row="1" Text="{Binding FilterText, UpdateSourceTrigger=PropertyChanged}"
Margin="47.333,0,0,654.333" VerticalContentAlignment="Center" Foreground="Black" Padding="6" FontSize="16" HorizontalAlignment="Left" Width="268"/>
<TreeView x:Name="Hierarchy" Grid.Column="4" HorizontalAlignment="Left" Height="631" Margin="0,58,0,0" Grid.Row="1" VerticalAlignment="Top" Width="226"
ItemsSource="{Binding Path=Projects}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding ChildFolders}">
<StackPanel Orientation="Horizontal" >
<Image Source="{Binding Icon}" MouseUp="SelectedFolder_Event" Margin="5, 5, 5, 5"></Image>
<TextBox x:Name="FolderNames" Text="{Binding Name, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" BorderThickness="0" FontSize="16" Margin="5" IsReadOnly="True" PreviewMouseDoubleClick="SelectAll" LostFocus="TextBoxLostFocus"/>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
And here is my View Model template.
private ObservableCollection<Project> projects;
private string filterText;
private CollectionViewSource projectCollection;
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<Project> Projects
{
get { return projects; }
set
{
if (value != projects)
{
projects = value;
OnPropertyChanged("Projects");
}
}
}
public string FilterText
{
get
{
return filterText;
}
set
{
filterText = value;
this.projectCollection.View.Refresh();
OnPropertyChanged("SearchProjectsText");
}
}
private void projectCollection_Filter(object sender, FilterEventArgs e)
{
if (string.IsNullOrEmpty(FilterText))
{
e.Accepted = true;
return;
}
Project project = e.Item as Project;
if (project.Name.ToUpper().Contains(FilterText.ToUpper()) || project.Path.ToUpper().Contains(FilterText.ToUpper()))
{
e.Accepted = true;
}
else
{
e.Accepted = false;
}
}
public void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
The methods above are the ones that are used to filter and update the List View based on the TextBox's text.
Simple Example for using filters in CollectionView. Refer below code.
<Grid >
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Filter"/>
<TextBox Text="{Binding TextValue, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Width="200"/>
</StackPanel>
<ListBox ItemsSource="{Binding EmpView}" DisplayMemberPath="Name"/>
</StackPanel>
</Grid>
class ViewModel : INotifyPropertyChanged
{
private string myVar;
public string TextValue
{
get { return myVar; }
set { myVar = value;
OnPropertyChanged("TextValue"); EmpView.Refresh();}
}
public ICollectionView EmpView { get; set; }
public List<Employee> Employees { get; set; }
public ViewModel()
{
Employees = new List<Employee>()
{
new Employee() {Name = "Test1"},
new Employee() {Name = "Version2"},
new Employee() {Name = "Test2"},
new Employee() {Name = "Version4"},
new Employee() {Name = "Version5"}
};
EmpView = CollectionViewSource.GetDefaultView(Employees);
EmpView.Filter = Filter;
}
private bool Filter(object emp)
{
if (string.IsNullOrWhiteSpace(TextValue))
{
return true;
}
var emp1 = emp as Employee;
return TextValue != null && (emp1 != null && emp1.Name.Contains(TextValue));
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
class Employee
{
public string Name { get; set; }
}
So I have done binding before and I'm not sure what is wrong with my binding this time. Can someone please explain to me why I can't bind my labels to the appropriate properties? The listbox works perfectly and as expected but I don't know why the labels aren't working.
XAML
<Label Content="{Binding MetricName}" Height="25"></Label>
<Label Content="{Binding MetricUnit}" Height="25"></Label>
<ListBox x:Name="EconomicSelection" Grid.Column="1" HorizontalAlignment="Left" Height="150" Margin="5,5,0,5" VerticalAlignment="Top" Width="399" FontFamily="{DynamicResource FontFamily}" FontSize="11" ItemsSource="{Binding EconomicBenchmarks}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label Content="P" Grid.Column="0"/>
<TextBox Text="{Binding Percentile}" Grid.Column="0" Width="30"/>
<Label Content="Value: " Grid.Column="1"/>
<TextBox Text="{Binding Value}" Grid.Column="1"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
C#
private Metric economicMetric;
public Metric EconomicMetric
{
get { return economicMetric; }
set
{
if (value == economicMetric) return;
economicMetric = value;
OnPropertyChanged();
}
}
private string metricName;
public string MetricName
{
get { return metricName; }
set
{
if (value == metricName) return;
metricName = value;
OnPropertyChanged();
}
}
private string metricUnit;
public string MetricUnit
{
get { return metricUnit; }
set
{
if (value == metricUnit) return;
metricUnit = value;
OnPropertyChanged();
}
}
private ObservableCollection<EconomicBenchmark> economicBenchmarks = new ObservableCollection<EconomicBenchmark>();
public ObservableCollection<EconomicBenchmark> EconomicBenchmarks
{
get { return economicBenchmarks; }
}
public EconomicMeasuresTable()
{
InitializeComponent();
}
public event PropertyChangedEventHandler PropertyChanged;
public event EconomicMeasuresChangedEventHandler EconomicMeasuresChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
if (propertyName == "EconomicMetric")
{
metricName = economicMetric.EconomicName;
metricUnit = economicMetric.Unit;
economicBenchmarks.Clear();
foreach (var economicBenchmark in economicMetric.EconomicBenchmarks)
{
economicBenchmarks.Add(economicBenchmark);
}
}
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
private void SetButton_Click(object sender, RoutedEventArgs e)
{
if (EconomicMeasuresChanged != null)
{
EconomicMeasuresChanged(this, new EventArgs());
}
}
}
public delegate void EconomicMeasuresChangedEventHandler(object sender, EventArgs e);
List
List<Metric> metrics = new List<Metric>();
metrics.Add(new Metric { EconomicItem = "NVP", EconomicName = "Net Present Value", EconomicBenchmarks = GetEconomicBenchmarks(new[] { 10, 50, 90 }, new[] { 400, 550, 700 }), Unit = "$m" });
metrics.Add(new Metric { EconomicItem = "ROI", EconomicName = "Return On Investment", EconomicBenchmarks = GetEconomicBenchmarks(new[] {10, 50, 90}, new[] {10, 20, 30}), Unit = "%" });
metrics.Add(new Metric { EconomicItem = "STOIIP", EconomicName = "STOIIP", EconomicBenchmarks = GetEconomicBenchmarks(new[] {10, 50, 90}, new[] {500, 550, 600}), Unit = "MMbbl" });
What you are doing in this code is maybe incomplete and a bit strange. First of all, i would never use OnPropertyChanged to set any value, but maybe thats only my opinion...
Anyway, in the code you have shown us,you set metricName and metricUnit in OnPropertyChanged if property is EconomicMetric. But you never set EconomicMetric anywhere so the binding properties would never be set. You need to have a better look at your code.
What I understand from your code is Metric class contains the collection EconomicBenchmarks, then why don't you bind directly that collection with view like this,
<ListBox x:Name="EconomicSelection" Grid.Column="1" HorizontalAlignment="Left" Height="150" Margin="5,5,0,5" VerticalAlignment="Top" Width="399" FontSize="11" ItemsSource="{Binding EconomicMetric.EconomicBenchmarks}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label Content="P" Grid.Column="0"/>
<TextBox Text="{Binding Percentile}" Grid.Column="0" Width="30"/>
<Label Content="Value: " Grid.Column="1"/>
<TextBox Text="{Binding Value}" Grid.Column="1"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>