This is my first attempt at MVVM. My application's core is loosely based Josh Smith's msdn article. And I am also using the mvvm light framework.
I have a main window containing a command list area and a workspace area which shows usercontrols/views as tabitems, each usercontrol has a corresponding viewmodel. The mainWindow also has a viewmodel containing my command list, and the workspace viewmodels have a base workspace viewmodel.
My default view has a master datagrid, of MappingSets, that can have one selected item. The commands launch new tabitems with views that handle MappingSet detail based on that selected item. I have a View/ViewModel that, depending on the command used should return either a tabitem for creating a new MappingSet with no existing data, or a tabitem containing the detail of the selected item for editing, or a tabitem containing detail the selected item as the base for a new MappingSet.
Having Set the scene, what I have not managed to work out is command dependent way to pass parameters, such as the identifier of the selected MappingSet object, to instantiate my viewmodel in one of the three states mentioned above? For instance would the mvvmlight messenger be appropriate for this task?
This is a perfect scenario for the messenger/eventaggregator. However, your message chain might be a bit convoluted. From what I'm understanding, your Main window holds a list of commands (like a menu or a ribbon). Here is how I see the chain of events.
You select a MappingSet from the datagrid, this causes a MappingSetSelected message to be fired (with a payload of the selected MappingSet)
The main window listens for that message and stores the currently selected MappingSet
When the user clicks the button a "EditMappingSet" or "CreateNewMappingSet" message is fired (or if the Window is responsible for creating the new views, it creates them itself).
If there are only three options, you could have them binding to three different commands and within the commands do the passing of your self-defined variable.
private RelayCommand _openMappingSetCommand;
//Command that one of your options is bound to
public ICommand ViewMappingSetOption1
{
get
{
if (_openMappingSetCommand == null)
{
_openMappingSetCommand = new RelayCommand(param => this.DoTabRequest("your parameter");
}
return _openMappingSetCommand ;
}
}
// Method that creates your viewmodel
private void DoTabRequest(parameterType parameter)
{
WorkspaceViewModel viewModel = null;
if (viewModel == null)
{
viewModel = (WorkspaceViewModel)Activator.CreateInstance(typeof (viewModelType), parameter);
this.Workspaces.Add(viewModel);
}
this.ActiveWorkspace = viewModel;
}
Then allow for that parameter on the constructor of your viewmodel and do whatever else you need based on that.
Related
How can I get multiple instances of a usercontrol without the viewmodel being shared? Each usercontrol (and thus viewmodel) should be an instance of its own.
I have read a solution in this question: MVVMLight UserControl View Model-Create new Instance of User control for each view but I cannot get it to work!
I have a listview and a tabcontrol. When I click an item of the listview a new tab must be created with as content the usercontrol which holds information from the selected listitem. The problem is that when selecting multiple items all the items contain the information from the last selected item.
This is my viewmodellocator:
public DossierDetailViewModel DossierDetail
{
get
{
return new DossierDetailViewModel();
}
}
And I call the new usercontrol like this:
DossierDetailViewModel newDossier = new DossierDetailViewModel();
newDossier.TabName = SelectedDossier.Omschrijving;
this.OpenDossiers.Add(newDossier);
Messenger.Default.Send<DTO.Dossier.Dossier>(SelectedDossier, "SetDossier");
EDIT:
Is there no one who can help me or put me in the right direction? :(
The answer of this problem can be found here: https://mvvmlight.codeplex.com/discussions/577555
I was going through the Caliburn Micro documenation here. Simultaneously, I was trying to put up some rough code for experiment. I am a little confused about how to activate item using a container and how to pass an object to the ViewModel that we are activating.
Lets consider a master/detail scenario. The master contains a list (say datagrid) and the details contain specific row from the master for update(say tab item inside tab control). In the documentation (for ease of understanding), I believe the detail ViewModel was directly instantiated using code like this
public class ShellViewModel : Conductor<IScreen>.Collection.OneActive {
int count = 1;
public void OpenTab() {
ActivateItem(new TabViewModel {
DisplayName = "Tab " + count++
});
}
}
So, to apply the above fundamental concept in real world app, we need to instantiate the DetailViewModel (TabViewModel above) using container(say MEF). The challenge then is to know whether the particular DetailViewModel is already opened in the TAB Control. The immediate crude thing that came to my mind was maintaining a List of the Opened Tabs (DetailViewModels). But then we are again referencing DetailViewModel in the MasterViewModel defeating the purpose. Is there any options available to solve this issue.
The second thing that is troubling me is how to pass the Objects from MasterViewModel (Selected Detail Item) to the DetailViewModel. If we use the EventAggregator here then each of the opened DetailViewModels will receive the event which I am not sure how to handle.
If anyone can throw some light on the above two issues, I would be grateful
Update:
The Master is Conductor like this
public class MainViewModel : Conductor<IScreen>.Collection.OneActive, IShell {
....
}
And the detail is defined like this
public class TabViewModel : Screen {
....
}
Both are in the same Window.
I'm not sure exactly what the issue is. In your conductor of many, you have an Items collection provided by Caliburn.Micro. When you come to display a detail view, you can check this collection for the existence of that detail view (using the primary key which you have from the master view).
If the item is already in the Items collection then just activate it (using the ActivateItem method). If the item isn't in the collection, then instantiate it (presumably using a factory if you're using MEF), and add it to the Items collection, and then activate it.
I'm trying to create an application similar to Visual Studio in that we have a main content area (i.e. where documents are displayed in a TabControl, not a true MDI interface), with a menu on the side.
So far, I have everything working, except the content. My goal is that when a user double clicks on an item in the navigation menu on the side, it opens the document in the Content region. This works, but every time I double click it spawns a new instance of that same view. There's a chance that I could have multiple views of the same type (but different "names") in the TabControl content container.
Right now, my code looks something like this...
IRegion contentRegion = IRegionManager.Regions[RegionNames.ContentRegion];
object view = IUnityContainer.Resolve(viewModel.ViewType, viewModel.UniqueName);
if (!IUnityContainer.IsRegistered(viewModel.ViewType, viewModel.UniqueName))
{
IUnityContainer.RegisterInstance(viewModel.UniqueName, view);
contentRegion.Add(view);
}
contentRegion.Activate(view);
However, it appears that the view is never registered, even though I register it... I imagine I'm probably doing this wrong -- is there another way to do this? (re: the right way)
So, the problem was trying to do it this entire way. The smart method (for anyone else trying to do this) is to make use of Prism the correct way.
What I ended up doing was instead Navigating by:
1. In the Navigation Menu, constructing a UriQuery (included in Prism) with the UniqueID of the view I want to display (which is guaranteed to be unique) and adding that to the View I wanted to navigate to, i.e.:
IRegionManager.RequestNavigate(RegionNames.ContentRegion, new Uri(ViewNames.MyViewName + query.ToString(), UriKind.Relative));
where query is the UriQuery object.
2. Register the View and ViewName in the Module via:
IUnityContainer container = ServiceLocator.Current.GetInstance<IUnityContainer>();
container.RegisterType<object, MyView>(Infrastructure.ViewNames.MyViewName);
3. In the View, make sure the ViewModel is a parameter on the constructor. Let Prism inject this manually for us. Inside the constructor, make sure you set the DataContext to the incoming ViewModel.
4. Finally, make sure your ViewModel implements INavigationAware interface... This is a very simple implementation of it (UniqueID is a property on the ViewModel):
public virtual bool IsNavigationTarget(NavigationContext navigationContext)
{
if (navigationContext.Parameters != null)
return (navigationContext.Parameters["UniqueID"] == UniqueID);
return false;
}
public virtual void OnNavigatedFrom(NavigationContext navigationContext)
{
}
public virtual void OnNavigatedTo(NavigationContext navigationContext)
{
if (navigationContext.Parameters != null)
UniqueID = navigationContext.Parameters["UniqueID"];
}
From here, Prism will ensure that only one view of your "UniqueID" will exists, while allowing for others of the same view, but different ViewModel (or data for that ViewModel, i.e. viewing two users in different tabs, but both use the same templated view).
I'm working on my first project in WPF/XAML, and there's a lot I've not figured out.
My problem is simple - I need a window that has a bunch of fields at the top, with which the user will enter his selection criteria, a retrieve button, and a data grid. When the user clicks on the button, a query is run, and the results are used to populate the grid.
Now the simple and obvious and wrong way to implement this is to have a single module containing a single window, and have everything contained within it - entry fields, data grid, the works. That kind of mangling of responsibilities makes for an unmaintainable mess.
So what I have is a window that is responsible for little more than layout, that contains two user controls - a criteria control that contains the entry fields and the retrieve button, and a data display control that contains the data grid.
The question is how to get the two talking to each other.
Years back, I would have added a function pointer to the criteria control. The window would have set it to point to a function in the display control, and when the button was clicked, it would have called into the display control, passing the selection criteria.
More recently, I would have added an event to the criteria control. I would have had the window set a handler in the display control to listen to the event, and when the button was clicked, it would have raised the event.
Both of these mechanisms would work, in WPF. But neither is very XAMLish. It looks to me like WPF has provided the ICommand interface specifically to accommodate these kinds of connection issues, but I've not yet really figured out how they are intended to work. And none of the examples I've seen seem to fit my simple scenario.
Can anyone give me some advice on how to fit ICommand to this problem? Or direct me to a decent explanation online?
Thanks!
MVVM is the prevalent pattern used with WPF and Silverlight development. You should have a read up on it.
Essentially, you would have a view model that exposes a command to perform the search. That same view model would also expose properties for each of your criteria fields. The view(s) would then bind to the various properties on the view model:
<TextBox Text="{Binding NameCriteria}"/>
...
<Button Command="{Binding SearchCommand}".../>
...
<DataGrid ItemsSource="{Binding Results}"/>
Where your view model would look something like:
public class MyViewModel : ViewModel
{
private readonly ICommand searchCommand;
private string nameCriteria;
public MyViewModel()
{
this.searchCommand = new DelegateCommand(this.OnSearch, this.CanSearch);
}
public ICommand SearchCommand
{
get { return this.searchCommand; }
}
public string NameCriteria
{
get { return this.nameCriteria; }
set
{
if (this.nameCriteria != value)
{
this.nameCriteria = value;
this.OnPropertyChanged(() => this.NameCriteria);
}
}
}
private void OnSearch()
{
// search logic, do in background with BackgroundWorker or TPL, then set Results property when done (omitted for brevity)
}
private bool CanSearch()
{
// whatever pre-conditions to searching you want here
return !string.IsEmpty(this.NameCriteria);
}
}
In my Silverlight app I have a view containing a tab control and a view model of this view.
When the selected tab is changed, I need to refresh its data. In order to do that in the view model I'm using a command triggered by EventTrigger in the view and passing the appropriate event args to it (as described here http://weblogs.asp.net/alexeyzakharov/archive/2010/03/24/silverlight-commands-hacks-passing-eventargs-as-commandparameter-to-delegatecommand-triggered-by-eventtrigger.aspx).
Each tab item has its own view model and, therefore, to distinguish which view model I have to use to refresh the data, I'm watching the header in the tab item which I can get from the event args, e.g:
_tabSelectionChangedCommand = new DelegateCommand<SelectionChangedEventArgs>(TabSelectionChanged);
public ICommand TabSelectionChangedCommand
{
get { return _tabSelectionChangedCommand; }
}
private void TabSelectionChanged(SelectionChangedEventArgs e)
{
var tabItem = (TabItem)e.e.AddedItems[0];
if (tabItem.Header == "Header1" )
{
TabItem1ViewModel.Refresh();
}
.....
}
So, my question is :
Is good that I'm using in the view model the types related to the UI(TabItem, SelectionChangedEventArgs) and are there better ways to do what I've described above?
Maybe you can bind the SelectedIndex of the TabControl to a property defined in your viewmodel and attach an InvokeActionCommand to the TabControl and subscrible to its SelectionChanged event.
Then when the command gets called, check which index it is then load the data accordingly?