Hello everyone,
I'm trying to use an API to show the current Bitcoin price.
The API returns the result and everything but it just won't show it on the UWP application.
The strange thing is that it did show the result one time but after that it never showed the result again.
Yes the API is really fast.
Here is my MainPage code:
public sealed partial class MainPage : Page, INotifyPropertyChanged
{
public string Price { get; set; }
private DispatcherTimer _timer;
public event PropertyChangedEventHandler PropertyChanged;
public MainPage()
{
this.InitializeComponent();
this._timer = new DispatcherTimer();
this._timer.Interval = TimeSpan.FromSeconds(20);
this._timer.Tick += OnUpdate;
this._timer.Start();
}
private async void OnUpdate(object sender, object e)
{
this.Price = (await API.GetData(1))[0]["price_eur"];
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Price"));
}
}
Here is my API class code:
class API
{
public static async Task<List<Dictionary<string, string>>> GetData(int limit)
{
var url = "https://api.coinmarketcap.com/v1/ticker/?convert=EUR&limit=" + limit;
using (var client = new HttpClient())
{
var response = await client.GetAsync(url);
if (response.IsSuccessStatusCode)
{
var result = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<List<Dictionary<string, string>>>(result);
}
else
{
return null;
}
}
}
}
Here is my MainPage xaml code:
<RelativePanel>
<Rectangle x:Name="rctOrange" Fill="Orange" RelativePanel.AlignRightWithPanel="True" RelativePanel.AlignBottomWithPanel="True" Stretch="UniformToFill"/>
<TextBlock x:Name="tbPrice" FontSize="80" Text="{x:Bind Price, Mode=OneWay}" RelativePanel.AlignVerticalCenterWith="rctOrange" RelativePanel.AlignHorizontalCenterWith="rctOrange"/>
</RelativePanel>
I hope you guys can find the problem because I'm getting crazy.
Thanks in advance!
You shouldn't implement INotifyPropertyChanged on your page. You should create an ViewModel that implements INotifyPropertyChanged.
private async void OnUpdate(object sender, object e)
{
this.Price.Value = (await API.GetData(1))[0]["price_eur"];
}
class PriceModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _value;
public string Value
{
get { return _value; }
set
{
_value = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Value"));
}
}
}
Related
I'm trying to learn the MVVM structure. How can I update a variable that changes constantly in another class in the UI.
I created a simple example because the project codes are too much. But I failed.
I would be very grateful if you could tell me where I went wrong. Thanks.
MyModel
public class Temperature : INotifyPropertyChanged
{
private double _memsTemperature;
private double _cpuTemperature;
private double _animalTemperature;
public double MemsTemperature
{
get { return _memsTemperature; }
set
{
_memsTemperature = value;
OnPropertyChanged("MemsTemperature");
}
}
public double CpuTemperature
{
get { return _cpuTemperature; }
set
{
_cpuTemperature = value;
OnPropertyChanged("CpuTemperature");
}
}
public double AnimalTemperature
{
get { return _animalTemperature; }
set
{
_animalTemperature = value;
OnPropertyChanged("AnimalTemperature");
}
}
System.Windows.Threading.DispatcherTimer dispatcherTimer = new System.Windows.Threading.DispatcherTimer();
public Temperature()
{
dispatcherTimer.Tick += DispatcherTimer_Tick;
dispatcherTimer.Interval = TimeSpan.FromSeconds(1);
dispatcherTimer.Start();
}
private void DispatcherTimer_Tick(object sender, System.EventArgs e)
{
MemsTemperature = MemsTemperature + 1;
CpuTemperature = CpuTemperature + 2;
AnimalTemperature = AnimalTemperature + 3;
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
MainWindowViewModel
public class MainWindowViewModel
{
public double MemTemp { get; set; }
public MainWindowViewModel()
{
MemTemp = new Temperature().MemsTemperature;
}
}
Main Window Xaml and C# Code
<TextBlock Text="{Binding MemTemp, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
}
The MainWindowViewModel should expose a Temperature property, e.g. like this:
public class MainWindowViewModel
{
public Temperature Temperature { get; } = new Temperature();
}
and the Binding should then look like this:
<TextBlock Text="{Binding Temperature.MemsTemperature}"/>
Neither Mode=TwoWay nor UpdateSourceTrigger=PropertyChanged makes sense on the Binding of a TextBlock's Text property.
The OnPropertyChanged method would simpler and safer be implemented like this:
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
You have a XAML page with UI controls that bind to those constantly-changing properties. When you send out the PropertyChanged notifications, the UI control will automatically update itself.
The problem with the code you wrote is that you never bound to the actual temperature. XAML doesn't know how to translate MemTemp into anything other than it's name unless you write a DataTemplate for it.
For example, (assuming a grid) something like this:
<TextBlock Grid.Row="0" Grid.Column="0" Text="Animal: "/>
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding MemTemp.AnimalTemperature}"/>
I would define an explicit worker class which performs the measurements. This class
has an event (OnMeasurement), which can be subscribed in the ViewModel:
// Arguments for the mesurement event (temperature, ...)
public class MeasurementEventArgs : EventArgs
{
public double Temperature { get; }
public MeasurementEventArgs(double temperature)
{
Temperature = temperature;
}
}
public class MeasurementWorker
{
private readonly CancellationTokenSource _tcs = new CancellationTokenSource();
// Provides an event we can subscribe in the view model.
public event Action<object, MeasurementEventArgs> OnMeasurement;
public void Stop()
{
_tcs.Cancel();
}
// Measurement routine. Perform a measurement every second.
public async Task Start()
{
try
{
var rnd = new Random();
while (!_tcs.IsCancellationRequested)
{
var temperature = 20 * rnd.NextDouble();
OnMeasurement?.Invoke(this, new MeasurementEventArgs(temperature));
await Task.Delay(1000, _tcs.Token);
}
}
catch (TaskCanceledException) { }
// TODO: Create an error event to catch exceptions from here.
catch { }
}
}
In your MainWindow class you instantiate your viewmodel and your worker:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel(new MeasurementWorker());
}
// Register in XAML with <Window ... Closing="StopMeasurement">
public async void StopMeasurement(object sender, System.ComponentModel.CancelEventArgs e)
{
var vm = DataContext as MainWindowViewModel;
await vm.StopMeasurement();
}
}
In your view model you can subscribe to the worker event and raise OnPropertyChanged in your callback function:
public class MainWindowViewModel : INotifyPropertyChanged
{
private double _memsTemperature;
private readonly MeasurementWorker _mw;
private readonly Task _measurementWorkerTask;
public double MemsTemperature
{
get => _memsTemperature;
set
{
_memsTemperature = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(MemsTemperature)));
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void ProcessMeasurement(object sender, MeasurementEventArgs args)
{
MemsTemperature = args.Temperature;
}
// You can call this if you want to stop your measurement. Should be called if you close your app.
public async Task StopMeasurement()
{
_mw.OnMeasurement -= ProcessMeasurement;
_mw.Stop();
// Clean shutdown
await _measurementWorkerTask;
}
public MainWindowViewModel(MeasurementWorker mw)
{
_mw = mw;
_mw.OnMeasurement += ProcessMeasurement;
_measurementWorkerTask = _mw.Start();
}
}
I have a public boolean in my UWP app used to show/hide a ProgressBar. I've used the same pattern elsewhere and it seems to work okay, but I'm having an issue on a specific page where it doesn't seem to update in the UI only if I set the boolean after an awaited async call (same pattern as the working page).
Here is my XAML View:
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<TextBlock Text="{ x:Bind Vm.IsLoaded }" Margin="112,272,-112,-272"></TextBlock>
</Grid>
And the codebehind:
public sealed partial class MainPage : Page
{
public MainPageViewModel Vm => DataContext as MainPageViewModel;
public MainPage()
{
this.InitializeComponent();
this.DataContext = new MainPageViewModel();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
Vm.GetProjectData();
}
}
Here is my MainPageViewModel.cs
public class MainPageViewModel : ViewModel
{
public ObservableCollection<Project> Projects { get; set; } = new ObservableCollection<Project>();
private bool _isLoaded;
public bool IsLoaded
{
get { return _isLoaded; }
set
{
_isLoaded = value;
OnPropertyChanged();
}
}
public async Task GetProjectData()
{
// If I put `IsLoaded = true;` here it displays `true` in the UI
var projectsResponse = await HttpUtil.GetAsync(StringUtil.ProjectsUrl());
// If I put `IsLoaded = true;` here it still displays `false` in the UI
if (projectsResponse.IsSuccessStatusCode)
{
var projectsResponseString = await projectsResponse.Content.ReadAsStringAsync();
var projects = JsonUtil.SerializeJsonToObject<List<Project>>(projectsResponseString);
foreach (var project in projects)
{
Projects.Add(project);
}
IsLoaded = true;
}
}
}
And my ViewModel.cs
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
No matter where I put the IsLoaded = true; it always hits OnPropertyChanged().
Here is my working code:
ProjectViewViewModel.cs:
public class ProjectViewViewModel : ViewModel
{
public ObservableCollection<Story> MyData { get; set; } = new ObservableCollection<Story>();
private bool _dataIsLoaded;
public bool DataIsLoaded
{
get { return _dataIsLoaded; }
set
{
_dataIsLoaded = value;
OnPropertyChanged();
}
}
public async Task GetData(Project project)
{
DataIsLoaded = false;
var stringResponse = await HttpUtil.GetAsync(StringUtil.Query(project.Id, "MB"));
if (stringResponse.IsSuccessStatusCode)
{
// Do Stuff
DataIsLoaded = true;
}
}
}
ProjectView.xaml.cs
public sealed partial class ProjectView : Page
{
public Project Project { get; set; }
public bool IsLoaded { get; set; }
public ProjectViewViewModel Vm => DataContext as ProjectViewViewModel;
public ProjectView()
{
this.InitializeComponent();
this.DataContext = new ProjectViewViewModel();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
Project = e.Parameter as Project;
Vm.GetData(Project);
}
}
I feel like I'm missing something extremely obvious but I can't see the wood through the trees and it's driving me nuts. Any help is greatly appreciated!
I believe the problem you are having is in your mark up. x:Bind has a default binding mode of OneTime; so the text in your text block is bound to the value of IsLoaded at application start up, or when the data context for the text block changed.
Setting the binding mode to OneWay should result in the value in the text block updating after the async function has returned.
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<TextBlock Text="{ x:Bind Path=Vm.IsLoaded, Mode=OneWay }" Margin="112,272,-112,-272"></TextBlock>
</Grid>
If you're interested, this article goes into detail on the use of x:Bind. Also, this article covers the values in the BindingMode enumeration.
I have a bad time trying to pass a parameter to a command.
I have the following in XAML code:
<Button Text="{Binding ButtonText}" x:Name="btnCaptureNegotiation" BackgroundColor="#3276b1"
TextColor="White" Clicked="OnCaptureNegotiationClicked"
CommandParameter="{Binding Client, Path=cod_cte}" Command="{Binding LoadULastNegotiationCommand}" ></Button>
<StackLayout Orientation="Vertical" x:Name="captureLayout" IsVisible="{Binding IsVisible}">
<!-- more code -->
And in code-behind I binded like this:
public Client client;
public NegociationVM negotiation = new NegotiationVM();
public ClientItemPage(Client client)
{
this.client = client;
negotiation.Client = client; //STOP WORKING after adding this line
InitializeComponent();
captureLayout.BindingContext = negotiation;
btnCaptureNegotiation.BindingContext = negotiation;
}
private void OnCaptureNegotiationClicked(object sender, EventArgs args)
{
negotiation.IsVisible = !negotiation.IsVisible;
}
...
And NegotiationVM class:
public class NegotiationVM : INotifyPropertyChanged
{
private bool _isVisible = false;
private string _buttonText = "Capturar Seguimiento";
private Client _client;
public Client Client{
get { return _client; }
set {
if (this._client != value)
_client = value;
NotifyPropertyChanged("Client");
}
}
private Models.NegotiationRepository _negotiationRepo;
public ICommand LoadULastNegotiationCommand { get; private set; }
public int LoadLasNegotiationResult { get; private set; }
public event PropertyChangedEventHandler PropertyChanged;
public NegotiationVM(){
LoadULastNegotiationCommand = new Command<string (LoadLastNegotiationAsync);
}
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
async void LoadLastNegotiationAsync(string value)
{
_negotiationRepo = new Models.NegotiationRepository();
LoadLasNegotiationResult = await _negotiationRepo.GetLastNegotiationActiveAsync(value);
NotifyPropertyChanged("LoadLastNegotiationAsync");
}
public bool IsVisible
{
get
{
return _isVisible;
}
set
{
if (this._isVisible != value)
_isVisible = value;
if (this._isVisible){
this.ButtonText = "Cancel";
}else{
this.ButtonText = "Capture Negotation";
}
NotifyPropertyChanged("IsVisible");
}
}
public string ButtonText {
get
{
return _buttonText;
}
set
{
if (this._buttonText != value)
_buttonText = value;
NotifyPropertyChanged("ButtonText");
}
}
}
I found that the command is fired and tries to get resource from service, but I get 404 because I found that is not sending a parameter, I just put a breakpoint in async void LoadLastNegotiationAsync(string value) method to find that.
Because it wasn't sending anything, In code-behind Page, in the public constructor, I set the Client to the property of the same name in negotation (instance of NegotiationVM). As the comment suggest, the command STOP working and never gets fired by the button just by adding that line.
What is wrong with that binding? How can I properly send the string property of that Client?
If Cliente has a property named cod_cte. bind like so:
CommandParameter="{Binding Cliente.cod_cte}"
If the property is named Client rather than Cliente, omit the trailing e on Cliente:
CommandParameter="{Binding Client.cod_cte}"
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 7 years ago.
Improve this question
Some time ago i decided to study MVVM and async/await.
With entity framework code-first my application can write new users to database.
At MSDN async databinding article i got for for test async binding class NotifyTaskCompletion.cs (was renamed to TaskPropertyWatcher.cs) for async loading users from database. This class working.
After that second article was read.
I copy-paste full async class from article, bind AsyncCommand to button.
Problem: Have NullReferenceException when binded Button was clicked.
This is not a compiler error.
Maybe someone can help with this "magic"?
AsyncCommandBase class error debug info:
AsyncCommand class error debug info:
Example solution from MSDN(first item at page) working perfecty...
My DAL AddUser method:
public static async Task AddUser(User usr)
{
using (var cntx = new ServiceDBContext())
{
cntx.Users.Add(usr);
await cntx.SaveChangesAsync();
}
}
Entity model:
[Table("Users")]
public partial class User
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ClientId { get; set; }
[StringLength(60)]
public string ClientType { get; set; }
[StringLength(160)]
public string ClientName { get; set; }
[StringLength(60)]
public string Mobile { get; set; }
[StringLength(50)]
public string EMail { get; set; }
}
My ViewModel part:
public CustomerAddViewModel()
{
AddClient = AsyncCommand.Create(() => DAL.DbService.AddUser(Client));
}
private User _user = new User();
public User Client
{
get
{
return _user;
}
set {
_user = value;
RaisePropertyChanged();
}
}
private void RaisePropertyChanged([CallerMemberName]string propertyName = "")
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
View constructor:
public CustomerAddView()
{
DataContext = new CustomerAddViewModel();
InitializeComponent();
}
My View databinding part:
<Button Command="{Binding AddClient}" x:Name="button" Content="Add user" HorizontalAlignment="Left" Margin="5,185,0,0" VerticalAlignment="Top" Width="365" Height="26"/>
<TextBox Text="{Binding Client.ClientName}" HorizontalAlignment="Left" Margin="5,34,0,0" VerticalAlignment="Top" Width="365" ></TextBox>
<TextBox Text="{Binding Client.ClientType}" HorizontalAlignment="Left" Margin="5,34,0,0" VerticalAlignment="Top" Width="365" ></TextBox>
<TextBox Text="{Binding Client.EMail}" HorizontalAlignment="Left" Margin="5,34,0,0" VerticalAlignment="Top" Width="365" ></TextBox>
<TextBox Text="{Binding Client.Phone}" HorizontalAlignment="Left" Margin="5,34,0,0" VerticalAlignment="Top" Width="365" ></TextBox>
MSDN code:
public interface IAsyncCommand : ICommand
{
Task ExecuteAsync(object parameter);
}
public abstract class AsyncCommandBase : IAsyncCommand
{
public abstract bool CanExecute(object parameter);
public abstract Task ExecuteAsync(object parameter);
public async void Execute(object parameter)
{
await ExecuteAsync(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
protected void RaiseCanExecuteChanged()
{
CommandManager.InvalidateRequerySuggested();
}
}
public class AsyncCommand<TResult> : AsyncCommandBase, INotifyPropertyChanged
{
private readonly Func<CancellationToken, Task<TResult>> _command;
private readonly CancelAsyncCommand _cancelCommand;
private TaskPropertyWatcher <TResult> _execution;
public AsyncCommand(Func<CancellationToken, Task<TResult>> command)
{
_command = command;
_cancelCommand = new CancelAsyncCommand();
}
public override bool CanExecute(object parameter)
{
return Execution == null || Execution.IsCompleted;
}
public override async Task ExecuteAsync(object parameter)
{
_cancelCommand.NotifyCommandStarting();
Execution = new TaskPropertyWatcher<TResult>(_command(_cancelCommand.Token));
RaiseCanExecuteChanged();
await Execution.TaskCompletion;
_cancelCommand.NotifyCommandFinished();
RaiseCanExecuteChanged();
}
public ICommand CancelCommand
{
get { return _cancelCommand; }
}
public TaskPropertyWatcher<TResult> Execution
{
get { return _execution; }
private set
{
_execution = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
private sealed class CancelAsyncCommand : ICommand
{
private CancellationTokenSource _cts = new CancellationTokenSource();
private bool _commandExecuting;
public CancellationToken Token { get { return _cts.Token; } }
public void NotifyCommandStarting()
{
_commandExecuting = true;
if (!_cts.IsCancellationRequested)
return;
_cts = new CancellationTokenSource();
RaiseCanExecuteChanged();
}
public void NotifyCommandFinished()
{
_commandExecuting = false;
RaiseCanExecuteChanged();
}
bool ICommand.CanExecute(object parameter)
{
return _commandExecuting && !_cts.IsCancellationRequested;
}
void ICommand.Execute(object parameter)
{
_cts.Cancel();
RaiseCanExecuteChanged();
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
private void RaiseCanExecuteChanged()
{
CommandManager.InvalidateRequerySuggested();
}
}
}
public static class AsyncCommand
{
public static AsyncCommand<object> Create(Func<Task> command)
{
return new AsyncCommand<object>(async _ => { await command(); return null; });
}
public static AsyncCommand<TResult> Create<TResult>(Func<Task<TResult>> command)
{
return new AsyncCommand<TResult>(_ => command());
}
public static AsyncCommand<object> Create(Func<CancellationToken, Task> command)
{
return new AsyncCommand<object>(async token => { await command(token); return null; });
}
public static AsyncCommand<TResult> Create<TResult>(Func<CancellationToken, Task<TResult>> command)
{
return new AsyncCommand<TResult>(command);
}
}
P.S Sorry for my bad English. Thanks in advance for any help!
You're getting a NullReferenceException because TaskCompletion is null.
There was a bug in the original code download for the second (async commands) article, where NotifyTaskCompletion will have a null TaskCompletion if the task is completed before the NotifyTaskCompletion is constructed.
This bug did not exist in the first article (which did not have any TaskCompletion at all), and was fixed a while ago for the second article. I recommend you re-download it.
I want to have AutoCompleteBox to complete addresses by request on server.
I have this method to fill my AutoCompleteBox:
private async void getNearStreets()
{
if (acbAddress.Text.Length > 2)
{
ApiRequest request = new ApiRequest("hintAddress", new HintAddress(appSettings.InstanceId, acbAddress.Text, appSettings.SmsCode));
var postData = JsonConvert.SerializeObject(request);
var response = await HttpHelper.SendRequestGetResponse(postData);
ApiResponseTest apiResponse = (ApiResponseTest)JsonConvert.DeserializeObject<ApiResponseTest>(response);
var wordList = this.Resources["autoCompleteWordList"] as AutoCompleteWordList;
wordList.Clear();
foreach (var adresa in apiResponse.data.result)
{
HintAddressResponse adrResponse = (HintAddressResponse)JsonConvert.DeserializeObject<HintAddressResponse>(adresa.ToString());
wordList.Add(adrResponse.street);
}
}
}
And this is my class:
public class AutoCompleteWordList : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<string> _listOfAddresses;
public ObservableCollection<string> ListOfAddresses
{
get { return _listOfAddresses; }
set
{
_listOfAddresses = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("ListOfAddresses"));
}
}
public AutoCompleteWordList()
{
ListOfAddresses = new ObservableCollection<string>();
}
public void Add(string address)
{
ListOfAddresses.Add(address);
}
public void Clear()
{
ListOfAddresses.Clear();
}
}
and view:
<phone:PhoneApplicationPage.Resources>
<data:AutoCompleteWordList x:Key="autoCompleteWordList" />
</phone:PhoneApplicationPage.Resources>
<toolkit:AutoCompleteBox x:Name="acbAddress" VerticalAlignment="Top"
ItemsSource="{Binding Source={StaticResource autoCompleteWordList}, Path=ListOfAddresses}"
TextChanged="acbAddress_TextChanged"/>
My problem is that I am downloading data, I added them to collection but DropDownDialog doesn't show up. I think I must alert that I have new data but I don't know how. Thanks for help
When I looked at msdn for first time I missed PopulateComplete method. But It's what I need and when I added at end of my method it works.