I have two regions. A navigation region and a main region.
My navigation region contains two buttons which call the RequestNavigate method.
The first button loads a view without any parameters
this.tRegionManager.RequestNavigate(RegionNames.MainRegion, ViewNames.VInfoMainViewUri);
The second button should load the same view with some parameters
this.tRegionManager.RequestNavigate(RegionNames.MainRegion, new Uri(ViewNames.VInfoMainViewUri.OriginalString + "" + query.ToString(), UriKind.Relative));
This works fine if no view is loaded. If any view is loaded, a click on any button causes nothing.
I tried to remove every active view from my region, but this causes an error
IViewsCollection col = tRegionManager.Regions[args.RegionName].Views;
foreach (var obj in col)
{
tRegionManager.Regions[args.RegionName].Remove(obj);
}
The region does not contain the specified view.
Parameter name: view
How can I fix this probem?
If you want to create a new view even when there is already an existing view of the same type in the region, you need to implement the INavigationAware interface either in your View or your ViewModel (Prism will check first the view, and if it doesn't implement INavigationAware it will also check the ViewModel).
You are interested specifically in the IsNavigationTarget method, which tells Prism if the current instance of the View should be reused, or if another instance should be created to satisfy the navigation request. So, to always create a new View you would do:
public class MyViewModel : INavigationAware {
bool INavigationAware.IsNavigationTarget(NavigationContext navigationContext)
{
return false;
}
void INavigationAware.OnNavigatedFrom(NavigationContext navigationContext)
{
}
void INavigationAware.OnNavigatedTo(NavigationContext navigationContext)
{
}
}
All of this is explained in greater detail in Chapter 8 of the Prism 4 documentation; they also have an illustration of how it works.
Related
I have an application where I am using usercontrols as "pages" of the application. I have a currentpage binding in ApplicationViewModel on my MainWindow, and I navigate between pages by changing the binding of currentpage with commands attached to a side menu control. I am using the MVVM pattern and all of my ViewModels derive from a BaseViewModel class.
The navigation works, but when I input text into a text box, then navigate away and then back, the user-input text is reset to it's default binding.
I've already tried updating the source trigger and setting the mode to TwoWay. My "page" has a viewmodel which it is bound to, and otherwise works.
On my page, in the parent grid all controls are in:
DataContext="{x:Static core:MyPageViewModel.Instance}">
And the control:
<TextBox Text="{Binding TextBoxTest, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
And in my viewmodel:
public static MyPageViewModel Instance => new MyPageViewModel();
public string TextBoxTest { get; set; } = "Change Me!";
I would like the value I enter to remain when I navigate away, and then return, to the page. I assume it's because when I navigate away from my usercontrol I'm unloading it, and when I navigate back I'm getting a new instance of the viewmodel. I just don't know how to keep a single one that remains in memory.
You should post more code, it's not clear from the pieces.
I can try to guess anyway the problem is here:
public static MyPageViewModel Instance => new MyPageViewModel();
This generates a new ViewModel every time it is accessed by your view, because it is the equivalent of writing:
public static MyPageViewModel Instance { get { return new MyPageViewModel(); } }
instead, you should write something like
public static MyPageViewModel Instance { get; } = new MyPageViewModel();
This way, the first time it is accessed, it returns the default value (new MyPageViewModel()), and now that static variable will always point to the same view model, instead of creating a new one.
Guido C. was exactly correct. I changed my viewmodel instance from the way I had it in my question to this:
public static MyPageViewModel Instance { get; } = new MyPageViewModel();
And it worked.
I have problem passing object to secondary view in MVVM light WPF. I have main view Model. follow of operation. I am able to wire things up using MVVM light and Modren UI navigation Services. The issue is that i am not able to send object of Main Customer view model to secondary View Model. I want to set data-context of target View from source View Model. I have tried this but does not seem to be working. I prefer no code behind and i have spent a lot of time without any success.
public virtual void NavigateTo(string pageKey, object parameter)
{
lock (_pagesByKey)
{
if (!_pagesByKey.ContainsKey(pageKey))
{
throw new ArgumentException(string.Format("No such page: {0}. Did you forget to call NavigationService.Configure?", pageKey), "pageKey");
}
var frame = GetDescendantFromName(Application.Current.MainWindow, "ContentFrame") as ModernFrame;
// Set the frame source, which initiates navigation
if (frame != null)
{
frame.Source = _pagesByKey[pageKey];
//i Dont know if this should work or not
frame.DataContext = parameter;
}
Parameter = parameter;
_historic.Add(pageKey);
CurrentPageKey = pageKey;
}
}
any help will be greatly appreciated. I just need to how i can set the datacontext of target View without using code behind. Thanks
There's multiple possibilities but one that does not create dependencies between your viewmodels is to use pub/sub system in MVVMLight. Basically it goes like this:
When you select some entity from your view and transition to another, viewmodel sends a message that carriers that given entity along. In the other viewmodel you receive the message and set some property accordingly (for editing, adding new entity, etc.)
// mainviewmodel
Messenger.Default.Send(new MyMessage(myObj));
// otherviewmodel
Messenger.Default.Register<MyMessage>(this, message =>
{
/* do something with message.MyObj */
});
// mymessage
public class MyMessage : MessageBase
{
...
public MyObj MyObj { get; set; }
}
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 am working on a Windows Phone 7 application. Now I need to switch the view after a user tapped the designated button which takes user to another view.
Which component, theoretically, in MVVM should be in charge of the navigation, i.e. switching views? Code snippets would be good to show demonstration.
I have tried inserting the switching code in View and it works alright, but I encountered a situation where I call an asynchronous web service and would like to navigate user to the new view only after the operation is done, the navigation code should be inside the event handler.
Thank you.
P/S: My project's deadline is coming soon, I have no time to rebuild my project using MVVM tools, such as MVVM Light, Caliburn Micro, and etc.
I put a Navigate methods in the base class that all my ViewModel's share:
protected void Navigate(string address)
{
if (string.IsNullOrEmpty(address))
return;
Uri uri = new Uri(address, UriKind.Relative);
Debug.Assert(App.Current.RootVisual is PhoneApplicationFrame);
BeginInvoke(() =>
((PhoneApplicationFrame)App.Current.RootVisual).Navigate(uri));
}
protected void Navigate(string page, AppViewModel vm)
{
// this little bit adds the viewmodel to a static dictionary
// and then a reference to the key to the new page so that pages can
// be bound to arbitrary viewmodels based on runtime logic
string key = vm.GetHashCode().ToString();
ViewModelLocator.ViewModels[key] = vm;
Navigate(string.Format("{0}?vm={1}", page, key));
}
protected void GoBack()
{
var frame = (PhoneApplicationFrame)App.Current.RootVisual;
if (frame.CanGoBack)
frame.GoBack();
}
So the ViewModel base class executes the navigation if that's what you are asking. And then typically some derived ViewModel class controls the target of the navigation in response to the execution of an ICommand bound to a button or hyperlink in the View.
protected SelectableItemViewModel(T item)
{
Item = item;
SelectItemCommand = new RelayCommand(SelectItem);
}
public T Item { get; private set; }
public RelayCommand SelectItemCommand { get; private set; }
protected override void SelectItem()
{
base.SelectItem();
Navigate(Item.DetailPageName, Item);
}
So the View only knows when a navigate action is needed and the ViewModels know where to go (based on ViewModel and Model state) and how to get there.
The view should have a limited number of possible destinations. If you have to have a top-level navigation on every page, that should be part of your layout or you can put them in a child view.
I put navigation outside of MVVM in a class that is responsible for showing/hiding views.
The ViewModels use a messagebroker with weakevents to publish messages to this class.
This setup gives me most freedom and doesn't put any responsibilities in the MVVM classes that do not belong there.
Say I am implementing a view for Food. (ASP.NET MVC2)
Then depending on the type (say fruit or vegetable for example) I will change the view.
Can I do this without creating a seperate view for fruit and vegetable?
I.e. Say we have url structure like http://localhost:xxxx/Food/{foodID}
and don't want
http://localhost:xxxx/Veg/{foodID}
http://localhost:xxxx/Fruit/{foodID}
So I want to be able to change the view depending on the type. I'm using a tabstrip control from telerik, to give you an idea of the difference in the views - it'd just be say - not displaying one particular tab for Veg, and displaying it if fruit, for example.
Can a view accept two different view models ? so when we hit http://localhost:xxxx/Food/{foodID} the code determines what type the object is (Fruit or Vegetable) then sends either FruitViewModel or VegetableViewModel ? If I just send one viewmodel how can I control the logic to display or not display certain things in the view?
If you define FoodViewModel as a base class and have FruitViewModel and VegetableViewModel extend it, you could have ViewPage<FoodViewModel> and pass in either. Then, your view can check for which specific subclass it got and render the appropriate output.
Alternatively, if the only difference between FruitViewModel and VegetableViewModel is that one is Fruit and one is Vegetable (but all other properties are shared, i.e., Name, Calories, Color, Cost), have a FoodType property on the shared FoodViewModel and use it to conditionally render the appropriate output.
What alternative is the best depends on how much the Fruit and Veg Views differ:
Alternative 1:
You can create two different Views (you can pass the view name to the View method):
public ViewResult Food(int id)
{
var food = ...
if (/* determine if food is Veg or Fruit */)
return View("Fruit", new FruitViewModel { ... });
else
return View("Veg", new VegViewModel { ... });
}
By returning a different View the url doesn't change (as it does when using return RedirectToAction("Action", "Controller", ...) which implies a HTTP redirect.
Alternative 2:
You can have a common FoodViewModel extended by both the FruitViewModel and the VegViewModel. Then your FoodView can be of type FoodViewModel and decide what to show in your View code.
If the only thing you need to change is the tabstrip setting. You can provide a property called "ShowTabItem" on your ViewModel, then bind that property to your TabStripItem in your view.
public class FoodViewModel
{
Food _food;
public FoodViewModel(Food food)
{
}
public bool ShowTabItem
{
get
{
return _food.Type == FoodType.Vegetable;
}
}
}
Bind your ShowTabItem property to the Visibility or Enabled property of the tabstrip item. (whatever fits)
then your controller will simply be
public ActionResult Food(int id)
{
Food food = getFood(id);
return View(new FoodViewModel(food));
}
Let your ViewModel decide what needs to be displayed.
Hope this helps!