is there any way to handle events from mediator inside the ViewModel. If VM implemetns interface INotification it must be singleton or creates/destroy based on mediators rules.
Or how to solve this issue. For example: when i create new Customer(), domain model generates event CustomerCreatedEvent() and i want to update counter on my mobile app UI.
I know there are events in c#, where can i subscribe/unsubscribe VM but it is "diffrend kind" of event.
Thanks for advice.
Update 01
I found a solution when VM and Notification are independet units.
public class CustomerCreatedHandler : INotification<CustomerCreatedEvent>
{
private readonly CustomerPageViewModel _viewModel;
public CustomerCreatedHandler(CustomerPageViewModel viewModel)
{
_viewModel = viewModel;
}
public Task Handle(CustomerCreatedEvent customerCreatedEvent)
{
// set property of VM
_viewModel.Count = customerCreatedEvent.Count;
return Task.CompletedTask;
}
}
It should be working. VM is singleton and Notification handler can be destroyed after work is finished.
Related
Most applications today are web apps. And as such, most of the guides and examples are written for web apps. Where I work, we are making WPF application, and there is one thing I cannot figure out:
What sits in place of the controller, in a WPF application (using the MVVM pattern)?
To elaborate:
In web applications, the UI typically talks to a controller over HTTP. The controller then talks to services/repositories/etc (depending on the architecture).
I have yet to see a clear example of the communication between the UI and back-end in a WPF application.
The examples I have seen either uses a very tight coupling (typically in tutorials) or an HTTP backend (which basically makes it a web app). So if you want the advantages of the loose coupling from the HTTP architecture, but it all has to be in a single app, how do I achieve this?
In MVVM, the view binds to a view model class. The view model class may be injected with models or services.
Let's for example say that you want to display a list of some items that you receive from some REST API.
In the view, you would define an ItemsControl that binds to a collection property of the view model:
<ItemsControl ItemsSource="{Binding Items}" />
You may also have a button that binds to a command that is responsible for fetching the data:
The view model may be implemented something like this:
public class ViewModel : INotifyPropertyChanged
{
private readonly IRestService _service;
public ViewModel(IRestService service)
{
_service = service;
GetDataCommand = new RelayCommand(GetData);
}
private IEnumerable<string> _items;
public IEnumerable<string> Items
{
get { return _items; }
set { _items = value; NotifyPropertyChanged(nameof(Items)); }
}
public ICommand GetDataCommand { get; }
private async void GetData(object _)
{
var data = await Task.Run(() => _service.GetData());
Items = data;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
RelayCommand is a custom implementation of the ICommand interface that executes an Action<object>. Most implementations you'll find online doesn't support async/await, i.e. you cannot await the command. That's why GetData returns void instead of Task in the sample above. The service method is called on a background thread to keep the UI responsive.
Could I get help with a little issue I am encountering regarding splitting projects into different tiers. In my ViewModel logic I have code where I create a new instance of a window when a button is clicked (I use ICommand interface for that)
The problem is however, that this requires my View folder which is in the presentation layer, I can’t reach it as my presentation layer is dependent on my ViewModel in the logic layer.
I would just move the code that deals with the creation of the pages to the view code behind but I also pass the current instance of a viewmodel as a parameter for that new window being created (for eventhandling purposes).
Any help is much appreciated! Thanks.
A view model shouldn't create instances of windows. What you could do is to inject your view model with a service that is responsible for creating windows, e.g.:
public class MainWindowViewModel
{
private readonly IWindowService _windowService;
public MainWindowViewModel(IWindowService windowService)
{
_windowService = windowService;
CreateWindowCommand = new DelegateCommand(() =>
{
_windowService.CreateWindow(new SomeViewModel());
});
}
public ICommand CreateWindowCommand { get; }
}
Define the IWindowService interface in the view model project and the concrete implementation of it in the view/presentation project:
public class WindowService : IWindowService
{
public void CreateWindow(SomeViewModel vm)
{
Window win = new Window();
win.DataContext = vm;
win.Show();
}
}
After hours of searching I am still without answer to this question. I have read this nice writing about async MVVM and made my viewmodel to use factory method.
public class MainViewModel
{
// sic - public, contrary to the pattern in the article I cite
// so I can create it in the Xaml as below
public MainViewModel()
{
}
private async Task InitializeAsync()
{
await DoSomethingAsync();
}
public static async Task<MainViewModel> CreateAsync()
{
var ret = new MainViewModel();
await ret.InitializeAsync();
return ret;
}
}
This is clear for me, but I can't understand how to make instance of MainViewModel and set it to datacontext in MainPage. I can't simply write
<Page.DataContext>
<viewModel:MainViewModel/>
</Page.DataContext>
because I should use MainViewModel.CreateAsync()-method. And I can't do it on code-behind, which I even want to do, because code-behind -constructor is normal method, not an async-method. So which is proper way to continue?
made my viewmodel to use factory method
I'm normally a fan of that approach - it's my favorite way to work around the "no async constructors" limitation. However, it doesn't work well in the MVVM pattern.
This is because VMs are your UI, logically speaking. And when a user navigates to a screen in an app, the app needs to respond immediately (synchronously). It doesn't necessarily have to display anything useful, but it does need to display something. For this reason, VM construction must be synchronous.
So, instead of trying to asynchronously construct your VM, first decide what you want your "loading" or "incomplete" UI to look like. Your (synchronous) VM constructor should initialize to that state, and it can kick off some asynchronous work that updates the VM when it completes.
This is not too hard to do by hand, or you can use the NotifyTaskCompletion approach that I described in an MSDN article on async MVVM data binding to drive the state transition using data bindings.
You have to initalize the viewmodel before the window is open. Go to your App.xaml file and remove the part: StartupUri="MainWindow.xaml". Then you go to the App.xaml.cs and add this:
protected async override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
var mainWindow = new MainWindow { DataContext = await CreateAsync() };
mainWindow.Show();
}
I would re-factor. Make the MainViewModel construction / instantiation lightweight. Then create a Load or Initialize method on your VM. From the code-behind create an instance, set it to the DataContext, then invoke the init method and let it run.
E.g.
/// <summary>Interaction logic for MainWindow.xaml</summary>
public partial class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();
var dc = new MainViewModel();
dc.Initialize("Hello", " ", "world");
this.DataContext = dc;
}
}
public class MainViewModel
{
/// <summary>Simple constructor</summary>
public MainViewModel() { }
public void Initialize(params object[] arguments)
{
//use the task to properly start a new thread as per:
//http://stackoverflow.com/a/14904107/1144090 and
//https://msdn.microsoft.com/en-us/library/hh965065.aspx
//(what would happen if we simply invoke init async here?)
this.InitializeAsync(arguments)
.ContinueWith(result =>
{
if (!result.IsFaulted)
return;
MessageBox.Show("Unexpected error: " + Environment.NewLine + result.Exception.ToString());
});
}
private async Task InitializeAsync(params object[] arguments)
{
await Task.Delay(2333);
MessageBox.Show(String.Concat(arguments));
}
}
Note that this is the quick-and-dirty solution, the other two answers (paired with a dependency injection framework) will give you proper high-level structure for your solution.
Firstly, you should make default constructor as private to avoid misusing your class (the article you cite does this - the constructor is private).
The approach you are using to set DataContext is not suitable for MVVM pattern (the View shouldn't create its ViewModel itself).
You should create your View and ViewModel in the higher level layer and have that layer bind them. Says if the Page is your main View you should create them in App.xaml.cs by overriding OnStartup, something like this:
var page = new Page();
var dataService = new YourDataService(); // iff Create or the ctor require arguments
var viewModel = await MainViewModel.CreateAsync(dataService);
page.DataContext = viewModel;
page.Show();
The setup
I have a window and a viewmodel. The viewmodel has a command which executes a Task. When the task completes, I want the effect to be that the window closes.
What is the most acceptable (best-practice) way of doing this?
I tend to think that Dispatcher.Invoke is hacky and bad, but this is a moot point becaues the viewmodel does not have a reference to the window, or to its dispatcher.
edit: To clarify, the window belongs to the UI thread. The command itself calls a wrapper for doing an async http request (which returns a task). The command can append a ContinueWith.
I don't want to tightly couple the viewmodel to the view (such as by passing the Window to view model)
public class Api
{
Task MakeHttpCall();
}
public class ViewModel, DependencyObject
{
private Api _api;
public Task DoHttpCall() { return _api.MakeHttpCall(); }
public MyCommand MyCommandInst { get; private set; }
}
public class MyCommand : ICommand
{
void Execute(object parameter)
{
var viewModel = GetViewModel(parameter);
viewModel.DoHttpCall().ContinueWith( t => HandleCompletionAndSomehowTriggerWindowClose(t));
}
}
And then the view.xaml:
<Button Command={Binding MyCommandInst} CommandParameter={Binding}>Do Stuff</Button>
I use MVVM Light to help facilitate this process. The ViewModel has no reference to the View it just publishes a message to close and the View is registered to receive those messages.
In the code behind of my view, I subscribe to the Messenger service like this:
public class MyView
{
public MyView()
{
InitializeComponent();
Messenger.Default.Register<NotificationMessage>(this, msg =>
{
if ((msg.Sender == this.DataContext) && (msg.Notification.ToUpper() == "CLOSE"))
this.Close();
});
}
}
Then in the ViewModel (either in the callback method from your async process or at the end of the command method if not running async):
Messenger.Default.Send(new NotificationMessage(this, "Close"));
Generally, I believe, closing a window with the MVVM pattern depends on raising some form of Close event on the view-model that the view subscribes to:
public class MyView
{
public MyView(MyViewModel viewModel)
{
this.DataContext = viewModel;
viewModel.Close += (_, __) => Dispatcher.Invoke(this.Close);
}
}
Raise the Close event from your task's ContinueWith action, and you're done.
I've following architecture:
desktop application, .Net 4.5, C#, WPF, MVVM Light, Messenger, IoC - ViewModel locator, so ViewModels doen't know anyhing about Views.
I have main view with data grid of some elements, and I want to display details of each individual element in new/child windows after double click on data grid.
I've bind event double click on main view to main view model. From this event handler in main view model, message is sent via Messanger.
New view (new/child window) is created in main view via delegate of also double click.
New/child window is a view which locate his view model and this view model register to the specific message in his constructor.
The problem is that new/child window (new view, and view model so on) is created too late, because message is already sent when new view model register for it.
Do you know maybe some patterns for such architecture. Any ideas will be appreciated.
It would help to know exactly what you try to do.
If your problem is just to display a detailed Window when double click on a row, I would say: create only one childWindow at start, and play with its visbility when required.
If you really need a new window each time, you could create it from your viewModel with an injected service for example.
In any case, you never has to create your window from main view! Either you create one window at start, either you dynamically create it from view model.
You cannot hope to create it from view and send the message in your view model.
Edit about the injected service, you could use something like that:
public interface IWindowService
{
void Open<TWindow>(ViewModelBase viewModel)
where TWindow : Window;
}
public class WindowService : IWindowService
{
private readonly IUIDispatcher _dispatcher;
public WindowService(IUIDispatcher dispatcher)
{
_dispatcher = dispatcher;
}
public void Open<TWindow>(ViewModelBase viewModel)
where TWindow : Window
{
_dispatcher.Run(() => OpenThreadSafe<TWindow>(viewModel));
}
private static void OpenThreadSafe<TWindow>(ViewModelBase viewModel) where TWindow : Window
{
var view = (TWindow) Activator.CreateInstance(typeof(TWindow), viewModel);
view.Show();
}
}
public class UIDispatcher : IUIDispatcher
{
public void Run(Action action)
{
var dispatcher = DispatcherHelper.UIDispatcher;
if (dispatcher == null)
{
action();
return;
}
DispatcherHelper.CheckBeginInvokeOnUI(action);
}
Note this DispatcherHelper come from MVVMlight, but you could erplace it easily.
Hope it helps.
The problem is that the ViewModel Locator creates the viewmodel instance only when it is needed (lazy loading).
just configure the ViewModelLocator to instantiate the viewmodel eager instead of lazy. This is done by passing the parameter "true" to the IoC Container.
Sample:
namespace Administration.ViewModel
{
public class ViewModelLocator
{
public ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
//Eager Loading
SimpleIoc.Default.Register<UserManagementViewModel>(true);
//Lazy Loading
SimpleIoc.Default.Register<InformationManagementViewModel>();
}
public UserManagementViewModel UserManagementViewModel
{
get
{
return ServiceLocator.Current.GetInstance<UserManagementViewModel>();
}
}
public InformationManagementViewModel InformationManagementViewModel
{
get
{
return ServiceLocator.Current.GetInstance<InformationManagementViewModel>();
}
}
public static void Cleanup()
{
SimpleIoc.Default.Unregister<UserManagementViewModel>();
SimpleIoc.Default.Unregister<InformationManagementViewModel>();
}
}
}