Blazor how to await properly for data to be loaded before printing? - async-await

I want to print some data on a'blank-page' the data gets loaded async from mongodb in a List (print is done by JS via JSRuntime).
I call a razor page with a blank layout and do a foreeach loop from the List to create a table. The problem that I have is, that the pages gets rendered before the list of the foreeachloop is loaded.
How do I wait properly till list is loaded 100% and then show/ print the page?
Right now I use a work around; look at the 'OnInitialized()' while loop. Pretty ugly.
#page "/PrintPages/Praxen"
#inject PraxisKontaktePrintVM ViewModel
#inject IJSRuntime IJS
#inject NavigationManager NavigationManger
<table class="table">
<tbody>
#foreach (var praxis in ViewModel.AerzteLst)
{
<tr>
<td>
#foreach (var dr in praxis.PraxisDresNamensLst)
{
<p>#dr</p>
}
</td>
</tr>
}
</tbody>
</table>
#code {
protected override void OnInitialized()
{
//If I dont wait till Lists are loaded a empty list is show on the print because of async load DB
//TODO get rid of this work around
var TODO3 = 0;
bool myWait = true;
var counter = 0;
while (ViewModel.AerzteLst.Count <= 0 && myWait)
{
System.Threading.Thread.Sleep(100);
counter += 1;
if (counter >= 50)
{
myWait = false;
}
}
}
protected override void OnAfterRender(bool firstRender)
{
// execute conditionally for loading data, otherwise this will load
// every time the page refreshes; on the second button press data is there
if (firstRender)
{
StateHasChanged();
IJS.InvokeVoidAsync("Print");
}
NavigationManger.NavigateTo("/praxen");
}
}
The ViewModel is an injected Class via Startup.cs. And looks like this:
public class PraxisKontaktePrintVM : BaseViewModel
{
readonly MongoAux _mongoAux;
public PraxisKontaktePrintVM(MongoAux mongoAux)
{
_mongoAux = mongoAux;
IniStartup();
}
public List<ArztPraxis> HeadLst = new();
public List<ArztPraxis> BodyLst = new();
public async void IniStartup()
{
var res = await _mongoAux.ReadAllAsync<ArztPraxis>(MySecret.MyArztPraxisColletionName);
HeadsLst = res.Where(x => x.IsActiv == true && x.PraxisFachrichtung == MySecret.FachrichtungEnum.HeadArzt).ToList();
BodyLst = res.Where(x => x.IsActiv == true && x.PraxisFachrichtung == MySecret.FachrichtungEnum.BodyArzt).ToList();
}
}

The direct problem is async void IniStartup(). An async void method runs unobserved, the root cause of your problems. Almost always avoid async void.
And never use Thread.Sleep() in real code.
Step 1: make it an async Task and don't call it from the constructor:
public PraxisKontaktePrintVM(MongoAux mongoAux)
{
_mongoAux = mongoAux;
//IniStartup(); // remove
}
public async Task IniStartup()
{
... // as before
}
Note that this is also in line with best practices for DI, don't put (heavy) logic in a constructor. And constructors are not async.
Step 2: call it from your page in the correct order:
protected override async Task OnInitializedAsync()
{
await ViewModel.IniStartup();
StateHasChanged(); // in case IniStartup has multiple async steps
await Task.Delay(1); // make sure the last state is rendered
await IJS.InvokeVoidAsync("Print"); // await it
NavigationManger.NavigateTo("/praxen"); // and off again
}
Remove the OnAfterRender, you don't seem to need it.

Related

Intercepting property changes with CommunityToolKit.Mvvm amd Xamarin Forms

I've been moving code from mvvmlight to the CommunityToolkit.Mvvm framework with my Xamarin Forms project and have hit a snag.
In mvvmlight, I would have a property like this
bool loginOK;
public bool LoginOK
{
get => loginOK;
set => Set(()=>LoginOK, ref loginOK, value, true);
}
in CommunityToolkit.Mvvm, this becomes
bool loginOK;
public bool LoginOK
{
get => loginOK;
set => SetProperty(ref loginOK, value);
}
Accorrding to the docs, if the property changes, the PropertyChanging event is fired
In my code behind in (in Xam.Forms), I have this
protected override void OnAppearing()
{
base.OnAppearing();
ViewModel.PropertyChanged += ObservableObject_PropertyChanged;
}
async void ObservableObject_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "LoginOK":
if (ViewModel.LoginOK)
{
if (ViewModel.SkipWelcome)
{
var d1 = await image.ScaleTo(4, 500);
if (!d1)
{
Device.StartTimer(TimeSpan.FromMilliseconds(500), () =>
{
Device.BeginInvokeOnMainThread(async () => await Shell.Current.GoToAsync("//TabBar"));
return false;
});
}
}
}
else
{
var d2 = await image.ScaleTo(8, 500);
if (!d2)
{
var d3 = await image.ScaleTo(0, 500);
if (!d3)
{
Device.StartTimer(TimeSpan.FromMilliseconds(500), () =>
{
Device.BeginInvokeOnMainThread(async () => await Shell.Current.GoToAsync("//Welcome"));
return false;
});
}
}
}
break;
}
}
When I run this and set a break point on the line
var d2 = await image.ScaleTo(8,500);
The break is not hit, but the imge shows
Am I doing something wrong to intercept the property changing or is there something I'm missing from the docs?
The main issue you are seeing is because loginOK defaults to false and when a login attempt fails LoginOK does not technically change and therefore SetProperty does not result in raising the PropertyChanged event. If you look at the codebase for the toolkit you will notice that they always check whether the value has changed before raising any notifications.
There are a number of options that you could consider to work around this problem:
1 - switch to bool?
If you switch to using a nullable bool then it's value will default to null and therefore when a login attempt fails false will cause PropertyChanged to fire.
2 - use an enum
Similar to the bool? approach in point 1 but instead use something like a LoginResult enum like the following:
public enum LoginResult
{
None = 0,
Failed,
Success
}
This will make you code much more readable than point 1.
3 - fire a specific event
Rather than rely on PropertyChanged you could create your own event (e.g. LoginAttempted) you could then keep your bool indicating success or not if you so desired.
4 - make use of a Command
Events are sometimes consider a bad concept of the past so you could instead create an ICommand property on your View and assign it to your View Model allowing the View Model to call Execute on it and pass the log result up to the view. Something like:
View
public ViewConstructor()
{
ViewModel.LoginAttemptCommand = new RelayCommand<bool>(() => OnLoginAttempted());
}
public void OnLoginAttempted(bool result)
{
// Old logic on whether login succeeded.
}
ViewModel
public ICommand LoginAttemptCommand { get; set; }
// Wherever you set LoginOK = false/true call:
LoginAttemptCommand.Execute(false); // or true if it succeeds.

Akka.Net BecomeStacked/UnbecomeStacked behavior issue

I have a problem with the behavior switch model.
I have a simple receive actor with 2 behaviors: Ready & DoJob.
The Ready one contains a message handler plus one instruction I need to be evaluated at each behavior switch (cpt++).
Below is the code of the actor:
public class BecomeUnbecome : ReceiveActor
{
private int cpt=0;
public BecomeUnbecome()
{
this.Become(this.Ready);
}
public void Ready()
{
cpt++;
Receive<BeginWork>(msg =>
{
Console.WriteLine($"Go and work!");
BecomeStacked(this.DoJob);
});
}
public void DoJob()
{
Receive<Work>(msg =>
{
Console.WriteLine("Start working...");
Console.WriteLine($"Counter: {cpt}\nWork done\n");
UnbecomeStacked();
});
}
}
The main code is:
int counter = 0;
while (counter < 10)
{
actor.Tell(new BeginWork());
actor.Tell(new Work());
counter++;
}
The program execution shows cpt++ in Ready() is evaluated once next to the call to Become in the constructor.
I cannot find any reasonable workaround to that.
Does anyone have any idea ?

Vaadin - run client side javascript after image fully loaded

I need to print a picture on client side. I used this as a template. My PrintUI looks like this:
#Override
protected void init(VaadinRequest request) {
Item item = ..get item ..
StreamResource imageStream = ... build image dynamically ...
Image image = new Image(item.getName(), imageStream);
image.setWidth("100%");
setContent(image);
setWidth("100%");
// Print automatically when the window opens
JavaScript.getCurrent().execute("setTimeout(function() {print(); self.close();}, 0);");
}
This works so far in IE but in chrome it opens the printing preview showing an empty page. The problem is that the image is loaded in some way that chrome does not wait for it and starts the printing preview immideatly.
To verify this, I tried: (setting a 5sec timeout)
JavaScript.getCurrent().execute("setTimeout(function() {print(); self.close();}, 0);");
Then it works in IE and Chrome, but its of course an ugly hack, and if the connection is slower than 5sec, then again it will fail.
In pure JS it would work like this, but Im not sure how to reference the element from vaadin in cient-side js. Any ideas?
You can use AbstractJavascriptExtension.
Example extension class:
#JavaScript({ "vaadin://scripts/connector/wait_for_image_load_connector.js" })
public class WaitForImageLoadExtension extends AbstractJavaScriptExtension {
private List<ImageLoadedListener> imageLoadedListeners = new ArrayList<>();
public interface ImageLoadedListener {
void onImageLoaded();
}
public void extend(Image image) {
super.extend(image);
addFunction("onImageLoaded", new JavaScriptFunction() {
#Override
public void call(JsonArray arguments) {
for (ImageLoadedListener imageLoadedListener : imageLoadedListeners) {
if (imageLoadedListener != null) {
imageLoadedListener.onImageLoaded();
}
}
}
});
}
public void addImageLoadedListener(ImageLoadedListener listener) {
imageLoadedListeners.add(listener);
}
}
and javascript connector (placed in wait_for_image_load_connector.js) with the waiting method you have linked:
window.your_package_WaitForImageLoadExtension = function() {
var connectorId = this.getParentId();
var img = this.getElement(connectorId);
if (img.complete) {
this.onImageLoaded();
} else {
img.addEventListener('load', this.onImageLoaded)
img.addEventListener('error', function() {
alert('error');
})
}
}
Then you can do something like that:
Image image = new Image(item.getName(), imageStream);
WaitForImageLoadExtension ext = new WaitForImageLoadExtension();
ext.extend(image);
ext.addImageLoadedListener(new ImageLoadedListener() {
#Override
public void onImageLoaded() {
JavaScript.eval("print()");
}
});
In your case, when calling print() is the only thing you want to do after the image is loaded, you can also do it without server-side listener by just calling it in the connector:
if (img.complete) {
print();
} else {
img.addEventListener('load', print)
img.addEventListener('error', function() {
alert('error');
})
}

MVVM - View loading and eventhandling

In my windows phone app, I need to track some events to get a good flow. But I'm not sure how to handle them in good sequence.
What needs to be done at startup of the app:
Main view is loaded and corresponding view model instantiated
In the constructor of the view model I initiate a login sequence that signals when completed with an eventhandler
Now when the login sequence has finished AND the view is completely loaded I need to startup another sequence.
But here is the problem, the order of these 2 events 'completing' is not always the same...
I've use the EventToCommand from MVVMLight to signal the view model that the view has 'loaded'.
Any thoughts on how to synchronize this.
As you should not use wait handles or something similar on the UI thread. You will have to sync the two method using flags in your view model and check them before progressing.
So, implement two boolean properties in your view model. Now when the login dialog is finished set one of the properties (lets call it IsLoggedIn) to true, and when the initialization sequence is finished you set the other property (how about IsInitialized) to true. The trick now lies in the implementation of the setter of these two properties:
#region [IsInitialized]
public const string IsInitializedPropertyName = "IsInitialized";
private bool _isInitialized = false;
public bool IsInitialized {
get {
return _isInitialized;
}
set {
if (_isInitialized == value)
return;
var oldValue = _isInitialized;
_isInitialized = value;
RaisePropertyChanged(IsInitializedPropertyName);
InitializationComplete();
}
}
#endregion
#region [IsLoggedIn]
public const string IsLoggedInPropertyName = "IsLoggedIn";
private bool _isLoggedIn = false;
public bool IsLoggedIn {
get {
return _isLoggedIn;
}
set {
if (_isLoggedIn == value)
return;
var oldValue = _isLoggedIn;
_isLoggedIn = value;
RaisePropertyChanged(IsLoggedInPropertyName);
InitializationComplete();
}
}
#endregion
public void InitializationComplete() {
if (!(this.IsInitialized && this.IsLoggedIn))
return;
// put your code here
}
Alternatively you can remove the InitializationComplete from the setters and change InitializationComplete to:
public void InitializationComplete() {
// put your code here
}
Then subscribe to the 'PropertyChanged' event use the following implementation:
private void Class1_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) {
if (e.PropertyName == IsInitializedPropertyName || e.PropertyName == IsLoggedInPropertyName) {
if (this.IsInitialized && this.IsLoggedIn)
InitializationComplete();
}
}

callback / delegate?

i have a doubt..
i would like to create a function and it will look like this...
public class A //this is just a class file
{
function dowork()
{
//work 1
INPUT = here in this line it should call a delegate function or raise event etc...
//work 2 using INPUT
}
}
public class B
{
function myfn()
{
A objA = new A();
objA.dowork();
}
}
In the "Class A" we will raise event or so & it will display a windows form to user and then user will input some value & we need to return that value to Class A -> dowork method.... then only we should continue "work 2"
this should also support multi threading... anyone have idea how we can implement this??
thanks :)
You can use ManulResetEvent for this purpose: You run your input form and when it done that form set the event so you can catch it from A.dowork method. While the input in action you run the infinite loop, check event state and process application event to make you app responsible in this time:
public class A //this is just a class file
{
private ManualResetEvent _event;
public void dowork()
{
//work 1
_event = new ManualResetEvent(false);
//INPUT = here in this ...
Worker worker = new Worker();
worker.DoInput(_event);
while(true)
{
if(_event.WaitOne())
break;
Application.DoEvents();
}
//work 2 using INPUT
}
}
class Worker
{
private ManualResetEvent _event;
public void DoInput(ManualResetEvent #event)
{
_event = #event;
// Show input form here.
// When it done, you call: _event.Set();
}
}
Also, I suggest you (if you can) use Async library (it is available as a standalone setup). There you can implement it in much more straightforward way:
public class A //this is just a class file
{
public async void dowork()
{
//work 1
//INPUT = here in this ...
Worker worker = new Worker();
wait worker.DoInput();
//work 2 using INPUT
}
}
class Worker
{
public async void DoInput()
{
InputForm form = new InputForm();
wait form.ShowInput();
}
}
public class B
{
async void myfn()
{
A objA = new A();
wait objA.dowork();
}
}
As you see you just wait while other piece of code get executed without any UI locking and events.
I can provide deeper explanation of how async/wait works here if you need.

Resources