Callback from ViewModel to View - c#

My View calls a method in ViewModel to Fetch Data. After fetching the data, I build my View(Grid) based on the data that got back from the ViewModel.
getData() Method in the View Model runs in a BackgroundWorker thread. Now my question is how do I get back to View after the View is done fetching all the data?
ViewModel
{
getData()
{
WorkerMethods()
WorkerCompletedMethod()
{
Refresh()
}
}
Refresh()
{
WorkerMethod()
WorkerCompleted()
{
data - Retrieved.
This is where all the calls are really DONE
}
}
}
From the View, I will be calling
View()
{
VM.getData()
//Before I call this method, I want to make sure Refresh() is completed
BuildUI()
}
I want the BuildUI() method to be executed only after the VM.getData() is executed fully and in turn is done with Refresh() method as well which is what has the Data I need to be able to build the UI Dynamically.
This is what I am going to do. Please correct me if this is not right approach.
In the View code behind,
View
{
public delegate void DelegateRefresh();
Init()
{
DelegateRefresh fetcher = RefreshData;
fetcher.BeginInvoke(null, null);
}
public void RefreshData()
{
_viewModel.GetData();
**while (_viewModel.IsBusy)**
{
continue;
}
BuildUI();
}
BuildUI()
{
//Code to build the UI Dynamically using the data from VM.
}

You should retrieve the data once the BackgroundWorker has completed its work. Your view model should implement the INotifyPropertyChanged interface and expose the data through a property that the view binds to. The view model can then notify the view when the data is available (i.e. the BackgroundWorker has completed its work).

One approach is to use messaging. That is, register your message on the view, then send a message from the view model to the view, when this message is received then you could call your BuildUI method.
For example, if you were using the MvvmLight framework, here's one way of passing back an error message to show up in a dialog. You might not want to show a dialog (I had this code on hand), but the process is the same, it's just a different message type to register and send.
ViewModel:
public class ErrorMessage : DialogMessage
{
// See MvvmLight docs for more details, I've omitted constructor(s)
/// <summary>
/// Registers the specified recipient.
/// </summary>
/// <param name="recipient">The recipient of the message.</param>
/// <param name="action">The action to perform when a message is sent.</param>
public static void Register(object recipient, Action<ErrorMessage> action)
{
Messenger.Default.Register<ErrorMessage>(recipient, action);
}
/// <summary>
/// Sends error dialog message to all registered recipients.
/// </summary>
public void Send()
{
Messenger.Default.Send<ErrorMessage>(this);
}
}
public class SomeViewModel : ViewModelBase
{
public void SendErrorMessage(string message)
{
var errorMessage = new ErrorMessage(message);
errorMessage.Send();
// Or in your case, when the background worker is completed.
}
}
View:
public partial class SomeView : Window
{
public SomeView()
{
InitializeComponent();
ErrorMessage.Register(this, msg =>
{
MessageBoxResult result = MessageBox.Show(msg.Content, msg.Caption,
msg.Button, msg.Icon, msg.DefaultResult, msg.Options);
msg.ProcessCallback(result);
// Or in your case, invoke BuildUI() method.
});
}

Related

How to implement the creation of Background-tasks or services for a specific user?

In my ASP.Net Core with MVC (Razorpages) I want to be able to let users click on a 'START' button which in the backend should start a (potentially long-running) background task or service.
It should occasionally send updates to the UI for that user. Probably through some JS/AJAX request.
The user, while being on that page, should receive those updates and see them on-screen.
It is important that updates are not shared across all users (hence wanting to create the background-service for a specific user).
Also when the user navigates to a different page and navigates back, it should still keep updating from the same background-service/thread.
My own 2 cents
My first instinct is that I should keep track of the user's ID within the background-task, so I can retrieve the background-service that belongs to a specific user and receive updates when the user is on the correct page.
However, the way I have implemented this seems like a big code smell and I do not fully understand how to actually update the user's UI with it... I seem to be stuck.
My question
I was wondering if others may have ideas on how to successfully implement the creation of Background-tasks or services for a specific user? Or that maybe I am missing something obvious I haven't thought of myself. Anybody any ideas?
Thanks!
What I tried was the following.
I created a background service:
public class SomeService : IHostedService
{
private Guid _userId;
public EventHandler UpdateUiEvent;
public void SetUserId(Guid userId)
{
_userId = userId;
}
public async Task StartAsync(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
// Do stuff on a loop
UpdateUI();
await Task.Delay(1000, cancellationToken);
}
}
public Task StopAsync(CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
private void UpdateUI()
{
EventArgs eventArgs = new EventArgs();
UpdateUiEvent?.Invoke(this, eventArgs);
}
}
Secondly I have a controller with an action which starts the service when a user calls the method 'StartServiceForUser' from the Razor-page (AJAX):
public class SomeController : Controller
{
private readonly ILogger<SomeController> _logger;
private readonly IServiceProvider _serviceProvider;
public SomeController(ILogger<SomeController> logger, IServiceProvider serviceProvider)
{
_logger = logger;
_serviceProvider = serviceProvider;
}
/// <summary>
/// Called from AJAX-request in Razor-page UI.
/// </summary>
/// <returns></returns>
[HttpPost]
public async Task<IActionResult> StartServiceForUser(Guid userId, CancellationToken cancellationToken)
{
using (var scope = _serviceProvider.CreateScope())
{
var service = scope.ServiceProvider.GetService<SomeService>();
if (service == null) throw new Exception("Service is null.");
service.SetUserId(userId);
service.UpdateUiEvent += OnUiUpdated;
await service.StartAsync(cancellationToken);
}
return Ok("Service has started");
}
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
/// <summary>
/// Called when the UpdateUI event is fired in the 'SomeService' class.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnUiUpdated(object? sender, EventArgs e)
{
// How will I push this update to the user?
}
}
As seen in the comment of the OnUiUpdated method, I do not understand how to proceed further...
How will I push this update to the user?
I have the feeling I am doing something completely wrong and do not know how else to get my head around this problem. Any help is appreciated!
Edit 1 somebody suggested in the comments I should take a look at SignalR for client-server communication. This helps, however does not yet solve the problem of having a long-running (or continuous-running) operation and updating the UI regularly.

Is there a better way to use barcode scanner?

I'm currently using using a BarcodeScanner in UWP App.
To implement it I followed some tutorials on Microsoft docs.
It's working fine but not like I want it to work.
The barcode scanner can only get the value through a DataReceived event.
So when I want to return a value from a BarcodeScanner, it's impossible.
Here I'm registering the scanner :
private static async Task<bool> ClaimScanner()
{
bool res = false;
string selector = BarcodeScanner.GetDeviceSelector();
DeviceInformationCollection deviceCollection = await
DeviceInformation.FindAllAsync(selector);
if (_scanner == null)
_scanner = await BarcodeScanner.FromIdAsync(deviceCollection[0].Id);
if (_scanner != null)
{
if (_claimedBarcodeScanner == null)
_claimedBarcodeScanner = await _scanner.ClaimScannerAsync();
if (_claimedBarcodeScanner != null)
{
_claimedBarcodeScanner.DataReceived += ClaimedBarcodeScanner_DataReceivedAsync;
[...]
}
}
}
And once I'm receiving data it triggers that event :
private static async void ClaimedBarcodeScanner_DataReceivedAsync(ClaimedBarcodeScanner sender, BarcodeScannerDataReceivedEventArgs args)
{
await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
if (CurrentDataContext != null && CurrentDataContext is IScannable)
{
IScannable obj = (IScannable)CurrentDataContext;
obj.NumSerie = CryptographicBuffer.ConvertBinaryToString(BinaryStringEncoding.Utf8, args.Report.ScanDataLabel);
}
else if (CurrentDataContext != null && CurrentDataContext is Poste)
{
Poste p = (Poste)CurrentDataContext;
string code = CryptographicBuffer.ConvertBinaryToString(BinaryStringEncoding.Utf8, args.Report.ScanDataLabel);
p.CodePoste = code.Substring(0, 6);
}
});
}
And as you can see I'm kind of forced to do everything in that method (updating instances of others classes, etc.).
Currently I'm calling the BarcodeScanner like that in the ViewModel :
public void ScanPosteCodeAsync()
{
BarcodeScannerUtil.ScanBarcodeUtil(CurrentPoste);
}
But I have no control of my CurrentPoste instance and what I would do is more like :
public void ScanPosteCodeAsync()
{
string returnedCode = BarcodeScannerUtil.ScanBarcodeUtil()
this.CurrentPoste.Code = returnedCode;
}
Is there any way to return the value of the scanner in order to use the returned value in my ViewModel ?
Well a similar pattern exists for WPF devs when using MVVM and you need to get/update the models that your view model (VM) is exposing. Perhaps they are in a database. Rather than pollute your nice VM with ugly DB code, a "service" can be passed into the VM. Now, "serivce" doesn't necessarily mean SOA/microservices, maybe its just another class in a different project. The point is you put all your barcode stuff there and when something is received, perhaps it fires an event that your VM listens to or perhaps it just queues it up somewhere ready for your VM to request via the service interface.
I already have all the barcode code in a service class, and there's the problem because I don't want the service class to update my current model. The major issue I have is that I don't know how to do to make my VM listen to the DataReceived event
Well, from what I can see your service is not decoupled from UWP MVVM. For the event, have you considered exposing a secondary event purely for the VM client? I find that works well for me.
Like an event in the VM listening to the data received event ?
Yes, but it doesn't have to be a listending to a physical event type just the concept. C# events imply that can be more than one subscriber which doesn't really make sense for barcode apps. There should only be one foreground reader.
Here I shall use Action<string> to pass the barcode from BarcodeScanner to the client, in this case the VM. By using an Action and moving the barcode processing to the client we keep the BarcodeScanner completely unaware of MVVM. Windows.ApplicationModel.Core.CoreApplication.MainView was making BarcodeScanner incredibly coupled to stuff it shouldn't care about.
First of all we want to decouple everything so first up is an interface representing the important bits of the barcode scanner:
public interface IBarcodeScanner
{
Task<bool> ClaimScannerAsync();
void Subscribe(Action<string> callback);
void Unsubscribe();
}
With that defined we shall pass it into your VM like so:
public class MyViewModel
{
private readonly IBarcodeScanner _scanner;
/// <summary>
/// Initializes a new instance of the <see cref="MyViewModel"/> class.
/// </summary>
/// <param name="scanner">The scanner, dependency-injected</param>
public MyViewModel(IBarcodeScanner scanner)
{
// all business logic for scanners, just like DB, should be in "service"
// and not in the VM
_scanner = scanner;
}
Next we add some command handlers. Imagine we have a button that when clicked, kicks off a barcode subscription. Add the following to the VM:
public async void OnWidgetExecuted()
{
await _scanner.ClaimScannerAsync();
_scanner.Subscribe(OnReceivedBarcode);
}
// Barcode scanner will call this method when a barcode is received
private void OnReceivedBarcode(string barcode)
{
// update VM accordingly
}
Finally, the new look for the BarcodeScanner:
public class BarcodeScanner : IBarcodeScanner
{
/// <summary>
/// The callback, it only makes sense for one client at a time
/// </summary>
private static Action<string> _callback; // <--- NEW
public async Task<bool> ClaimScannerAsync()
{
// as per OP's post, not reproduced here
}
public void Subscribe(Action<string> callback) // <--- NEW
{
// it makes sense to have only one foreground barcode reader client at a time
_callback = callback;
}
public void Unsubscribe() // <--- NEW
{
_callback = null;
}
private void ClaimedBarcodeScanner_DataReceivedAsync(ClaimedBarcodeScanner sender, BarcodeScannerDataReceivedEventArgs args)
{
if (_callback == null) // don't bother with ConvertBinaryToString if we don't need to
return;
// all we need do here is convert to a string and pass it to the client
var barcode = CryptographicBuffer.ConvertBinaryToString(BinaryStringEncoding.Utf8,
args.Report.ScanDataLabel);
_callback(barcode);
}
}
So what was the problem?
In summary you sort of got yourself caught up in somewhat of a circular dependency problem whereby the VM depended on the BarcodeScanner and the BarcodeScanner depended on presentation APIs - something it shouldn't need to know about. Even with the good attempt at abstractions you had in the BarcodeScanner with regards to IScannable (sadly not a case with Poste), the scanning layer is making assumptions as to the type of users using it. It was just to vertical.
With this new approach you could very use it for other types of apps including UWP console apps if you needed to.

WPF - Closing window using MVVF command that runs new thread

The setup
I have a window and a viewmodel. The viewmodel has a command which executes a Task. When the task completes, I want the effect to be that the window closes.
What is the most acceptable (best-practice) way of doing this?
I tend to think that Dispatcher.Invoke is hacky and bad, but this is a moot point becaues the viewmodel does not have a reference to the window, or to its dispatcher.
edit: To clarify, the window belongs to the UI thread. The command itself calls a wrapper for doing an async http request (which returns a task). The command can append a ContinueWith.
I don't want to tightly couple the viewmodel to the view (such as by passing the Window to view model)
public class Api
{
Task MakeHttpCall();
}
public class ViewModel, DependencyObject
{
private Api _api;
public Task DoHttpCall() { return _api.MakeHttpCall(); }
public MyCommand MyCommandInst { get; private set; }
}
public class MyCommand : ICommand
{
void Execute(object parameter)
{
var viewModel = GetViewModel(parameter);
viewModel.DoHttpCall().ContinueWith( t => HandleCompletionAndSomehowTriggerWindowClose(t));
}
}
And then the view.xaml:
<Button Command={Binding MyCommandInst} CommandParameter={Binding}>Do Stuff</Button>
I use MVVM Light to help facilitate this process. The ViewModel has no reference to the View it just publishes a message to close and the View is registered to receive those messages.
In the code behind of my view, I subscribe to the Messenger service like this:
public class MyView
{
public MyView()
{
InitializeComponent();
Messenger.Default.Register<NotificationMessage>(this, msg =>
{
if ((msg.Sender == this.DataContext) && (msg.Notification.ToUpper() == "CLOSE"))
this.Close();
});
}
}
Then in the ViewModel (either in the callback method from your async process or at the end of the command method if not running async):
Messenger.Default.Send(new NotificationMessage(this, "Close"));
Generally, I believe, closing a window with the MVVM pattern depends on raising some form of Close event on the view-model that the view subscribes to:
public class MyView
{
public MyView(MyViewModel viewModel)
{
this.DataContext = viewModel;
viewModel.Close += (_, __) => Dispatcher.Invoke(this.Close);
}
}
Raise the Close event from your task's ContinueWith action, and you're done.

MVVM light, send message via Messenger from ViewModel to new child window, which is not initialized yet

I've following architecture:
desktop application, .Net 4.5, C#, WPF, MVVM Light, Messenger, IoC - ViewModel locator, so ViewModels doen't know anyhing about Views.
I have main view with data grid of some elements, and I want to display details of each individual element in new/child windows after double click on data grid.
I've bind event double click on main view to main view model. From this event handler in main view model, message is sent via Messanger.
New view (new/child window) is created in main view via delegate of also double click.
New/child window is a view which locate his view model and this view model register to the specific message in his constructor.
The problem is that new/child window (new view, and view model so on) is created too late, because message is already sent when new view model register for it.
Do you know maybe some patterns for such architecture. Any ideas will be appreciated.
It would help to know exactly what you try to do.
If your problem is just to display a detailed Window when double click on a row, I would say: create only one childWindow at start, and play with its visbility when required.
If you really need a new window each time, you could create it from your viewModel with an injected service for example.
In any case, you never has to create your window from main view! Either you create one window at start, either you dynamically create it from view model.
You cannot hope to create it from view and send the message in your view model.
Edit about the injected service, you could use something like that:
public interface IWindowService
{
void Open<TWindow>(ViewModelBase viewModel)
where TWindow : Window;
}
public class WindowService : IWindowService
{
private readonly IUIDispatcher _dispatcher;
public WindowService(IUIDispatcher dispatcher)
{
_dispatcher = dispatcher;
}
public void Open<TWindow>(ViewModelBase viewModel)
where TWindow : Window
{
_dispatcher.Run(() => OpenThreadSafe<TWindow>(viewModel));
}
private static void OpenThreadSafe<TWindow>(ViewModelBase viewModel) where TWindow : Window
{
var view = (TWindow) Activator.CreateInstance(typeof(TWindow), viewModel);
view.Show();
}
}
public class UIDispatcher : IUIDispatcher
{
public void Run(Action action)
{
var dispatcher = DispatcherHelper.UIDispatcher;
if (dispatcher == null)
{
action();
return;
}
DispatcherHelper.CheckBeginInvokeOnUI(action);
}
Note this DispatcherHelper come from MVVMlight, but you could erplace it easily.
Hope it helps.
The problem is that the ViewModel Locator creates the viewmodel instance only when it is needed (lazy loading).
just configure the ViewModelLocator to instantiate the viewmodel eager instead of lazy. This is done by passing the parameter "true" to the IoC Container.
Sample:
namespace Administration.ViewModel
{
public class ViewModelLocator
{
public ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
//Eager Loading
SimpleIoc.Default.Register<UserManagementViewModel>(true);
//Lazy Loading
SimpleIoc.Default.Register<InformationManagementViewModel>();
}
public UserManagementViewModel UserManagementViewModel
{
get
{
return ServiceLocator.Current.GetInstance<UserManagementViewModel>();
}
}
public InformationManagementViewModel InformationManagementViewModel
{
get
{
return ServiceLocator.Current.GetInstance<InformationManagementViewModel>();
}
}
public static void Cleanup()
{
SimpleIoc.Default.Unregister<UserManagementViewModel>();
SimpleIoc.Default.Unregister<InformationManagementViewModel>();
}
}
}

Custom Commands in wpf

I am working ona WPF application that has a toolbar/menu that will have the use for several custom commands. Probably around 15-20. I have seen documentation on how to create custom commands, but none of them necessarily apply to what I am trying to do.
I am using a controller to handle the business logic in my application, and I am trying to keep my view from doing any logic at all.
What I would like to do is create a directory in my project that holds the custom command classes so that I can decouple them from the controller and the view, but I would still like them to be called from the view such as a normal commmand is.
I have also seen the use of a DelegateCommand class, but am not quite sure if that is the direction I want to head in.
I would like to be able to have an arbitrary custom command class such as the following
public CustomCommand: ICommandd
{
public bool CanExecute(object parameter)
{
//arbitrary logic
}
public void Execute(object parameter)
{
}
}
The idea is that I would have 10-20 of these, and I want to keep them separate from everything else, and have them be called when needed.
I know that there is a way I can separate my custom commands, but am not quite sure.
I am new to using commands, so I still am trying to get a hold of the concept.
thanks,
The concept is you bind a command to a button and the command drives two properties of this button: "on click" and "enabled" resulting in the interface you posted.
The main reason you want to do commanding is to be able to bind button clicks to actions in your view model.
If you create one custom command which takes an action as constructor parameter you can wire methods from your view model directly to your command.
public class RelayCommand: ICommandd
{
Action action;
Func<bool> canExecute;
public RelayCommand(Action action) : this(action, () => true) {}
public RelayCommand(Action action, Func<bool> canExecute)
{
this.action = action;
this.canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return canExecute();
}
public void Execute(object parameter)
{
action();
}
}
Usage in your view model would be
public RelayCommand SaveCommand { get; set; }
SaveCommand = new RelayCommand(OnSave);
public void Save()
{
// save logic...
}
If you want to wire CanExecute, too you can use the second ctor and provide a CanSave Method.
public RelayCommand SaveCommand { get; set; }
SaveCommand = new RelayCommand(OnSave, CanSave);
public void Save()
{
// save logic...
}
public bool CanSave()
{
return // ...
}
As you may noticed I dropped the command parameter in my implementation. This will be sufficient in most cases and saves you extra parameters in your handler methods. For the 10% left I implemented a RelayCommand<T> which takes an Action instead of Action and changes the Execute method to
public void Execute(object parameter)
{
action((T)parameter);
}
which requires a parameterized handler
SaveCommand = new RelayCommand<SomeType>(OnSave);
public void Save(SomeType toSave)
{
// save logic using parameter
}
This saves you all casting issues you encounter when using object variables and keeps your view models type safe.
Use RelayCommand, it doesn't require you to create a class for each command, you simply add both methods into the constructor as lambda expressions/delegates.
I use it all around my projects, it is a real time-saver.
I have ended up answering my own question through the following post,
http://www.codeproject.com/KB/WPF/CentralizingWPFCommands.aspx?display=Print

Categories

Resources