Is there a better way to use barcode scanner? - c#

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.

Related

Publish/Subcribe Notification Service Over gRPC Using protobuf-net.grpc

I'm trying to write a notification system between a server and multiple clients using gRPC server streaming in protobuf-net.grpc (.NET Framework 4.8).
I based my service off of this example. However, if I understand the example correctly, it is only able to handle a single subscriber (as _subscriber is a member variable of the StockTickerService class).
My test service looks like this:
private readonly INotificationService _notificationService;
private readonly Channel<Notification> _channel;
public ClientNotificationService(INotificationService notificationService)
{
_notificationService = notificationService;
_notificationService.OnNotification += OnNotification;
_channel = Channel.CreateUnbounded<Notification>();
}
private async void OnNotification(object sender, Notification notification)
{
await _channel.Writer.WriteAsync(notification);
}
public IAsyncEnumerable<Notification> SubscribeAsync(CallContext context = default)
{
return _channel.AsAsyncEnumerable(context.CancellationToken);
}
INotificationService just has an event OnNotification, which is fired when calling its Notify method.
I then realized that System.Threading.Channels implements the Producer/Consumer pattern, but I need the Publisher/Subscriber pattern. When trying it out, indeed only one of the clients gets notified, instead of all of them.
It would also be nice if the server knew when a client disconnects, which seems impossible when returning _channel.AsAsyncEnumerable.
So how can I modify this in order to
serve multiple clients, with all of them being notified when OnNotification is called
and log when a client disconnects?
For 1, you'd need an implementation of a publisher/subscriber API; each call to SubscribeAsync will always represent a single conversation between gRPC endpoints, so you'll need your own mechanism for broadcasting that to multiple consumers. Maybe RX is worth investigating there
For 2, context.CancellationToken should be triggered by client-disconnect
Many thanks to Marc Gravell
I rewrote the NotificationService like this, using System.Reactive.Subjects (shortened) - no need for an event, use an Action instead:
public class NotificationService<T>
{
private readonly Subject<T> _stream = new Subject<T>();
public void Publish(T notification)
{
_stream.OnNext(notification);
}
public IDisposable Subscribe(Action<T> onNext)
return _stream.Subscribe(onNext);
}
}
My updated ClientNotificationService, which is exposed as a code-first gRPC service:
public class ClientNotificationService : IClientNotificationService
{
private readonly INotificationService<Notification> _notificationService;
public ClientNotificationService(INotificationService<Notification> notificationService)
{
_notificationService = notificationService;
}
public async IAsyncEnumerable<Notification> SubscribeAsync(CallContext context = default)
{
try
{
Channel<Notification> channel = Channel.CreateUnbounded<Notification>(
new UnboundedChannelOptions { SingleReader = true, SingleWriter = true });
CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(context.CancellationToken);
using (_notificationService.Subscribe(n => channel.Writer.WriteAsync(n, cts.Token)))
{
await foreach (Notification notification in channel.AsAsyncEnumerable(cts.Token))
{
yield return notification;
}
}
}
finally
{
// canceled -> log, cleanup, whatever
}
}
}
Note: Solution provided by OP on question section.

Communication between Topshelf service (acting as TCP server) and selfhosted OWIN WebAPI

I have a Topshelf windows service that acts as a TCP server. Inside this service, I also have a self-hosted (OWIN) WebAPI.
My goal is to somehow allow the WebAPI to communicate with the TCP server that's contained and running in the same service. Naturally I could simply use something like a "trigger" file or a shared DB that could be polled frequently, though I'd like to know of any more optimal/native ways to achieve this.
To get a better idea of the project, think of a single page application consuming my API and making certain calls with arbitrary string parameters. This data should then be passed to clients (C++ console apps using winsock) that are connected to the running TCP server.
The following Container is instantiated and passed to the Topshelf HostConfigurator
class ContainerService
{
private APIService _apiService;
private EngineService _engineService;
protected IDisposable WebAppHolder { get; set; }
public bool Start(HostControl hostControl)
{
var host = hostControl;
_apiService = new APIService();
_engineService = new EngineService();
// Initialize API service
if (WebAppHolder == null)
{
WebAppHolder = _apiService.Initialize();
}
// Initialize Engine service
_engineService.Initialize();
return true;
}
public bool Stop(HostControl hostControl)
{
// Stop API service
if (WebAppHolder != null)
{
WebAppHolder.Dispose();
WebAppHolder = null;
}
// Stop Engine service
_engineService.Stop();
return true;
}
}
Standard Topshelf stuff in program entry point (as mentioned above):
HostFactory.Run(hostConfigurator =>
{
hostConfigurator.Service<ContainerService>(containerService =>
{
containerService.WhenStarted((service, control) => service.Start(control));
containerService.WhenStopped((service, control) => service.Stop(control));
});
hostConfigurator.RunAsLocalSystem();
hostConfigurator.SetServiceName("Educe Service Host");
hostConfigurator.SetDisplayName("Communication Service");
hostConfigurator.SetDescription("Responsible for API and Engine services");
});
TCP Server:
public void Initialize()
{
_serverListener = new TcpListener(new IPEndPoint(hostAddress, (int)port));
_serverListener.Start();
_threadDoBeginAcceptTcpClient = new Thread(() => DoBeginAcceptTcpClient(_serverListener));
_threadDoBeginAcceptTcpClient.Start();
}
...
public void DoBeginAcceptTcpClient(TcpListener listener)
{
while(!_breakThread)
{
// Set the event to nonsignaled state.
TcpClientConnected.Reset();
// Start to listen for connections from a client.
Console.WriteLine("Waiting for a connection...");
// Accept the connection.
listener.BeginAcceptTcpClient(DoAcceptTcpClientCallback, listener);
// Wait until a connection is made and processed before continuing.
TcpClientConnected.WaitOne();
}
}
// Process the client connection.
public void DoAcceptTcpClientCallback(IAsyncResult ar)
{
// Get the listener that handles the client request.
TcpListener listener = (TcpListener)ar.AsyncState;
// End the operation and display the received data on the console.
Console.WriteLine("Client connection completed");
Clients.Add(listener.EndAcceptTcpClient(ar));
// Signal the calling thread to continue.
TcpClientConnected.Set();
}
WebAPI Controller:
public class ValuesController : ApiController
{
// GET api/values/5
public string Get(int id)
{
return $"Foo: {id}";
}
}
As mentioned earlier, what I seek is "communication" between the WebAPI and the windows service. How can I pass the "id" parameter from the WebAPI call to the _engineService object in my windows service? Perhaps something similar to WPF's MVVM Light Messenger? The idea is that it would then be parsed and sent to the appropriate TcpClient that is stored in the Clients List.
Any advice on how to achieve this will be appreciated. Please feel free to ask for clarification/more code.
Did you find any answer to your issue yet ?
I don't quite understand what you try to achieve looking for a communication between the two of them ? Do you want to somehow rely on TCP/IP to relay this id or in-memory ?
Potentially, you could consider a Mediator pattern and use this kind of library that seems quite useful in the case I understood : https://github.com/jbogard/MediatR
In a simpler approach, I would rely on events to achieve what you are trying to do, which is having a reactive communication from the HTTP request to the c++ users.
Did I understand you needs ? I am quite curious about the solution
I'm assuming you are trying to take an HTTP GET request's ID parameter and send it to TCP clients who are connected to the EngineService. If your EngineService is initialized before your ApiService, I think this is a question of how to get a handle to the one-and-only EngineService instance from within an ApiService's controller instances.
If I'm following you, you could make the EngineService a public static property of your ContainerService and reference it as ContainerService.EngineService from the controller (or anywhere in the app for that matter) or better register your EngineService as a singleton in a DI container an inject it into the ApiService.
Solution (calls to WebAPI trigger EngineService)
I now use RabbitMQ/EasyNetQ to achieve communication between the WebApi and the EngineService object containing my TCP clients.
I have incidentally split them into two separate Projects/Topshelf services now.
The following is the new "communication" component and it is instantiated in the EngineService constructor.
public class Communication
{
private readonly Logger _logger;
private readonly IBus _bus;
public delegate void ReceivedEventHandler(string data);
public event ReceivedEventHandler Received;
protected virtual void OnReceive(string data)
{
Received?.Invoke(data);
}
public Communication()
{
_logger = new Logger();
_bus = RabbitHutch.CreateBus("host=localhost", reg => reg.Register<IEasyNetQLogger>(log => _logger));
SubscribeAllQueues();
}
private void SubscribeAllQueues()
{
_bus.Receive<Message>("pipeline", message =>
{
OnReceive(message.Body);
});
}
public void SubscribeQueue(string queueName)
{
_bus.Receive<Message>(queueName, message =>
{
OnReceive(message.Body);
});
}
}
An event handler is then added.
This means that as soon as a message arrives to the bus, the data will be relayed to the event handler which will subsequently relay it to the first connected TCP client in the list.
public void Handler(string data)
{
//Console.WriteLine(data);
_clients[0].Client.Send(Encoding.UTF8.GetBytes(data));
}
...
_comPipe.Received += Handler;
And finally on the WebApi's controller:
public string Get(int id)
{
ServiceCom.SendMessage("ID: " + id);
return "value";
}
ServiceCom class. Allows sending a string message on the bus.
public static class ServiceCom
{
public static void SendMessage(string messageBody)
{
var messageBus = RabbitHutch.CreateBus("host=localhost");
messageBus.Send("pipeline", new Message { Body = messageBody });
}
}
Now that this is done, I am now looking to implement a way for the connected TCP clients to trigger updates/events in an additional SPA project that will act as a Portal / Client Management App.
My approach will probably make use of KnockOut.js and SignalR to achieve dynamic Views where TCP client events are displayed immediately and similarly actions on to WebAPI will trigger events in the TCP clients. I know it sounds like a bizarre combination of processes but it is all according to plan and working out as expected :)

Marshalling data back to UI Thread from within Async Action

I've got an ICommand that needs to set data to a property on the UI Thread.
public override async void Execute(object parameter)
{
var vm = (MyVm)parameter;
var data = await _myDataService.GetData();
vm.MyData = data; // must be set on UI Thread due to binding.
}
Now I want to wrap my call in an event logger (I originally wanted to do AOP and decorate the method with a logging attribute, but I couldn't figure it out in a PCL). So I moved onto wrapping my call like this.
public override void Execute(object parameter)
{
EventLogger.LogEvent(this,
EventLogEntryType.Command,
EventLogErrorSeverity.Warning,
Errors.GetServiceAreaCommand_ErrorMessage,
async () =>
{
var vm = (MyVm)parameter;
var data = await _myDataService.GetData();
vm.MyData = data; // must be set on UI Thread due to binding.
});
}
Here's the LogEvent method.
public static void LogEvent(object sender,
EventLogEntryType entryType,
EventLogErrorSeverity eventLogErrorSeverity,
string friendlyErrorMessage,
Action action)
{
var name = sender.GetType().Name.SplitCamelCase();
var startEntry = new EventLogEntry(entryType);
LogEvent(string.Format("Start: {0}", name), startEntry);
try
{
action.Invoke();
}
catch (Exception ex)
{
var exEntry = new EventLogEntry(EventLogEntryType.Error, friendlyErrorMessage, false, ex)
{
ErrorSeverity = eventLogErrorSeverity
};
LogEvent(string.Format("Error: {0}", name), exEntry);
if (eventLogErrorSeverity == EventLogErrorSeverity.Critical)
{
throw;
}
}
var endEntry = new EventLogEntry(entryType);
LogEvent(string.Format("Finish: {0}", name), endEntry);
}
The problem is that it appears as though I'm STILL setting the property on a background thread instead of the Main thread (IllegalStateException in Android).
What is the cleanest way to set the data as is being done in the first example, while still wrapping the Action in a logging method?
I also had success creating a base class for ICommand, but it A) changed the method signatures for CanExecute and Execute, and B) it also (obviously) doesn't extend it's capabilities beyond Commands.
I'm looking for a clean way to log methods (BeforeExecute, AfterExecute, OnError) no matter what they do.
As an aside, the ideal logging mechanism would be to use an Interceptor, but I'm just not strong enough in my C# chops to implement it.
[Log(EventLogEntryType.Command, EventLogErrorSeverity.Warning, "Some Friendly Message")]
public override async void Execute(object parameter)
{
var vm = (MyVm)parameter;
var data = await _myDataService.GetData();
vm.MyData = data; // must be set on UI Thread due to binding.
}
If you have (caveat below) access to the Activity object in your code then you can probably do;
Activity.RunOnUiThread(() => {
//Execute my code on UIThread here
});
But it's an if, because I note you're using a PCL, or have referenced using one, so I suspect that a shared library is not going to know anything about the Activity (unless you pass that too). Very much depends on your app structure and where this code is, but within the main Xamarin.Android project where your views are the above should work

MVP Navigation in WinForms

Have been learning about MVP and have tried writing a test app using it in WinForms. I'm struggling to find a well explained example on how to navigate between my forms/views. As an example, the program starts and I want to show a login dialog then go into my main view if the login was successful. At the moment, my Main method looks something like this:
static void Main()
{
var loginView = Injector.Resolve<ILoginView>();
if (loginView.DoLogin() != LoginResult.OK) return;
var mainView = Injector.Resolve<IMainView>();
Application.Run(mainView); // won't work as mainView isn't a form
}
The Injector object is just a wrapper around an IoC tool (currently StructureMap). The thing is, I've read that I shouldn't really be manually creating instances via the Injector as they should really be done via constructor injection.
I've managed to do this up to a point but not when it comes to navigation. I can't think of an elegant way of moving through my views and was wondering if anyone here might shed some light on this? I've read a little on application controllers but have not found an example to show it clearly.
In regards to the navigation question:
I've managed to do this up to a point but not when it comes to
navigation. I can't think of an elegant way of moving through my views
and was wondering if anyone here might shed some light on this? I've
read a little on application controllers but have not found an example
to show it clearly.
Below is a simplified version of a construct I've used. Note that the setup and tear down hooks are called automatically when the NavigateTo method is called. Also, +1 to #AlexBurtsev, as his answer hints at this very same approach.
// Presenter can and should offer common services for the
// subclasses
abstract class Presenter
{
public void Display()
{
OnDisplay();
}
public void Dismiss()
{
OnDismiss();
}
protected virtual OnDisplay() // hook for subclass
{
}
protected virtual OnDismiss() // hook for subclass
{
}
private NavigationManager _navMgr;
internal NavigationMgr NavigationManager
{
get
{
return _navMgr;
}
set
{
_navMgr = value;
}
}
}
// NavigationManager is used to transition (or navigate)
// between views
class NavigationManager
{
Presenter _current;
// use this override if your Presenter are non-persistent (transient)
public void NavigateTo(Type nextPresenterType, object args)
{
Presenter nextPresenter = Activator.CreateInstance(nextPresenterType);
NavigateTo(nextPresenter);
}
// use this override if your Presenter are persistent (long-lived)
public void NavigateTo(Presenter nextPresenter, object args)
{
if (_current != null)
{
_current.Dismiss()
_current.NavigationMgr = null;
_current = null;
}
if (nextPresenter != null)
{
_current = nextPresenter;
_current.NavigationMgr = this;
_current.Display(args);
}
}
}
class MainMenuPresenter : Presenter
{
private IMainMenuView _mainMenuView = null;
// OnDisplay is your startup hook
protected override void OnDisplay()
{
// get your view from where ever (injection, etc)
_mainMenuView = GetView();
// configure your view
_mainMenuView.Title = GetMainTitleInCurrentLanguage();
// etc
// etc
// listen for relevent events from the view
_mainMenuView.NewWorkOrderSelected += new EventHandler(MainMenuView_NewWorkOrderSelected);
// display to the user
_mainMenuView.Show();
}
protected override void OnDismiss()
{
// cleanup
_mainMenuView.NewWorkOrderSelected -= new EventHandler(MainMenuView_NewWorkOrderSelected);
_mainMenuView.Close();
_mainMenuView = null;
}
// respond to the various view events
private void MainMenuView_NewWorkOrderSelected(object src, EventArgs e)
{
// example of transitioning to a new view here...
NavigationMgr.NavigateTo(NewWorkOrderPresenter, null);
}
}
class NewWorkOrderPresenter : Presenter
{
protected override void OnDisplay()
{
// get the view, configure it, listen for its events, and show it
}
protected override void OnDismiss()
{
// unlisten for events and release the view
}
}
I haven't used WinForms for a long time, but I'll try to answer this. I would use the same strategy as WPF Prism do.
About MainView and Application.Run:
Create a main Region (root Form), with empty container inside which can hold UserControl (I forgot exact class names), then when you need to switch root view, you do RootView.SetView(UserControl view) which will do something like Form.Clear(), Form.AddChild(view).
About the navigation and using container:
You could create a service for navigation: INavigationService which you inject in constructors with method like INavigationService.NavigateView(String(or Type) viewName, params object[] additionalData)
You can insert a method in mainView that returns the actual form.Then you can call
Mainview:IMainView
{
Form GetView()
{
//return new Form();
}
}
In Main you can call ,
Application.Run(mainView.GetView())

Callback from ViewModel to View

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.
});
}

Categories

Resources