Multithreading problems in WPF application using a barcode scanner - c#

I am using an MVVM style architecture and the whole application works fine. But I am introducing a scanner to the app and am now having numerous multithreading issues. The following is just some pseudocode but is basically how I need it to work:
View.xaml
<DataGrid ItemsSource="{Binding MyList}"/>
View.xaml.cs
class View : UserControl
{
public View()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
ViewModel.cs
class ViewModel
{
private Scanner scanner;//this is my scanner, duh
public ViewModel()
{
scanner = new Scanner();
scanner.ScanEvent += ScanEvent;
//all this does is when the scanner scans something
//then it will trigger an event looking for method ScanEvent()
}
public ObservableCollection<string> MyList{ get; set; }
public void ScanEvent()
{
string strBarcode = scanner.strBarcode;
MyList.Insert(0, strBarcode);//this is where the error is thrown
}
}
The error that is thrown is This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread. The scanner works fine when I set it to one of my objects so I don't understand why I can't do the same with this ObservableCollection? Here is a snippet from my scanner class that deals with the event:
Scanner.cs
internal class Scanner
{
public delegate void EventHandler();
public event EventHandler ScanEvent = delegate { };
public Scanner()
{
m_pCoreScanner.BarcodeEvent += new _ICoreScannerEvents_BarcodeEventEventHandler(OnBarcodeEvent);
RegisterForEvents();
}
public void OnBarcodeEvent(short eventType, ref string scanData)
{
strBarcode = GetBarcodeFromXml(scanData);
ScanEvent();
}
//this class is huge, so I only included pertinent code
}

As the exception message says, you'll have to update the ObservableCollection in the UI (or Dispatcher) thread, because a UI element's property (DataGrid.ItemsSource) is bound to the collection.
Try this:
public void ScanEvent()
{
string strBarcode = scanner.strBarcode;
Application.Current.Dispatcher.Invoke(
new Action(() => MyList.Insert(0, strBarcode)));
}

If you want to avoid doing Dispatcher.Invoke everywhere when wanting to get back to the UI thread - which I agree is fugly and is not very helpful for testing you could try using Reactive Extensions (Rx .Net).
Using Rx you can handle the event processing in a 'push' manner instead of the standard .Net events way (pull model). This will then allow you to compose LINQ style queries over the event\data and importantly in your case handle the scheduling back onto the dispatcher (UI) thread.
Below is the code above re-written to use Rx instead of the standard .Net event model:
.Net framework version = 4.0 (WPF)
Rx version = 2.0.21103.1
There are newer version of the Rx framework available on nuGet, you will also need to include Rx-WPF if you're going to use them with WPF.
internal class Scanner
{
public IObservable<EventArgs> ScanEvent
{
get
{
return Observable.FromEventPattern<EventHandler, EventArgs>(
h => m_pCoreScanner.BarcodeEvent += h, h => m_pCoreScanner.BarcodeEvent -= h)
.Select(x => x.EventArgs);
}
}
}
class ViewModel : IDisposable
{
private Scanner scanner;
private IDisposable _disposable;
public ViewModel()
{
scanner = new Scanner();
MyList = new ObservableCollection<string>();
_disposable = scanner.ScanEvent
.ObserveOn(DispatcherScheduler.Current)
.Subscribe(x =>
{
string strBarcode = scanner.strBarcode;
MyList.Insert(0, strBarcode);
});
}
public void Dispose()
{
_disposable.Dispose();
}
public ObservableCollection<string> MyList { get; set; }
}
A good place to start with learning Rx is this free online e-book - http://www.introtorx.com/

If you use a framework such as MvvmLight, you could leverage the message mechanisms. You register a handler in your VM and then send the message from your scanner library
http://wbsimms.com/mvvm-light-toolkit-messaging-example/
These messages work across assemblies via a Default singleton, but all depends on whether you're scanner library is customisable.

Related

How do I properly append data to my collection using MVVM?

So I have created a service that is going to connect to my database and grab a few proxies every here and there so it's going to be doing is contiguously, I am going to have to make it async or with a backgroundworker so it won't deadlock the UI.
However, I've gotten to the part where I've setup my relay command and I want to invoke that function that grabs the proxies.
I have created a service that has the function in it, I didnt add the connecting stuff etc yet so this is mostly hypothetical but the question still stands.
public class ProxyDeliveryService
{
public ProxyDeliveryService()
{
}
public Proxy GrabProxy()
{
//Do work..
//Return the proxy
return null;
}
}
How do I append the data to my collection in my ViewModel with a good MVVM approach? No singletons or anything like that.
This here is throwing an error because it's expecting a delegate with a object parameter. Action<object> and a predicate so just like any other RelayCommand
public class ProxyContainerViewModel : ObservableObject
{
private ProxyDeliveryService pds = new ProxyDeliveryService();
public ObservableCollection<Proxy> Proxies { get; set; } = new ObservableCollection<Proxy>();
public RelayCommand Grabproxies { get; set; } = new RelayCommand(pds.GrabProxy(), true);
public ProxyContainerViewModel()
{
}
}
I think you are overcomplicating this. What's wrong with:
public ICommand Grabproxies { get; set; } = new RelayCommand(CreateProxy, true);
private void CreateProxy(object param)
{
Proxies.Add(pds.GrabProxy());
}

WPF: Execute some code asynchronously in .NET 3.5

I have a MVVM WPF app. I have a window, let's say "LvWindow", with a listview that is loaded from data comming from a database. From main window "MainWindow", I have a menu, which has some options. When I select the option to access "LvWindow", it is open. Then from ViewModel, in the constructor I have a call to a database from which I request some data that then I load into the listview.
My goal is to make the process to request data from database and then load it in the listview asynchronous. I want this in order to not block the whole app, I mean, during this window is loaded, user can go to the main window menu and select to open another type of window. Windows are open in tabs.
While the process of requesting data from database and being loaded into listview in window "LvWindow", I show a splash saying "Loading" on it(in fact this is a rectangle with zindex set to a higher number to avoid user can interact with listview until it is completely loaded). This splash will be closed when the listview is loaded with the data comming from database.
So to make the process asynchronous, I know in winforms it can be done with delegates by using beginInvoke, endInvoke and callbacks methods, see here.
Also, another possibility is to use a background worker, like posted here.
So in WPF which is the best way to do it? using delegates as winforms or background workers?
ATTEMPT #1:
I have tried XANIMAX solution as this:
public class TestViewModel : BaseViewModel
{
private static Dispatcher _dispatcher;
public ObservableCollection<UserData> lstUsers
public ObservableCollection<UserData> LstUsers
{
get
{
return this.lstUsers;
}
private set
{
this.lstUsers= value;
OnPropertyChanged("LstUsers");
}
}
public TestViewModel()
{
ThreadPool.QueueUserWorkItem(new WaitCallback((o) =>
{
var result = getDataFromDatabase();
UIThread((p) => LstUsers = result);
}));
}
ObservableCollection<UserData> getDataFromDatabase()
{
return this.RequestDataToDatabase();
}
static void UIThread(Action<object> a)
{
if(_dispatcher == null) _dispatcher = Dispatcher.CurrentDispatcher;
//this is to make sure that the event is raised on the correct Thread
_dispatcher.Invoke(a); <---- HERE EXCEPTION IS THROWN
}
}
but in line _dispatcher.Invoke(a) an exception is thrown:
TargetParameterCountException: the parameter count mismatch
UserData is my data model, it is a class with some public properties. Something like:
public class UserData
{
public string ID{ get; set; }
public string Name { get; set; }
public string Surname { get; set; }
// Other properties
}
so the problem is that the call to database is returning "RequestDataToDatabase" is returning a collection of UserData (ObservableCollection) so the exception is thrown.
I do not know how to solve it. Could you help me, please?
Final solution:
As XAMIMAX said in the comments:
Change the signature from static void UIThread(Action a) to static void UIThread(Action a)
modify UIThread((p) => LstUsers = result); by UIThread(() => LstUsers
= result);
As you can't await asynchronous methods in a constructor in C# 7.0 (but async Main is coming in 7.1) you can extract your async function calls to a separate function in your ViewModel and synchronously call this within your View's code-behind constructor, after you have created your ViewModel and assigned it to the View's DataContext:
public MainWindow()
{
this.vm = new MyViewModel();
this.DataContext = this.vm;
this.InitializeComponent();
this.vm.AsychronousFunctionToCallDatabase();
}
As XAMIMAX says, you want to implement a ViewModel to handle business logic between your View and your models. Then if your ViewModel implements INotifyPropertyChanged and and you set up Binding in your XAML to your properties in the ViewModel - then the display will refresh after the database call without blocking the UI thread. Note, if you have any collections populated from the database call then they should be of type ObservableCollection.
But as Kundan says, in your AsychronousFunctionToCallDatabase() function you should include an await statement or a create a Task on the line that calls the database - this will return control to the calling function (in this case, to the MainWindow constructor).
Here is one of the possible solutions for you.
In your View Model you would have something like this:
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Threading;
namespace VM
{
public class TestViewModel : BaseViewModel
{
private static Dispatcher _dispatcher;
List<object> ListToDisplay { get; set; }//INPC omitted for brevity
public TestViewModel()
{
ThreadPool.QueueUserWorkItem(new WaitCallback((o) =>
{
var result = getDataFromDatabase();
UIThread(() => ListToDisplay = result);
}));
}
List<object> getDataFromDatabase()
{
//your logic here
return new List<object>();
}
static void UIThread(Action a)
{
if(_dispatcher == null) _dispatcher = Dispatcher.CurrentDispatcher;
//this is to make sure that the event is raised on the correct Thread
_dispatcher.Invoke(a);
}
}
}
There are couple of options to run the method asynchronously.
async await - https://msdn.microsoft.com/library/hh191443(vs.110).aspx
Task Parallel Library :https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/task-based-asynchronous-programming

How to handle events that happened on different page

I have a UWP Project with 2 pages so far. The MainPage.xaml is the basic layout of the app ( hamburger menu, search bar, etc.). The other part of this MainPage contains a frame into which the other page LandingPage.xaml is loaded. I want to capture the user input from an AutosuggestBox in the MainPage.xaml and show the results on LandingPage.xaml ( which is in a frame present inside MainPage.xaml).
I tried inheriting the MainPage, but that's not allowed.
While Marian's answer would certainly work, I think it's far from being 'clean' or 'good' code.
First and foremost, you should implement the MVVM pattern in your UWP apps (if you don't do it already) and use a dependency injection framework for that. A very basic, easy to understand one is MVVMLight, while a more sophisticated framework of choice could be Autofac. I advise you to start with the former, it's much quicker to wrap your head around it first.
In MVVM there's a concept that solves just your problem: messengers. I wouldn't like to get into the details here, since there already a lot of very good resources about this written by much smarter people than me. For example this article from the author of MVVMLight himself: https://msdn.microsoft.com/en-us/magazine/jj694937.aspx (I know it's from 2013 and speaks about Windows 8, but fear not, the concepts are just the same.)
The idea is that distinct ViewModels shouldn't have strict dependencies on each other - it makes unit testing (which is one of the main points of doing MVVM in the first place) hard. So in your case, you should have two ViewModels: MainViewModel and LandingViewModel. One for MainPage, and one for LandingPage, respectively. Now you should implement a handler in MainPage's code-behind for AutoSuggestBox's QuerySubmitted event and call a function in MainViewModel. In that function, you would instantiate a new message with the string coming from your AutoSuggestBox (which you can acquire either from doing data binding to it or through the event handler of QuerySubmitted, it's up to you) and send it via the Messenger. In LandingViewModel, you would subscribe to this exact message and then it's again just a matter of few lines to display the received message through data binding on LandingPage.
I know it looks like a lot of hassle for just something very basic like this, especially if you compare it to Marian's straight to the point solution. But trust me, in the long run writing clean code, nicely separated, easily unit testable ViewModels will make up for the additional effort that you have to put into them initially to make them work. After such a system is set up between two ViewModels, adding a third (which I assume you'll need to do soon) is absolutely trivial and can be done very quickly.
If you're not using MVVM I'd suggest adding x:FieldModifier="public" on the AutoSuggestBox and add a public static property to MainPage to store its instance.
MainPage.xaml.cs
public static MainPage Current { get; private set; }
public MainPage()
{
Current = this;
// Rest of your code in ctor
}
Then you can access it using
string text = MainPage.Current.NameOfYourAutoSuggestBox.Text;
Just use a simple message passing mechanism of your own, like this:
public class Messages {
public static Messages Instance { get; } = new Messages();
private readonly List<Subscription> subscriptions;
private Messages() {
subscriptions = new List<Subscription>();
}
public void Send<T>(T message) {
var msgType = message.GetType();
foreach (var sub in subscriptions)
if (sub.Type.IsAssignableFrom(msgType))
sub.Handle(message);
}
public Guid Subscribe<T>(Action<T> action) {
var key = Guid.NewGuid();
lock (subscriptions) {
subscriptions.Add(new Subscription(typeof(T), key, action));
}
return key;
}
public void Unsubscribe(Guid key) {
lock (subscriptions) {
subscriptions.RemoveAll(sub => sub.Key == key);
}
}
public bool IsSubscribed(Guid key) {
lock (subscriptions) {
return subscriptions.Any(sub => sub.Key == key);
}
}
public void Dispose() {
subscriptions.Clear();
}
}
internal sealed class Subscription {
internal Guid Key { get; }
internal Type Type { get; }
private object Handler { get; }
internal Subscription(Type type, Guid key, object handler) {
Type = type;
Key = key;
Handler = handler;
}
internal void Handle<T>(T message) {
((Action<T>)Handler).Invoke(message);
}
}
It's small and simple but it allows the subscription of different messages in parallel, separated by message type. You can subscribe, in a case similar to yours, with:
Messages.Instance.Subscribe<TextChangeArgs>(OnTextChanged);
and your other pages can send their messages using:
Messages.Instance.Send(new TextChangeArgs(...));
From all subscribers, only those interested in this specific message type will receive the message. You can (and, of course, should) also unsubscribe. Some more error handling could also be necessary in a real world scenario.
If necessary, you can add extra functionality like throttling easily (to avoid too many consecutive messages in a given time period).

RelayCommand is not refreshing execute/canexecute changes

I'm newbee in mvvm (and mvvlight of course). I have 3 modelviews (a MainWindow which have a container, and another 2 modelviews (Login and Menu)). In the LoginModelView, when the user login is successfully, this call the MenuViewModel (With Messenger.Default) changing the page in the MainWindow container. All is alright until that, then i call a Message.Default.Send sending a object from LoginModelView to MenuModelView which is correctly listened, catching the object associed and executing the method associated (ConfiguraMenu) wich define a RelayCommand (checked line by line and the method is executed without any exception) but the problem is this RelayCommand is not working until i back to the LoginViewModel and i login again. I try CommandManager.InvalidateRequerySuggested() and is not working either.
This is the code for the LoginViewModel:
//This method is called when the user press the login button. No problem with this
public void ActionVerificaUsuario()
{
Miusuario = db.getUsuario(Txtusuario, Txtpassword);
if (Miusuario.esUsuario())
{
Messenger.Default.Send(new MoveToViewMessage(Page.MenuView));
Messenger.Default.Send((UsuarioModel)Miusuario);
}
}
This code is for the MenuViewModel:
public RelayCommand AbreExeClaseCommand { get; private set; }
public MenuViewModel()
{
Messenger.Default.Register<UsuarioModel>(this, usuario_recibido => {Miusuario = usuario_recibido;ConfiguraMenu(); });
}
private void ConfiguraMenu() {
Mimenu = new MenuModel(Miusuario);
AbreExeClaseCommand = new RelayCommand(() => { Messenger.Default.Send(new MoveToViewMessage(Page.NeverReachedView)); }, () => Mimenu.Sw_reportes);
CommandManager.InvalidateRequerySuggested();
AbreExeClaseCommand.RaiseCanExecuteChanged();
}
I tried to hardcode the CanExecute with true but the Execute is still without work until back and login again.
I hope you can help me (i'm scratching my head for various days with none result).
MvvmLight provides two different RelayCommand classes in two different namespaces:
Galasoft.MvvmLight.Command
Galasoft.MvvmLight.CommandWpf
Make sure, that you are using the correct namespace Galasoft.MvvmLight.CommandWpf in your WPF application.
There was a bug in MVVMLight, which resulted in not working CanExecute() behavior. They fixed it with the new .CommandWpf namespace in MVVMLight Version V5.0.2.
You can also check out this GalaSoft blog post and the change log for further information.
You try to bind the CanExecute to a propertie.
So my guess is you didn't use RaisePropertie Changed in this propertie.
You must have something like:
public class MenuModel : ViewModelBase
{
// Other pieces of code....
private bool _sw_reportes;
public bool Sw_reportes
{
get { return _sw_reportes; }
set { _sw_reportes = value;
RaisePropertyChanged(() => Sw_reportes); }
}
}

INotifyCollectionChanged UI not updated

I have problem with updating UI. I have class which, is used to binding my UI elements:
public class engine : INotifyCollectionChanged
{
RWProject project = new RWProject();
public ObservableCollection<string> ProjectListBinding
{
get { return project.list(); }
}
public event NotifyCollectionChangedEventHandler CollectionChanged;
private void OnCollectionChanged(NotifyCollectionChangedEventArgs eventArgs)
{
if (this.CollectionChanged != null)
{
this.CollectionChanged(this, eventArgs);
}
}
private ICommand _usunProjekt;
public ICommand UsunProjekt
{
get
{
_usunProjekt = new UsunProjektCommand();
return _usunProjekt;
}
}
private ICommand _dodajProjekt;
public ICommand DodajProjekt
{
get
{
_dodajProjekt = new DodajNowyProjektCommand();
return _dodajProjekt;
}
}
}
ProjectListBinding is a list of files names inside folder, and this names are displayed on listview control.
Commands DodajProjekt creating in same folder, new file (UsunProjekt - removing)
Commands are binded to buttons.
I need to rise event
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset))
to update UI after command is executed, but I don't knew where to attach this part of code. Or maybe I should do it in different way ?
With code I already have, bindings and commands are working fine, only updating not working at all.
Can You help me solve this ?
Piotr
You should not be implementing INotifyCollectionChanged. From your view you need to bind to ProjectListBinding which will automatically raise INotifyCollectionChanged for you and update the UI. Of course, assuming that the class that you have (i.e. engine) is your view model.
When running your command, you should update ProjectListBinding for INotifyCollectionChanged events to be risen. That is, both your commands DodajNowyProjektCommand and UsunProjekt should be operating on ProjectListBinding.

Categories

Resources