Referencing a UIElement in a ViewModel from XAML - wpf

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).

Related

Show different ViewModels with TabControl (MVVM)

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?

Binding not working inside a <ItemsControl.ItemTemplate>

I can not get the binding of a text property for a DataTemplate in MVVM design pattern.
To show the problem I expose below a simplification of my problem, where I bind two different view properties to the same model property (aka AnObject.Text).
My code in MainWindow.xaml is:
...
<Button Grid.Row="0" Content="{Binding ButtonText}" />
...
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding MyItems}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<DockPanel>
<Label Content="aaaaa" />
<TextBlock Text="{Binding ItemText}" />
</DockPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
...
My code behind in MainWindow.xaml.cs (which sets the same DataContext for Button and every item in <ItemsControl ItemsSource>):
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
}
My code in MainWindowViewModel.cs is:
...
public ObservableCollection<object> MyItems => MyConverter.GetCollection(MyData.List);
public string ItemText => "dddd"; // this DOES works
public string ItemText => AnObject.Text; // this does NOT work
...
public string ButtonText => AnObject.Text; // this DOES works (note, same object property!)
...
Any idea why my binding inside the DataTemplate does not work?
Thanks in advance!
There are various things to understand here:
Button control will have the DataContext set to MainWindowViewModel instance. This is the reason why ButtonText variable value is getting reflected in Button control text.
For ItemsControl the DataContext is the the same as for the Button, i.e. the MainWindowViewModel instance.
Each item in the ItemsControl ItemsSource acts as a DataContext for the elements in the ItemTemplate, i.e. the DockPanel and its child elements. This is managed automatically by the framework. So essentially you will need a public property named ItemText in the class which will act as a DataContext for Dockpanel.
In your case the ItemText property is not the part of the objects which are in list.

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.

How to setup UserControl

In an application I have the (simplified) task:
The application manages information about persons. The persons are stored somewhere (doesn't matter).
The user can add and remove persons to the list.
The list of persons (that is used quite often in the program) looks like this:
<UserControl>
...
<StackPanel>
<ListBox
ItemsSource="{Binding Persons}"
SelectedItem="{Binding SelectedPerson}"
SelectionMode="Single"/>
<StackPanel Orientation="Horizontal">
<Button Content="Add Person" Command="{Binding AddPersonCommand}" />
<Button Content="Remove Person" Command="{Binding RemovePersonCommand}" />
</StackPanel>
</StackPanel>
...
</UserControl>
Behind that works a ViewModel that I want to use for implementation.
Now I want wo have this control embedded into another Control/Window like this:
<personcontrol:PersonControl PersonsCollectionDP="{Binding PersonsFromMainVM}" SelectedPersonDP="{Binding SelectedPersonFromMainVM}" />
(PersonsVM and SelectedPersonVM are Properties in the VM of the UC/Window that embeds the PersonControl-UC, PersonsDP and SelectedPersonDP are DependencyProperty of the PersonControl-UC.)
I have the problem having the properties of the UC as a DependencyProperty and (at the same time) having as Properties in the UC-ViewModel.
How can I accomplish that?
UPDATE 1
Found this link where exactly my problem is discussed but still not answered. Maybe someone has a new idea...
It is all about datacontext. You could keep the bindings in usercontrol definition and later in parts where you embed your UC bind datacontext to a corresponding VM which provides your datasource of persons.
<Window>
<personcontrol:PersonControl DataContext="{Binding PersonsVM}" />
</Window>
And in VM you would have
public class WindowVM
{
public PersonsUCProviderVM PersonsVM {get;set;}
}
public class PersonsVM
{
public List<Person> Persons {get;set;}
public Person SelectedPerson {get;set;}
public ICommand AddPersonCommand {get;set;}
public ICommand RemovePersonCommand {get;set;}
}
EDIT:
usually you use elementname binding when working with usercontrol and DP.(never set the DataContext to self)
<personcontrol:PersonControl PersonsCollectionDP="{Binding PersonsFromMainVM}" SelectedPersonDP="{Binding SelectedPersonFromMainVM}" />
then your usercontrol stuff should look like this
<UserControl x:Name="uc">
<StackPanel>
<ListBox
ItemsSource="{Binding ElementName=uc, Path=PersonsCollectionDP}"
SelectedItem="{Binding ElementName=uc, Path=SelectedPersonDP}"
SelectionMode="Single"/>
</StackPanel>
</UserControl>

Binding TabControl to ObservableCollection With Different Content

'm trying to create w mainwindow that will have a tabcontrol containing multiple items ( each one is shown only on demand )... let's say we have item of type A, item of type B and item of type C
I want ( using MVVM pattern) to have an observablecollection of objects that represent my tabitems and that are related to my userscontrols ( each tabitem is a usercontrol)...
The problem is that i didn't figure out how to do it.
i've a tabItemViewModelBase class :
public class TabItemViewModelBase : ViewModelBase
{
//Fields :
RelayCommand _closeCommand;
//Constructor:
public TabViewModel(string header)
{
this.Header = header;
}
}
in my mainwindow data context, i've an observable collection of this class :
//Propriétés
ObservableCollection<TabViewModel> _tabItems;
In my MainWindow i've the following Tag for the TabControl Item
<TabControl Padding="0" ItemsSource="{Binding Path=Workspaces}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Header}"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
**<view:ClientView/>**
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
but as you may see, all the items are attached to ClientView User control and i'm using tabviewitem to create my items, i need a property or a way to specify the content form for each element of the observablecollection...
( i've a ClientListingViewModel Class , and ClientCreationViewModel class) , and i can't use both because i don't know how to specify the view for each of them!
Thanks!
To specify views that target specific viewmodels, you can do this in a datatemplate.
First you need to add a namespace reference to your view and viewmodel namespaces. I've included an example using a Window, but it also works for UserControls.
<Window ...
xmlns:v="clr-namespace:PutYourViewNamespaceHere"
xmlns:vm="clr-namespace:PutYourViewModelNamespaceHere">
Next you will need to define the datatemplates in the resources section of your container.
In the example below, I'm using the ClientListingView as the datatemplate for ClientListingViewModel
<Window.Resources>
<DataTemplate DataType="{x:Type vm:ClientListingViewModel">
<v:ClientListingView />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:ClientCreationViewModel">
<v:ClientCreationView />
</DataTemplate>
</Window.Resources>

Resources