I am developing my first WPF browser application.
I load invoices in a dataGrid then I filter with textBox or comboBox.
Because it takes few seconds to load, I'am trying to put a loading animation according the following example:
here
I want to filter my dataGrid depending on two comboBox.
But I have this error
This type of CollectionView does not support changes to its
SourceCollection from a thread different from the Dispatcher thread
at the line invoiceCollection.Clear();
and invoiceCollection.Add(inv); in SearchFilter();
I tried
App.Current.Dispatcher.Invoke((Action)delegate // <--- HERE
{
//code here
});
but I still have the same error.
ViewModel
public class ConsultInvoiceViewModel : ViewModelBase
{
public Context ctx = new Context();
private ICollectionView _dataGridCollection;
private ObservableCollection<Invoice> invoiceCollection;
public ConsultInvoiceViewModel()
{
if (!WPFHelper.IsInDesignMode)
{
var tsk = Task.Factory.StartNew(InitialStart);
tsk.ContinueWith(t => { MessageBox.Show(t.Exception.InnerException.Message); }, CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.FromCurrentSynchronizationContext());
}
}
private void InitialStart()
{
try
{
State = StateEnum.Busy;
DataGridCollection = CollectionViewSource.GetDefaultView(Get());
DataGridCollection.Filter = new Predicate<object>(Filter);
}
finally
{
State = StateEnum.Idle;
}
}
private void SearchFilter()
{
Task tsk = Task.Factory.StartNew(()=>
{
try
{
State = StateEnum.Busy;
using (var ctx = new Context())
{
var invs = ctx.Invoices
.Where(s.supplier == 1)
.GroupBy(x => new { x.suppInvNumber, x.foodSupplier })
.ToList()
.Select(i => new Invoice
{
suppInvNumber = i.Key.suppInvNumber,
foodSupplier = i.Key.foodSupplier,
totalPrice = i.Sum(t => t.totalPrice),
});
.
App.Current.Dispatcher.Invoke((Action)delegate
{
invoiceCollection.Clear();
});
if (invs != null)
foreach (var inv in invs)
{
App.Current.Dispatcher.Invoke((Action)delegate
{
invoiceCollection.Add(inv);
});
}
}
}
finally
{
State = StateEnum.Idle;
}
});
tsk.ContinueWith(t => { MessageBox.Show(t.Exception.InnerException.Message); }, CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.FromCurrentSynchronizationContext());
}
public static readonly PropertyChangedEventArgs StateArgs = ViewModelBase.CreateArgs<ConsultInvoiceViewModel>(c => c.State);
private StateEnum _State;
public StateEnum State
{
get
{
return _State;
}
set
{
var oldValue = State;
_State = value;
if (oldValue != value)
{
OnStateChanged(oldValue, value);
OnPropertyChanged(StateArgs);
}
}
}
protected virtual void OnStateChanged(StateEnum oldValue, StateEnum newValue)
{
}
}
ViewModelBase
public abstract class ViewModelBase : INotifyPropertyChanged
{
#region "INotifyPropertyChanged members"
public event PropertyChangedEventHandler PropertyChanged;
//This routine is called each time a property value has been set.
//This will //cause an event to notify WPF via data-binding that a change has occurred.
protected void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
public void OnPropertyChanged(PropertyChangedEventArgs args)
{
if (PropertyChanged != null)
PropertyChanged(this, args);
}
public static PropertyChangedEventArgs CreateArgs<T>(Expression<Func<T, Object>> propertyExpression)
{
return new PropertyChangedEventArgs(GetNameFromLambda(propertyExpression));
}
private static string GetNameFromLambda<T>(Expression<Func<T, object>> propertyExpression)
{
var expr = propertyExpression as LambdaExpression;
MemberExpression member = expr.Body is UnaryExpression ? ((UnaryExpression)expr.Body).Operand as MemberExpression : expr.Body as MemberExpression;
var propertyInfo = member.Member as PropertyInfo;
return propertyInfo.Name;
}
}
There is a really nice way to solve this in .Net 4.5 and greater:
private object _lock = new object();
BindingOperations.EnableCollectionSynchronization("YourCollection", _lock);
So you don't need to use the Dispatcher everytime you want to manipulate your collection.
Here are some resources for further information:
http://10rem.net/blog/2012/01/20/wpf-45-cross-thread-collection-synchronization-redux
BindingOperations.EnableCollectionSynchronization mystery in WPF
https://msdn.microsoft.com/en-us/library/hh198845%28v=vs.110%29.aspx?f=255&MSPPError=-2147217396
Maybe try another example:
Xaml:
<Grid>
<ListView ItemsSource="{Binding Items}">
<ListView.ItemTemplate>
<DataTemplate>
<Label Content="{Binding .}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
Code:
namespace WpfApplication2
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new Data();
}
}
public class Data
{
public ObservableCollection<int> Items { get; set; }
public Data()
{
Items = new ObservableCollection<int>();
Filters();
}
public void Filters()
{
Task.Factory.StartNew(DoWork);
}
public void DoWork()
{
int i = 0;
while (true)
{
System.Threading.Thread.Sleep(1000);
App.Current.Dispatcher.BeginInvoke(new Action(() => { Items.Add(++i); }));
}
}
}
}
I finally what it was working. It was pretty simple.
public class ConsultInvoiceViewModel : ViewModelBase
{
public Context ctx = new Context();
private ICollectionView _dataGridCollection;
private ObservableCollection<Invoice> invoiceCollection;
public ConsultInvoiceViewModel()
{
invoiceCollection = new ObservableCollection<Invoice>();
DataGridCollection = CollectionViewSource.GetDefaultView(Get());
if (!WPFHelper.IsInDesignMode)
{
var tsk = Task.Factory.StartNew(InitialStart);
tsk.ContinueWith(t => { MessageBox.Show(t.Exception.InnerException.Message); }, CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.FromCurrentSynchronizationContext());
}
}
private void InitialStart()
{
try
{
State = StateEnum.Busy;
Get();
}
finally
{
State = StateEnum.Idle;
}
}
private void SearchFilter()
{
Task tsk = Task.Factory.StartNew(()=>
{
try
{
State = StateEnum.Busy;
using (var ctx = new Context())
{
var invs = ctx.Invoices
.Where(s.supplier == 1)
.GroupBy(x => new { x.suppInvNumber, x.foodSupplier })
.ToList()
.Select(i => new Invoice
{
suppInvNumber = i.Key.suppInvNumber,
foodSupplier = i.Key.foodSupplier,
totalPrice = i.Sum(t => t.totalPrice),
});
.
App.Current.Dispatcher.Invoke((Action)delegate
{
invoiceCollection.Clear();
if (invs != null)
foreach (var inv in invs)
{
invoiceCollection.Add(inv);
}
});
}
}
finally
{
State = StateEnum.Idle;
}
});
tsk.ContinueWith(t => { MessageBox.Show(t.Exception.InnerException.Message); }, CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.FromCurrentSynchronizationContext());
}
private ObservableCollection<Invoice> Get()
{
using (var ctx = new Context())
{
var invs = ctx.Invoices
foreach (var inv in invs)
{
App.Current.Dispatcher.Invoke((Action)delegate
{
invoiceCollection.Add(inv);
});
}
}
Related
This is a request-response model (like HTTP) but over sockets/websockets. We know which response corresponds to which request by comparing the request IDs.
The workflow is as following:
Subscribe to the observables Error, ContractDetails (_itemObservable) and ContractDetailsEnd (_itemEndObservable)
Match request id to response id and push the corresponding messages via .OnNext(...) to ContractDetails. When there is nothing else left to push, push a final message to ContractDetailsEnd which essentially does .OnCompleted.
Cleanup – unsubscribe, i.e. dispose the observables
In the first snippet below I use a List<TItemArgs>, CancellationTokenSource and a TaskCompletionSource<TItemArgs>. That's completely unnecessary if everything was Rx style. It would have definitely been less lines of code too.
The second snippet is my personal attempt to make it look more Rx like. It has some issues that I want to resolve:
I don't think the try/catch block is necessary since .Subscribe could handle errors
.Timeout didn't work out for me – if the request is not matched within a few seconds, it should return a Result<IEnumerable<TItemArgs>>.FromError(new TimeoutError(...))
In addition to the errors, a message pushed to _errorSubject should also return an error such as Result<IEnumerable<TItemArgs>>.FromError(new RemoteError(...)) but .Merge(errorMessages.Any(_ => false)) is not working.
In my attempt I use ReplaySubject in opposed to AsyncSubject because I believe AsyncSubject is only useful when I'm only interested in the last value of the sequence and want to avoid getting all previous values, which is not in my case. In my case I want to return all values, so ReplaySubject would be more suitable as it keeps track of all the previous values and wants all subscribers to receive the same values, regardless of when they subscribed.
public async ValueTask<Result<IEnumerable<TItemArgs>>> ExecuteAsync(Action<int> action)
{
var requestId = _client.GetNextRequestId();
var data = new List<TItemArgs>();
var cts = new CancellationTokenSource(_timeout);
var tcs = new TaskCompletionSource<IEnumerable<TItemArgs>>();
cts.Token.Register(() =>
{
tcs.TrySetCanceled();
}, false);
void OnError(ErrorData msg)
{
tcs.SetException(new IBClientException(msg.RequestId, msg.Code, msg.Message, msg.AdvancedOrderRejectJson));
}
void OnDetails(TItemArgs item)
{
data.Add(item);
}
void OnDetailsEnd(TItemListEndArgs item)
{
tcs.TrySetResult(data);
}
var disposable = new CompositeDisposable();
_client.Error
.Where(e => HasRequestId && e.RequestId == requestId)
.Subscribe(OnError)
.DisposeWith(disposable);
_itemObservable
.Where(item => MatchRequest(item, _itemRequestIdExtractor, requestId))
.Subscribe(OnDetails)
.DisposeWith(disposable);
_itemEndObservable
.Where(item => MatchRequest(item, _itemListEndRequestIdExtractor, requestId))
.Subscribe(OnDetailsEnd)
.DisposeWith(disposable);
action(requestId);
try
{
await tcs.Task.ContinueWith(x =>
{
disposable.Dispose();
cts.Dispose();
}, TaskContinuationOptions.RunContinuationsAsynchronously);
return Result<IEnumerable<TItemArgs>>.FromSuccess(tcs.Task.Result);
}
catch (Exception e)
{
return Result<IEnumerable<TItemArgs>>.FromError(new RemoteError(e.Message, null));
}
}
My attempt
using System.Reactive.Concurrency;
using System.Reactive.Linq;
using System.Reactive.Subjects;
var client = new IBClient();
var result = await client.GetContractDetailsAsync();
if (result.Success)
{
foreach (var item in result.Data!)
{
Console.WriteLine($"RequestId: {item.RequestId} | Data: {item.ContractDetails}");
}
}
public sealed class IBClient
{
private int _nextValidId;
private readonly Subject<ErrorData> _errorSubject = new();
public IObservable<ErrorData> Error => _errorSubject.AsObservable();
private readonly Subject<ContractDetailsData> _contractDetailsSubject = new();
public IObservable<ContractDetailsData> ContractDetails => _contractDetailsSubject.AsObservable();
private readonly Subject<RequestEndData> _contractDetailsEndSubject = new();
public IObservable<RequestEndData> ContractDetailsEnd => _contractDetailsEndSubject.AsObservable();
public ValueTask<Result<IEnumerable<ContractDetailsData>>> GetContractDetailsAsync()
{
return new PendingRequest<ContractDetailsData, RequestEndData>(
this,
ContractDetails,
ContractDetailsEnd,
e => e.RequestId,
e => e.RequestId)
.ExecuteAsync(reqId => TestCall(reqId));
}
private void TestCall(int requestId)
{
_contractDetailsSubject.OnNext(new ContractDetailsData(requestId, "hey from test call"));
_contractDetailsSubject.OnNext(new ContractDetailsData(requestId, "hey two"));
// TODO: Errors doesn't seem to work
// _errorSubject.OnNext(new ErrorData(requestId, 123, "Error happened", ""));
_contractDetailsEndSubject.OnNext(new RequestEndData(requestId));
// There shouldn't be matched.
_contractDetailsSubject.OnNext(new ContractDetailsData(123, "fake ones, so we know it works"));
_contractDetailsEndSubject.OnNext(new RequestEndData(123));
}
public int GetNextRequestId()
{
return Interlocked.Increment(ref _nextValidId);
}
}
public sealed class PendingRequest<TItemArgs, TItemListEndArgs>
{
private readonly TimeSpan _timeout = TimeSpan.FromSeconds(2);
private readonly IBClient _client;
private readonly IObservable<TItemArgs> _itemObservable;
private readonly IObservable<TItemListEndArgs> _itemEndObservable;
private readonly Func<TItemArgs, int>? _itemRequestIdExtractor;
private readonly Func<TItemListEndArgs, int>? _itemListEndRequestIdExtractor;
public PendingRequest(
IBClient client,
IObservable<TItemArgs> itemObservable,
IObservable<TItemListEndArgs> itemEndObservable,
Func<TItemArgs, int>? itemRequestIdExtractor = null,
Func<TItemListEndArgs, int>? itemListEndRequestIdExtractor = null)
{
_client = client;
_itemObservable = itemObservable;
_itemEndObservable = itemEndObservable;
_itemRequestIdExtractor = itemRequestIdExtractor;
_itemListEndRequestIdExtractor = itemListEndRequestIdExtractor;
}
private bool HasRequestId => _itemRequestIdExtractor != null && _itemListEndRequestIdExtractor != null;
public async ValueTask<Result<IEnumerable<TItemArgs>>> ExecuteAsync(Action<int> action, IScheduler? scheduler = null)
{
scheduler ??= ImmediateScheduler.Instance;
var requestId = _client.GetNextRequestId();
var results = new ReplaySubject<TItemArgs>();
try
{
var errorMessages = _client.Error
.Where(e => HasRequestId && e.RequestId == requestId);
using (_itemObservable
.Where(item => MatchRequest(item, _itemRequestIdExtractor, requestId))
.ObserveOn(scheduler)
.Subscribe(results))
using (_itemEndObservable
.Any(item => MatchRequest(item, _itemListEndRequestIdExtractor, requestId))
.Merge(errorMessages.Any(_ => false)) // TODO: ???
.ObserveOn(scheduler)
.Subscribe(_ => results.OnCompleted()))
{
action(requestId);
// Don't want an Exception thrown if there result list is empty
await results.DefaultIfEmpty();
return Result<IEnumerable<TItemArgs>>.FromSuccess(results.ToEnumerable());
}
}
catch (Exception ex)
{
return Result<IEnumerable<TItemArgs>>.FromError(new RemoteError(ex.Message, null));
}
}
private bool MatchRequest<T>(T item, Func<T, int>? idExtractor, int id)
{
return !HasRequestId || (idExtractor != null && idExtractor(item) == id);
}
}
public sealed class ContractDetailsData
{
public ContractDetailsData(int requestId, string contractDetails)
{
RequestId = requestId;
ContractDetails = contractDetails;
}
public int RequestId { get; }
public string ContractDetails { get; }
}
public sealed class ErrorData
{
public ErrorData(int requestId, int code, string message, string advancedOrderRejectJson)
{
RequestId = requestId;
Code = code;
Message = message;
AdvancedOrderRejectJson = advancedOrderRejectJson;
}
public int RequestId { get; }
public int Code { get; }
public string Message { get; }
public string AdvancedOrderRejectJson { get; }
}
public sealed class RequestEndData
{
public RequestEndData(int requestId)
{
RequestId = requestId;
}
public int RequestId { get; }
}
public class IBClientException : Exception
{
public IBClientException(int requestId, int errorCode, string message, string advancedOrderRejectJson)
: base(message)
{
RequestId = requestId;
ErrorCode = errorCode;
AdvancedOrderRejectJson = advancedOrderRejectJson;
}
public IBClientException(string err)
: base(err)
{
}
public IBClientException(Exception e)
{
Exception = e;
}
public int RequestId { get; }
public int ErrorCode { get; }
public string? AdvancedOrderRejectJson { get; }
public Exception? Exception { get; }
}
public abstract record Error(int? Code, string Message, object? Data);
public record RemoteError : Error
{
public RemoteError(string message, object? data) : base(null, message, data)
{
}
public RemoteError(int? code, string message, object? data) : base(code, message, data)
{
}
}
public record Result<T>(bool Success, T? Data, Error? Error)
{
public Result(T data) : this(true, data, default)
{
}
public Result(Error error) : this(false, default, error)
{
}
public static Result<T> FromSuccess(T data)
{
return new Result<T>(data);
}
public static Result<T> FromError<TError>(TError error) where TError : Error
{
return new Result<T>(error);
}
}
public static class DisposableExtensions
{
public static T DisposeWith<T>(this T disposable, ICollection<IDisposable> collection)
where T : IDisposable
{
ArgumentNullException.ThrowIfNull(disposable);
ArgumentNullException.ThrowIfNull(collection);
collection.Add(disposable);
return disposable;
}
}
Here's a good start:
IObservable<TItemArgs> observable =
Observable
.Merge(
_itemObservable
.Where(item => MatchRequest(item, _itemRequestIdExtractor, requestId))
.Select(item => Notification.CreateOnNext(item))
.Take(1),
_itemEndObservable
.Any(item => MatchRequest(item, _itemListEndRequestIdExtractor, requestId))
.Select(item => Notification.CreateOnCompleted<TItemArgs>()))
.Dematerialize();
And here's a final observable without the ReplaySubject.
private static async Task<Result<IEnumerable<Details>>> Test(IScheduler scheduler) =>
await
Observable
.Defer(() =>
{
var client = new IBClient();
var requestId = 1;
return Observable.Create<Result<IEnumerable<Details>>>(o =>
{
IDisposable subscription =
Observable
.Merge(
client.Details.Where(x => x.RequestId == requestId).Select(x => Notification.CreateOnNext(x)),
client.DetailsEnd.Any(x => x.RequestId == requestId).Select(x => Notification.CreateOnCompleted<Details>()),
client.Error.Where(x => x.RequestId == requestId).Select(x => Notification.CreateOnError<Details>(new Exception($"Code: {x.Code}, Message: {x.Message}"))))
.Dematerialize()
.Synchronize()
.ToArray()
.Select(items => Result<IEnumerable<Details>>.FromSuccess(items))
.Catch<Result<IEnumerable<Details>>, Exception>(ex => Observable.Return(Result<IEnumerable<Details>>.FromError(new Error(null, ex.Message, null))))
.Timeout(TimeSpan.FromSeconds(2))
.ObserveOn(scheduler)
.Subscribe(o);
client.Emit(2, 4);
client.Emit(requestId, 42);
client.EmitEnd(2);
client.Emit(requestId, 62);
client.Emit(requestId, 123);
//client.EmitError(requestId, 123, "Something bad happened");
client.EmitEnd(requestId);
client.Emit(requestId, 1);
return subscription;
});
});
Using Stateless library for .NET I wrote a state machine that does a few things:
Read a barcode ticket
do some validation
check for some more validation
do some digital I/O(turn a light green and open a gate)
frame a transaction and commit it
My StateMachine code is as follows:
public class MainStateMachine
{
public delegate void tvOnTransitioned(StateMachine<State, Trigger>.Transition otransition);
public delegate void tvUnhandledTrigger(State estate, Trigger etrigger);
public delegate void tvEntryExit();
public delegate bool bnGuardClause();
public delegate void vLogMsg(string sstring);
public delegate void tvOnTransition_Barcode(sbarcodeserial);
StateMachine<State, Trigger>.TriggerWithParameters<string> oBarcode_Trigger_With_Parameters;
public tvOnTransitioned OnTransitioned = null;
public tvUnhandledTrigger OnUnhandledTrigger = null;
#region SuperState
public tvEntryExit OnEntry_Input = null;
public tvEntryExit OnExit_Input = null;
#endregion
#region Substates_Entry_Exit
public tvEntryExit OnEntry_CSC_BarcodeValidation = null;
public tvEntryExit OnEntry_PaymentSatisfied = null;
public tvEntryExit OnEntry_FrameTransaction = null;
#endregion
#region Transitions
public tvEntryExit OnTransition_BarcodeValid = null;
public tvEntryExit OnTransition_OCR_Validation_OK = null;
public tvEntryExit OnTransition_LD_RisingEdge = null;
public tvEntryExit OnTransition_BarcodeError = null;
public tvEntryExit OnTransition_BarcodeInvalid = null;
public tvEntryExit OnTransition_OCR_ValidationNot_OK = null;
#endregion
public tvOnTransition_Barcode OnTransition_DO_CSCBarcodeValidation = null;
public enum State
{
//Super states
Input,
//sub states
CSC_Barcode_Validation, OCR_BarcodeValidation, PaymentSatisfied, Transaction
}
public enum Trigger
{
OpenLane,
Trg_CloseLane,
Trg_BC,
Trg_BarCodeRead,
Trg_BarcodeError,
Trg_BarcodeInvalid,
Trg_BarcodeValid,
Trg_OCR_ValidationNot_OK,
Trg_OCR_Validation_OK,
Trg_LDRisingEdge,
Trg_LDFallingEdge
}
State _state = State.Input;
StateMachine<State, Trigger> _sMachine;
object oLock;
public MainStateMachine()
{
this.oLock = new object();
_sMachine = new StateMachine<State, Trigger>(() => _state, s => _state = s);
this._sMachine.OnTransitioned((otransition) => { if (this.OnTransitioned != null) this.OnTransitioned(otransition); });
this._sMachine.OnUnhandledTrigger((estate, etrigger) => { if (this.OnUnhandledTrigger != null) this.OnUnhandledTrigger(estate, etrigger); });
this.oBarcode_Trigger_With_Parameters = this._sMachine.SetTriggerParameters<string>(Trigger.Trg_BC);
//Tag/Barcode Input state
_sMachine.Configure(State.Input)
.Permit(Trigger.Trg_BC, State.CSC_Barcode_Validation)
.OnEntry(() => { if (this.OnEntry_Input != null) this.OnEntry_Input(); })
.OnExit(() => { if (this.OnExit_Input != null) this.OnExit_Input(); });
_sMachine.Configure(State.CSC_Barcode_Validation)
.SubstateOf(State.Input)
.OnEntryFrom(this.oBarcode_Trigger_With_Parameters, (sbarcodeserial) => { if (this.OnTransition_DO_CSCBarcodeValidation != null) this.OnTransition_DO_CSCBarcodeValidation( sbarcodeserial); })
.Permit(Trigger.Trg_BarcodeError, State.Transaction)
.Permit(Trigger.Trg_BarcodeValid, State.OCR_BarcodeValidation)
.OnEntry(() => { if (OnEntry_CSC_BarcodeValidation != null) OnEntry_CSC_BarcodeValidation(); });
_sMachine.Configure(State.OCR_BarcodeValidation)
.SubstateOf(State.Input)
.OnEntryFrom(Trigger.Trg_BarcodeValid, () =>
{
if(this.OnTransition_BarcodeValid != null) this.OnTransition_BarcodeValid();
})
.Permit(Trigger.Trg_OCR_ValidationNot_OK, State.Transaction)
.Permit(Trigger.Trg_OCR_Validation_OK, State.PaymentSatisfied);
_sMachine.Configure(State.PaymentSatisfied)
.Permit(Trigger.Trg_LDRisingEdge, State.Transaction)
.OnEntryFrom(Trigger.Trg_OCR_Validation_OK, () =>
{
if (this.OnTransition_OCR_Validation_OK != null) this.OnTransition_OCR_Validation_OK();
})
.OnEntry(() => { if (this.OnEntry_PaymentSatisfied != null) OnEntry_PaymentSatisfied(); }); //.OnExit(() => { if (this.OnExit_PaymentSatisfied != null) OnExit_PaymentSatisfied(); });
_sMachine.Configure(State.Transaction)
.OnEntryFrom(Trigger.Trg_LDRisingEdge, () =>
{
if (this.OnTransition_LD_RisingEdge != null) this.OnTransition_LD_RisingEdge();
})
.OnEntryFrom(Trigger.Trg_BarcodeError, () => { if (this.OnTransition_BarcodeError != null) this.OnTransition_BarcodeError(); })
.OnEntryFrom(Trigger.Trg_BarcodeInvalid, () => { if (this.OnTransition_BarcodeInvalid != null) this.OnTransition_BarcodeInvalid(); })
.OnEntryFrom(Trigger.Trg_OCR_ValidationNot_OK, () => { if (this.OnTransition_OCR_ValidationNot_OK != null) this.OnTransition_OCR_ValidationNot_OK(); })
.OnEntry(() => { if (this.OnEntry_FrameTransaction != null) OnEntry_FrameTransaction(); }); // on entry into this State, I have to frame a Transaction and commit it
}
public bool TryFireTrigger(Trigger etrigger)
{
bool bReturn = false;
lock (this.oLock)
{
if (this._sMachine.CanFire(etrigger))
{
try
{
this._sMachine.Fire(etrigger);
bReturn = true;
}
catch (Exception oException)
{
bReturn = false;
Console.WriteLine(oException.StackTrace);
}
}
}
return bReturn;
}
}
This State machine is for a vehicle when it enters an unattended booth in a parking lot. I can't figure out as to how to create a transaction object, because it's properties change as and how it undergoes a state change.
Do I pass the Transaction object to each state and get it's modified state back and pass it to next state? I'm not sure how to go about it. Any help is appreciated.
Edit: Another question. What is a substate?
Substates inherit the allowed transitions of their superstate. When entering directly into a substate from outside of the superstate, entry actions for the superstate are executed. Likewise when leaving from the substate to outside the superstate, exit actions for the superstate will execute.
I am creating an application in Xamarin.Forms - it is an event management application - in the application the user can sign up to events and create events. When you create an event - you are given a code for that event. There is a window in which you can input the code and then it registers you to that event. So for example if someone wants to check who is coming to a birthday party they could put the code on the invitation and the people who receive the invitation could open the app - input the code - and say whether they are going or not and add some messages.
I followed this tutorial to implement SQLite into Xamarin.Forms:
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/data-cloud/data/databases
Here is the code:
public class LocalEventDatabase : ILocalEventDatabase
{
static readonly Lazy<SQLiteAsyncConnection> lazyInitializer = new Lazy<SQLiteAsyncConnection>(() =>
{
return new SQLiteAsyncConnection(Constants.DatabasePath, Constants.Flags);
});
static SQLiteAsyncConnection Database => lazyInitializer.Value;
static bool initialized = false;
public LocalEventDatabase()
{
InitializeAsync().SafeFireAndForget(false, new Action<Exception>(async(Exception ex) =>
{
await Acr.UserDialogs.UserDialogs.Instance.AlertAsync(string.Format($"Don't panic! An exception has occurred: {ex.Message}"), "An exception has occurred", "OK");
}));
}
async Task InitializeAsync()
{
if (!initialized)
{
if (!Database.TableMappings.Any(m => m.MappedType.Name == typeof(Event).Name))
{
await Database.CreateTablesAsync(CreateFlags.None, typeof(Event)).ConfigureAwait(false); // not entirely understood what configure await is?
}
initialized = true;
}
}
public async Task<List<Event>> GetRegisteredEventsAsync()
{
return await Database.Table<Event>().ToListAsync();
}
public async Task<Event> GetEventAsync(Guid id)
{
return await Database.Table<Event>().Where(i => i.EventGuid == id).FirstOrDefaultAsync();
}
public async Task<int> InsertEventAsync(Event #event)
{
return await Database.InsertAsync(#event);
}
public async Task<int> DeleteEventAsync(Event #event)
{
return await Database.DeleteAsync(#event);
}
}
Here is the code behind for searching through the codes:
public class FindEventViewModel : ViewModelBase
{
private ObservableCollection<Event> _results;
private ObservableCollection<Event> _events;
public ICommand SearchCommand => new Command(OnSearchCommand);
public string EntryText { get; set; }
// FAB stands = 'floating action button'
public ObservableCollection<Event> Results
{
get => _results;
set
{
_results = value;
RaisePropertyChanged(nameof(Results));
}
}
public ObservableCollection<Event> Events
{
get => _events;
set
{
_events = value;
RaisePropertyChanged(nameof(Events));
}
}
public FindEventViewModel(IDialogService dialogService,
IEventService eventService,
IShellNavigationService shellNavigationService,
IThemeService themeService,
ILocalEventDatabase localEventDatabase) : base(dialogService, eventService, shellNavigationService, themeService, localEventDatabase)
{
}
public async Task<FindEventViewModel> OnAppearing()
{
await LoadData();
return this;
}
private async Task LoadData()
{
var data = await _eventService.GetAllEventsAsync();
Events = new ObservableCollection<Event>(data);
Results = new ObservableCollection<Event>();
}
public async void OnSearchCommand()
{
if (SearchEvents(EntryText).SearchResult == SearchResult.Match)
{
_dialogService.ShowAdvancedToast(string.Format($"Found matching event"), Color.Green, Color.White, TimeSpan.FromSeconds(2));
var eventFound = SearchEvents(EntryText).EventFound;
Results.Add(eventFound);
RaisePropertyChanged(nameof(eventFound));
// may be unneccessary to use it directly rather than with DI but in this case it is needed in order to evaluate what the user has pressed
var result = await UserDialogs.Instance.ConfirmAsync(new Acr.UserDialogs.ConfirmConfig()
{
Title = "Sign up?",
Message = string.Format($"Would you like to sign up to {eventFound.EventTitle}?"),
OkText = "Yes",
CancelText = "No",
});
if (result)
{
await _localEventDatabase.InsertEventAsync(eventFound);
_dialogService.ShowAdvancedToast(string.Format($"You signed up to {eventFound.EventTitle} successfully"), Color.CornflowerBlue, Color.White, TimeSpan.FromSeconds(2));
MessagingCenter.Send(eventFound as Event, MessagingCenterMessages.EVENT_SIGNEDUP);
}
}
else
{
_dialogService.ShowAdvancedToast(string.Format($"No event found matching query - retry?"), Color.Red, Color.White, TimeSpan.FromSeconds(2));
}
}
public EventSearchResult SearchEvents(string code)
{
foreach (Event _event in Events)
{
if (_event.EventCode == code)
{
return new EventSearchResult()
{
SearchResult = SearchResult.Match,
EventFound = _event,
};
}
}
return new EventSearchResult()
{
SearchResult = SearchResult.NoMatch,
};
}
}
The method returns a search result and the event found. One problem - the user can sign up to the same event twice. In the tutorial I followed it doesn't mention how to check for any duplicate items in the database table?
I need to call C# event in xamarin.forms after the function is completed in js.Please guide
function readTextFile(file)
{
try
{
var blob = null;
var rawFile = new XMLHttpRequest();
rawFile.open(""GET"", file);
rawFile.responseType = ""blob"";//force the HTTP response, response-type header to be blob
rawFile.onload = function()
{
blob = rawFile.response;//xhr.response is now a blob object
wavesurfer.loadBlob(blob);
wavesurfer2.loadBlob(blob);
}
rawFile.send();//I need to call C# event here.Please guide.
}
catch(err)
{
}
}
Solution:
You can implement it by using CustomRenderer
1.create a HybridWebView in forms
public class HybridWebView : View
{
Action<string> action;
public static readonly BindableProperty UriProperty = BindableProperty.Create (
propertyName: "Uri",
returnType: typeof(string),
declaringType: typeof(HybridWebView),
defaultValue: default(string));
public string Uri {
get { return (string)GetValue (UriProperty); }
set { SetValue (UriProperty, value); }
}
public void RegisterAction (Action<string> callback)
{
action = callback;
}
public void Cleanup ()
{
action = null;
}
public void InvokeAction (string data)
{
if (action == null || data == null) {
return;
}
action.Invoke (data);
}
}
in contentPage.xaml
<ContentPage.Content>
<local:HybridWebView x:Name="hybridWebView" Uri="xxx.html"
HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" />
</ContentPage.Content>
in code behind
public MainPage ()
{
...
hybridWebView.RegisterAction (data => DisplayAlert ("Alert", "JS action has been called", "OK")); //you can do something other as you want
}
in iOS project
[assembly: ExportRenderer (typeof(HybridWebView), typeof(HybridWebViewRenderer))]
namespace xxx.iOS
{
public class HybridWebViewRenderer : ViewRenderer<HybridWebView, WKWebView>, IWKScriptMessageHandler
{
const string JavaScriptFunction = "function invokeCSharpAction(data){window.webkit.messageHandlers.invokeAction.postMessage(data);}";
WKUserContentController userController;
protected override void OnElementChanged (ElementChangedEventArgs<HybridWebView> e)
{
base.OnElementChanged (e);
if (Control == null) {
userController = new WKUserContentController ();
var script = new WKUserScript (new NSString (JavaScriptFunction), WKUserScriptInjectionTime.AtDocumentEnd, false);
userController.AddUserScript (script);
userController.AddScriptMessageHandler (this, "invokeAction");
var config = new WKWebViewConfiguration { UserContentController = userController };
var webView = new WKWebView (Frame, config);
SetNativeControl (webView);
}
if (e.OldElement != null) {
userController.RemoveAllUserScripts ();
userController.RemoveScriptMessageHandler ("invokeAction");
var hybridWebView = e.OldElement as HybridWebView;
hybridWebView.Cleanup ();
}
if (e.NewElement != null) {
string fileName = Path.Combine (NSBundle.MainBundle.BundlePath, string.Format ("Content/{0}", Element.Uri));
Control.LoadRequest (new NSUrlRequest (new NSUrl (fileName, false)));
}
}
public void DidReceiveScriptMessage (WKUserContentController userContentController, WKScriptMessage message)
{
Element.InvokeAction (message.Body.ToString ());
}
}
}
in Android project
[assembly: ExportRenderer(typeof(HybridWebView), typeof(HybridWebViewRenderer))]
namespace xxx.Droid
{
public class HybridWebViewRenderer : ViewRenderer<HybridWebView, Android.Webkit.WebView>
{
const string JavascriptFunction = "function invokeCSharpAction(data){jsBridge.invokeAction(data);}";
Context _context;
public HybridWebViewRenderer(Context context) : base(context)
{
_context = context;
}
protected override void OnElementChanged(ElementChangedEventArgs<HybridWebView> e)
{
base.OnElementChanged(e);
if (Control == null)
{
var webView = new Android.Webkit.WebView(_context);
webView.Settings.JavaScriptEnabled = true;
webView.SetWebViewClient(new JavascriptWebViewClient($"javascript: {JavascriptFunction}"));
SetNativeControl(webView);
}
if (e.OldElement != null)
{
Control.RemoveJavascriptInterface("jsBridge");
var hybridWebView = e.OldElement as HybridWebView;
hybridWebView.Cleanup();
}
if (e.NewElement != null)
{
Control.AddJavascriptInterface(new JSBridge(this), "jsBridge");
Control.LoadUrl($"file:///android_asset/Content/{Element.Uri}");
}
}
}
}
And
public class JavascriptWebViewClient : WebViewClient
{
string _javascript;
public JavascriptWebViewClient(string javascript)
{
_javascript = javascript;
}
public override void OnPageFinished(WebView view, string url)
{
base.OnPageFinished(view, url);
view.EvaluateJavascript(_javascript, null);
}
}
in your Html
function readTextFile(file)
{
try
{
var blob = null;
var rawFile = new XMLHttpRequest();
rawFile.open(""GET"", file);
rawFile.responseType = ""blob"";//force the HTTP response, response-type header to be blob
rawFile.onload = function()
{
blob = rawFile.response;//xhr.response is now a blob object
wavesurfer.loadBlob(blob);
wavesurfer2.loadBlob(blob);
}
rawFile.send();
invokeCSharpAction(data); //you can pass some params if you need
}
catch(err)
{
}
}
For more detail you can refer here .
I am using Xamarin to develop an Apple Watch app. I am trying to send a message from my watch to the iPhone with my SendMessage function. When I do this, I get in the out error the message payload could not be delivered. I can only read part of the message ("payload could n...") because I am writing it on a label (since my debugger doesn't work in Xamarin I can't take a look at the message), but after doing some googling I'm assuming that's what it is written. Any idea why? Here is my code:
public sealed class WCSessionManager : NSObject, IWCSessionDelegate
{
// Setup is converted from https://www.natashatherobot.com/watchconnectivity-say-hello-to-wcsession/
// with some extra bits
private static readonly WCSessionManager sharedManager = new WCSessionManager();
private static WCSession session = WCSession.IsSupported ? WCSession.DefaultSession : null;
#if __IOS__
public static string Device = "Phone";
#else
public static string Device = "Watch";
#endif
public event ApplicationContextUpdatedHandler ApplicationContextUpdated;
public delegate void ApplicationContextUpdatedHandler(WCSession session, Dictionary<string, object> applicationContext);
public event MessageReceivedHandler MessageReceived;
public delegate void MessageReceivedHandler(Dictionary<string, object> message, Action<Dictionary<string, object>> replyHandler);
private WCSession validSession
{
get
{
#if __IOS__
// Even though session.Paired and session.WatchAppInstalled are underlined, it will still build as they are available on the iOS version of WatchConnectivity.WCSession
Console.WriteLine($"Paired status:{(session.Paired ? '✓' : '✗')}\n");
Console.WriteLine($"Watch App Installed status:{(session.WatchAppInstalled ? '✓' : '✗')}\n");
return (session.Paired && session.WatchAppInstalled) ? session : null;
//return session;
#else
return session;
#endif
}
}
private WCSession validReachableSession
{
get
{
return session.Reachable ? validSession : null;
}
}
private WCSessionManager() : base() { }
public static WCSessionManager SharedManager
{
get
{
return sharedManager;
}
}
public void StartSession()
{
if (session != null)
{
session.Delegate = this;
session.ActivateSession();
Console.WriteLine($"Started Watch Connectivity Session on {Device}");
}
}
[Export("sessionReachabilityDidChange:")]
public void SessionReachabilityDidChange(WCSession session)
{
Console.WriteLine($"Watch connectivity Reachable:{(session.Reachable ? '✓' : '✗')} from {Device}");
// handle session reachability change
if (session.Reachable)
{
// great! continue on with Interactive Messaging
}
else {
// 😥 prompt the user to unlock their iOS device
}
}
#region Application Context Methods
public void UpdateApplicationContext(Dictionary<string, object> applicationContext)
{
// Application context doesnt need the watch to be reachable, it will be received when opened
if (validSession != null)
{
try
{
var NSValues = applicationContext.Values.Select(x => new NSString(JsonConvert.SerializeObject(x))).ToArray();
var NSKeys = applicationContext.Keys.Select(x => new NSString(x)).ToArray();
var NSApplicationContext = NSDictionary<NSString, NSObject>.FromObjectsAndKeys(NSValues, NSKeys);
UpdateApplicationContextOnSession(NSApplicationContext);
}
catch (Exception ex)
{
Console.WriteLine($"Exception Updating Application Context: {ex.Message}");
}
}
}
public void GetApplicationContext()
{
UpdateApplicationContext(new Dictionary<string, object>() { { "GET", null } });
}
[Export("session:didReceiveApplicationContext:")]
public void DidReceiveApplicationContext(WCSession session, NSDictionary<NSString, NSObject> applicationContext)
{
Console.WriteLine($"Recieving Message on {Device}");
if (ApplicationContextUpdated != null)
{
var keys = applicationContext.Keys.Select(k => k.ToString()).ToArray();
IEnumerable<object> values;
try
{
values = applicationContext.Values.Select(v => JsonConvert.DeserializeObject(v.ToString(), typeof(DoorWatchDTO)));
}
catch (Exception)
{
values = applicationContext.Values.Select(v => JsonConvert.DeserializeObject(v.ToString()));
}
var dictionary = keys.Zip(values, (k, v) => new { Key = k, Value = v })
.ToDictionary(x => x.Key, x => x.Value);
ApplicationContextUpdated(session, dictionary);
}
}
[Export("session:didReceiveMessage::")]
public void DidReceiveMessage(WCSession session, NSDictionary<NSString, NSObject> message, WCSessionReplyHandler replyHandler)
{
if (MessageReceived != null)
{
var keys = message.Keys.Select(k => k.ToString()).ToArray();
IEnumerable<object> values;
values = message.Values.Select(v => JsonConvert.DeserializeObject(v.ToString()));
var dictionary = keys.Zip(values, (k, v) => new { Key = k, Value = v })
.ToDictionary(x => x.Key, x => x.Value);
MessageReceived(dictionary, (dict) =>
{
var NSValues = dict.Values.Select(x => new NSString(JsonConvert.SerializeObject(x))).ToArray();
var NSKeys = dict.Keys.Select(x => new NSString(x)).ToArray();
var NSDict = NSDictionary<NSString, NSObject>.FromObjectsAndKeys(NSValues, NSKeys);
replyHandler.Invoke(NSDict);
});
}
}
public void SendMessage(Dictionary<string, object> message, Action<Dictionary<string, object>> replyHandler, WKInterfaceLabel label)
{
if (validSession != null)
{
try
{
var NSValues = message.Values.Select(x => new NSString(JsonConvert.SerializeObject(x))).ToArray();
var NSKeys = message.Keys.Select(x => new NSString(x)).ToArray();
var NSMessage = NSDictionary<NSString, NSObject>.FromObjectsAndKeys(NSValues, NSKeys);
var reply = new WCSessionReplyHandler((replyMessage) =>
{
var values = replyMessage.Values.Select(x => JsonConvert.SerializeObject(x)).ToArray();
var keys = replyMessage.Keys.Select(x => x.ToString()).ToArray();
var dict = keys.Zip(values, (k, v) => new { Key = k, Value = v })
.ToDictionary(x => x.Key, x => (object)x.Value);
replyHandler.Invoke(dict);
});
validSession.SendMessage(NSMessage, reply, (error) =>
{
label.SetText(error.ToString()); // I can see the error in here: "payload could n..."
});
}
catch (Exception ex)
{
Console.WriteLine($"Exception sending message: {ex.Message}");
}
}
}
private void UpdateApplicationContextOnSession(NSDictionary<NSString, NSObject> NSApplicationContext)
{
NSError error;
var sendSuccessfully = validSession.UpdateApplicationContext(NSApplicationContext, out error);
if (sendSuccessfully)
{
Console.WriteLine($"Sent App Context from {Device} \nPayLoad: {NSApplicationContext.ToString()} \n");
#if __IOS__
Logging.Log("Success, payload: " + NSApplicationContext.ToString());
#endif
}
else
{
Console.WriteLine($"Error Updating Application Context: {error.LocalizedDescription}");
#if __IOS__
Logging.Log("error: " + error.LocalizedDescription);
#endif
}
}
#endregion
I solved it. Instead of implementing IWCSessionDelegate, I simply implement WCSessionDelegate instead and override the functions as needed.