Accessing a static property of a view model is null - c#

in my app I need to call a method in my main view model from a sub settings view model to change a tab control in the main view. Basically its a list of viewsmodels in a tab control. The settings viewmodel has checkboxes to turn tabs on or off.
Anyway, I setup a static property in the main viewmodel of the instance. In my settings viewmodel constructor I get a null response but if I retrieve the instance on each checkbox property change it works. Seems like a timing issue on when the instances are created. Is there an event or something that can tell me when the main viewmodel instance isn't null?
<Window.Resources>
<DataTemplate DataType="{x:Type skyTelescope:SkyTelescopeVM}">
<skyTelescope:SkyTelescopeV />
</DataTemplate>
<DataTemplate DataType="{x:Type rotator:RotatorVM}">
<rotator:RotatorView />
</DataTemplate>
<DataTemplate DataType="{x:Type focuser:FocuserVM}">
<focuser:FocuserView />
</DataTemplate>
<DataTemplate DataType="{x:Type settings:SettingsVM}">
<settings:SettingsV />
</DataTemplate>
</Window.Resources>

There's a lot to learn with wpf and the mvvm approach is very different from event driven code.
Your markup looks like you're doing viewmodel first... or something like that.
If you have a MainWindowViewModel exposes say a SubVM property.
Bind SubVM to the content property of a contentcontrol in mainwindow.
Set SubVM to an instance of a viewmodel such as SettingsVM.
This is then templated out in the view using the matching datatemplate.
You see a SettingsV appear.
The datacontext of SettingsV is your SubVM.
Bind controls in SettingsV to properties in SubVM and the values can transfer between them.
That allows you to control when you instantiate SettingsVM in MainWindowViewModel.
You therefore "know" whether you have an instance of it or not.
You can cache instances of viewmodels in a dictionary.
Use a type as your key and you can instantiate one if you don't have it in your dictionary, then re-use it if you wanted to retain state.
You can make MainWindow instantiate MainWindowViewModel by defining it's datacontext in xaml.
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
With viewmodel first there's an oddity with the way templating works. If you wanted to reset (unbound) view state then you need to force the re-templating by setting your SubVM property null first.
This command takes a Type as a parameter
private RelayCommand<Type> navigateCommand;
public RelayCommand<Type> NavigateCommand
{
get
{
return navigateCommand
?? (navigateCommand = new RelayCommand<Type>(
vmType =>
{
CurrentViewModel = null;
CurrentViewModel = Activator.CreateInstance(vmType);
}));
}
}
Obviously, this doesn't stash any instance of a vm away in a dictionary, it's just instantiating every time.

Related

MVVM - datatemplate creates a new view

I have two DataTemplates that gets switched depending on the current ViewModel. However whenever I switch my ViewModel, it seems to call the respective View's constructor and calls the InitializeComponent() call within the constructor, which means that whenever I switch the DataTemplate, it generates a new view that is bound to the respective DataTemplate. I am not sure why this is happening but is there a way to prevent the creation of a new View when switching ViewModels?
Below is the DataTemplates located at my MainView.
<Window.Resources>
<DataTemplate DataType="{x:Type viewModels:FirstPanelViewModel}">
<views:FirstPanelView />
</DataTemplate>
<DataTemplate DataType="{x:Type viewModels:SecondPanelViewModel}">
<views:SecondPanelView />
</DataTemplate>
</Window.Resources>
The template is being displayed in a ContentControl.
<ContentControl Grid.Row="1" Content="{Binding CurrentViewModel}" />
This is my SecondPanelView which is the same as my FirstPanelView, it's very simple.
public partial class FirstPanelView
{
public FirstPanelView()
{
InitializeComponent();
}
}
public partial class SecondPanelView
{
public SecondPanelView()
{
InitializeComponent();
}
}
My Ioc makes sure that I generate only one instance of the SecondPanelView
container.Register<IFirstPanelViewModel, FirstPanelViewModel>(new PerContainerLifetime())
container.Register<ISecondPanelViewModel, SecondPanelViewModel>(new PerContainerLifetime());
DataContext is being bounded in each view by a custom markup extension.
DataContext="{Binding Source={common:Locate}, Path=FirstPanelViewModel}"
DataContext="{Binding Source={common:Locate}, Path=SecondPanelViewModel}"
Which is just calling GetInstance of the respective ViewModel.
public IFirstViewModel FirstViewModel
{
get { return _container.GetInstance<IFirstPanelViewModel>(); }
}
public ISecondViewModel SecondViewModel
{
get { return _container.GetInstance<ISecondPanelViewModel>(); }
}
This is an old issue, but I was also struggling with this issue. The answer is to place the view instances directly in the resources and bind them to content controls in the data templates. If you do so, the view is instantiated only once.
<Window.Resources>
<views:FirstPanelView x:Key="FirstPanelViewKey"/>
<views:SecondPanelView x:Key="SecondPanelViewKey"/>
<DataTemplate x:Key="DT1">
<ContentControl Content="{StaticResource FirstPanelViewKey}" />
</DataTemplate>
<DataTemplate x:Key="DT2">
<ContentControl Content="{StaticResource SecondPanelViewKey}" />
</DataTemplate>
</Window.Resources>
I wasn't able to solve my problem even after extending ContentControl. The issue I ran into using that approach is that ContentControl's dependency property was not directly interfacable/overridable which forced me to hack on the existing dependency property. Also the intialization of a DataTemplate seems to fall deeper than the simple ContentControl.
So I decided to change the way that my views are being displayed by simply toggling their visibility. This approach worked for me since I essentially want my views to stay in the background doing its own thing and ready to be interfaced at its previous state in any moment.

Replace current binding source object with another object

I'm totally new to C# and WPF and I'm trying to do my best with the data binding. I have a MyClass which implements INotifyPropertyChanged; so everytime I change a property value, this is updated in my UI. Then I have bound the DataContext of a stackpanel to an object of MyClass. Like this:
<StackPanel Name="stackPanel1" DataContext="{Binding}">
<TextBlock Name="textBlock1" Text="{Binding Path=Title, Mode=OneWay}" />
</StackPanel>
In code behind I do this:
item = new MyClass();
stackPanel1.DataContext = item;
and the binding is working fine. If I replace my current binding source object with another one, I have to manually set this by typing again the datacontext binding:
item = new MyClass();
stackPanel1.DataContext = item;
item1 = new MyClass();
.
. //manipulate item1
.
if (item1 is ok)
item=item1;
stackPanel1.DataContext = item;
Is there a better way to replace my source object and have all the associated bindings updated?
When you say stackPanel1.DataContext = item;, you are setting the property, not binding it.
When you set the property, you are setting it equal to an instance of the object. When you bind it, you are telling it it will be getting its value from some other location, so look in that location anytime it needs to get the value.
Providing your class that contains the bound properties implements INotifyPropertyChanged, then the UI will be alerted anytime a bound property changes, which causes the binding to get reevaluated.
For example, if you had set the DataContext initially with
MyWindow.DataContext = this;
where this was your Window, and your Window had a propety of type MyClass called Item, then you could bind the DataContext using the following
<StackPanel DataContext="{Binding Item}" ...>
and anytime you updated the property Item, your StackPanel's DataContext would also update (providing you implement INotifyPropertyChanged).
If you're interested, I like to blog about beginner concepts in WPF, and you may be interested in checking out my article What is this "DataContext" you speak of?, which is a very simple explanation of what the DataContext is and how it's used.
To summarize, WPF has two layers: the UI layer and the Data Layer. The DataContext is the data layer, and when you write {Binding SomeProperty}, you are actually binding to the data layer. Typically you set the data layer (DataContext) once in your code behind, and then use Bindings in your XAML to make your UI layer display information from the data layer.
(You may also be interested in checking out my Simple MVVM Example, which contains a very simple working code sample, and illustrates some examples of how INotifyPropertyChanged is implemented and how the UI layers and Data layers can be completely separate)
You may add a CurrentItem property in your MainWindow (or UserControl or whatever it is) and also implement INotifyPropertyChange for that property. Then set
DataContext = this;
in the MainWindow's constructor and bind like this:
Text="{Binding Path=CurrentItem.Title}"
Now whenever you set
var item = new MyClass();
...
CurrentItem = item;
the binding will be updated.
DataContext="{Binding}"
and
stackPanel1.DataContext = item;
Both do basically the same thing. The difference being that one is done in XAML and the other is in code. While the first example would allow binding to be updated given a binding parent the second one must be updated every time you want to change what the stackpanel is attached to. IMHO you should create a common binding parent to bind against. This would allow you to change the child bindings without having to set the context everytime.
<StackPanel Name="parentPanel">
<StackPanel Name="stackPanel1" DataContext="{Binding Path=Child}">
<TextBlock Name="textBlock1" Text="{Binding Path=Title, Mode=OneWay}" />
</StackPanel>
</StackPanel>
parent = new ParentClass();
parent.Child= new MyClass();
parentPanel.DataContext = parent ;
Now if notify property changed was created on ParentClass correctly you can changing the binding for the child stack panel
parent.Child= new NewClass();

How do I set view/view model data template at runtime?

This MVVM stuff is making my head hurt. I have an application which has a list of editors in a left pane. On the right is a tab control where the editors will be displayed. I have a main application view model that contains a collection of view models. I call this collection Workspaces. This is borrowed from the MvvmDemoApp that Microsoft provides here.
public ObservableCollection<WorkspaceViewModel> Workspaces
{
get
{
...
}
}
These workspaces are bound to a tab control in the main application window like so:
<DataTemplate x:Key "WorkspacesTemplate">
<TabControl
IsSynchonizedWithCurrentItem="True"
ItemSource="{Binding Workspaces}"
SelectedItem="{Binding ActiveWorkspace}"/>
</DataTemplate>
...
<ContentControl
Content="{Binding}"
ContentTemplate="{StaticResource WorkspacesTemplate}"/>
The view models are tied to a view using DataTemplates like so:
<DataTemplate DataType="{x:Type vm:MessageLogViewModel}">
<vw:MessageLogView/>
</DataTemplate>
This works fine. However, now I need to make the application configurable where the list of editors are read from a config file. I imagine this config file will contain the view and view model components for each editor. But, how do I tie the two together so that when someone binds to a view model (or a collection of view models), the correct view gets displayed (similar to what the DataTemplate does but in code, not XAML)?
I'm trying to stay away for Inversion of Control (IoC) techniques. I'm not sure our team is ready for that must sophistication.
IoC is the perfect solution for this however without this option you could creating the XAML data template in the view model using an XmlWriter and expose it as a property to bind to.
Edit: Bindings
You have your list of view models. Create and expose this XamlTemplate property in each view model (in a base view model class). The property should create Xaml along the lines of:
<DataTemplate xmlns:vw="...">
<vw:MessageLogView/>
</DataTemplate>
Then use a ContentControl to bind to:
<ContentControl Content="{Binding ViewModel}"
ContentTemplate="{Binding ViewModel.XamlTemplate}" />

WPF composite Windows and ViewModels

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.

How do I Encapsulate a WPF ViewModel/View UserControl as a Control

I've built a WPF UserControl View/ViewModel pair: the view XAML handles the layout and bindings, and the ViewModel handles the logic, in-line with the recommended MVVM pattern.
I would like to be able to re-use this as a control.
How do I hide/encapsulate the ViewModel associated with the view, so that I can use the control as I would a standard control [such as a button] ?
i.e. How do I hide the control's viewmodel ?
depends on how you bind ViewModel class to the control.
if you do like this:
YourControl()
{
DataContex = new ViewModel();
}
then I don't see any problems. add reference to your control and use it.
You can create your ViewModel as a StaticResource within your XAML. The problem with setting the DataContext to your ViewModel is that you can't use that you can no longer use your DataContext from the window or page you in which you use the control.
In your XAML declare your ViewModel:
<myNS:MyViewModel x:Key="ViewModel />
Reference your view model within your XAML:
<TextBlock Text="{Binding Source={StaticResource ViewModel}, Path=TextToBind}" />
In your Code Behind you can access and initialize quickly, I usually make a property for easy reference to my view model.
private MyViewModel viewModel
{
get { return this.Resources["ViewModel"] as MyViewModel; }
}

Categories