How to call async function from property setter? - asynchronous

I have a wpf application with a TextBox bound to ActualPageNumber property in the VM. I also have a DataGrid bound to an ObservableCollection which displays the given page. The data are stored in DB. When I change the ActualPageNumber, the setter accesses the db which can be slow. That is why I wanted an async setter, to keep the gui responsive.
I understand there is no async setter: https://blog.stephencleary.com/2013/01/async-oop-3-properties.html
I also found useful stuff like https://stackoverflow.com/a/9343733/5852947, https://stackoverflow.com/a/13735418/5852947, https://nmilcoff.com/2017/07/10/stop-toggling-isbusy-with-notifytask/
Still I struggle how to go on this case. AsyncEx library can be the solution, an example would be nice.
I just would like to notify the user that the page is actually loading. If I could call async from the setter I could do it, but then I still can not use await in the setter because it is not async.

I also have a DataGrid bound to an ObservableCollection which displays the given page.
This is going to be the difficult part. DataGrid (and DataTable and friends) are designed with a synchronous API, and have never been updated to support asynchrony.
I'm not terribly familiar with DataGrid, but I'd say your options are:
Replace the DataGrid with your own custom control - say, a ListView that displays custom controls. Then you can display a loading spinner since you control the custom control. There are some common patterns for this like NotifyTask.
There might be a way to virtualize the data in the DataGrid in a way that it would asynchronously load. I'm not familiar enough with DataGrid to say whether this is actually possible, but it's worth looking into.

1) For the responsiveness of the DataGrid, this binding property might help: IsAsync=True
<DataGrid ItemsSource="{Binding MyCollection, IsAsync=True}"
also look into these DataGrid properties:
VirtualizingPanel.IsVirtualizing
VirtualizingPanel.VirtualizationMode (you'll probably need Recycling)
VirtualizingPanel.IsVirtualizingWhenGrouping
EnableRowVirtualization
EnableColumnVirtualization
But be careful, virtualization can play tricks on you. For example, I had a RowHeader (with the row number) and the values got scrambled when virtualization was on.
2) About the async setter for data binding: I was using a custom version of IAsyncCommand (see Stephen Cleary's example).
I used the command in 2 ways: a) binding to it from the view (avoiding the async setter altogether) or b) launching it from the setter (not nice).
Example: I created an UpdateCommand as an AsyncCommand and placed everything I needed done asynchronously (like getting the values from the DB). Everything in this command is wrapped within a display+hide of a "in progress"-like control - in my case, a transparent cover with a spinner + "please wait...", to prevent other user actions (the "screen" is visible while the task is performed). Stripped down sample:
....
public MainWindowViewModel()
{
UpdateCommand = AsyncCommand.Create(Update); // our own custom implementation of AsyncCommand
}
....
public AsyncCommand UpdateCommand { get; }
internal async Task Update(object arg)
{
await SafeWrapWithWaitingScreenAsync(async () =>
{
var value = (int)arg; // or the ActualPageNumber, if used from a1)
var data = await GetDataFromDb(value).ConfigureAwait(false);
...// fill in MyCollection (which is the DataGrid's ItemsSource) using the data
OnPropertyChanged(nameof(MyCollection));// if still needed
}).ConfigureAwait(false);
}
....
public async Task SafeWrapWithWaitingScreenAsync(Func<Task> action)
{
DisplayWaitingScreen = true; //Visibility of the "Waiting screen" binds to this
try
{
await action().ConfigureAwait(false);
}
catch (Exception ex)
{
HandleException(ex); // display/log ex
}
finally
{
DisplayWaitingScreen = false;
}
}
a) Binding to the command from the view and
a1) in the command's body use ActualPageNumber property instead of the arg value
or a2) passing a CommandParameter which binds to the same property as TextBox.Text does. Example (could be missing something, couse is not the real code):
<TextBox Text="{Binding ActualPageNumber, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<TextBox.InputBindings>
<KeyBinding Key="Return" Command="{Binding UpdateCommand}" CommandParameter="{Binding ActualPageNumber}" />
<KeyBinding Key="Enter" Command="{Binding UpdateCommand}" CommandParameter="{Binding ActualPageNumber}" />
</TextBox.InputBindings>
</TextBox>
b) Not sure this is right, but before seeing Stephen's approach with NotifyTaskCompletion<TResult> (which I will probably use in the future), for the setter, I launched the command something like:
private int actualPageNumber;
public int ActualPageNumber
{
get => actualPageNumber;
set
{
actualPageNumber = value;
OnPropertyChanged(); //the sync way
UpdateCommand.Execute(value);
}
}

Related

Using CEFSharp ILifeSpanHandler interface to Handle Popups

I have an issue with handling popups. I have implemented ILifeSpanHandler and OnBeforeBrowse (amoungst others) from the IRequestHandler.
How do I know in the ILifeSpanHandler what URL is being called? I am unable to get it in either the OnAfterCreated or OnBeforePopup. Currently I see it first in OnBeforeBrowse.
I have no code as my question is a "How to". In OnBeforePopup I have checked targetUrl however it seems to be there for decoration as I have read that it is not implemented anyway. I have also looked at the browner/chromiumWebBrowser objects, browser and newBroswer seem to be nothing. One would expect in OnAfterCreated chromiumWebBrowser would return an object but it is nothing in my case.
I am testing with the following
Public Sub OnAfterCreated(chromiumWebBrowser As IWebBrowser, browser As IBrowser) Implements ILifeSpanHandler.OnAfterCreated
Try
Debug.Print(vbNewLine)
Debug.Print("OnAfterCreated")
Debug.Print(String.Concat("OnAfterCreated - MainFrame.Url "), browser.MainFrame.Url)
Debug.Print("OnAfterCreated")
Debug.Print(vbNewLine)
Catch ex As Exception
End Try
End Sub
And I have the following
Public Function OnBeforePopup(chromiumWebBrowser As IWebBrowser, browser As IBrowser, frame As IFrame, targetUrl As String, targetFrameName As String, targetDisposition As WindowOpenDisposition, userGesture As Boolean, popupFeatures As IPopupFeatures, windowInfo As IWindowInfo, browserSettings As IBrowserSettings, ByRef noJavascriptAccess As Boolean, ByRef newBrowser As IWebBrowser) As Boolean Implements ILifeSpanHandler.OnBeforePopup
Try
Debug.Print(vbNewLine)
Debug.Print("OnBeforePopup")
Debug.Print(String.Concat("OnBeforePopup - targetUrl "), targetUrl)
Debug.Print(String.Concat("OnBeforePopup - browser.MainFrame.Url "), browser.MainFrame.Url)
Debug.Print(String.Concat("OnBeforePopup - chromiumWebBrowser.Address "), chromiumWebBrowser.Address)
Debug.Print("OnBeforePopup")
Debug.Print(vbNewLine)
Catch ex As Exception
End Try
Return False
End Function
I have seen different approaches in handling popups using ILifeSpanHandler interface. One approach that I've seen also here in Stack Overflow and was accepted as the correct answer to that particular question is to return true in the OnBeforePopup implementation of ILifeSpanHandler then pass the targetURL argument to a handler that creates the popup.
This approach is very unideal because you are destroying the link between the page that actually opened the popup and the popup itself. If you access via JavaScript the opener property of the window object inside the popup you would notice that it is null. And the page that opened the popup would never know that the popup was actually opened because returning true cancels the creation.
The other approach is to let Cef create the popup and the programmer just decides whether to show the browser as a popup window or a child to control (like in tabbed browsing). This is error-free and almost ideal. But the problem with this approach is that you are not able to listen to events such as FrameLoadStart, FrameLoadEnd, AddressChanged, TitleChanged, etc.
One approach that is tagged experimental by the Cef developers is to return a new IWebBrowser instance via newWebBrowser out parameter. This has so many many side effects. The page that opened the popup would, of course, recognize the popup as his although it was not the original browser (IBrowser) that it created. The page may just ignore it like btcclicks.com and in that case, there'd be no problem. But there are websites like drops.xyz that is so particular with his stuff and will discard everything that is not originally his. So this is a problem.
So what is the correct approach?
The ChromeWebBrowser control
Now I'm going to share with you an undocumented approach in handling popups. Speaking of ChromeWebBrowser control, it is very much of help that we know how it creates the webbrowser which, in reality, it doesn't. The control just hosts the webbrowser window handle. It has a private field called managedCefBrowserAdapter (ManagedCefBrowserAdapter class) that handles the actual creation of the web browser. ChromiumWEbBrowser implements the IWebBrowserInternal that has a method OnAfterBrowserCreated with a single parameter of type IBrowser. The control then invokes browser.GetHost().GetWindowHandle() to get the actual window handle (HWND) of the webbrowser it is being hosted. It is quite good.
The problem of the ChromeWebBrowser is that it won't have a constructor that accepts an IBrowser as an argument. It only has constructor that accepts HtmlString, string and IRequestContext arguments. These control waits for the
invocation of OnHandleCreated (a base class override) where it calls the managedCefBrowserAdapter.CreateBrowser after which it waits till its implementation of IWebBrowserInternal's OnAfterBrowserCreated is invoked.
Again, what is the approach that works?
Now, this approach that actually works is a product of long series of trial and error. One caution though is that I don't know why and how it works but I know it works.
First, I did not use ChromeWebBrowser. But I copied its code omitting the part where it creates .net control. In this case, I am targeting the browser's window handle (HWND) to be host by any object that exposes a HWND. Obviously I created a class (NativeCefWebBrowser) that uses the modified code. The ChromeWebBrowser orignal constructors were still there untouched becuase they are used to the create the parent webrowser. But I added one constructor that accept the following arguments: ICefBrowserParent parent (an interface I've created and IBrowser browser that receives the browser argument in the ILifeSpanHandler's OnBeforePopup. I also added a public method AttachBrowser that has a single parameter IBrowser that recieves the IBrowser argument in the ILifeSpanHandler's OnAfterCreated. It the browser that will be kept by CefNativeWebBrowser class.
Why didn't I keep the browser instance received form ILifeSpanHandler.OnBeforePopup but used the instance received from ILifeSpanHandler.OnAfterCreated when they are the same browser instance? This is one of those parts that I don't know why. One thing I noticed is that when I called browser.GetHost().GetWindowHandle() during ILiffeSpanHandler.OnBeforePopup, the first window handle I received was the different compared to when I invoked the method during ILifeSpanHandler.OnAfterCreatd. Because of that, I store the browser instance from the latter that I passed to the NativeCefWebBrowser.AttachBrowser for its safekeeping.
In the NativeCefWebBrowser(ICefBrowserParent parent, IBrowser browser) contructor, I set the private following fields to true: browsercreated, browserinitialized (chromewebbrwoser orginal fields) and isAttachingBrowser (added private field). You don't call the ManagedCefBrowserAdapter's CreateBrowser in this contructor in instead call its OnAfterBrowserCreated passing the browser instance. You don't much in this constructor as you will wait the ILifeSpanHandler implementor to pass you the browser instance it will receive during its OnAfterCreated method. Take note that when calling the ManagedCefBrowserAdapter's OnAfterBrowserCreated method, ManagedCefBrowserAdapter will still invoke IWebBrowserInternal implementation of OnAfterBrowserCreated that when happens you have to exit immediately when isAttachingBrowser is true as the following code will no sense no more.
After calling the NativeCefWebBrowser(ICefBrowserParent, IBroser) construct, you can normally set event listeners as you will normally do.
And that's it.
The following are parts of the code that I wrote
The ICefBrowserParent interface
public interface ICefBrowserParent
{
IntPtr Handle { get; }
Size ClientSize { get; }
bool Disposing { get; }
bool IsDisposed { get; }
bool InvokeRequired { get; }
IAsyncResult BeginInvoke(Delegate d);
object Invoke(Delegate d);
event EventHandler Resize;
}
As you would notice, the methods, properties and events in this interface are already implemented by the System.Windowns.Forms.Control class. So if you implementing this from class inhering Control class, you would not need to implement this anymore. This interface is only for non-Control class.
class NativeCefWebBrowser
{
public NativeCefWebBrowser(ICefBrowserParent, IBroser)
{
requestContext = browser.GetHost().RequestContext;
this.parent = parent; // added field
HasParent = true; // IWebBrowserInternal. I don't know what's this for
mustSetBounds = true; // added field
browserCreated = true;
isAttachingBrowser = true; // added field
InitializeFieldsAndCefIfRequired();
managedCefBrowserAdapter.OnAfterBrowserCreated(browser);
}
}
ILifeSpanHandler.OnBeforePopup(..., out IWebBrowser newWebBrowser)
{
CefNativeWebBrowser b = new CefNativeWebBrowser
(
parent, // defined else where
browser
);
// Attach event handlers
b.TitleChanged...;
newWebBrowser = b;
}
ILifeSpanHandler.OnAfterCreated(...)
{
((CefNativeWebBrowser)webBrowser).AttachBrowser(browser);
}

How do I redraw UICollectionView when MvxCollectionViewSource's data changes?

I am using Xamarin and MVVMCross to implement an IOS app. A view I'm working on is displaying correctly, but ONLY when I hard-code the data being bound to inside my ViewModel and NOT when (as by design and necessity) my data arrives late from SQLite, only an indeterminate time after the view is shown.
What I am using and accomplished so far:
Working/Showing storyboard for my View that has a UICollectionView
inside (called: CollectionView in code below)
Custom layout and XIB file for every UICollectionViewCell that also displays correctly in my view
A view that works and displays correctly only if ViewModel data is fully populated the moment ViewDidLoad() is called.
Problem:
My data in my ViewModel is updated by the Model's databases in an uncertain amount of time whilst the view is happily being shown. When I bind the data as shown below (and trying two-way/one-way bindings and the like as well), I don't get updates on my view as the final data comes in later.
What I can't seem to do:
Redraw the UICollectionView or maybe refresh the
MvxCollectionViewSource below to ensure that as the ViewModel's data changes, I can actually redraw the UICollectionView and show my
custom cells with new and updated data.
THE CODE(TM)
The CollectionView cells are implemented as follows. I followed all examples online and from that Stuart Bloke and his Kittens to make sure I implement all the patterns exactly the same:
[Register("MyCell")]
public partial class MyCell : MvxCollectionViewCell
{
public static readonly UINib Nib = UINib.FromName("MyCell", NSBundle.MainBundle);
public static readonly NSString Key = new NSString("MyCell");
public MyCell(IntPtr handle) : base(handle)
{
this.DelayBind(() => {
var set = this.CreateBindingSet<MyCell, SomeModelClass>();
set.Bind(Label1).To(item => item.Label1);
set.Bind(Label2).To(item => item.Label2);
set.Apply();
});
}
public static MyCell Create()
{
return (MyCell)Nib.Instantiate(null, null)[0];
}
}
My ViewDidLoad() in the View looks something like this:
CollectionView.RegisterNibForCell(MyCell.Nib, MyCell.Key);
var source = new MvxCollectionViewSource(CollectionView, MyCell.Key);
CollectionView.Source = source;
var set = this.CreateBindingSet<MyView, MyViewModel>();
set.Bind(source).To(vm => vm.ListOfStuff);
set.Apply();
CollectionView.ReloadData();
NB! The ListOfStuff shown above is really just a List of a custom class containing 2 strings right now.
TL:DR: I don't know ListOfStuff's values the moment I call the above code. When I hard-code them in the ViewModel, I get joy. If I don't, I don't, even as data gets updated correctly later.
I now reach out to you, the neurons of the brain of crowdsourcing...
Instead of using a List<T> use ObservableCollection<T> and new items should be added to the CollectionView.
The UI needs to know when the collection has changed. ObservableCollection<T> implements INotifyCollectionChanged and INotifyPropertyChanged and communicates with the UI when the collection changes.
You shouldn't need ReloadData() anymore if you're using ObservableCollection<T>.
This extension method might be of use when adding range of IEnumerable<T>
public static class ObservableCollectionExtensionMethod
{
public static void AddRange<TSource>(this ObservableCollection<TSource> source, IEnumerable<TSource> collection)
{
foreach (var i in collection) source.Add(i);
}
}

How to send stream from ViewModel to the MediaElement on the XAML page?

Just need to speak a text string from the ViewModel (inherited from MVVM Light ViewModelBase) to the MediaElement on the XAML page.
var synthesisStream = await synthesizer.SynthesizeSsmlToStreamAsync(text);
media.AutoPlay = true;
media.SetSource(synthesisStream, synthesisStream.ContentType);
media.Play();
The code above has no separation of ViewModel. We see media is directly handled in code-behind.
In my ViewModel, I stopped at
var synthesisStream = await synthesizer.SynthesizeSsmlToStreamAsync(text);
var msg=new PlaySpeechSynthesisStreamMessage(synthesisStream);
Messenger.Default.Send<PlaySpeechSynthesisStreamMessage>(msg);
For the message:
public class PlaySpeechSynthesisStreamMessage
{
public SpeechSynthesisStream Stream { get; set; }
public PlaySpeechSynthesisStreamMessage(SpeechSynthesisStream stream)
{
Stream = stream;
}
}
Is Messenger the right way to handle this situation? How can we write a RelayCommand or something to pass the stream to media?
A related article MVVM pattern violation: MediaElement.Play() seems to address this issue, but it is not in MVVM Light and there is no way to pass the stream, either.
I think a message is a good solution to handle this kind of situation.
You just have to complement the sending of the message in the ViewModel with the handling of it in the View:
Messenger.Default.Register<PlaySpeechSynthesisStreamMessage>(this, msg => {
media.AutoPlay = true;
media.SetSource(msg.Stream, msg.Stream.ContentType);
media.Play();
});
Alternatively, you can use the event approach described in the question you cited. In this case you will have to define a class that inherits from EventArgs with a property of type SpeechSynthesisStream, then define your event as follow:
public event EventHandler<YourEventArgsClass> PlaySpeechSynthesisStreamEvent;
and raise it this way:
var synthesisStream = await synthesizer.SynthesizeSsmlToStreamAsync(text);
var eventArgs = new YourEventArgsClass(synthesisStream);
if (PlaySpeechSynthesisStreamEvent != null)
PlaySpeechSynthesisStreamEvent(this, eventArgs);
In this case of course you will have to handle the event in the View.
I find the solution with the event handler a little bit trickier than the one with messages, because you will have to wire the event handling to the DataContext of the View and, depending of how the application is structured, the DataContext property of the View could not always be available from the beginning of the View lifetime: for example, in many cases I tend to set it via a Navigation Service and / or Bootstrapper during the navigation to the view: in this case, DataContext is null in the costructor of the View so it is impossible to wire the event handler there. So, you have to find another place to wire it, remembering that methods such OnNavigatedFrom (e.g., in Windows 10 UWP apps) can be called more than once in the life cycle of the view and surely we don't want to wire the event handler more than once.
If the framework exposes it (such in Windows 10 UWP), the DataContextChanged event could be a good place to wire event handlers related to the ViewModel (and possibly to remove previous ones, if an instance of a View can be used with different instance of the ViewModel class during the lifetime of the application).

How can I emulate ErrorProvider in .NET Compact Framework?

Since there is no ErrorProvider class in .NETCF, how can I implement similar functionality (not necessarily exactly like ErrorProvider)?
I am using all the regular databinding constructs to bind controls to a datatable, using the DataRow.RowError property and DataRow.SetColumnError method, but I can't find events on any of DataTable, BindingManagerBase, etc. that I can hook into to receive any sort of notification.
Am I stuck calling a method to manually iterate through all the controls on my form and change some look/feel of the bound control?
Thanks,
MrB
The ErrorProvider class seems pretty basic - actually, a little too basic. If you have Red Gate Reflector, I would recommend disassembling the class and looking at it. Otherwise, create a Dictionary<Control, String>.
Here is a quick idea on creating your own provider:
Dictionary<Control, String> ErrorSet = new Dictionary<Control, String>();
public void SetError(Control control, String message)
{
// code for adding error information
ErrorSet.Add(control, message);
}
public String GetError(Control control)
{
// code for retrieving error information
return ErrorSet[control];
}
public String Clear()
{
// code for clearing all errors
}
I don't have R-G reflector here or I would provide more sample methods. But this ought to provide some sort of sample to work from.

MVP on Asp.Net WebForms

I'm not clear about this....
When having a gridview on the View, is the controller who has to set up the Data source, columns, etc? or I just have to expose the DataBinding stuff, fire it from the controller and let the html/codebehind on the view handle all the rendering and wiring up?
To be more precise: on the view should I have
private GridView _gv
public _IList<Poco> Source {
get {_gv.DataSource;}
set {_gv.DataSource = value;
_gv.DataBind();}
}
Or should it be (from MVP pattern - Passive View and exposing complex types through IView (Asp.Net, Web Forms))
private GridView _datasource;
public DataSource
{
get { return _datasource; }
set
{
_datasource = value;
_datasource.DataBind();
}
}
Maybe I'm having it all wrong ....
Where can I find an example that is not a "Hello world" example on MVP for ASP.Net???
Your controller should be in charge of setting the "result" of the databinding. The view is in charge of displaying it propertly.
So for example, your webform/usercontrol (View) could have the data source exposed as an object property that your View should know how to handle when it receives it:
public MyObject DataSource
{
set
{
_datasource = value;
_datasource.DataBind();
}
}
So if you need to have an ItemDataBound event, I would still handle it in the view. Even though there could be business logic in the event. If you need to have business logic in the event, I would put it in the MyObject result before it is passed to the view.
So an example would be to have a property of "MyObject" be "AllowDelete" and in your ItemDataBound, the value of this property determines if a column in the GridView is enabled or not.
Having just listened to a recent Hanselminutes on this topic, it might be worth having a look at the http://webformsmvp.com/ project, which seems to bring a bit of rigidity into separating concerns within WebForms.

Resources