I'm newbee in mvvm (and mvvlight of course). I have 3 modelviews (a MainWindow which have a container, and another 2 modelviews (Login and Menu)). In the LoginModelView, when the user login is successfully, this call the MenuViewModel (With Messenger.Default) changing the page in the MainWindow container. All is alright until that, then i call a Message.Default.Send sending a object from LoginModelView to MenuModelView which is correctly listened, catching the object associed and executing the method associated (ConfiguraMenu) wich define a RelayCommand (checked line by line and the method is executed without any exception) but the problem is this RelayCommand is not working until i back to the LoginViewModel and i login again. I try CommandManager.InvalidateRequerySuggested() and is not working either.
This is the code for the LoginViewModel:
//This method is called when the user press the login button. No problem with this
public void ActionVerificaUsuario()
{
Miusuario = db.getUsuario(Txtusuario, Txtpassword);
if (Miusuario.esUsuario())
{
Messenger.Default.Send(new MoveToViewMessage(Page.MenuView));
Messenger.Default.Send((UsuarioModel)Miusuario);
}
}
This code is for the MenuViewModel:
public RelayCommand AbreExeClaseCommand { get; private set; }
public MenuViewModel()
{
Messenger.Default.Register<UsuarioModel>(this, usuario_recibido => {Miusuario = usuario_recibido;ConfiguraMenu(); });
}
private void ConfiguraMenu() {
Mimenu = new MenuModel(Miusuario);
AbreExeClaseCommand = new RelayCommand(() => { Messenger.Default.Send(new MoveToViewMessage(Page.NeverReachedView)); }, () => Mimenu.Sw_reportes);
CommandManager.InvalidateRequerySuggested();
AbreExeClaseCommand.RaiseCanExecuteChanged();
}
I tried to hardcode the CanExecute with true but the Execute is still without work until back and login again.
I hope you can help me (i'm scratching my head for various days with none result).
MvvmLight provides two different RelayCommand classes in two different namespaces:
Galasoft.MvvmLight.Command
Galasoft.MvvmLight.CommandWpf
Make sure, that you are using the correct namespace Galasoft.MvvmLight.CommandWpf in your WPF application.
There was a bug in MVVMLight, which resulted in not working CanExecute() behavior. They fixed it with the new .CommandWpf namespace in MVVMLight Version V5.0.2.
You can also check out this GalaSoft blog post and the change log for further information.
You try to bind the CanExecute to a propertie.
So my guess is you didn't use RaisePropertie Changed in this propertie.
You must have something like:
public class MenuModel : ViewModelBase
{
// Other pieces of code....
private bool _sw_reportes;
public bool Sw_reportes
{
get { return _sw_reportes; }
set { _sw_reportes = value;
RaisePropertyChanged(() => Sw_reportes); }
}
}
Related
I'm binding the title of my Xamarin.Forms.ContentPage to a property BuggyTitle in my view model (VM). The VM derives from MvxViewModel. Here's the simplified version:
BuggyPage.xaml:
<?xml version="1.0" encoding="UTF-8"?>
<local:ContentPage Title="{Binding BuggyTitle}"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyProject.BuggyPage"
xmlns:local="clr-namespace:Xamarin.Forms;assembly=MyProject">
<ContentPage.Content NavigationPage.HasNavigationBar="false">
<Grid>
<ScrollView>
<!--and so on-->
</ContentPage.Content>
</local:ContentPage>
BuggyViewModel.cs:
namespace MyProject
{
[ImplementPropertyChanged]
public class BuggyViewModel : MvxViewModel
{
private Random _random;
public string BuggyTitle {get; set;}
public BuggyViewModel()
{
_random = new Random();
}
public override void Start()
{
base.Start();
BuggyTitle = "" + _random.Next(1000);
RaisePropertyChanged("BuggyTitle"); // this seems to make no difference
}
}
}
There's not much going on in the code behind other than a call to InitializeComponent() in the constructor.
The page is mapped to the VM generically in my project (not actually 'my' project, it's existing design), and it boils down to these (again, simplified) lines of code:
public static Page CreatePage(MvxViewModelRequest request)
{
var viewModelName = request.ViewModelType.Name;
var pageName = viewModelName.Replace ("ViewModel", "Page");
var pageType = (typeof (MvxPagePresentationHelpers)).GetTypeInfo ().Assembly.CreatableTypes().FirstOrDefault(t => t.Name == pageName);
var viewModelLoader = Mvx.Resolve<IMvxViewModelLoader>();
var viewModel = viewModelLoader.LoadViewModel(request, null);
var page = Activator.CreateInstance(pageType) as Page;
page.BindingContext = viewModel;
return page;
}
The problem:
When BuggyPage loads, I initially get the correct value for the title. Whenever it is displayed after that, even though I can see in the debugger that BuggyTitle is getting updated correctly, the change does not appear in the page.
Question:
Why don't updates to BuggyTitle get reflected in the page?
Edit 1:
To further describe the weirdness, I added a Label to my ContentPage, with x:Name="BuggyLabel" and Text="{Binding BuggyLabelText}".
In my code-behind, I added this:
var binding_context = (BindingContext as BuggyViewModel);
if (binding_context != null)
{
BuggyLabel.Text = binding_context.BuggyLabelText;
}
I set a breakpoint at BuggyLabel.Text =. It gets hit every time the page loads, and BuggyLabel.Text already seems to have the correct value (i.e, whatever binding_context.BuggyLabelText is set to). However, the actual page displayed only ever shows what the text in this label is initially set to.
And yes, have clean/built about a million times.
Edit 2 (further weirdness):
I put this in the code-behind so that it runs during page load:
var binding_context = (BindingContext as BuggyViewModel);
if (binding_context != null)
{
Device.BeginInvokeOnMainThread(() =>
{
binding_context.RefreshTitleCommand.Execute(null);
});
}
This again changes values in the debugger, but these changes don't get reflected in the displayed page.
I then added a button to the page and bound it to RefreshTitleCommand, and wham! the page updates its display.
Unfortunately I can't use this. Not only is it incredibly hackish, I can't have the user pressing buttons to have the page display what it's meant to on load.
I wonder if there's some caching going on with MvvmCross or Xamarin.
Answer
You need to add RaisePropertyChanged in BuggyTitle property declaration.
ViewModel
namespace MyProject
{
[ImplementPropertyChanged]
public class BuggyViewModel : MvxViewModel
{
private Random _random;
string _BuggyTitle { get; set; }
public string BuggyTitle
{
get { return _BuggyTitle; }
set { _BuggyTitle = value; RaisePropertyChanged(() => BuggyTitle); }
}
public BuggyViewModel()
{
_random = new Random();
}
public override void Start()
{
base.Start();
BuggyTitle = "" + _random.Next(1000);
}
}
}
-----New Update------
Code behind code
var binding_context = (BindingContext as BuggyViewModel);
if (binding_context != null)
{
Device.BeginInvokeOnMainThread(() =>
{
BuggyLabel.Text = binding_context.BuggyLabelText;
});
}
I don't have any experience at all with Xamarin (but i do want to try it out in the future when i get as comfortable as possible with UWP), but i guess the Data Binding process should be working similar to what i am used to there ...
You are mentioning that you have no problem with the values that are set when the page first loads, however when you actually update the values there's no "linking" to the visual layer, despite at debug time you actually seeing the value being set to something completely different from it's initial state.
Since you are dealing with properties-only viewmodel (Collections for instance in UWP are another level of events which need to be exposed), RaisePropertyChanged seems like the correct choice.
What i cannot understand is if when you first create your page, the Binding which you are creating is at least specified as One-Way mode, so changes in your viewmodel properties are propagated onto your UI when their set accessor methods are called.
You are setting your page context to viewmodel (each i figure is the same as DataContext in UWP/WPF), and therefore you can actually access those properties with the {Binding } markup. But what is the default mode for this operation in Xamarin ? (in UWP it is actually OneWay, and therefore it would work right of the bat for this situation ...).
I have seen that in Xamarin it might be a bit different , since you also have the Default option. Can that be it?
PS. Hopefully this might be useful to you, despite my lack of experience with Xamarin.
Edit2
Implementing the INotifyPropertyChanged,
public class BuggyViewModel : MvxViewModel, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private Random _random;
string _BuggyTitle { get; set; }
public string BuggyTitle
{
get { return _BuggyTitle; }
set { _BuggyTitle = value; RaisePropertyChanged(() =>
BuggyTitle); }
}
public BuggyViewModel()
{
_random = new Random();
}
public override void Start()
{
base.Start();
BuggyTitle = "" + _random.Next(1000);
}
protected void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
I was setting a controls binding context from the property changed event of one of its properties.
This made my control stop tracking changes despite everything else binding correctly still, and the control would also correctly bind initially (first time is fine, further changes do not fire the property changed event again).
page.BindingContext = viewModel;
This might seems a silly question, but I'm in a learning curve so asking this.
In fact, I'm trying to find an alternative to a previous unresolved question:
WPF: How to make Calls to Dispatcher.Invoke() Synchronous?
In MVVM Application, we define ICommand for a Button's Command Binding, which may call another method, load another ViewModel or execute some instructions etc.
Update with code:
Here is my ICommand, binding to button, which will load the ViewModel to show EndView:
public ICommand EndCommand => new RelayCommand(p =>
{
WixBootstrapperData.CurrentViewModel = new EndViewModel(WixBootstrapperData);
});
But when I tried to load same ViewModel from another method, it did the loading, but never showed the EndView, and skipped to other instructions till the end of method, which is in fact end of application itself. Here is the snippet:
BootstrapperApplication.ApplyComplete += (sender, e) =>
{
WixBootstrapperData.CurrentDispatcher.Invoke((Action)(() =>
{
if (e.Restart == ApplyRestart.RestartRequired)
{
//This would be loaded, but never showed the related View and skipped to next instruction
WixBootstrapperData.CurrentViewModel = new EndViewModel(WixBootstrapperData);
}
//However, This would be loaded and related View would also be displayed
WixBootstrapperData.CurrentViewModel = new FinishViewModel(WixBootstrapperData);
}
));
}
Can we call same ICommand from another Method to get same behavior? Or some alternative way?
Does defining an event and subscribing to that would give same behavior from within a method execution?
You can execute a command from within another method.
First, it is advised to check if the command can be executed, by using .CanExecute() - which returns a bool
If you can indeed execute the commnad, then you can call .Execute() on that command.
Example:
Let's say your ICommand is a RelayCommand, and called MyCommand.
Let's say you want to call it from SomeOtherMethod():
public RelayCommand MyCommand { get; set; }
public void SomeOtherMethod()
{
if (MyCommand.CanExecute())
{
MyCommand.Execute();
}
}
The same methods are available if you're using a DelegateCommand, as well - I use these using Prism.
Hope this helps! :)
I'm fairly new to WPF and MVVM with Prism and I'm having an issue with Prism navigation. Each time I navigate to a particular view, I want to load a new view; however, if I enter some data or fully process some data, navigate away and come back, the existing data is always kept in the view.
I have read similar issues here on SO such as this and this. Both point to using INavigationAware or IRegionMemberLifetime. I have tried to implement both in my ViewModel; however, none seem to solve the issue for me.
If I use INavigationAware and set IsNavigationTarget to false, the view never loads. If I use IRegionMemberLifetime and set KeepAlive to false, it still retains my data.
I could post code; however, it's simple and looks like the same that's in the linked issues. Has anyone had similar issues with Prism navigation?
Any help is appreciated.
Edit - Adding sample code
In an effort to get rid of any red herrings with other code I've got, I created a new Prism app with the bare necessities. My issue with the view not displaying when navigating back has cleared up; however, the view is still retaining the entered values.
Here is some sample code:
Model:
public class SomeObject
{
public string ObjectName { get; set; }
}
I created a basic view with just two TextBox controls, 1 bound and 1 not:
<StackPanel>
<TextBlock Text="ModuleA" />
<TextBox Text="{Binding DisplayedSomeObject.ObjectName, UpdateSourceTrigger=PropertyChanged}" />
<TextBox />
</StackPanel>
ViewModel:
[Export]
[PartCreationPolicy(CreationPolicy.NonShared)]
[RegionMemberLifetime(KeepAlive=false)]
public class ModuleAViewModel : BindableBase, IConfirmNavigationRequest, INavigationAware
{
private SomeObject displayedSomeObject = new SomeObject();
public SomeObject DisplayedSomeObject
{
get { return displayedSomeObject; }
set
{
displayedSomeObject = value;
}
}
[ImportingConstructor]
public ModuleAViewModel()
{
}
void IConfirmNavigationRequest.ConfirmNavigationRequest(NavigationContext navigationContext, Action<bool> continuationCallback)
{
continuationCallback(true);
}
bool INavigationAware.IsNavigationTarget(NavigationContext navigationContext)
{
return false;
}
void INavigationAware.OnNavigatedFrom(NavigationContext navigationContext)
{
}
void INavigationAware.OnNavigatedTo(NavigationContext navigationContext)
{
var newSomeObject = new SomeObject();
this.DisplayedSomeObject = newSomeObject;
}
}
When I run this, both the bound and un-bound controls retain their values when navigated back to.
Have you done some troubleshooting on Navigation? Set a breakpoint to when the page is navigated to and see how all the data is coming back in. I thought I had the same problem before and I was foolishly loading a object into my VM that was never getting destroyed.
Because of this, it appeared the VM was being kept alive, but in reality it was not. It would load like normal every time, but it would be pulling from the object that wasn't reloading.
So, set a breakpoint on the navigateTo and step through to see if the data is getting reloaded or not.
EDIT:
Looking at the code above, I believe you need to add IRegionMemberLifetime as well. I am currently using Prism 4.5, so I don't know if this has changed in 5, but I have to add that for it to actually destroy it.
public class ModuleAViewModel : BindableBase, IConfirmNavigationRequest, INavigationAware, IRegionMemberLifetime
{
...
bool IRegionMemberLifetime.KeepAlive
{
get { return false; }
}
}
I'm new on Caliburn Micro and want some advice on which path to take to devolop my app interface and navigation between views.
My idea is to have a MainWindow which will contain a menu of buttons, each one related with a specific view. Each view will be stored in a separated WPF UserControl. The mainWindow will also contain a TabControl bound to an ObservableCollection of tabs on viewmodel. Everytime a button on menu is clicked, I want to add a new tab with a ContentPresenter inside that will dynamically load a view and its corresponding viewmodel.
So my questions:
1) Should I use a Screen Collection here?
2) Should the UserControl implement Screen interface?
3) How do I tell MainWindow ViewModel which view to load on the new added tab maintaining viewmodels decoupled?
Thanks to everyone in advance.
UPDATE
After a lot of reading and some help of the community I managed to resolve this. This is the resultant AppViewModel:
class AppViewModel : Conductor<IScreen>.Collection.OneActive
{
public void OpenTab(Type TipoVista)
{
bool bFound = false;
Screen myScreen = (Screen)Activator.CreateInstance(TipoVista as Type);
myScreen.DisplayName = myScreen.ToString();
foreach(Screen miItem in Items)
{
if (miItem.ToString() == myScreen.ToString())
{
bFound = true;
ActivateItem(miItem);
}
}
if (!bFound) ActivateItem(myScreen);
}
public ObservableCollection<MenuItem> myMenu { get; set; }
public ObservableCollection<LinksItem> myDirectLinks { get; set; }
public ICommand OpenTabCommand
{
get
{
return new RelayCommand(param => this.OpenTab((Type) param), null);
}
}
public AppViewModel()
{
OpenTab(typeof(ClientsViewModel));
MenuModel menu = new MenuModel();
myMenu = menu.getMenu();
myDirectLinks = menu.getLinks();
}
public void CloseTab(Screen param)
{
DeactivateItem(param, true);
}
}
I have to keep the ICommand from OpenTabCommand because the name convention of Caliburn.micro doesn't seems to work inside DataTemplate. Hope it could help someone else. Thanks to all
I've done something very similar using Caliburn.Micro, and based it on the SimpleMDI example included with the examples, with a few tweaks to fit my needs.
Much like in the example, I had a main ShellViewModel:
public class ShellViewModel : Conductor<IScreen>.Collection.OneActive
{
}
with a corresponding ShellView containing a TabControl - <TabControl x:Name="Items">, binding it to the Items property of the the Conductor.
In this particular case, I also had a ContextMenu on my ShellView, bound (using the Caliburn.Micro conventions), to a series of commands which instantiated and Activated various other ViewModels (usually with a corresponding UserControl, using the ActivateItem method on the Conductor.
public class YourViewModel: Conductor<IScreen>.Collection.OneActive
{
// ...
public void OpenItemBrowser()
{
// Create your new ViewModel instance here, or obtain existing instance.
// ActivateItem(instance)
}
}
In that case, I didn't require the ViewModels to be created with any particular dependency, or from any other locations in the program.
At other times, when I've needed to trigger ViewModel from elsewhere in the application, I've used the Caliburn.Micro EventAggregator to publish custom events (e.g. OpenNewBrowser), which can be handled by classes implementing the corresponding interface (e.g. IHandle<OpenNewBrowser>), so your main ViewModel could have a simple Handle method responsible for opening the required View:
public class YourViewModel: Conductor<IScreen>.Collection.OneActive, IHandle<OpenNewBrowser>
{
// ...
public void Handle(OpenNewBrowser myEvent)
{
// Create your new ViewModel instance here, or obtain existing instance.
// ActivateItem(instance)
}
}
This section of the documentation will probably be useful, especially the Simple MDI section.
Additional code I mentioned in the comments:
I sometimes use a generic method along these lines ensure that if I have an existing instance of a screen of a particular type, switch to it, or create a new instance if not.
public void ActivateOrOpen<T>() where T : Screen
{
var currentItem = this.Items.FirstOrDefault(x => x.GetType() == typeof(T));
if (currentItem != null)
{
ActivateItem(currentItem);
}
else
{
ActivateItem(Activator.CreateInstance<T>());
}
}
Used like:
public void OpenBrowser()
{
this.ActivateOrOpen<BrowserViewModel>();
}
I was recently bit by a weird thing with lambda expression and variable captures. The code was a WPF/MVVM application using .NET 4.5 (VS2012). I was using different constructors of my viewmodel to setup the callback for a RelayCommand (this command would then be bound to a menu item in my view)
In essence, I had the following code:
public class MyViewModel : ViewModelBase
{
public MyViewModel(Action menuCallback)
{
MyCommand = new RelayCommand(menuCallback);
}
public MyViewModel(Func<ViewModelBase> viewModelCreator)
// I also tried calling the other constructor, but the result was the same
// : this(() => SetMainContent(viewModelCreator())
{
Action action = () => SetMainContent(viewModelCreator());
MyCommand = new RelayCommand(action);
}
public ICommand MyCommand { get; private set; }
}
and then created instances of the above using:
// From some other viewmodel's code:
new MyViewModel(() => new SomeViewModel());
new MyViewModel(() => new SomeOtherViewModel());
These were then bound to a WPF Menu - each menu item had a MyViewModel instance as its data context . The weird thing was that the menus only worked once. Regardless of which of the items I tried, it would call the appropriate Func<ViewModelBase> - but only one time. If I tried to select another menu item or even the same item again, it simply didn't work. Nothing got called and no output in the VS debug output about any errors.
I'm aware of issues with variable captures in loops, so I made a guess that this issue was related so changed my VM to:
public class MyViewModel : ViewModelBase
{
public MyViewModel(Action buttonCallback)
{
MyCommand = new RelayCommand(buttonCallback);
}
private Func<ViewModelBase> _creator;
public MyViewModel(Func<ViewModelBase> viewModelCreator)
{
// Store the Func<> to a field and use that in the Action lambda
_creator = viewModelCreator;
var action = () => SetMainContent(_creator());
MyCommand = new RelayCommand(action);
}
public ICommand MyCommand { get; private set; }
}
and called it the same way. Now everything works as it should.
Just for fun, I also worked around the whole Func<ViewModelBase> constructor by creating the appropriate Action outside of the MyViewModel constructor:
// This code also works, even without the _creator field in MyViewModel
new MyViewModel(() => SetMainContent(new SomeViewModel()));
new MyViewModel(() => SetMainContent(new SomeOtherViewModel()));
So I managed to get it working, but I'm still curious why it works like this. Why doesn't the compiler properly capture the Func<ViewModelBase> in the constructor?
I'm guessing the following code would also work
public MyViewModel(Func<ViewModelBase> viewModelCreator)
{
var action = () => { creator = viewModelCreator; SetMainContent(creator()); };
MyCommand = new RelayCommand(action);
}
If so, then the reason it isn't working the first way is that you aren't actually closing around the viewModelCreator variable, you're closing around the result of calling it.
I'm still playing around with the code in LINQPad, but it doesn't actually seem like I'm getting the same issue that you are. Perhaps it's something specific to RelayCommand. Could you post more of your code?