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.
Related
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.
I have just updated a project from Catel 3.4 to Catel 4.0 and a custom apply button that had been working now never gets enabled.
AddCustomButton(new DataWindowButton("Apply", ExecuteApply, canExecuteApply));
In Catel 3.4 the canExecuteApply got called when the window got focus or any control was changed. In 4.0 it gets called twice when the window is created and never again.
I suspect this has something to do with the IViewPropertySelector part of the update guide, however registering the default implementation had no effect and I can't figure out what namespace the AutoDetectViewPropertiesToSubscribe extension method is in.
Edit: I have found I am getting the same behavior with some AsynchronousCommand instances elsewhere in the application. The CanExecute delegate fires when the control is created then never again.
Edit 2: These were the same issue with diffrent solutions. For an explanation of the issue see Geert van Horrik's answer.
If the command is registered in a view model you can use
ViewModelCommandManager.InvalidateCommands(true);
to get the can execute state to re-evaluate. For a DataWindowButton as described above I had to manually call RaiseCanExecuteChanged on the button's command since that command does not belong to a vie model as far as i can tell.
var catelCommand = (applyButton.Command as ICatelCommand);
if (catelCommand != null)
{
catelCommand.RaiseCanExecuteChanged();
}
In either case, this is far from the approach with the best performance characteristics, but if the same behavior you had before the upgrade is desired, you can make these calls in the following event subscription:
System.Windows.Input.CommandManager.RequerySuggested += RequerySuggested;
Hope this helps anyone else facing this issue.
The reason is that in the past (pre 4.0), Catel subscribed to the CommandManager of WPF and invalidated all the commands on all view models on nearly everything (mouse move, focus, etc). To improve performance (a lot), we decided to only invalidate commands automatically when a property changes on a specific view model.
For example, if you have a vm where you change a property, it will automatically re-evaluate the commands on that vm. You can still manually re-evaluate commands using this code (inside a vm):
ViewModelCommandManager.InvalidateCommands(true);
How about this? I had a problem where my nested user controls must cause my outer user control's commands to update. It is not elegant but it gets the job done for me, until I can get a better way.
public partial class App : Application
{
private static IViewModelManager _ViewModelManager;
public App()
: base()
{
var dependencyResolver = this.GetDependencyResolver();
_ViewModelManager = dependencyResolver.Resolve<IViewModelManager>();
System.Windows.Input.CommandManager.RequerySuggested += RequerySuggested;
}
private void RequerySuggested(object sender, EventArgs e)
{
foreach (IViewModel viewModel in _ViewModelManager.ActiveViewModels)
{
(viewModel as ViewModelBase).GetViewModelCommandManager().InvalidateCommands(true);
}
}
}
(I'm using c#/Xamarin, but doubt the issue is specific to that.)
When I add a View Controller and then remove it - it's DidReceiveMemoryWarning is still called (in the Simulator and on real devices), so it can't have been released. I've narrowed it down to this:-
UIViewController vc=(UIViewController)this.Storyboard.InstantiateViewController(identifier);
this.AddChildViewController(vc);
vc.RemoveFromParentViewController();
vc=null;
and calling DidMoveToParentViewController and WillMoveToParentViewController (as described in the docs) doesn't help either:
UIViewController vc=(UIViewController)this.Storyboard.InstantiateViewController(identifier);
this.AddChildViewController(vc);
vc.DidMoveToParentViewController(this);
vc.WillMoveToParentViewController(null);
vc.RemoveFromParentViewController();
vc=null;
then simulate a memory warning and vc DidReceiveMemoryWarning gets called even though there is no reference to it. How is this possible when it's been removed as child controller and there is no reference to it.
(The same is happening when I use a segue set up in a Storyboard to go to a detail view in a UINavigationController, for example, after going "back" to the root controller, the detail controller still gets DidReceiveMemoryWarning messages.
Any help to understand this would be appreciated
UPDATE:
Now the problem I have is with a simple UIViewController embedded in a UINavigationController
I add the navigation controller:
this.nc=(UINavigationController)this.Storyboard.InstantiateViewController("NavigationController");
this.AddChildViewController(this.nc);
this.nc.DidMoveToParentViewController(this);
and remove later (after it's loaded):
this.nc.WillMoveToParentViewController(null);
this.nc.RemoveFromParentViewController();
this.nc=null;
and this all works fine (it's not retained). BUT if I add this simple line in the ViewDidLoad method of the ViewController thats embedded, then the ViewController IS retained!
Console.WriteLine("this.NavigationController={0}",this.NavigationController);
ie, just accessing "this.NavigationController" causes the VC to be retained!
So each time I run it, I get another ViewController retained!
Any ideas?
It could be that your view controller’s initialization method has some side effect causing it to stay alive. A common example would be that it creates an NSTimer object, which retains its target. Go through the methods that are called when the view controller is instantiated from the storyboard and see if anything retains it.
I have one parent conductor. I want to show first view-model inside it. Then after first one is closed (i.e. some operation is done), I want to show a different view-model.
I'm using Caliburn.Micro.Contrib, where a ConductResult displays child VM in a Conductor. It has a cool extension method AfterClosingDo, which runs a coroutine after that child was deactivated and closed.
However, when I run another ConductResult using AfterClosingDo, basically this happens:
first child VM is closed
Deactivated event occurs, ConductResult runs AfterClosing action
in AfterClosing, I open second child VM using ConductResult in parent Conductor
second child VM is properly activated
however, the deactivation of first child VM still isn't completed, and null item is set as active in the Conductor
First VM basically shows progress of a load operation, second VM shows actual data. After the load is complete, I want to show the data in parent Conductor (using second VM, of course).
So, my question: is there a clean way to do this in Caliburn.Micro, preferably by not overriding default behavior of Conductor, Screen, etc.
I was thinking of using EventAggregator, though I'm not sure, if it's the best solution.
I had a very similar issue where I had a conductor opening a child VM, then a confirmation box would pop-up saying "do you want to close" and had fired a callback for the original items CanClose method which had the same sort of effect as you described.
The popup VM would close, but in closing it would fire a callback which was supposed to close the first VM.
My conductor ended up re-activating the original VM which was annoying. The order of events was:
Open VM 1
Try Close VM 1
CanClose guard method fired
Popup VM 2 (using same conductor) in CanClose of VM 1
Confirm button on VM 2 is clicked
Confirm button fires callback for CanClose and closes VM1
VM2 Closes
Conductor remembers that VM 1 was active before VM 2 so re-opens VM 1 after it's closed
In the end I just implemented an interface which fired after the close.
Child items which have work to do after they have closed implement the interface (IAfterClose)
Then I provided an override for DeactivateItem on the conductor:
public override void DeactivateItem(IScreen item, bool close)
{
var afterClose = item as IAfterClose;
base.DeactivateItem(item, close);
if (afterClose != null && close)
afterClose.AfterClose();
}
This made sure that the callback wasn't fired too early. Not sure if this will benefit you (since I've not used the contrib library) but it might give you some ideas.
The only downside to this was that I had to make a fix to DefaultCloseStrategy as when the callback would fire it would throw a null reference exception in there. The fix I applied seemed to cause no ill-effect but I've not really looked at why the null ref exception was thrown.
I couldn't find any other way to do this since the last event that fires seems to be the deactivation events and they are still to early.
I'm pretty new to C#/WPF, coming from Obj-c background. I'm not really sure how this works in terms of object oriented design, and how different classes see each other.
So I have a large view (MainView) that has some custom plots and a datagrid on the bottom (Datagrid is in its separate xaml and has .cs file behind it). There is a custom object that gets added to the plots that when you drag it, the datagrid gets updated (by using dataGrid.ScrollIntoView). The code for the ScrollIntoView is in the xaml.cs file of the Datagrid.
To me this makes sense since the MainView has all the components and "sees" all the objects, so when the event handler of the dragWindow gets called, then the MainView asks for the DataGrid, and calls its method to update its column position. (This is the way I understand it, please feel free to correct me if I'm wrong).
Now I want to go the other way as well. So that if I update the scrollbar horizontally, then the dragwindow in the MainView will get updated. This does not make as much sense to me. I can create an event handler in the xaml.cs of the datagrid. But it does not see the dragWindow in the MainView right? So I'm not quite sure how to start implementing this functionality. Any help is always appreciated. Thanks!
Your grid control should expose an event to notify any consumers (MainView in this case) that scrolling has taken place.
public class YourGridControl
{
public event EventHandler GridScrolled;
}
MainView can then attach a handler to this event in the designer or in the code:
gridCtrl.GridScrolled += OnGridScrolled;
private void OnGridScrolled(object sender, EventArgs e)
{
// Logic here
}