Show different ViewModels with TabControl (MVVM) - wpf

I worte an application with different ViewModels, one for Users, Groups and Machines. Every ViewModel hast their own View. I am using Caliburn.Micro.
Right now I switch between the Views with Buttons like this:
XAML:
<StackPanel Orientation="Horizontal">
<Button VerticalAlignment="Top" Margin="5" Height="30" x:Name="ShowUsers" Content="Users"/>
<Button VerticalAlignment="Top" Margin="5" Height="30" x:Name ="ShowGroups" Content="Groups"/>
<Button VerticalAlignment="Top" Margin="5" Height="30" x:Name ="ShowMachines" Content="Machines"/>
</StackPanel>
<ContentControl Grid.Row="1" x:Name="ActiveItem"/>
C#:
public AdminViewModel(GroupManagementViewModel groupManagementViewMode, MachineManagementViewModel machineManagementViewModel, UserManagementViewModel userManagementViewModel)
{
this._groupManagementViewModel = groupManagementViewMode;
this._machineManagementViewModel = machineManagementViewModel;
this._userManagementViewModel = userManagementViewModel;
}
protected override void OnActivate()
{
base.OnActivate();
ShowUsers();
}
public void ShowUsers()
{
ActivateItem(_userManagementViewModel);
}
public void ShowGroups()
{
ActivateItem(_groupManagementViewModel);
}
public void ShowMachines()
{
ActivateItem(_machineManagementViewModel);
I would like to change those Buttons with using a TabControl and I tried several things and now my code looks like this:
XAML(with Tabcontrol):
<TabControl>
<TabItem Header="User" x:Name="ShowUsers">
</TabItem>
<TabItem Header="Groups" x:Name="ShowGroups">
</TabItem>
<TabItem Header="Machines" x:Name="ShowMachines">
</TabItem>
</TabControl>
<ContentControl Grid.Row="1" x:Name="ActiveItem"/>
So I gave the TabItems the x:Name of the command in my ViewModel but it only shows the same viewmodel for every TAB
I would be very thankful for every hint.
Best Regards
Zain

Using Caliburn.Micro, the XAML markup in the view should be as simple as this:
<TabControl x:Name="Items" />
The view model should then inherit from Conductor<T>.Collection.OneActive and add the tab view models to the Items property:
public class AdminViewModel : Conductor<object>.Collection.OneActive
{
public AdminViewModel(GroupManagementViewModel groupManagementViewMode,
MachineManagementViewModel machineManagementViewModel,
UserManagementViewModel userManagementViewModel)
{
Items.Add(groupManagementViewMode);
Items.Add(machineManagementViewModel);
Items.Add(userManagementViewModel);
}
}
The tab view model should inherit from Screen. You can then set the DisplayName property of them to modify the tab header.

The TabControl element has an ItemSource property that can be used to make this work. For instance:
You could bind ItemSource to an observable collection of a type that is a base class to all three of your viewModels you want to use in the tab control:
public ObservableCollection<BaseViewModel> TabControlViewModels { get; }
ItemSource would be binded to this.
Looking into the TabControl (ItemsControl) might be helpful for things such as: SelectedIndex, ItemsPanel, ItemTemplate, and ItemContainerStyle to make it look the way you want.
Also see here for more details: How do I bind a TabControl to a collection of ViewModels?

Related

C# WPF What's wrong with this template?

I'm trying to create a new window (child of MainWindow) that will display different numbers of rows with data each time. I used the following template that I copied from a wpf template tutorial. I've checked that the data are right and I can project them right with a MessageBox.Show window but I can't make them appear as bound properties. Here is the xaml...
<ListView x:Name="listUnits" x:FieldModifier="public" HorizontalAlignment="Left" Height="Auto" Margin="5,5,5,5" VerticalAlignment="Top" Width="Auto">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Id:"/>
<TextBlock Text="{Binding pu_Id}" Width="Auto"/>
<TextBlock Text=" "/>
<TextBlock Text="{Binding pu_unitName}" Width="Auto"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
So although pu_unitName contains the data, they're are displayed in the new window.
The binding is done in the constructor:
public partial class ChooseUnit : Window
{
public ChooseUnit(List<XML_Handler.PrUnits> thisList)
{
InitializeComponent();
listUnits.ItemsSource = thisList;
}
}
The PrUnits is a class which contains variables including pu_id and pu_unitName. So what's wrong with this code?
Since you can only bind to public properties, you need to make sure that pu_Id and pu_UnitName are properties (and not fields) of the PrUnits class. Then your bindings will work.
Also note that this is not a binding:
listUnits.ItemsSource = thisList;
In general, you would set the DataContext of the parent window to an instance of a view model class and bind the ItemsSource property of the ListView to a property that returns the List<XML_Handler.PrUnits>:
<ListView x:Name="listUnits" ItemsSource="{Binding Units}">
...
In your .cs:
public partial class ChooseUnit : Window
{
public ChooseUnit(List<XML_Handler.PrUnits> thisList)
{
InitializeComponent();
this.DataContext = thisList;
}
}
What this does it assigns the data context of your window to the list of itemsa you are trying to pass. At this point every element in that window will have access to the passed in list.
And then in your XAML do this:
<ListView x:Name="listUnits" ItemsSource="{Binding .}" x:FieldModifier="public" HorizontalAlignment="Left" Height="Auto" Margin="5,5,5,5" VerticalAlignment="Top" Width="Auto">
This allows us to use binding as the entire window has access to the list. Hence ItemsSource="{Binding .}".
EDIT
Reading more into your question and thanks to Ash your class needs to expose Properties with INotifyPropertyChanged interface implemented.
This interface is expected by the WPF framework so it can subscribe to the changed events and update the UI.

How to choose View dynamically based on current DataContext view model

I have a Page which will receive a different DataContext (View Model), dynamically.
I can't figure out how to use DataTemplate in a switch/case fashion, to render the appropriate view based on the current context.
I would imagine that I will have multiple DataTemplates like this:
<DataTemplate DataType="{x:Type LocalViewModels:ABC}">
<LocalViews:ABC/>
</DataTemplate>
but can't figure out in what container to put them. Only one of them will be rendered at a time, so ListBox makes no sense to me.
Given the following XAML of a Window
<Window.Resources>
<DataTemplate DataType="{x:Type local:ABC}">
<Border BorderThickness="2" BorderBrush="Red">
<TextBlock Text="{Binding Text}"/>
</Border>
</DataTemplate>
</Window.Resources>
<StackPanel>
<ContentControl Content="{Binding}"/>
</StackPanel>
you can simply assign an instance of ABC to the Window's DataContext to create the templated view.
class ABC
{
public string Text { get; set; }
}
...
public MainWindow()
{
InitializeComponent();
DataContext = new ABC { Text = "Hello, World." };
}
All details are here: Data Templating Overview.

WPF: binding several views to a TabControl's items

In the current project we work on, we have a main window with several views (each with its own viewmodel) that are presented as items in a tab control. E.g: One tab item is an editor, and contains the editor view as follows:
<TabItem Header="Test Editor">
<TestEditor:TestEditorView DataContext="{Binding TestEditorViewModel}"/>
</TabItem>
Another one shows results:
<TabItem Header="Results Viewer">
<ResultViewer:ResultViewer x:Name="resultViewer1" DataContext="{Binding Path=ResultViewModel}" />
</TabItem>
etc.
I'd like to have the TabItems bound to something in the main window's viewmodel, but I can't figure out how to bind the view's name to any property without breaking the MVVM pattern. I'd like to have something like:
<TabControl.ContentTemplate>
<DataTemplate>
<TestEditor:TestEditorView DataContext ="{Binding TabDataContext}"/>
</DataTemplate>
</TabControl.ContentTemplate>
only with some binding instead of having to know at design time what type will be used as content.
Any ideas?
Usually I have the TabControl's Tabs stored in the ViewModel, along with the SelectedIndex, then I use DataTemplates to determine which View to display
View:
<Window>
<Window.Resources>
<DataTemplate DataType="{x:Type ResultViewModel}">
<ResultViewer:ResultViewer />
</DataTemplate>
<DataTemplate DataType="{x:Type EditorViewModel}">
<TestEditor:TestEditorView />
</DataTemplate>
</Window.Resources>
<TabControl ItemsSource="{Binding TabCollection}"
SelectedIndex="{Binding SelectedTabIndex}" />
</Window>
ViewModel:
public class MyViewModel : ViewModelBase
{
publicMyViewModel()
{
TabCollection.Add(new ResultsViewModel());
TabCollection.Add(new EditorViewModel());
SelectedTabIndex = 0;
}
private ObservableCollection<ViewModelBase> _tabCollection
= new ObservableCollection<ViewModelBase>();
public ObservableCollection<ViewModelBase> TabCollection
{
get { return _tabCollection };
}
private int _selectedTabIndex;
public int SelectedTabIndex
{
get { return _selectedTabIndex; }
set
{
if (value != _selectedTabIndex)
{
_selectedTabIndex = value;
RaisePropertyChanged("SelectedTabIndex");
}
}
}
}

Referencing a UIElement in a ViewModel from XAML

I'm relatively new to using WPF and the MVVM architecture. I have a question about referencing UIelements from a XAML window's DataContext.
I have menu items that are bound to Views DataContext using this syntax:
<MenuItem Header="About" Command="{Binding AboutCommand}" />
I'd like to use a similar paradigm to add items to a grid. Right now I am using a class WorkflowDesigner. I can add it to my grid using the following code in my ViewModel:
grid.AddChildren(wd.View)
where view is of type UIElement.
What I'd rather do is add is reference to it from my XAML file without putting anything in my codebehind so that I can use the XAML mostly as a skin. Is it possible to use a tag just takes its UIElement from the datacontext of the XAML file?
This is possible, but it's not within the spirit of MVVM to have your ViewModel provide controls to your view. Ideally your ViewModel should have no dependencies on System.Windows.Controls at all.
If you must, then you can use a ContentControl:
<ContentControl Content={Binding wd.View} />
To handle this I'd create a ViewLocator class and put an instance of it into your resource dictionary. Then use this:
<ContentControl Content={Binding Source={StaticResource ViewLocator}, Path=WorkflowDesigner} />
I'm not sure if I quite understand your problem, but if you have a class you wish to present in your view from your ViewModel, you could use an ItemsControl to display different classes using a DataTemplate.
Say you have class User
public class User
{
public string Id { get; set;}
public string Name { get; set;}
}
public class UserViewModel
{
private ObservableCollectionaUser<User> _users = new......
public ObservableCollection<User> Users
{
get
{
return _users;
}
}
}
In UserView, you could have
<ItemsControl ItemsSource="{Binding Users}">
<ItemsControl.Resources>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Id}" />
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</ItemsControl.Resources>
</ItemsControl>
This way, a User would be presented in the view using the template declared above. Then you would not have to use UIElements in your ViewModel.
The ItemsControl could refer to grid items, and have items presented in a grid with SharedGridScope (if I remember correctly).

Silverlight 4 MVVM: Unable to Databind ICommand

I have created a Silverlight User Control. The markup is:
<StackPanel Grid.Row="4" Grid.Column="0" Orientation="Horizontal" Width="Auto" Margin="5">
<Button Content="OK" Margin="0,0,5,5" MinWidth="50" Command="{Binding OKCommand}" />
</StackPanel>
The code behind declares a Dependency property 'OKCommand' as:
public ICommand OKCommand
{
get
{
return (ICommand)GetValue(OKCommandProperty);
}
set
{
SetValue(OKCommandProperty, value);
}
}
public static readonly DependencyProperty OKCommandProperty
= DependencyProperty.Register("OKCommand", typeof(ICommand), typeof(TestUserControl), new PropertyMetadata(null, OKCommandProperty_PropertyChangedCallback));
private static void OKCommandProperty_PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
}
Now I want to use the user control on another page where which is the View & the ViewModel defines the command to which I want the OKCommand to be bound. The XAML markup is as such:
<local:TestControl OKCommand="{Binding Path=TestControlOk}"/>
However when I click the button it does not execute anything. Any clues as to what I am doing wrong here.
You need to show the view model that contains the TestControlOk property so we can tell if that's part of the problem.
UserControls do not register themselves as the data context automatically so the binding inside the user control won't have anything to bind to. Do you have
this.DataContext = this;
anywhere in the UserControl codebehind to enable your first binding to actually work?
Alternatively, you can do something like so:
<UserControl .....
x:Name="MyUserControl">
<StackPanel Grid.Row="4" Grid.Column="0" Orientation="Horizontal" Width="Auto" Margin="5">
<Button Content="OK" Margin="0,0,5,5" MinWidth="50"
Command="{Binding OKCommand, ElementName=MyUserControl}" />
</StackPanel>
</UserControl>
Note the ElementName= part of the binding pointing to the root UserControl element in your XAML.

Resources