I have a class in WPF that is referenced in XAML as a view control:
<ctrl:MyController x:Key="Controller"/>
I can now access this as a static resource and all is fine:
Command="{Binding Source={StaticResource Controller}, Path=HistoryFutureRetrieveLeft,
Mode=OneTime}">
However I now need to create the controller using an IOC container. I can do this with the following code:
Resources.Add("Controller", App.IocContainer.Resolve<MyController>());
But I have to remove the line from the XAML,:
<ctrl:MyController x:Key="Controller"/>
This isn't a problem at run time but this does cause a problem when trying to edit the XAML because all the bindings now say the "Resource Controller is not found". Is there any way to allow the IOC container to create the controller at run time but to define the controller in the XAML file purely for the VS designer?
You can leave statically defined controller in XAML to satisfy designer, and replace it in runtime with a proper one from container.
Update:
Could you use DataContext instead of modifying resources?
this.DataContext = App.IocContainer.Resolve<MyController>()
Then you can specify data context type in XAML (d:DataContext) to make designer happy and have intellisense.
Related
I am writing a multi-page WPF app using MVVM and I'm having a problem I can't seem to solve effectively.
If there's an error at any point in the app, I've created single sub-view that can be shown inside a view to display the error and info on any page (View).
The way I'm trying to do this is:
The page (View) has a ViewModel that handles the business logic including determining something went wrong. It will then create an instance of the Error Sub-View inside a content control where the error will be displayed.
The ViewModel signals which error has occurred by calling a public method on the Sub-View's ViewModel and passes this function a byte index so the Error Sub-View can lookup and get the information to be displayed.
The Sub-ViewModel sets this error info in a property "ActiveError". ActiveError has a RaisePropertyChanged method and the Error Sub-View has text fields bound to this property to display the data.
Simple stuff. However, the Error Sub-View is displayed but never with any data.
I thought I solved this by making ActiveError static until I figured out what was really happening (I think). The ActiveError data isn't being shown because in this process, 3 instances of the Error Sub-ViewModel are being created.
One is created when we create the Sub-ViewModel:
Page ViewModel
New Sub-ViewModel instance
ConnectionErrorVM = new ConnectionErrorViewModel();
CurrentView = ConnectionErrorVM;
Page Xaml
New Sub-View instance
<Page.Resources>
<DataTemplate x:Name="ConnectionErrorViewTemplate"
DataType="{x:Type vm:ConnectionErrorViewModel}">
<v:ConnectionErrorView DataContext="{Binding}" />
</DataTemplate>
</Page.Resources> ...
//Further down the page in grid
<ContentControl x:Name="ConnectionMessagesView"
Content="{Binding CurrentView}"/>
And now the second is created here:
Sub-View Xaml
Second Sub-ViewModel
<UserControl.DataContext>
<vm:SystemErrors/>
</UserControl.DataContext>
And now the 3rd:
Page ViewModel###
Calling the SetActiveError method
SystemErrors error = new SystemErrors();
error.SetActiveError(errorCodeToIndex);
It seems like this is a useful approach for a lot of things in MVVM, but I can't think of a way to do it without this without the extra instances because the datacontext must always be set in the Sub-View and the PageView model will always have to create an instance to call a method. How do approach this without band-aids like singletons or being fine with doing things wrong because the static keyword saves us?
I am working on refactoring an application to use property injection. This has required me to create the views from the code behind like such. Originally everything was created in XAML and bindings were set to the view models by static resources. This gave no control over injecting the service for getting data into the view model.
This is the App.xaml.cs:
public App()
{
this.MainWindow = new MainWindow(new MainWindowViewModel(new DbDataService()));
MainWindow.Show();
}
MainWindowViewModel is set as the datacontext.
App.xaml contains this resource for viewing the product type class in a listbox:
<DataTemplate x:Key="DataTemplate" DataType="{x:Type classes:Product}">
The MainWindow has a tab for each view. I have a tab's view created like this in MainWindow's constructor:
ProductsTab.Content = new MainView(mainWindowViewModel);
When I do this, I get the following error in the MainView on this line:
<dxe:ListBoxEdit Grid.Column="0" ItemTemplate="{StaticResource DataTemplate}" ...etc... />
"'Provide value on 'System.Windows.StaticResourceExtension' threw an exception.' Line number '37' and line position '31'."
What I have tried:
I have tried creating the Tab's view in XAML and setting its data context to the parents, however, I still got this exception.
I have tried changing my static resources to dynamic resources, which causes no error at runtime, but the list box does not display the information correctly.
What I need to know:
How to use static/dynamic resources with decency injection.
If there is a better approach to this problem, such as creating the templates in code and injecting them into the views?
Some of my concerns:
I want to add dependency injection to this application, however, I feel like the approach I'm using not only breaks the resources but breaks the MVVM pattern.
Try to create and call your MainWindow in the OnStartup method of your App class:
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
this.MainWindow = new MainWindow(new MainWindowViewModel(new DbDataService()));
MainWindow.Show();
}
}
This should work provided that you define the "DataTemplate" resource in your App.xaml (or in a ResourceDictionary that you merge from App.xaml).
im a little bit stuck with my current project and hope someone can help me out of this.
My application works with plugins so that the user is able to add additional functionality to the application. Now i would like to have the configuration window in the same style (Maybe a plugin need some kind of configurations).
The configuration window loads all plugins and get the configuration ViewModel from the plugins. All the ViewModels are stored in an ObservableCollection. These ViewModels should be displayed in a TabControl (one tab per ViewModel)
But i dont know the type of UserControl the plugin is using, because the plugin come up with its own UserControl for configuration purposes.
Otherwise i would create a TabControl, bind its ItemsSource to the ObservableCollection and specify the UserControl in the Resources (DataTemplates).
But how to do it in case the UserControls are unknown to compile time?
I thought about using a ObservableCollection instead of ViewModels, but im not realy happy with that and even dont know if this will work.
Do you have some idea how to deal with that?
Kind regards,
SyLuS
You could use a ContentControl to achieve this.
It's used to show views depending on their viewmodel.
In your xaml you can specifiy which view should be shown. Based on the viewmodel which is the current DataContext.
<ContentControl>
<ContentControl.Resources>
<DataTemplate DataType="{x:Type vm:MyViewModel}">
<v:MyView/>
</DataTemplate>
</<ContentControl.Resources>
</ContentControl>
But when you say you are using a plugin system, maybe something like PRISM, you have to setup the datatemplates automatically. Never done this before. But maybe I gave you a point where you can start.
Suppose I have two Pages in WPF namely Page1.xaml and Page2.xaml. I have one viewmodel named PageViewModel.cs. Both of those pages share the same viewmodel.
I can write my code by two methods:
Mehod1:
PageViewModel.cs
public static class PageViewModel
{
}
Page1.xaml
<Window.........>
<Window.DataContext>
<vm:PageViewModel />
</Window.DataContext>
</Window>
Page2.xaml
<Window.........>
<Window.DataContext>
<vm:PageViewModel />
</Window.DataContext>
</Window>
App.xaml
Default xaml code.
Method2:
PageViewModel.cs
public class PageViewModel
{
}
Page1.xaml
<Window DataContext={StaticResource PageViewModel}>
..........
..........
</Window>
Page2.xaml
<Window DataContext={StaticResource PageViewModel}>
..........
..........
</Window>
App.xaml
<vm:PageViewModel x:Key="PageViewModel" />
Can anybody explain the difference between above mentioned two methods?
The primary difference will be that in your first example, any object or method can access your view model, its data, and its methods.
In the second, you have an actual instance (albeit contained in a globally accessible object), so while other objects could still get to it, its not as easy as "access the static (read, global) instance".
Both have the same effect, you get data shared between the two views.
One additional option you may want to consider would be to pass the view model in on the constructor of the view. You have to use the code-behind, but you can give both view's a reference to the same view model object without having any global variables.
If these are subviews, then you could do the following:
MainView.xaml.cs
public void MainView()
{
SubViewModel subVm = new SubViewModel();
//If you are instantiating your views
MySubView view1 = new MySubView(subVm);
MySecondSubView view2 = new MySecondSubView(view2);
//Otherwise
view1.DataContext = subVm;
view2.DataContext = subVm;
}
In the spirit of the locator pattern, you could also simply bind the DataContext property of the sub views to a SubViewModel property on your main View Model.
The one thing to be aware of with this is that the View Model's lifetime will end once both sub views are destroyed. If you need a longer lifetime, then you should use the latter option and point it towards a long-lived object.
In general, I would stay away from static classes. They make unit testing, and good design in general, much more difficult to achieve. If you need a singleton, at least implement one properly as opposed to just using a static class.
This won't answer your question, BradleyDotNET has done that, but I just can't help my self here.
This is a perfect example for using a ViewModelLocator, try to install a framework such as GalaSoft MVVM Light. You may use your Locator to keep track of your viewmodels, static viewmodels are bad pie(you are going to run into alot of problems you can avoid).
I can't see where you have declared your static resource, but I assume it is in App.xaml ?
Check this post for using a viewmodel locator, don't be alarmed by the IOC stuff :). This is really handy and a great way to solve your issue.
Binding to someviewmodel, assuming the vmlocator is defined in App.xaml and that SomeViewModel is present there.
DataContext="{Binding Source={StaticResource ViewModelLocator}, Path=SomeViewModel}"
Hope it helps,
Cheers Stian
I have a wizard created in WPF consisting of pages as UserControl objects. What I'm trying to do is to load plugins from .DLL files which contain the following:
A code file for the plugin logic.
A XAML user control which will present configuration options for the plugin, displayed in the main wizard.
A view model for the user control.
I've been able to successfully load in and instantiate the UserControl object as well as the View Model, and I have gotten to the stage where the Control appears in it's own wizard page as intended.
(This is probably the view model that's instantiated correctly, since I set the title for the wizard page in the view model and that all works fine)
The problem I'm getting is that the UserControl I've loaded from the DLL isn't displaying correctly. Instead of displaying the UserControl contents, it just shows the plaintext MyDLL.MyCustomUserControl from the line x:Class="MyDLL.MyCustomUserControl" where the actual user control should be.
The contents of my user control that I'm loading from the DLL is:
<UserControl x:Class="MyDLL.MyCustomUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<StackPanel>
<Label Content="This is the Plugin Options Page" />
<TextBox Text="{Binding Path=PluginStringText}" />
</StackPanel>
</UserControl>
The property PluginStringText exists in the View Model for this User Control.
I have a feeling I need to somehow assign or bind the View Model to the User Control after loading it in from the DLL.
As part of my wizard, there is a section where I define Data Templates (this is another UserControl which contains the Wizard's pages), but I don't know how to actually add extra templates during runtime. I have a feeling this is causing the issue.
Searching through many topics reveals I could probably do this through the code behind file for the view, but then I have no way of obtaining a reference to the view's code behind in the first place to call a method. Is there a way of adding a new DataTemplate entry from the View's View Model class?
<UserControl.Resources>
<DataTemplate DataType="{x:Type viewModel:ExistingPage1ViewModel}">
<view:ExistingPage1View />
</DataTemplate>
<DataTemplate DataType="{x:Type viewModel:ExistingPage2ViewModel}">
<view:ExistingPage2View />
</DataTemplate>
<...>
Thanks in advance if someone could point me in the right direction
I found a way to resolve this.
The problem was as I had expected, there was no mapping between the ViewModel data type and the View in which to render the ViewModel with.
I discovered a useful guide here http://www.ikriv.com/dev/wpf/DataTemplateCreation/ which explains how to create a new data template within code, very similar to if you'd hard coded the template in the XAML.
This was all well and good, but I still had to find a way to call the method to create the data template.
Well - It turned out I didn't need to call the method at all in the code behind! I just put the method to create the data template directly in my view model which is responsible for populating the wizard's pages.
This also has the added benefit that I do not have to hard code my existing pages datatemplate's in the xaml.
I will add the code referenced at http://www.ikriv.com/dev/wpf/DataTemplateCreation/ for future reference:
The method to create the DataTemplate object:
DataTemplate CreateTemplate(Type viewModelType, Type viewType)
{
const string xamlTemplate = "<DataTemplate DataType=\"{{x:Type vm:{0}}}\"><v:{1} /></DataTemplate>";
var xaml = String.Format(xamlTemplate, viewModelType.Name, viewType.Name, viewModelType.Namespace, viewType.Namespace);
var context = new ParserContext();
context.XamlTypeMapper = new XamlTypeMapper(new string[0]);
context.XamlTypeMapper.AddMappingProcessingInstruction("vm", viewModelType.Namespace, viewModelType.Assembly.FullName);
context.XamlTypeMapper.AddMappingProcessingInstruction("v", viewType.Namespace, viewType.Assembly.FullName);
context.XmlnsDictionary.Add("", "http://schemas.microsoft.com/winfx/2006/xaml/presentation");
context.XmlnsDictionary.Add("x", "http://schemas.microsoft.com/winfx/2006/xaml");
context.XmlnsDictionary.Add("vm", "vm");
context.XmlnsDictionary.Add("v", "v");
var template = (DataTemplate)XamlReader.Parse(xaml, context);
return template;
}
This DataTemplate then needs to be registered as an application resource:
Registering the DataTemplate:
Application.Current.Resources.Add(template.DataTemplateKey;, template);
Once again, this code was provided thanks to Ivan Krivyakov at http://www.ikriv.com/dev/wpf/DataTemplateCreation/