Trying to create a TabControl Region inside another Region. The TabControl has a set number of Views that will be added to it, with their own respective ViewModels.
But either the View doesn't show up, the tabitem doesn't show up with only one View displayed instead, or I get the following error:
System.ArgumentException: 'This RegionManager does not contain a Region with the name 'ParentTabRegion'. (Parameter 'regionName')'
MainMenuView:
<Grid>
<ContentControl prism:RegionManager.RegionName="ContentRegion" />
</Grid>
MainMenuViewModel:
public class MainMenuViewModel : BindableBase
{
private readonly IRegionManager _regionManger;
public MainMenuViewModel(IRegionManager regionManager)
{
_regionManger = regionManager;
_regionManger.RequestNavigate("ContentRegion", "ParentView");
}
}
ParentView:
<Grid>
<TabControl prism:RegionManager.RegionName="ParentTabRegion" />
</Grid>
ParentViewModel:
public class ParentViewModel : BindableBase
{
private readonly IRegionManager _regionManger;
private Child1View _tab1 = new Child1View();
private Child1View Tab1
{
get { return _tab1; }
set { SetProperty(ref _tab1, value); }
}
private Child2View _tab2 = new Child2View();
private Child2View Tab2
{
get { return _tab2; }
set { SetProperty(ref _tab2, value); }
}
public ParentViewModel(IRegionManager regionManger)
{
_regionManger = regionManger;
// Gives 'This RegionManager does not contain a Region with the name 'GeneralDataTabRegion'. (Parameter 'regionName')' error
_regionManger.AddToRegion("ParentTabRegion", typeof(Child1View));
_regionManger.AddToRegion("ParentTabRegion", typeof(Child2View));
//I've also tried the following
// Same error as above
// _regionManger.Regions["ParentTabRegion"].Add(typeof(Tab1View));
// _regionManger.Regions["ParentTabRegion"].Add(typeof(Tab2View));
// Same error as above
// _regionManger.AddToRegion("ParentTabRegion", Tab1);
// _regionManger.AddToRegion("ParentTabRegion", Tab2);
// Only the last registered view is displayed
// _regionManger.RegisterViewWithRegion("ParentTabRegion", typeof(Tab1));
// _regionManger.RegisterViewWithRegion("ParentTabRegion", typeof(Tab2));
}
}
I also have the prism namespace in all the views:
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
Maybe I'm not registering the ParentTabRegion somehow? But I don't have to register the other regions and they seem to just work out of the box.
Let me know if you know what I'm doing wrong or if there is something I'm missing. Thank you.
I would just comment but can't due to low reputation. Anyway..
Check this post
Prism 7 throws and exception when working with nested views
As stated in the comments: "the problem is about how to inject scope region in ViewModel"
This video from Brian should help you with the issue.
https://app.pluralsight.com/library/courses/prism-mastering-tabcontrol
I tested some other things out. Since I don't need dynamic tabs, I found this to be the cleanest solution using Prism:
Parent ViewModel:
public ParentViewModel(IRegionManager regionManager)
{
_regionManager = regionManager;
_regionManager.RegisterViewWithRegion("ChildRegion", typeof(Child1View));
_regionManager.RegisterViewWithRegion("ChildRegion", typeof(Child2View));
}
Parent View:
<UserControl.Resources>
<Style TargetType="TabItem">
<Setter Property="Header"
Value="{Binding DataContext.Title}"/>
</Style>
</UserControl.Resources>
<Grid>
<TabControl prism:RegionManager.RegionName="ChildRegion" />
</Grid>
I ended up doing this a bit differently since I don't really need to dynamically add Tabs.
So what I ended up doing was just adding all the ViewModels to an ObservableCollection of BindableBase. Then I just added them to the view using a DataTemplate.
Parent ViewModel:
private ObservableCollection <BindableBase> _childTabs;
public ObservableCollection <BindableBase> ChildTabs
{
get { return _childTabs; }
set { _childTabs = value; }
}
public ParentViewModel()
{
ChildTabs = new ObservableCollection <BindableBase> {
new Child1ViewModel(),
new Child2ViewModel()
};
}
Parent View:
<TabControl ItemsSource="{Binding ChildTabs}"
SelectedIndex="0">
<TabControl.Resources>
<DataTemplate DataType="{x:Type vm:Child1ViewModel}">
<view:Child1 />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:Child2ViewModel}">
<view:Child2 />
</DataTemplate>
</DataTemplate>
</TabControl.Resources>
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Title}" />
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
I still feel like I'm doing something wrong though, this doesn't feel like MVVM to me...
Related
I'm stuck in a problem right now and I think I'm lost.
My project (C# WPF MVVM) consists of the following components (no framework):
ShellView (Window)
ShellViewModel
MainView (Usercontrol) <-- 2 Textboxes for Userinputs (int values)
MainViewModel <-- Added both integer and put the result in a int-property
ResultDisplay (UserControl) <-- One Label witch should display the result
Both user controls are loaded into the shell view and displayed there.
How can I now connect the ResultDisply with the property of the MainView.
I've tried setting the Data.Context of the DisplayView to the MainViewModel and binding its label to the MainViewModel's property (ResultOutput).
When setting the autoproperty (1234) it works and 1234 is displayed. But everything that changes during runtime is no longer displayed.
I've already thought about DependensyProperty, but I can't implement it.
MainViewModel:
`
private string resultOutput = "1234";
public string ResultOutput
{
get { return resultOutput; }
set
{
resultOutput = value;
OnPropertyChanged("Result");
}
}
private void AddTwoNumbers()
{
int result = Num1 + num2;
ResultOutput = result.ToString();
}
DisplayView:
<UserControl.DataContext>
<viewModel:MainViewModel />
</UserControl.DataContext>
<Grid>
<Label x:Name="ResultTB"
Content="{Binding ResultOutput}"
Background="#333"
Foreground="LightGray"
FontSize="40"
HorizontalContentAlignment="Left"
VerticalContentAlignment="Bottom"
Padding="8,0,0,5" />
</Grid>
</UserControl>
`
I hope someone can help me out of the darkness. :-)
<UserControl.DataContext>
<viewModel:MainViewModel />
</UserControl.DataContext>
This is wrong approach, because you create new instance of MainViewModel.
I've had the same problem, solution (without DependencyProperty) that works for me is to place your UserControl inside container (for example StackPanel) and bind DataContext of this container.
class ShellViewModel
{
public MainViewModel MainViewModel { get; }
public ShellViewModel()
{
MainViewModel = new MainViewModel();
}
}
<Window x:Class="ShellView" ...>
<StackPanel DataContext="{Binding MainViewModel}">
<MainView/>
<ResultDisplay/>
</StackPanel>
</Window>
So now MainView and ResultDisplay have the same ViewModel object
I am witting a new WPF application using C# with the help of Prism 6. and the MVVM design pattern.
I have a main windows with a Top, Right, and a Center region. On the top region I have a toolbar, when a user clicks "Show Message" button, I show a view called "Message" in the center region. I am able to do that using RegionManager.RequestNavigate method to show the "Message" view which is working fine.
However, my "Message" view have multiple tabs. When the user clicks on the TabControlItems, I want to be able to show different views in the tab-Content.
Here is how my Message view look like. The idea here is to have a collection of ViewModels, and display the tabs according to the collection.
<TabControl ItemsSource="{Binding ViewModelCollection}"
Background="Transparent">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<prism:InvokeCommandAction Command="{Binding TabSelectionChangedCommand}"
CommandParameter="{Binding ViewName}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding TabTitle}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<ContentControl prism:RegionManager.RegionName="{x:Static foundation:RegionNames.TabContentRegionName}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
Then in my MessageViewModel I have the following code
public class MessageViewModel : BindableBase
{
public ObservableCollection<TabBasedNavigationAwareViewModel> ViewModelCollection { get; set; }
public DelegateCommand<string> ChangeTabContent { get; set; }
public MessageViewModel(IUnitOfWork unitOfWork, IPassportManager passportManager, ICoreRegionManager regionManager, IUnityContainer container)
: base(unitOfWork, passportManager, regionManager)
{
ViewModelCollection = new ObservableCollection<TabBasedNavigationAwareViewModel>();
ViewModelCollection.Add(container.Resolve<FirstViewModel>());
ViewModelCollection.Add(container.Resolve<SecondViewModel>());
ChangeTabContent = new DelegateCommand<string>(HandleChangeContent, CanChangeContent);
}
protected bool CanChangeContent(string viewName)
{
return true;
}
protected void HandleChangeContent(string viewName)
{
IRegion region = RegionManager.Regions[RegionNames.TabContentRegionName];
region.RequestNavigate(new Uri("Modules.Messages.Views." + viewName, UriKind.Relative));
}
public override void OnNavigatedTo(NavigationContext navigationContext)
{
ChangeTabContent.Execute("FirstView");
}
}
The problem is when the application starts I get an error
The region manager does not contain the TabContent region.
I clearly understand the error and why it is happening. But not sure how to solve it. May be Regions is the wrong way to do when using tab-controls inside a main region. I also tried to call the following code from the MessageViewModel constructor RegionManager.RegisterViewWithRegion(RegionNames.TabContentRegionName, typeof(MessageView));
What is the correct way to manage/display the correct view when the user click on the tabs?
Please note that I am using Fody.PropertyChanged package so it automatically notify when the property changed.
UPDATED
I tried to remove regions from view, now I get the tabs and the content screen is showing the FirstViewModel full name instead of the corresponding view.
Here is my code without regions
public class MessageViewModel : BindableBase
{
public ObservableCollection<TabBasedNavigationAwareViewModel> ViewModelCollection { get; set; }
public MessageViewModel(IUnitOfWork unitOfWork, IPassportManager passportManager, ICoreRegionManager regionManager, IUnityContainer container)
: base(unitOfWork, passportManager, regionManager)
{
ViewModelCollection = new ObservableCollection<TabBasedNavigationAwareViewModel>();
ViewModelCollection.Add(container.Resolve<FirstViewModel>());
ViewModelCollection.Add(container.Resolve<SecondViewModel>());
}
}
Here is the view
<TabControl ItemsSource="{Binding ViewModelCollection}"
Background="Transparent">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding TabTitle}" />
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
Also add some DataTemplates for your tab view models to a resource dictionary nearby
<DataTemplate DataType="{x:Type FirstViewModel}">
<TextBlock Text="{Binding SomeContent}"/>
</DataTemplate>
Assum that I have 3 user Control(TIShowNames,TIEnterCode,TIShowFactor).
they have their views and their corresponding viewModel.
all these 3, are in mainwindowView.
Here is my mainwindowView Xaml:
<Controls:TransitionPresenter Name="transContainer" Grid.Row="2" RestDuration="0:0:1" IsLooped="False" Transition="{StaticResource SlideTransition}">
<TabControl Name="TCMain" Background="#00FFFFFF" BorderThickness="0" Padding="0 -5 0 0 ">
<TabItem Name="TIShowNames" Visibility="Collapsed">
<views:NameView x:Name="NameViewElement" />
</TabItem>
<TabItem Name="TIEnterCode" Visibility="Collapsed">
<views:CodeView x:Name="CodeViewElement" />
</TabItem>
<TabItem Name="TIShowFactor" Visibility="Collapsed">
<views:FactorDetailView x:Name="FactorDetailViewElement" />
</TabItem>
</TabControl>
</Controls:TransitionPresenter>
In my old Programming style i used to use this line of code for navigating through tab items(without any pattern):
private void ChangeTabItemTo(TabItem TI)
{
transContainer.ApplyTransition("TCMain", "TCMain");
TCMain.SelectedItem = TI;
}
I have a btn show in "TIShowNames", so when i clicks on that it has to go to "TIShowFactor".
In MVVM, ViewModel does not know any thing about view(this item tab is in its parent view!!!). so how he can change selected Tab Item without violating MVVM??
Another Try:
Changing Selectedindex wont work because of this error:
"System.Windows.Data Error: 40 : BindingExpression path error: 'Index'
property not found on 'object' ''MainWindowViewModel'
(HashCode=22018304)'. BindingExpression:Path=AAA;
DataItem='MainWindowViewModel' (HashCode=22018304); target element is
'TabControl' (Name=''); target property is 'IsSelected' (type
'Boolean')"
Update:
Controls:TransitionPresenter is from Fluid DLL
Update:
I want to hide tab item's header so no one can click the header and navigatoin through header is possibe only via btns in usercontrols
You could define a DataTemplate per view model type in the view:
<TabControl Name="TCMain"
ItemsSource="{Binding ViewModels}"
SelectedItem="{Binding ViewModel}"
Background="#00FFFFFF" BorderThickness="0" Padding="0 -5 0 0 ">
<TabControl.ContentTemplate>
<DataTemplate>
<ContentControl Content="{Binding}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type local:NameViewViewModel}">
<views:NameView />
</DataTemplate>
<DataTemplate DataType="{x:Type local:CodeViewViewModel}">
<views:CodeView />
</DataTemplate>
<DataTemplate DataType="{x:Type local:FactorDetailViewModel}">
<views:FactorDetailView />
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
...and bind the SelectedItem property to a source property that you set in your view model, e.g.:
public object ViewModel
{
get { return _vm; }
set { _vm = value; NotifyPropertyChanged(); }
}
...
ViewModel = new CodeViewViewModel(); //displays the CodeView
Expanding on mm8's answer, this is how I'd do it:
First of all, I would create a BaseViewModel class to be inherited by every view model that will represent each tab of the TabControl.
I like to implement it as an abstract class with an abstract string property called "Title", so I can dynamically create the tabs and display their names (or titles). This class would also implement the NotifyPropertyChanged interface.
public abstract class BaseViewModel : INotifyPropertyChanged
{
public abstract string Title { get; }
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Then I would create each view model inheriting from this base view model. for example:
public class NameViewModel : BaseViewModel
{
public override string Title
{
get
{
return "Name";
}
}
}
You would do the same for the other view models, only changing the "title" property of each of them.
Now I would create the MainView of the application and its corresponding view model.
The MainViewModel would have a collection of BaseViewModels and a "CurrentViewModel" (of type BaseViewModel) and would add all the view models you want to its collection on its constructor, like this:
public class MainViewModel : BaseViewModel
{
public override string Title
{
get
{
return "Main";
}
}
private ObservableCollection<BaseViewModel> _viewModels;
public ObservableCollection<BaseViewModel> ViewModels
{
get { return _viewModels; }
set
{
if (value != _viewModels)
{
_viewModels = value;
OnPropertyChanged();
}
}
}
private BaseViewModel _currentViewModel;
public BaseViewModel CurrentViewModel
{
get { return _currentViewModel; }
set
{
if (value != _currentViewModel)
{
_currentViewModel = value;
OnPropertyChanged();
}
}
}
public MainViewModel()
{
ViewModels = new ObservableCollection<BaseViewModel>();
ViewModels.Add(new NameViewModel());
ViewModels.Add(new CodeViewModel());
ViewModels.Add(new FactorDetailViewModel());
}
}
Finally, your main view would be similar to what mm8 posted:
(Notice the differences from my code to mm8's code: (1) You need to set the DisplayMemberPath of the TabControl to the "Title" property of the BaseViewModels and (2) You need to set the DataContext of the Window to your MainViewModel)
<Window ...>
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Grid>
<TabControl Name="TCMain"
ItemsSource="{Binding ViewModels}"
DisplayMemberPath="Title"
SelectedItem="{Binding CurrentViewModel}"
Background="#00FFFFFF" BorderThickness="0" Padding="0 -5 0 0 ">
<TabControl.ContentTemplate>
<DataTemplate>
<ContentControl Content="{Binding}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type local:NameViewModel}">
<local:NameView />
</DataTemplate>
<DataTemplate DataType="{x:Type local:CodeViewModel}">
<local:CodeView />
</DataTemplate>
<DataTemplate DataType="{x:Type local:FactorDetailViewModel}">
<local:FactorDetailView />
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
</Window>
Now it should work as expected. Everytime you change the active tab of the TabControl, the SelectedItem property of the control will change to the corresponding view model, which will be templated as its corresponding view.
This approach is called "View Model First" (instead of View First), by the way.
EDIT
If you want to have a button on one of the view models that has a command to change the current view model, this is how you do it:
I suppose you are familiarized with Josh Smith's RelayCommand. If you are not, just search for its implementation on the web.
You will need to create an ICommand property on your MainViewModel, which will be responsible to change the "CurrentViewModel" property:
private ICommand _showFactorDetailCommand;
public ICommand ShowFactorDetailCommand
{
get
{
if (_showFactorDetailCommand == null)
{
_showFactorDetailCommand = new RelayCommand(p => true, p => show());
}
return _showFactorDetailCommand;
}
}
private void show()
{
CurrentViewModel = ViewModels.Single(s => s.Title == "Factor");
}
The show() method above simply searches the collection of view models that has the title "Factor" and set it to the CurrentViewModel, which in turn will be the Content of the ContentControl that acts as the ContentTemplate of your TabControl inside your main view.
Remember that your FactorDetailViewModel should be implemented as follows:
public class FactorDetailViewModel : ViewModelBase
{
public override string Title
{
get
{
return "Factor";
}
}
}
The button inside your "NameView" will bind to this command which is a property of "MainViewModel" using RelativeSource binding:
<Button Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.ShowFactorDetailCommand}" Content="Show Factor" Height="20" Width="60"/>
You could make this command more generic, passing the title of the view model you would like to navigate to as the command parameter:
private ICommand _showCommand;
public ICommand ShowCommand
{
get
{
if (_showCommand == null)
{
_showCommand = new RelayCommand(p => true, p => show(p));
}
return _showCommand;
}
}
private void show(p)
{
var vm = (string)p;
CurrentViewModel = ViewModels.Single(s => s.Title == vm);
}
Then on your views, pass the Command Parameter too:
<Button Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.ShowCommand}" Content="Show Factor" CommandParameter="Factor" Height="20" Width="60"/>
Finally, to hide your TabItems completely, you need to set the ItemContainerStyle of your TabControl so that the Visibility of your TabItems has the value of "Collapsed".
<TabControl.ItemContainerStyle>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Visibility" Value="Collapsed"/>
</Style>
</TabControl.ItemContainerStyle>
That's how I defined TabControl:
<TabControl ItemsSource="{Binding OpenedProjects, UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding SelectedProject, Mode=OneWay}">
<!-- headers -->
<!-- header definition is unimportant for this question -->
<!-- content -->
<TabControl.ContentTemplate>
<DataTemplate>
<local:ProjectView />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
And these are the two methods I have defined in my Module class, that are used to register and use views:
protected override void _initializeViews() {
_container.RegisterType<MainMenuView>();
_container.RegisterType<ProjectsView>();
_container.RegisterType<ProjectView>();
_container.RegisterType<ContentView>();
}
protected override void _initializeRegions() {
IRegion menuRegion = _regionManager.Regions[RegionNames.MainMenuRegion];
IRegion projectsRegion = _regionManager.Regions[RegionNames.ProjectsRegion];
IRegion contentRegion = _regionManager.Regions[RegionNames.ContentRegion];
menuRegion.Add(_container.Resolve<MainMenuView>());
projectsRegion.Add(_container.Resolve<ProjectsView>());
contentRegion.Add(_container.Resolve<ContentView>());
}
And the View constructor:
public ProjectView(ProjectsViewModel vm) {
InitializeComponent();
DataContext = vm;
}
What I want to achieve is to inject ProjectView into TabControl's content area. Obviously, currently it doesn't work because of the ViewModel argument in the above constructor. How can I create this functionality, the PRISM way?
EDIT:
I found this: How to inject views into TabControl using Prism? however if I do the same as the author of that question, I'm getting:
System.InvalidOperationException: ItemsControl's ItemsSource property is not empty.
You TabControl didn't have a region so you can't inject something into your TabControl. Otherwise you only use simple MVVM to inject something into your view.
To use Prism to inject something in your TabControl. You only need this line:
<TabControl prism:RegionManager.RegionName="TabRegion"/>
And then you can inject something very easy into your View.
_regionManager.RequestNavigate("TabRegion", new Uri("ProjectView", UriKind.Relative));
Before that you have to add the View to your Containier with:
UnityContainer.RegisterType<object, ProjectView>("ProjectView");
To add the Headertext you can easy changed the Style of the TabItem and bind the Header to the ViewModel from the ProjectView:
<UserControl.Resources>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding DataContext.Name}" />
</Style>
</UserControl.Resources>
I hope that was the answer you looking for^^
The answer from #ascholz help me to implement this. Although the last step didn't work for me:
<UserControl.Resources>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding DataContext.Name}" />
</Style>
</UserControl.Resources>
What i did instead was:
1 - Create a Tab Control with a prism region (Inside MainWindows in my case).
<TabControl prism:RegionManager.RegionName="TabRegion"/>
2 - Create a "user control" of type TabItem (NewTabView) that holds the tab views. Note that i'm binding the Header here. The idea here would be to add a region here as well inside the grid (for the content of the tab), or to make every control that needs to be inside the tab a child of TabItem.
<TabItem
x:Class="Client.WPF.Views.NewTab"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
Header="{Binding Title}">
<Grid>
<Button Content="{Binding RandomNumber}"/>
</Grid>
</TabItem>
///The code behind should inherit from TabItem as well
public partial class NewTab : TabItem
///The viewmodel has a "Title" property
private string _title = "New Tab";
public string Title
{
get { return _title; }
set { SetProperty(ref _title, value); }
}
4 - Finally, i navigate to the NewTab like this (MainWindowViewModel code)
public DelegateCommand NewTab { get; }
public MainWindowViewModel(IRegionManager regionManager, IEventAggregator eventAggregato)
{
this.regionManager = regionManager;
this.eventAggregator = eventAggregator;
NewTab = new DelegateCommand(NewTabAction);
}
private void NewTabAction()
{
regionManager.RequestNavigate("TabRegion", "NewTab");
}
5 - As an added bonus, if you want to allow more than 1 instance of the tab, you can do something like this on the view model (NewTabViewModel).
///First add the IConfirmNavigationRequest interface
public class NewTabViewModel : BindableBase, IConfirmNavigationRequest
///...
///Then the implementation should look like this
public void ConfirmNavigationRequest(NavigationContext navigationContext, Action<bool> continuationCallback)
{
continuationCallback(true);///Will allow multiple instances (tabs) of this view
}
public void OnNavigatedTo(NavigationContext navigationContext)
{
}
public bool IsNavigationTarget(NavigationContext navigationContext)
{
return false;
}
public void OnNavigatedFrom(NavigationContext navigationContext)
{
}
Or you could also add views directly to the region. Although you would need to resolve views using the IContainerProvider. Something like this:
var view = containerProvider.Resolve<NewTab>();
regionManager.Regions["TabRegion"].Add(view);
I want to change UserControls on button clicks (I'm not going to complicate here, so I'll only mention important parts). So idea was to bind ViewModels of those UserControls to ContentControl, and than associate them Views using DataTemplates.
Here's the code:
<Window x:Class="Project.MainWindow">
<Window.Resources>
<DataTemplate DataType="{x:Type UserControl:ViewUserControlViewModel}" >
<UserControl:ViewUserControl/>
</DataTemplate>
<DataTemplate DataType="{x:Type UserControl:EditUserControlViewModel}" >
<UserControl:EditUserControl/>
</DataTemplate>
</Window.Resources>
<Grid>
<ContentControl DataContext="{Binding UserControlViewModel}" />
<Button Content="View" Click="ChangeToView()"/>
<Button Content="Edit" Click="ChangeToEdit()"/>
</Grid>
</Window>
ViewModel:
public class MainWindowViewModel : DependencyObject
{
public DependencyObject UserControlViewModel
{
get { return (DependencyObject)GetValue(UserControlViewModelProperty); }
set { SetValue(UserControlViewModelProperty, value); }
}
public static readonly DependencyProperty UserControlViewModelProperty =
DependencyProperty.Register("UserControlViewModel", typeof(DependencyObject), typeof(MainWindowViewModel), new PropertyMetadata());
public MainWindowViewModel()
{
UserControlViewModel = new EditUserControlViewModel();
}
}
But theres a problem. When I start project, I only see buttons but not any UserControls. What did I do wrong?
If your Window.DataContext is properly set to MainWindowViewModel this should do the job
<ContentControl Content="{Binding UserControlViewModel}" />
When doing mvvm your viewmodel should implement INotifyPropertyChanged and not inherit from DependencyObject.
public class MainWindowViewModel : INotifyPropertyChanged
{
private object _currentWorkspace; //instead of object type you can use a base class or interface
public object CurrentWorkspace
{
get { return this._currentWorkspace; }
set { this._currentWorkspace = value; OnPropertyChanged("CurrentWorkspace"); }
}
public MainWindowViewModel()
{
CurrentWorkspace= new EditUserControlViewModel();
}
//todo: to switch the workspace, create DelegeCommand/RelayCommand and set the CurrentWorkspace
//if you don't know about these commands let me know and i post it
public ICommand SwitchToViewCommand {get{...}}
public ICommand SwitchToEditCommand {get{...}}
}
xaml: you should set the Content Property to your CurrentWorkspace.
<ContentPresenter Content="{Binding UserControlViewModel}" />
<Button Content="View" Comamnd="{Binding SwitchToViewCommand}"/>
<Button Content="Edit" Comamnd="{Binding SwitchToEditCommand}"/>
! Don't forget to set the DataContext for your window to your MainWindowViewModel instance.
First of all you should post the code of your UserControl since (in your code snippet above) it's responsible for displaying some data.
Second you are not binding anything in your code.
Third your implementation of the ViewModel is wrong. You don't need to subclass a DependencyObject but instead implement the INotifyPropertyChanged interface in order to establish a ViewModel that is capable of notifying your View.
Fourth I don't know what you are doing with
<ContentControl DataContext="{Binding UserControlViewModel}" />
maybe you can explain further ?
Fifth when implementing the MVVM patterm (what you currently not do) you should avoid using events like the click event and instead use Commands.
(I know that's not a real answer yet, but I don't wanted to write in comment syntax)