ReactiveCommand in WPF throws exception with Subscribe - data-binding

I have a simple demo application with ReactiveUI:
//in the viewmodel
class MainViewModel : ReactiveObject
{
public ReactiveUI.ReactiveCommand<Unit, Unit> MyReactiveCommand { get; }
public MainViewModel()
{
MyReactiveCommand = ReactiveCommand.Create(() => { MessageBox.Show("Hello"); }, outputScheduler: RxApp.MainThreadScheduler);
}
}
In the view XAML
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Grid>
<WrapPanel HorizontalAlignment = "Left">
<Button Content="button" Command="{Binding MyReactiveCommand}"/>
</WrapPanel>
</Grid>
When you press the button there should be a Message Box but instead I get the following error:
System.InvalidOperationException: 'The calling thread cannot access
this object because a different thread owns it.'
I have tried returning a value and then subscribing like Glenn suggested but that had the same problem. At least with this code the Message Box opens before it crashes ;)
public class MainViewModel : ReactiveObject
{
public ReactiveCommand<Unit, Unit> MyReactiveCommand { get; }
public MainViewModel()
{
MyReactiveCommand = ReactiveCommand.CreateFromObservable(DoSometing);
MyReactiveCommand.Subscribe(x => { MessageBox.Show("Hello"); });
}
public IObservable<Unit> DoSometing()
{
return Observable.Start(() => { });
}
}

So a couple things to be aware of. ReactiveCommand.CreateFromObservable has a parameter called outputScheduler and this will be where the Subscribe's output will go to. You can pass RxApp.MainThreadScheduler here.
public class MainViewModel : ReactiveObject
{
public ReactiveCommand<Unit, Unit> MyReactiveCommand { get; }
public MainViewModel()
{
MyReactiveCommand = ReactiveCommand.CreateFromObservable(DoSometing, outputScheduler: RxApp.MainThreadScheduler);
MyReactiveCommand.Subscribe(x => { MessageBox.Show("Hello"); });
}
public IObservable<Unit> DoSometing()
{
return Observable.Start(() => { });
}
}
Note also make sure you have installed the NuGet package ReactiveUI.WPF

Related

Blazor state not updatting between child components when using events

I have two independent components in a Blazor wasm app between whichi am trying to communicate. under certain cases the communication fails and I cannot understand why.
The(simplified) setup is as follows
<ParentComponent>
<HeaderComponent>
<ProgressBar IsLoading="<Set by IsLoading property from header>" />
</HeaderComponent>
<ResultContainer />
</ParentComponent>
The code behind looks something like this:
public class ResultContainerStateManager
{
public event Action OnLoadStart;
public event Action OnLoadFinish;
public NotifyLoadStart() => this.OnLoadStart?.Invoke();
public NotifyLoadFinish() => this.OnLoadFinish?.Invoke();
}
public partial class HeaderComponent
{
[Inject]
public ResultContainerStateManager ResultContainerStateManager { get; set; }
private bool IsLoading { get; set; }
protected override void OnInitialized()
{
this.ResultContainerStateManager.OnLoadStart += () => this.IsLoading = true;
this.ResultContainerStateManager.OnLoadFinish += () => this.IsLoading = false;
base.OnInitializer();
}
}
public partial class ResultContainer
{
[Inject]
public ResultContainerStateManager ResultContainerStateManager { get; set; }
private bool IsLoading { get; set; }
protected override async Task OnParametersSetAsync()
{
<code>
if (shouldLoadData)
{
this.ResultContainerStateManager.NotifyLoadStart();
<more code>
this.ResultContainerStateManager.NotifyLoadFinish();
}
await base.OnParametersSetAsync();
}
}
public partial class ProgressBar
{
[Parameter]
public bool IsLoading { get; set; }
}
Where the IsLoading parameter from the progress bar is set from the IsLoading property from HeaderComponent, like
<div id="headerComponent">
<More html here>
<ProgressBar IsLoading="#this.IsLoading" />
</div>
I don't think it matters, but the progress bar itself uses the MatProgress component, like so:
#if (this.IsLoading)
{
<MatProgress Indeterminate="true" />
}
else
{
<Other html code>
}
The problem is that the progress bar starts when the ResultContainer executes the NotifyLoadStart() method, but it doesn't stop when the NotifyLoadFinish() method is executed.
I can see when debugging that the IsLoading property of the HeaderComponent is set back to false after the NotifyLoadFinish() call, but it has no Effect on the UI.
What I have tried so far:
injecting the ResultContainerStateManager directly into the Progress bar
I have tried changing the envents to Func and handling at all asynchronously
I have tried adding await Task.Yield() after each Notify call
I have tried adding this.StateHasChanged() calls in the event handlers and after each Notify call (I know the latter should not change anything at all, since it is not in the same hierarchy)
None of that changed anything and I would really like to understand why.
The only success I've had was when using EventCallbacks instead of the events. But I am using events in lots of other places and they all seem to work fine.
Could somebody tell me why events seem to fail and how this can be fixed?
Try this code
public async Task OnLoadStart()
{
this.IsLoading = true;
await InvokeAsync(() => { StateHasChanged(); });
}
public async Task OnLoadFinish()
{
this.IsLoading = false;
await InvokeAsync(() => { StateHasChanged(); });
}
protected override void OnInitialized()
{
this.ResultContainerStateManager.OnLoadStart += OnLoadStart;
this.ResultContainerStateManager.OnLoadFinish += OnLoadFinish;
}
Change : public event Action OnLoadStart;
To: public event Func<Task> OnLoadStart;
And: public event Action OnLoadFinish;
Tp: public event Func<Task> OnLoadFinish;
Implement IDisposable in the HeaderComponent component:
#implements IDisposable
public void Dispose()
{
this.ResultContainerStateManager.OnLoadStart -= OnLoadStart;
this.ResultContainerStateManager.OnLoadFinish -= OnLoadFinish;
}
Start coding asynchronously wherever you can.

SimpleInjectorContainerAdapter, registering Sagas

Does the SimpleInjectorContainerAdapter support Sagas that are registered through the SimpleInjector container, using the following code I always get the exception;
type RebusPlaypen.MyMessageA, RebusPlaypen could not be dispatched to any handl
ers
The following code demonstrates the issue. Could this be that I am implementing the Saga registration incorrectly, or does the SimpleInjectorContainerAdapter not support this type of registration ?
using Rebus.Bus;
using Rebus.Config;
using Rebus.Handlers;
using Rebus.Retry.Simple;
using Rebus.Routing.TypeBased;
using Rebus.Sagas;
using Rebus.SimpleInjector;
using Rebus.Transport.InMem;
using SimpleInjector;
using System;
using System.Threading.Tasks;
// Rebus.3.1.2
// Rebus.SimpleInjector.3.0.0
namespace RebusPlaypen
{
public interface IMyDependency
{
void DoSomethingGood();
}
public class MyDependency : IMyDependency
{
public void DoSomethingGood()
{
Console.WriteLine("I've done something");
}
}
public class MyMessageA
{
public Guid CollationId { get; set; }
public string FaveIceCreamFlavour { get; set; }
}
public class MyMessageB
{
public Guid CollationId { get; set; }
public string FaveBand{ get; set; }
}
public class MyMessageSagaData : ISagaData
{
public Guid Id {get;set;}
public int Revision {get;set;}
public Guid CollationId {get;set;}
public bool HasFaveBand { get; set; }
}
public interface IMyMessageSaga : IAmInitiatedBy<MyMessageA>,
IHandleMessages<MyMessageB>
{
}
public class MyMessageSaga: Saga<MyMessageSagaData>,
IMyMessageSaga
{
readonly IMyDependency _myDependency;
readonly IBus _bus;
public MyMessageSaga(IMyDependency myDependency,
IBus bus)
{
_myDependency = myDependency;
_bus = bus;
}
protected override void CorrelateMessages(ICorrelationConfig<MyMessageSagaData> config)
{
config.Correlate<MyMessageA>(s => s.CollationId, d => d.CollationId);
config.Correlate<MyMessageB>(s => s.CollationId, d => d.CollationId);
}
public async Task Handle(MyMessageA message)
{
Console.WriteLine("Handled MyMessageA");
_myDependency.DoSomethingGood();
await _bus.Send(new MyMessageB { CollationId = message.CollationId, FaveBand = "Depeche Mode" });
await PossiblyPerformCompleteAction();
}
public async Task Handle(MyMessageB message)
{
Console.WriteLine("Handled MyMessageB");
_myDependency.DoSomethingGood();
Data.HasFaveBand = true;
await PossiblyPerformCompleteAction();
}
async Task PossiblyPerformCompleteAction()
{
if (Data.HasFaveBand)
{
MarkAsComplete();
}
}
}
public static class RebusSimpleInjectorSagaDemo
{
public static void Run()
{
var container = new Container();
container.Register<IMyDependency, MyDependency>();
container.Register<MyMessageSaga>(Lifestyle.Transient);
container.Register<IMyMessageSaga>(() => container.GetInstance<MyMessageSaga>(), Lifestyle.Transient);
var network = new InMemNetwork(true);
var adapter = new SimpleInjectorContainerAdapter(container);
var _bus = Configure
.With(adapter)
.Logging(l => l.ColoredConsole(Rebus.Logging.LogLevel.Error))
.Transport(t => t.UseInMemoryTransport(network,"my_nice_queue"))
.Routing(r => r.TypeBased().MapAssemblyOf<MyMessageA>("my_nice_queue"))
.Options(o =>
{
o.SetNumberOfWorkers(1);
o.SetMaxParallelism(1);
o.SimpleRetryStrategy(maxDeliveryAttempts: 1);
})
.Start();
container.Verify();
_bus.Send(new MyMessageA { CollationId = Guid.NewGuid(), FaveIceCreamFlavour = "Strawberry" }).Wait();
Console.WriteLine("Running");
Console.ReadLine();
}
}
}
For completeness, the following changes allowed the code in the original question to work correctly with the Simple Injector container;
public static class RebusSimpleInjectorSagaDemo
{
public static void Run()
{
var container = new Container();
container.Register<IMyDependency, MyDependency>();
// The missing registration
container.RegisterCollection(typeof(IHandleMessages<>), new [] {Assembly.GetExecutingAssembly()});**
var network = new InMemNetwork(true);
var adapter = new SimpleInjectorContainerAdapter(container);
var _bus = Configure
.With(adapter)
.Logging(l => l.ColoredConsole(Rebus.Logging.LogLevel.Error))
.Transport(t => t.UseInMemoryTransport(network,"my_nice_queue"))
.Routing(r => r.TypeBased().MapAssemblyOf<MyMessageA>("my_nice_queue"))
.Options(o =>
{
o.SetNumberOfWorkers(1);
o.SetMaxParallelism(1);
o.SimpleRetryStrategy(maxDeliveryAttempts: 1);
})
.Start();
container.Verify();
_bus.Send(new MyMessageA { CollationId = Guid.NewGuid(), FaveIceCreamFlavour = "Strawberry" }).Wait();
Console.WriteLine("Running");
Console.ReadLine();
}
}
No matter which IoC container you use, you must ensure that your handlers are resolved by the IHandleMessages<TMessage> implementations they provide.
If you try and
container.GetAllInstances<IHandleMessages<MyMessageA>>();
or
container.GetAllInstances<IHandleMessages<MyMessageB>>();
you will see that no handlers are returned. That's why Rebus cannot find any handlers to dispatch your messages to :)

SearchBar and Xamarin.Form

Am working on a XF project which has a SearchBar. The XAML Declaration looks like following.
<SearchBar Placeholder="Result" Text="{Binding SearchedCustomer,Mode=TwoWay}"
SearchCommand="{Binding SearchCustomerCommand}"></SearchBar>
At ViewModel, I have following declared
private string _SearchedCustomer;
public string SearchedCustomer
{
get { return _SearchedCustomer; }
set { SetProperty(ref _SearchedCustomer, value); }
}
public DelegateCommand SearchCustomerCommand { get; set; }
private ObservableCollection<CustomerModel> _CustomerList;
public ObservableCollection<CustomerModel> CustomerList
{
get
{
if (_CustomerList == null)
FillCustomerDetails();
return _CustomerList;
}
set { SetProperty(ref _CustomerList, value); }
}
private void ExecuteSearchCustomerCommand()
{
var tempRecords = _CustomerList.Where(c => c.ReferenceText.Contains(SearchedCustomer));
CustomerList.Clear();
foreach (var item in tempRecords)
{
CustomerList.Add(item);
}
}
I also have the SearchCustomerCommand created in the Constructor as following
SearchCustomerCommand = new DelegateCommand(ExecuteSearchCustomerCommand).ObservesProperty(()=> SearchedCustomer);
When I type in the SearchBar, the SearchedCustomer Fields gets changed, however, the Command SearchCustomerCommand is not executed.
Could someone help me in identifying what I am doing wrong here ?

Using IPageDialogService in OnNavigatedTo on app startup

I've created a Prism Unity App using the Prism Template Pack and added another View and ViewModel (OtherPage and OtherPageViewModel) to it. This is my code:
App.xaml.cs
public partial class App : PrismApplication
{
public App(IPlatformInitializer initializer = null) : base(initializer) { }
protected override void OnInitialized()
{
InitializeComponent();
NavigationService.NavigateAsync("MainPage?title=Hello%20from%20Xamarin.Forms");
}
protected override void RegisterTypes()
{
Container.RegisterTypeForNavigation<MainPage>();
Container.RegisterTypeForNavigation<OtherPage>();
}
}
MainPageViewModel.cs
public class MainPageViewModel : BindableBase, INavigationAware
{
private INavigationService _navigationService;
private IPageDialogService _pageDialogService;
public DelegateCommand NavigateToOtherPageCommand { get; set; }
private string _title;
public string Title
{
get { return _title; }
set { SetProperty(ref _title, value); }
}
public MainPageViewModel(INavigationService navigationService,
IPageDialogService pageDialogService)
{
_navigationService = navigationService;
_pageDialogService = pageDialogService;
NavigateToOtherPageCommand = new DelegateCommand(async () => await NavigateToOtherPage());
}
public void OnNavigatedFrom(NavigationParameters parameters)
{
}
public async void OnNavigatedTo(NavigationParameters parameters)
{
if (parameters.ContainsKey("title"))
Title = (string)parameters["title"] + " and Prism";
await _pageDialogService.DisplayAlertAsync("Message", "Message for the user", "Ok");
}
private async Task NavigateToOtherPage()
{
await _navigationService.NavigateAsync("OtherPage");
}
}
OtherPageViewModel.cs
public class OtherPageViewModel : BindableBase, INavigationAware
{
private IPageDialogService _pageDialogService;
public OtherPageViewModel(IPageDialogService pageDialogService)
{
_pageDialogService = pageDialogService;
}
public void OnNavigatedFrom(NavigationParameters parameters)
{
}
public async void OnNavigatedTo(NavigationParameters parameters)
{
await _pageDialogService.DisplayAlertAsync("Message", "Message for the user", "Ok");
}
}
I'm attempting to use the IPageDialogService to display a message to the user in the MainPageViewModels's OnNavigatedTo method but no message is displayed and no error is raised.
However, if I navigate from MainPage to OtherPage using the NavigateToOtherPageCommand, the message is displayed correctly. Why doesn't the IPageDialogService work in the OnNavigatedTo method of the startup page (i.e MainPageViewModel)? It seems to work fine in the OnNavigatedTo method of any other page (i.e OtherPageViewModel).
Try to use await Task.Yield(). More information about this issue in this link: pageDialogService MainPage reference is always null inside OnNavigatedTo after app is launched

Caliburn Action not firing

<ItemsControl DockPanel.Dock="Right" x:Name="Actions">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button x:Name="Action"
HorizontalAlignment="Right"
Content="{Binding Label}"
Margin="3" Width="30"></Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
The above view binds with this viewmodel
public class DeploymentInputViewModel<T> : PropertyChangedBase
{
public BindableCollection<InputActionViewModel> Actions {get;set;}
}
I see my buttons. But when clicking it nothing happen.
The viewModels for InputActionViewModel:
public abstract class InputActionViewModel{
public InputActionViewModel()
{
}
public virtual Task Action()
{
return Task.FromResult<object>(null);
}
public string ActionToolTip { get; set; }
public string Label { get; set; }
public object Value { get; set; }
}
and also
public class InputCertificateActionViewModel : InputActionViewModel
{
[Import]
private IShell _shell;
[Import]
private IWindowsDialogs _dialogs;
private readonly IDeploymentSettingInputViewModel vm;
public InputCertificateActionViewModel(IDeploymentSettingInputViewModel vm)
{
this.vm = vm;
Label = "...";
ActionToolTip = "Pick a Certificate";
}
public bool IsManagementCertificate {get;set;}
public bool IsDeploymentCertificate { get; set; }
public async override Task Action()
{
if(IsManagementCertificate)
{
var subs = await _shell.IdentityModel.GetEnabledSubscriptionsAsync();
foreach(var sub in subs)
{
using (ManagementClient client = CloudContext.Clients.CreateManagementClient(sub.GetCredentials()))
{
var cert = _dialogs.SelectItemDialog("Select a certificate", "Pick one", true,
(await client.ManagementCertificates.ListAsync()).Select(c =>
new SelectItem(c.Thumbprint, Encoding.Default.GetString(c.PublicKey), c, (s) => c.Thumbprint.Contains(s))).ToArray())
.Tag as ManagementCertificateListResponse.SubscriptionCertificate;
this.vm.Value = cert.Thumbprint;
}
}
}else if(IsDeploymentCertificate)
{
}
}
}
I am adding actionViewModels by inserting directly into the observable code at startup.
haveActions.Actions.Add(DI.BuildUp(new InputCertificateActionViewModel(vm)
{
IsDeploymentCertificate = certAttribute.IsDeploymentCertificate,
IsManagementCertificate = certAttribute.IsManagementCertificate,
}));
haveActions is an instance of InputCertificateActionViewModel
Couldn't fit this all in a comment:
I can't have a peek at the Caliburn.Micro at the moment, but it might be something related to calling your method Action.
At a guess though, I'd say that by convention Caliburn.Micro expects to find a method that matches the Action<T> delegate to use for it's Actions, so your public virtual Task Action() won't be located and bound.
Have a quick check by defining a new method with a compatible signature, e.g public void MyMethod() and checking to see if it's located correctly and will function.
If that is the problem, you'll probably want to have a look at the IResult and Coroutines part of the Caliburn.Micro documentation, which looks like it will help you implement your desired behaviour.

Resources