x:Bind defaults to OneTime, which updates the target UI with the data when the Page's Loading event triggers the generated code's Initialize function.
I have a Page with a ViewModel property. This ViewModel class implements INPC for its properties. The data for the viewModel is loaded asynchronously, only after the page is loaded. So on Page initialization, and subsequently the generated code initialization, the UI target using x:Bind will have null data.
Since it is OneTime, it shouldn't change unless I manually call Update(which I don't).
So why does my x:Bind UI work?
The following is some simplified code snippets.
<Page x:Name="MyPage" x:Class="MyProject.Pages.MyPage">
<Button Command="{x:Bind ViewModel.GoToAnotherPageCommand}">
public sealed partial class MyPage : Page
{
public MyPageViewModel ViewModel { get; set; }
public MyPage()
{
this.InitializeComponent();
}
// called by an event bound to a Frame's Navigated, which all pages use
public void OnNavigatedTo()
{
this.ViewModel = new MyPageViewModel();
}
}
public class MyPageViewModel : INotifyPropertyChanged, INotifyPropertyChanging
{
// GoToAnotherPageCommand is an INPC property and its set in the constructor
The reason that your command works fine is because OnNavigatedTo will be called before the command instantiation. This means by the time the code tries to set the command, the ViewModel has already been instantiated and is no longer null.
To prove my point, first go open the file under the following path(could be ARM or *x64 depending on which platform you are running on) -
obj/x86/Debug/MyPage.g.cs
This is basically the code-generated file that hooks up all the x:Bind stuff for your page.
Now put a breakpoint at where the command is set. In my case, it's a method called Set_Windows_UI_Xaml_Controls_Primitives_ButtonBase_Command. Then put another breakpoint at OnNavigatedTo.
Now run the app, you will see that the OnNavigatedTo method gets called first.
If your page's NavigationCacheMode is set to Disabled, this behavior makes OnNavigatedTo the ideal place to instantiate x:Bind bindings so the page only uses memory to create these new objects when the user actually navigates to it, instead of doing everything inside the page constructor.
Don't do this inside the Loaded event of the Page though. Because it will get called after the command instantiation. You can try the following code to instantiate the ViewModel, and the result is very different(your command will not work).
public MyPage()
{
InitializeComponent();
Loaded += (s, e) => ViewModel = new MyPageViewModel();
}
The compiled binding system (x:Bind) is smart enough to check for initial null values and not consider them the actual value you wish to bind. It will wait for the first non-null value and bind that value.
This is by design, as binding to an initial null value is (almost) never the intention of the binding.
I didn't find the source of this information, but I believe it was in the Build talk detailing the x:Bind system in 2015.
Updated:
As Justin mentions in the comments below and in his own answer, the binding will not work if the view model is set after the binding operation happens.
I believe this is because the binding terminates when it encounter a null reference in the property chain, but I haven't tested this, so I might be incorrect.
Related
I'm got an other question for my WPF/MVVM application I'm working on since a while.
The main idea is to use a main window providing a navigation bar and a ContentControl.
The different "Modules" are all built as UserControl with each its own ViewModel.
The main call from the main viewmodel to start a module is
private void ShowAddressModule() {
ContentControlBindingProperty = new AddressModule(new AddressModuleViewModel);
}
In the real application the viewmodels are pre-loaded and so on, but the start is more or less the same.
The main view model contains a boolean property LongRunningOperation to do multiple operations on the main window while any long running operation.
As example showing a loading image or disable the main navigation while loading a new module or whatever.
So my idea is to provide a possibility to the modules (their view models) to active this "mode".
Example how it could look in the modules view model:
private void LoadContactList() {
MainWindow.LongRunningOperation = true;
LoadAllContactsInAThread(); /*Takes a long time*/
MainWindow.LongRunningOperation = false;
}
I tried to mark the property as static and public, but this will not work because of the OnPropertyChanged event.
If possible it would be great if the solution could be applied also to methods (including parameters) from the main window - so (as example) the modules could use as example the parents statusbar or so.
MainWindow.ShowErrorMessageInStatusBar("The error xyz occured!");
Hopefully I described good enought, what's my idea...
And hopefully anybody could provide me the needed tip how to handle this requirement.
Thanks in advance for any hints
Regards Markus
Each module could raise an event to indicate the start of a long running operation, and raise an event to indicate the end of a long running operation. Your main view model, when loading modules for the first time, could hook-up to these events and react to them accordingly.
Your sub view model would have some events like this:
Sub view model
public delegate void OnLongRunningOperationStartedEventHandler(object sender);
public delegate void OnLongRunningOperationFinishedEventHandler(object sender);
public event OnLongRunningOperationStartedEventHandler OnLongRunningOperationStarted;
public event OnLongRunningOperationFinishedEventHandler OnLongRunningOperationFinished;
private void LoadContactList() {
OnLongRunningOperationStarted?.Invoke(this);
LoadAllContactsInAThread(); /*Takes a long time*/
OnLongRunningOperationFinished.Invoke(this);
}
And your main view model will hook-up to them like this:
Main View Model
public bool LongRunningOperation { get; private set; }
// Keep track of the number of modules currently running long operations
private int _countLongRunningOperations = 0;
public LoadSubModules(){
// Depending on how you load your sub modules, this piece of code could move around
foreach (var module in submodules){
module.OnLongRunningOperationStarted += Module_LongOperationStarted;
module.OnLongRunningOperationFinished += Module_LongOperationFinished;
}
}
private void Module_LongOperationStarted(object sender){
_countLongRunningOperations += 1;
LongRunningOperation = true;
}
private void Module_LongOperationFinished(object sender){
_countLongRunningOperations -= 1;
if (_countLongRunningOperations == 0) {
LongRunningOperation = false;
}
The same principle (using events) could be used to bubble up error messages from each submodule to the main view model.
The quick and very dirty approach:
Grab a reference to mainwindow out of application.current.mainwindow. Cast it to MainWindow. It's a property set to whatever the first window you show is - MainWindow just happens to be the default name of the main window.
You can then set the property on that if it's a public dependency property. Make sure the dp binds twoway in it's metadata.
This is bad because you're referencing ui in your viewmodels and you have no application when you run tests on viewmodels in some test runner.
The quick and dirty approach
Add a public property to app and set this to your instance of mainwindowviewmodel in it's ctor. You can reference app from an piece of code. Add a public property to mainwindowviewmodel and bind to that.
This is bad because you have no application when you run tests on viewmodels in some test runner.
You could add a static with an interface abstracts this away and work round that though.
My suggestion
This hinges on the fact you can use dot notation to bind and that includes
Content.IsBusy on yourcontentcontrol.
You can therefore bind from a parent window to a dependency property of any usercontrol that happens to be in it's contentcontrol.
Add that property using an attached property and bind that to IsBusy in a base viewmodel. Inherit the viewmodels of your child views from that.
One thing to mention is that binding to an attached property is a little odd and rather than just
ElementName=YourContentControl, Path=Content.YourAttachedProperty
You need something like:
ElementName=YourContentControl, Path=Content.(local:AttachClass.YourAttachedProperty)
I'm having a strange issue, and im pretty much at my wits end trying to work it out.
I have a Conductor which activates and deactivates viewmodels used for editing data, these view models implements screen and use OnDeactivate to ensure that any changes are saved before closing.
However for some reason, OnDeactivate in one of my ViewModels is never called, even tho i can see it being passed to DeactivateItem of the conductor.
To do this have the following in my conductor:
private void SwitchScreen(Screen viewModel)
{
DeactivateItem(ActiveItem, true);
ActivateItem(viewModel);
NotifyOfPropertyChange(() => ProjectActionRegion);
}
public override void DeactivateItem(IScreen item, bool close)
{
base.DeactivateItem(item, close);
NotifyOfPropertyChange(() => ProjectActionRegion);
}
This ensures that when TryClose is called the region is correctly updated. The SwitchScreen is called each time a selection is made on a datagrid, loading the viewmodel. I can see that Deactivate item is called when i change selection, and i can see its passing the correct viewmodel into that method.
However OnDeactivate is still never called, and i have no idea why :/
protected override void OnDeactivate(bool close)
{
System.Windows.Forms.MessageBox.Show("SAVE ME!");
}
Edited to remove incorrect code (the base. was a mistake, this is my actual code)
EDIT:
I've just realized what the difference between the working versions and the broken version is. I have a view/viewmodel that works as a conductor, this works fine. However inside that viewmodel i load a second view/viewmodel that also works as a conductor, this one fails to work, i wonder if it has to do with with being inside another conductor (but not actually handled by that conductor, just loaded into that viewmodel)
For screen life-cycle to function correctly, all of the view models in your view hierarchy must be conducted. You should make your child conductor an active screen of your parent conductor.
You can either do this by making it the active item of the parent conductor, or by using the ConductWith method on the child conductor, passing in a reference to the parent conductor.
I have a Grid with a ScrollViewer around it. At the top of my ScrollViewer is a Button. On a Click on the Button, I want the ScrollViewer to scroll to a Control at the bottom of the ScrollViewer.
With the following XAML I can bring the Control into view:
<Button Grid.Row="2" Content="Some Button" Command="{Binding DoJumpCommand}" CommandParameter="{Binding ElementName=window}"/>
The Command in the ViewModel is:
if (parameter is MainWindowView)
{
var mainWindowView = parameter as MainWindowView;
mainWindowView.myJumpTarget.BringIntoView();
}
This works fine. But I'm not sure if this is clean MVVM because I pass the complete View into the ViewModel.
Is there a better way to do this?
When I first saw your question, I thought that the general solution to handling events with MVVM is to handle them in an Attached Property. However, looking again, it occurred to me that you're not actually handling any events... you just want to call a method from a UI control. So really, all you need is a way to pass a message from the view model to the view. There are many ways to do this, but my favourite way is to define a custom delegate.
First, let's create the actual delegate in the view model:
public delegate void TypeOfDelegate();
It doesn't need any input parameters, because you don't need to pass anything from the view model to the view, except a signal... your intention to scroll the ScrollViewer.
Now let's add a getter and setter:
public TypeOfDelegate DelegateProperty { get; set; }
Now let's create a method in the code behind that matches the in and out parameters of the delegate (none in your case):
public void CanBeCalledAnythingButMustMatchTheDelegateSignature()
{
if (window is MainWindowView) // Set whatever conditions you want here
{
window.myJumpTarget.BringIntoView();
}
}
Now we can set this method as one (of many) handlers for this delegate in a Loaded event handler in the view code behind:
private void Window_Loaded(object sender, RoutedEventArgs e)
{
// Assumes your DataContext is correctly set to an instance of YourViewModel
YourViewModel viewModel = (YourViewModel)DataContext;
viewModel.DelegateProperty += CanBeCalledAnythingButMustMatchTheDelegateSignature;
}
Finally, let's call our delegate from the view model... this is equivalent to raising the event:
if (DelegateProperty != null) DelegateProperty(dataInstanceOfTypeYourDataType);
Note the important check for null. If the DelegateProperty is not null, then all of the attached handler methods will be called one by one. So that's it! If you want more or less parameters, just add or remove them from the delegate declaration and the handling method... simple.
So this is an MVVM way to call methods on a UI control from a view model. However, in your case it could well be argued that implementing this method would be overkill, because you could just put the BringIntoView code into a basic Click handler attached to your Button. I have supplied this answer more as a resource for future users searching for a way to actually call a UI method from a view model, but if you also chose to use it, then great!
Sometimes, i need to do some calls from View to VM. I know, that it is not MVVM style, but still. Should i always re-check DataContext to be my VM (in case it can be changed by re-activation from tombstoning or something like that), or it is enough to store in once?
var vm = DataContext as MyViewModel;
if (vm == null) return;
vm.DoSomething();
DataContext is set via Mvvm Light Locator
DataContext="{Binding MyViewModel, Mode=OneWay, Source={StaticResource ViewModelLocator}}"
It always is safe, when it's limited to a page. Whether in constructor, Loaded event handler or OnNavigatedTo, you can create objects and never check them again - either the page was kept in memory with all the objects, or the constructor, Loaded and OnNavigatedTo were called again upon re-activating from tombstoning. Problems with tombstoning occur mostly in cases of one page relying on the fact, that other page created something.
I use that sometimes, like that:
private MyViewModel viewModel;
When is the page loaded, then add value to that field
viewModel = (MyViewModel)DataContext;
Than I can use it anytime in my view after it.
I know it is not MVVM, but still ;)
I'm new to both C# and WPF. I've written a simple program. I have a class called Counter, that exposes a read-only property Count that starts out at 0, and a public method Increment that simply increments the count by one. Counter implements INotifyPropertyChanged.
I have a Window class (code is below). I pass an instance of a Counter object to the constructor and perform a binding. The window has a button and a label. The label is bound to the counter's Count property, and the button calls Increment.
This all works.
However, most examples I've seen around the net and MSDN mostly deal with defining the binding in XAML. How can I modify my example here to move the binding operation out of code behind and into the markup? The Binding property in the Properties window of VS2010 doesn't seem to know how to do what I want. Perhaps it's not possible?
One additional question: I don't think this example fits MVVM... My Counter class stands alone, is not tied to a view anywhere except through its property. However, the CounterWindow class is holding a reference to it. Is this the proper location for this reference? I also though that perhaps I should be creating the window, then setting a property (e.g. CounterObject) that I would use instead of passing through via the constructor.
public partial class CounterWindow : Window {
Counter ctr;
public CounterWindow(Counter ctr) {
InitializeComponent();
this.ctr = ctr;
Binding b = new Binding("Count");
b.Source = ctr;
CounterLabel.SetBinding(Label.ContentProperty, b);
}
private void IncrementButton_Click(object sender, RoutedEventArgs e) {
ctr.Increment();
}
}
Something like this:
public CounterWindow(Counter ctr)
{
InitializeComponent();
DataContext = ctr;
}
Markup:
<Label Content="{Binding Count}" />
UPD.
There's two common approaches in MVVM: view-first and model-first.
View first means that you initially create the view, and then view creates view model, which it is bound to.
Model-first means that first you create the view model, then view model creates its view and passes itself (via constructor or via DataContext property setter) as data context of the view.
Hope this helps you.