I'm using DevExpress to add MVVM to my WinForms application. I have a form that contains a ViewModel, which is given to all of its user controls via constructor parameter.
DataBindings are working fine, but I want to add functionality to a ribbonbar by adding EventToCommand behaviour to the usercontrols toolbar item (BarButtonItem).
I do my bindings like this:
MVVMContext context = new MVVMContext();
context.ViewModelType = typeof(MyViewModel);
context.WithEvent<MyViewModel, EventArgs>(cmdA, "ItemClick")
.EventToCommand(x => x.SomeMethod());
context.WithEvent<MyViewModel, EventArgs>(cmdB, "ItemClick")
.EventToCommand(x => x.SomeOtherMethod());
context.SetViewModel(typeof(MyViewModel), viewModel);
viewModel is an instance of MyViewModel that contains the data and is received in the user controls constructor from the form.
However, when running the application, both buttons work but seem to bind to a separate instance of MyViewModel. How can I use the instance that I already have? Thanks!
Since you have passed the ViewModel instance from the external binding context you should set up your MVVMContext as follows:
// View(UserControl) side:
MVVMContext context = new MVVMContext();
// make sure that the MVVMContext will be destroyed when the UserControl destroyed
context.ContainerControl = this; // your View(UserControl)
context.SetViewModel(typeof(MyViewModel), viewModel);
This way prevent the automatic creation of the MyViewModel instance (the automatic creation is used when the context.ViewModelType is specified).
Then you can use the MVVMContext API as usual.
In your case, you can use the BindCommand instead of EventToCommand because of it specially designed to work with button-objects(like a BarButtonItems).
And, I strongly recommend you use the Fluent API which provides very clean and maintainable code:
var fluent = context.OfType<MyViewModel>();
fluent.BindCommand(cmdA, x => x.SomeMethod());
fluent.BindCommand(cmdB, x => x.SomeOtherMethod());
Related
I am creating a WPF application using .NET core 3.1
I have developed ASP.Net applications in the past and I was excited to use this in WPF. I did some searching and realized DI in WPF isn't as straightforward as it is in ASP.Net, meaning you have to register Views and ViewModels.
My structure is like this
MainWindow
|---BalanceIntegrationPage
|---BalanceIntegrationViewModel
Everything is handled in XAML with the MainWindow.xaml.cs having only generated code, and the BalanceIntegrationPage.xaml.cs has a single line added to it in the constructor
DataContext = new ScaleIntegrationViewModel();
That could not be handled in the xaml because DI requires parameters in the constructor.
Here is my app.xaml.cs:
protected override async void OnStartup(StartupEventArgs startupEventArgs)
{
base.OnStartup(startupEventArgs);
ServiceCollection services = new ServiceCollection();
services.AddScoped<MainWindow>();
services.AddScoped<ScaleInterfacePage>();
services.AddScoped<ScaleIntegrationViewModel>();
services.AddScoped<IScale>(provider => new Scale("1234"));
ServiceProvider serviceProvider = services.BuildServiceProvider();
MainWindow mainWindow = serviceProvider.GetService<MainWindow>();
mainWindow.Show();
}
My ScaleIntegrationViewModel looks like:
public ScaleIntegrationViewModel(IJMDataIntegration jmContext = null, IBalanceIntegrationContext localContext = null, IScale scale = null)
{
_jmContext = jmContext ?? new JMDataIntegration();
_localContext = localContext ?? new BalanceIntegrationContext();
_scale = scale ?? new Scale("1234");
//JK read from config
_commPort = "1234";
}
I also attempted to use the pattern described here
When I step through the code, my IScale object in the ViewModel constructor is always null.
Any Suggestions??
edit:
Based off a comment, I removed the ViewModel call in the page constructor and instead assigned it on the .xaml
This forced me to create a default paramaterless constructor which then breaks DI.
It almost starts to seem like I need to inject the services into the MainWindow ctor and then pass them down to everything I call from there. Makes no sense to me, since at that point, I may as well throw away DI and just new them up when I need them.
You are missing the configuration for some dependencies. From the code you've posted, you missed to configure IJMDataIntegration and IBalanceIntegrationContext:
protected override async void OnStartup(StartupEventArgs startupEventArgs)
{
base.OnStartup(startupEventArgs);
ServiceCollection services = new ServiceCollection();
services.AddScoped<MainWindow>();
services.AddScoped<ScaleInterfacePage>();
services.AddScoped<IJMDataIntegration, JMDataIntegration>();
services.AddScoped<IBalanceIntegrationContext, BalanceIntegrationContext>();
services.AddScoped<IScale>(provider => new Scale("1234"));
services.AddScoped<ScaleIntegrationViewModel>();
ServiceProvider serviceProvider = services.BuildServiceProvider();
MainWindow mainWindow = serviceProvider.GetService<MainWindow>();
mainWindow.Show();
}
Also, as already mentioned, you have to inject the view model into the MainWindow too. This where the dependency graph starts, the root of the application:
partial class MainWindow : Window
{
public MainWindow(ScaleIntegrationViewModel viewModel)
{
this.DataContext = viewModel;
}
}
To enable the full power of Dependency Injection (and make mocking easier) you should make use of Dependency Inversion throughout the application. This means you should only depend on interfaces and therefore only have interface types in your constructors:
partial class MainWindow : Window
{
public MainWindow(IScaleIntegrationViewModel viewModel)
{
this.DataContext = viewModel;
}
}
Controls like pages should be generated via DataTemplate and not instantiated directly in XAML. All you need to do, is to inject the page view models e.g. into another view model. Bind them to a ContentPresenter and define an implicit DataTemplate which targets the type of the page view model. This template contains the actual page. See this example.
Search for View-Model-First pattern if you need more details. Basically, a view can be defined as a data template and associated with a view model type. Data templates can be defined as resources, or they can be defined inline within the control that will display the view model. The content of the control is the view model instance, and the data template is used to visually represent it. This technique is an example of a situation in which the view model is instantiated first, followed by the creation of the view.
This is the preferred way, especially in conjunction with Dependency Injection.
Dependency injection means that you inject the dependencies, but don't construct them by yourself.
In your example, you construct the viewmodel manually inside of your page. This is not DI. You have to inject an instance of the viewmodel into the page.
You don't show us how you inject the page into the main window. I am not sure that using DI to inject a page into a window is a good decision. This is UI, and you can describe everything without DI (by using the data templating and pure XAML).
Anyway, to inject your viewmodel into the page, just introduce a constructor parameter in your page:
public ScaleInterfacePage(ScaleIntegrationViewModel vm)
{
InitializeComponent();
DataContext = vm;
}
That's it, you're all set.
I am attempting to register an interface for a viewmodel, so that I can inject the interface into another view model. I have something similar to the following code in the app constructor in a shared .net standard class library in a Xamarin Forms app using Caliburn Micro.
public App(SimpleContainer container)
{
this._container = container;
_container.Singleton<ISetupViewModel, SetupViewModel>();
InitializeComponent();
DisplayRootView<SetupView>();
}
Using this will display the correct view is displayed, however the SetupViewModel doesn’t get created. But when I change the registration to not use the interface as below, then the SetupViewModel is created and resolved.
_container.Singleton<SetupViewModel>();
Is there any reason that I can't register a view model with an interface?
This initially looked like a game, but bit after bit I ended up carrying this issue for quite a long time. Here is my situation. I fire Notifications from my Domain Model.
These notifications are just objects containing a title and a description that I store in a collection in order to render them at the top of the pages of my website. However I'm having trouble to find the appropriate "session" mechanism with MVC.
I started by using HttpContext.Items to store all my session data, but I found out that it wasn't suitable for Redirection Scenarios - when I redirect the user to an other Action Method. Indeed a new HttpContext is created and the Items object is lost.
Consequently I tried storing my session stuff in HttpContext.Session but the issue I have now is that there is no appropriate time to clear the Session (I don't want to carry on the notifications from one request to the other). OnActionExecuted and OnResultExecuted seem to run before the View is Rendered.
Here is how I display the notifications in my Layout page :
#foreach(var notification in ISession.Notifications)
{
#Html.Partial("_NotificationPartial", new Mvc.Models.NotificationViewModel(notification))
}
ISession is mapped to a store (HttpContext.Items / HttpContext.Session) in my IOC container.
Do you have any workaround idea ?
Try using the TempDataDictionary. It is included on the Controller base class as the TempData property. It is meant to persist data from one request to another. It is then cleared out automatically.
In the action method:
TempData["Notifications"] = new List<Notification>()
In the view:
#{
if(TempData["Notifications"] != null)
{
var notifications = TempData["Notifications"] as List<Notification>
}
}
I'm writing a workflow designer and I want to Send A Data From An Activity To Others without using Inargument on my custom activity so
I write A custom Native Activity and add a property to my context like this
context.Properties.Add("Name",Value );
and for calling the property i use this code:
var dynamicinarg = (string)context.Properties.Find("Name");
but when im Debugging it the dynamicinarg is null .
how can i fix it?
I'm writing a WPF/MVVM application using Prism/Unity. I'm having issues on how to transfer data from one usercontrol to another when navigating a region.
I have a region
This is populated by a user control("ContactsList") solely containing a grid, this grid is bound by ItemsSource & SelectedItem.
I want to navigate to ContractEdit and pass the Contact bound to SelectedItem.
LocalRegionManager.RequestNavigate(ContactRegions.MainRegion, ContactsUri.ContactsEdit);
I can pass a single value in he Datacontext or the Uri but I don't want to have to go back to the Database to get the data when I already have the whole Item in the list view model.
How can I pass a whole object from the list usercontrol to the edit control?
Thanks in advance.
Conclusion
I added the EventAggregator
public ListViewModel(IEventAggregator eventAggregator, IRegionManager regionManager)
{
_eventAggregator = eventAggregator;
_regionManager = regionManager;
}
Published the even when navigating
private void OnSendData()
{
_regionManager.RequestNavigate(ShellRegions.LeftRegion, ModuleAUris.Edit);
_eventAggregator.GetEvent<UserEvent>().Publish(_selectedItem);
}
Then subscribed to the even to get the object.
public EditViewModel(IEventAggregator eventAggregator, IRegionManager regionManager)
{
_regionManager = regionManager;
_eventAggregator = eventAggregator;
BackCommand = new DelegateCommand(OnBack);
_eventAggregator.GetEvent<UserEvent>().Subscribe((e) => UserEntity = e);
}
There is a handy example of this in the prism examples downloaded with the prism installer; where they use a separate file for subscribing/unsubscribing to an event. There's a desktop and Silverlight version.
It shows how to create a token used to unsubscribe from the event also.
Oli
You can have a NavigationManager that will know what is the current Contact. You just have to set it and get it for example in the NavigatedFrom and NavigatedTo, or somewhere else. It can either be in its own module or in the core project, as long as CurrentContact is an IContact.
You can also have a service in the list module that will access the current contact from other modules.
The service might be more appropriate if te NavigationManager has no other purpose.
edit
These services explained on msdn.