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).
Related
I am trying to create a simple dialog-type control that is based on the WPF Window class (Popup won't do the trick here).
In my app I register a DataTemplate in Application.Resources:
<Application.Resources>
<DataTemplate DataType="{x:Type local:EntitySelectorViewModel}">
<local:EntitySelector></local:EntitySelector>
</DataTemplate>
</Application.Resources>
In my Window control I set Window.Content and I expect WPF will set the ContentTemplate to an instance of EntitySelector based on the DataTemplate registration shown above:
[Export(typeof(EntitySelectorDialog))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public partial class EntitySelectorDialog : Window
{
[ImportingConstructor]
public EntitySelectorDialog(EntitySelectorViewModel vm)
{
InitializeComponent();
// DataContext = vm; // does not work
// EDIT: Per two answers shown below the following should work but it does not.
Content = vm;
}
}
The problem is that WPF does not resolve the ContentTemplate i.e. an instance of EntitySelector is not created. Furthermore, when I look at the XAML for EntitySelectorDialog I see that an instance of the shell has been injected
into the Window control (EntitySelectorDialog).
I don't know enough about Prism to know if I want to go with the flow and use the shell somehow or if I want to prevent Prism from injecting it at all. I don't think I have any need for it in this specific control so if it makes sense to just prevent Prism from injecting it I would prefer that route.
For the record I have tried removing the Prism attributes from my Window control and I new up the components manually. That appears to have no effect - Prism still manages to somehow inject the shell and my ContentTemplate is not resolved.
There is no XAML to show for the Window control (EntitySelectorDialog) except the Window declaration itself - I want the content to come entirely from the ContentTemplate (EntitySelector).
I've looked at this which may provide an answer but I don't know how to implement it without breaking the rest of the app:
Getting Unity to Resolve views in XAML
Set the Content of the window to a ContentControl and set or bind the Content property of this one to the view model:
[Export(typeof(EntitySelectorDialog))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public partial class EntitySelectorDialog : Window
{
[ImportingConstructor]
public EntitySelectorDialog(EntitySelectorViewModel vm)
{
InitializeComponent();
DataContext = vm;
Content = new ContentControl() { Content = vm };
}
}
You need to set vm to EntitySelectorDialog.Content to trigger WPF solve the DataTemplate according to the type. So you either add
Content = vm;
in constructor or add
Content = {Bing}
in Xaml.
I have a class library which contains several user controls. This library uses caliburn micro to bind the UserControls to their corresponding ViewModels using x:Name approach in the same class library.
Now I have an host application which hosts these UserControls in a Window. But this host application doesn't use Caliburn Micro at all. Then how can I initialize caliburn bootstrapper in the library itself.
HostApplication:
<Window>
<ContentControl>
<library:MainUserControl DataContext="{Binding MainUserControlViewModel}"/>
</ContentControl>
</Window>
Class Library
<UserControl x:Class="MainUserControl">
<ContentControl x:Name="OtherUserControlInSameLibrary"/>
</UserControl>
I tried to inherit Bootstrapper base class like AppBootstrapper: Bootstrapperbase and passing false to constructor parameter useApplication. And then I called Initialize() method. But still conventions are not applied.
MainUserControlViewModel Constructor
public MainUserControlViewModel()
{
AppBootstrapper appBootstrapper = new AppBootstrapper();
appBootstrapper.Initialize();
if (Debugger.IsAttached)
LogManager.GetLog = type => new DebugLog(type);
}
BootstrapperBase override
public class AppBootstrapper : BootstrapperBase
{
public AppBootstrapper():base(false)
{
}
}
I also tried to run caliburn debugger but nothing is showed in my output window.
Putting together the mentioned feedback by Caliburn.Micro mantainers and this blog post by Andy Race, it seems somehow it is doable, but the client application code has still to reference CM as well.
Apart from other needed settings (see references), the constraint is due to how the custom control view-model is bound to its view stated in XAML: the only way is to explicitly set the view-model by means of cal:Bind.Model.
...
xmlns:controls="clr-namespace:Control.Library;assembly=Control.Library"
...
<controls:SomeControlView
cal:Bind.Model="Control.Library.SomeControlViewModel" />
For the rest, the client application can totally ignore Caliburn.Micro.
For context, I am building a universal Windows Store app.
I'm just starting to learn C# and MVVM patterns and I need help correctly implementing binding.
I have followed this tutorial (Binding) and understand how it works, however in this example the code which does the binding is stored within the View Class.
public partial class MainPage : Page
{
public ObservableCollection<TermTest> MyTerms = new ObservableCollection<TermTest>();
public MainPage()
{
this.InitializeComponent();
MyTerms.Add(new TermTest("BNC", "Wire"));
MyTerms.Add(new TermTest("Lens", "Collects light"));
this.DataContext = new CollectionViewSource { Source = MyTerms };
}
As I understand it however this is poor design. In my implementation I will be using my Model to retrieve data which will get put into an Observable Collection. Then in my ViewModel I will want to bind the ObservableCollection to the XAML controls in which it is being used, not send the Collection to the View and then call a method in the View to populate the XAML controls.
Is that the correct way of doing this and, if so, how should it be done because I do not know how to expose the XAML controls to my ViewModel (and don't think I should be, right?).
I know I can expose the control creating a new instance of Mainpage but that is useless as I would need to bind to the current instance.
Mainpage Test = new MainPage();
Can someone please help me explain this - I have been through a lot reading and either not found the answer or not understood it!
Thanks, James
To begin, you definitely have the right idea.
What you want to do is create a ViewModel object (have it implement INotifyPropertyChanged) something like:
public class MainViewModel : INotifyPropertyChanged
{
//INPC implementation
public ObservableCollection<TermTest> MyTerms
{
//Standard INPC property stuff
}
}
Note that I used a property. You can only bind to properties, and you'll need the set method to raise the PropertyChanged event.
Then, you set the data context for the view. You can do this a number of ways, but the simplest is to do this:
public MainView() //Default Constructor
{
InitializeComponent();
DataContext = new MainViewModel();
}
Finally, bind!
<ListView ItemsSource="{Binding MyTerms}"/>
Also, if you don't want to touch the code behind of your window, you can do something like this:
<Window.Resources>
<YourNamespace:MainViewModel x:Key="MainViewModel"/>
</Window.Resources>
<Grid DataContext="{StaticResource MainViewModel}">
<ListView x:Name="TermsListView" ItemsSource="{Binding MyTerms}">
</ListView>
</Grid>
If you want understand in details this pattern I recommend you read this article:WPF MVVM step by step (Basics to Advance Level)
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.
I have a WPF Window which contains few UserControls, those controls contain another. And now, what is the most principal way how to create ViewModel for this Window and where to bind it.
I do expect that one firstly needs to create ViewModel for each of sub-controls.
There are a few ways to do this.
Inject the VM
I would recommend this method.
If your window is created in the App class like
var window = new MyWindow();
window.Show();
I would assign the VM before showing the window:
var window = new MyWindow();
window.DataContext = GetDataContextForWindow();
window.Show();
If one of your controls needs an own view model assign the VM wile creating the control instance.
DataBind
If you want to set the VM of a control you can bind the DataContext property to an VM instance provided by the surrounding VM.
<Controls:MyControl DataContext={Binding MyControlsVm} />
Code Behind
You may set the VM using the init method in code behind like
public MyWindow()
{
InitializeComponent();
DataContext = CreateViewModel;
}
You may use a trick if you don't want to create a VM for your main page:
public MyWindow()
{
InitializeComponent();
DataContext = this;
}
and just use the code behind class as VM.
I see the view as a visual representation of the ViewModel so I like WPF picking the view based on the instance of the ViewModel it wants to render.
I call this the View Locator pattern, I use this method to instantiate my view because I have found it to be very simple to implement.
It basically puts an entry in the ResourceDictionary of your app that tells WPF to use an IValueConverter to look up and instantiate the View when it comes across a ViewModel.
So a working example would be:
In your app.xaml:
<Application x:Class="MyApp.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml" >
<Application.Resources>
<ResourceDictionary Source="Resources.xaml"/>
</Application.Resources>
</Application>
In resources.xaml:
<DataTemplate DataType="{x:Type vm:ViewModelBase}">
<ContentControl Content="{Binding Converter={StaticResource ViewModelConverter}}"/>
</DataTemplate>
Set the DataContext of your startup Window Control e.g.
public MainWindow : Window
{
InitializeComponent();
DataContext = new MainViewModel();
}
And you're pretty much done. So if you have a MainViewModel like so:
public class MainViewModel : ViewModelBase
{
public ChildViewModel1 Child1 {get;set;}
public ChildViewModel2 Child2 {get;set;}
}
and you have a UserControl that resolves to your MainViewModel like so:
<UserControl x:Class="MainView">
<StackPanel>
<ContentPresenter Content="{Binding Child1}"/>
<ContentPresenter Content="{Binding Child2}"/>
</StackPanel>
</UserControl>
So your ViewModelConverter will return an instance of the appropriate View without any extra effort on your part.
On the child controls issue, why wouldn't one of the properties of the root view model be an instance of the child view model that you would pass onto the child control? The other option would be a converter that converts the non-view model based property into an instance of the child view model (like an adapter pattern).
You might be interested in the sample applications of the WPF Application Framework (WAF). They show how composite Views and ViewModels can be instantiated and how they interact which each other.