How to correctly databind collection to TabControl? - c#

I've looked at several qustions/answers on SO and for some reason I'm not getting anything to work for binding a collection to the TabControl. I am trying to do this so I don't have to assign the DataContext in the code-behind.
Here is the view model:
public class DocumentsCollectionViewModel : IEnumerable<DocumentViewModel> {
private readonly ObservableCollection<DocumentViewModel> mDocsCollection = new ObservableCollection<DocumentViewModel>();
public ObservableCollection<DocumentViewModel> Documents {
get { return mDocsCollection; }
}
// initially excluded from question as I thought it was understood :)
public IEnumerator<DocumentViewModel> GetEnumerator() {
return mDocsCollection.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator() {
return mDocsCollection.GetEnumerator();
}
}
...for completeness sake, the DocumentViewModel:
public class DocumentViewModel {
private readonly Document mDocument;
public string Name {
get { return mDocument.Name; }
}
}
In the XAML, I am a little confused about where to tell the tab control to use the Documents property in DocumentsCollectionViewModel:
<TabControl Name="DocumentsTab"
ItemsSource="{Binding localmodels:DocumentsCollectionViewModel}">
<TabControl.ItemTemplate>
<DataTemplate DataType="{x:Type localmodels:DocumentViewModel}">
<Label Style="{StaticResource DefaultFont}"
Content="{Binding Name}"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate DataType="{x:Type localmodels:DocumentViewModel}">
<Label Style="{StaticResource DefaultFont}"
Content="{Binding Name}"/>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>

Have you set the DataContext of your Window/UserControl which has this TabControl to the instance of your DocumentsCollectionViewModel?
Try doing this in the constructor of your Window containing the TabControl
public void MainWindow()
{
InitializeComponents();
this.DataContext = new DocumentsCollectionViewModel();
//Initialize the collection inside your VM
}
OR you can set DataContext in xaml like
<Window>
<Window.DataContext>
<localmodels:DocumentsCollectionViewModel/>
</Window.DataContext>
</Window>
then in your xaml just directly bind to Documents property
<TabControl Name="DocumentsTab"
ItemsSource="{Binding Documents}">
<TabControl.ItemTemplate>
<DataTemplate DataType="{x:Type localmodels:DocumentViewModel}">
<Label Style="{StaticResource DefaultFont}"
Content="{Binding Name}"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate DataType="{x:Type localmodels:DocumentViewModel}">
<Label Style="{StaticResource DefaultFont}"
Content="{Binding Name}"/>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>

Related

How to fill each tab of the tab control's itemslist with one user control dynamically from the mainviewmodel

My MainView contains a TabControl with an ItemTemplate and a ContentTemplate.
The TabControl's ItemsSource is bound to the Property ObservableCollection<TabViewModel> TabCollection in my MainViewModel.
TabViewModel:
namespace LuxUs.ViewModels
{
public class TabViewModel
{
public string Name { get; set; }
public object VM {get; set;}
public TabViewModel(string name)
{
Name = name;
}
public TabViewModel(string name, object vm)
{
Name = name;
VM = vm;
}
}
}
I want to create the tabs with their tab's header AND content dynamically from the MainViewModel like this...:
MainViewModel:
using System.Collections.ObjectModel;
namespace LuxUs.ViewModels
{
public class MainViewModel : ObservableObject, IPageViewModel
{
public ObservableCollection<TabViewModel> TabCollection { get; set; }
public MainViewModel()
{
TabCollection = new ObservableCollection<TabViewModel>();
TabCollection.Add(new TabViewModel("Dachdefinition", new DachdefinitionViewModel()));
TabCollection.Add(new TabViewModel("Baukörperdefinition"));
TabCollection.Add(new TabViewModel("Fassade"));
TabCollection.Add(new TabViewModel("Raumdefinition"));
TabCollection.Add(new TabViewModel("Treppenloch | Galerieöffnung"));
}
}
}
View:
<UserControl x:Class="LuxUs.Views.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:LuxUs.Views"
xmlns:models="clr-namespace:LuxUs.Models"
xmlns:vm="clr-namespace:LuxUs.ViewModels"
mc:Ignorable="d">
<Grid>
<Grid>
<TabControl Style="{DynamicResource TabControlStyle}" ItemsSource="{Binding TabCollection}" >
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<UserControl>
<ContentControl Content="{Binding VM}" />
</UserControl>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
</Grid>
</UserControl>
... but the content of each tab won't show. Instead, I get this text. The ViewModel ist the correct one but it should load the view instead of showing this text, of course:
The first tab's ViewModel DachdefinitionViewModel has only an empty constructor:
using System.Collections.ObjectModel;
namespace LuxUs.ViewModels
{
public sealed class DachdefinitionViewModel : ObservableObject
{
public DachdefinitionViewModel()
{
}
}
}
And here is its view Dachdefinition.xaml:
<UserControl x:Class="LuxUs.Views.Dachdefinition"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:LuxUs.Views"
xmlns:vm="clr-namespace:LuxUs.ViewModels"
mc:Ignorable="d">
<UserControl.DataContext>
<vm:DachdefinitionViewModel></vm:DachdefinitionViewModel>
</UserControl.DataContext>
<Grid Margin="50">
...
...
...
</Grid>
</UserControl>
Is the binding correct here or do I need to bind differently? Why is the view not showing up inside the first tab?
you need to connect view model with correct view via DataTemplate.
DataTemplate provides visual representation for data type which doesn't have it (your view model). If you don't specify DataTemplate, you will get default one: TextBlock with string representation of object (result of ToString() method, default to type name).
<Grid>
<Grid.Resources>
<DataTemplate DataType="{x:Type vm:DachdefinitionViewModel}">
<views:Dachdefinition />
</DataTemplate>
</Grid.Resources>
<TabControl Style="{DynamicResource TabControlStyle}"
ItemsSource="{Binding TabCollection}" >
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<UserControl>
<ContentControl Content="{Binding VM}" />
</UserControl>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
Your TabControl declaration should look like this:
<TabControl ItemsSource="{Binding TabCollection}">
<TabControl.Resources>
<DataTemplate DataType="{x:Type vm:DachdefinitionViewModel}">
<local:Dachdefinition/>
</DataTemplate>
</TabControl.Resources>
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding Name}"/>
<Setter Property="Content" Value="{Binding VM}"/>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
And the Dachdefinition UserControl must not set its own DataContext property, because the DataContext value is supposed to be inherit from the control's parent element, i.e. the TabItem.
<UserControl x:Class="LuxUs.Views.Dachdefinition" ...>
<!--
do not set UserControl.DataContext here
-->
<Grid Margin="50">
...
</Grid>
</UserControl>
Yes there is a problem in databinding.
at
<ContentControl Content="{Binding VM}" />
This line would just display ToString() value of the object bound to
it. (VM in this case).
Instead you can try using ContentTemplateSelection where you can choose the type of ContentTemplate in run time based on the type of object bound to it.
class TabContentTemplateSelector:DataTemplateSelector
{
public DataTemplate DefaultTemplate { get; set; }
public DataTemplate DachdeTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item is TabViewModel tabViewModel)
{
if (tabViewModel.VM != null && tabViewModel.VM is DachdefinitionViewModel)
{
return DachdeTemplate;
}
else
{
return DefaultTemplate;
}
}
return base.SelectTemplate(item, container);
}
}
<DataTemplate x:Key="DachdeTemplate">
</DataTemplate>
<DataTemplate x:Key="SomeOtherTemplate">
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
<local:TabContentTemplateSelector x:Key="myTabContentTemplateSelector"
DachdeTemplate="{StaticResource DachdeTemplate}"
DefaultTemplate="{StaticResource
SomeOtherTemplate}"
/>
</UserControl.Resources>
<Grid>
<TabControl ItemsSource="{Binding TabCollection}"
ContentTemplateSelector="{StaticResource
myTabContentTemplateSelector}" >
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
</Grid>
https://www.c-sharpcorner.com/UploadFile/41e70f/dynamically-selecting-datatemplate-for-wpf-listview-way-1/
Check this for how to dynamically change template.. In the template you can use any kind of user control.

DataTemplate with textbox for search

I uses MasterDetailsView control. It has MasterHeaderTemplate property. I want to add a TextBox in order to implementation of items search. I don't understand how to do this. Because DataTemplate don't has a needed property. It's UWP app, don't WPF.
TextBlock got value by MasterHeader property. But how do other binding. For example, placeholder text, event handlers.
MasterHeader="{x:Bind ViewModel.Title}"
MasterHeaderTemplate="{StaticResource MasterHeaderTemplate}"
<DataTemplate
x:Key="MasterHeaderTemplate">
<StackPanel>
<TextBlock
Text="{Binding}"
Style="{StaticResource HeaderStyle}" />
<TextBox
PlaceholderText="{???}" />
</StackPanel>
</DataTemplate>
You can use the Binding.ElementName property to set the name of your MasterDetailsView to use as the binding source for the Binding. Then you can access its DataContext(e.g. your ViewModel) and bind the property from ViewModel with PlaceholderText. For example:
.xaml:
<Page.Resources>
<DataTemplate x:Key="MasterHeaderTemplate">
<StackPanel>
<TextBlock
Text="{Binding}" />
<TextBox PlaceholderText="{Binding ElementName=MyDetailView,Path=DataContext.PlaceholderText}"/>
</StackPanel>
</DataTemplate>
</Page.Resources>
<Grid>
<controls:MasterDetailsView
ItemsSource="{Binding Lists}"
x:Name="MyDetailView" MasterHeader="{Binding Title}" MasterHeaderTemplate="{StaticResource MasterHeaderTemplate}">
<controls:MasterDetailsView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"></TextBlock>
</DataTemplate>
</controls:MasterDetailsView.ItemTemplate>
</controls:MasterDetailsView>
</Grid>
.cs:
public MainPage()
{
this.InitializeComponent();
ViewModel = new MyViewModel();
ViewModel.Title = "Header";
ViewModel.PlaceholderText = "MyPlaceholderText";
this.DataContext = ViewModel;
}
private MyViewModel ViewModel { get; set; }

TabItem Content IsEnabled binding

I'm creating a WPF application using MVVM pattern (at least I'm trying). There is <TabControl> with bound ItemsSource,which is an ObservableCollection<TabModel> Tabs. Tabs has Name and Items property, where Items is a list of ControlModel, which means Controls. I have problem with binding IsEnabled property to Grid where Items are placed.
Below there is a part of my code presenting the way I'm doing this:
private ObservableCollection<TabModel> tabs;
public ObservableCollection<TabModel> Tabs
{
get
{
if (tabs == null)
{
tabs = new ObservableCollection<TabModel>();
RefreshTabs();
}
return tabs;
}
set
{
tabs = value;
OnPropertyChanged("Tabs");
}
}
\\Tab Model
public string Name { get; set; }
private List<ControlModel> items;
public List<ControlModel> Items
{
get { return items; }
set
{
items = value;
OnPropertyChanged("Items");
}
}
And xaml...
<TabControl Margin="0,100,0,0" ItemsSource="{Binding Tabs,UpdateSourceTrigger=PropertyChanged}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<ScrollViewer VerticalScrollBarVisibility="Hidden">
<Grid Margin="5,5,5,5" IsEnabled="{Binding IsProductEditionEnabled}">
<!--<Grid Margin="5,5,5,5">-->
<ItemsControl ItemsSource="{Binding Items,UpdateSourceTrigger=PropertyChanged}" ItemTemplateSelector="{StaticResource ControlTemplateSelector}"/>
</Grid>
</ScrollViewer>
</DataTemplate>
</TabControl.ContentTemplate>
The part...
<Grid Margin="5,5,5,5" IsEnabled="{Binding IsProductEditionEnabled}">
is not working. There is no error. This grid is always disabled. By default it's false.
private bool isProductEditionEnabled = false;
public bool IsProductEditionEnabled
{
get { return isProductEditionEnabled; }
set
{
isProductEditionEnabled = value;
OnPropertyChanged("IsProductEditionEnabled");
}
}
The question is : How to bind IsEnabled in my case properly?
You are inside a DataTemplate so you need to specify where the parent DataContext is when you do the binding, something like this:
<DataTemplate>
<ScrollViewer VerticalScrollBarVisibility="Hidden">
<Grid IsEnabled="{Binding Path=DataContext.IsProductEditionEnabled,
RelativeSource={RelativeSource AncestorType={x:Type TabControl}}}">
</Grid>
</ScrollViewer>
</DataTemplate>

TabControl not finding data templates for TabControl items

I am binding a collection of TabViewModel items to a TabControl. Each of these has a header string property, and a content property of my own custom type BaseTabContentViewModel, an abstract class which each actual tab data viewmodel implements. Eg ValuationTabViewModel which is a sub-class of BaseTabContentViewModel.
I add the new TabViewModel to the Observable<TabViewModel> for the TabControl to pick up and it shows in the UI. I have overridden style templates for the layout of the tab control and header which work fine. The only trouble is the content doesn't find the template in my resource dictionary based on its type, it just displays the full qualified class name of the viewmodel, showing that it is not finding a default template for this class.
Why isn't the ValuationTabViewModel that is being displayed, finding the datatemplate for this type below?
My main view model.
public ObservableCollection<TabViewModel> DetailTabs { get; }
var valuationTab = new TabViewModel(DetailTabConstants.ValuationTab, new ValuationTabViewModel(_eventAggregator, _errorNotifier, _windsorContainer));
DetailTabs = new ObservableCollection<TabViewModel> { valuationTab };
Main XAML
<TabControl Margin="0,-2,0,0" x:Name="SelectionTabs" Style="{StaticResource DetailTabControl}" ItemsSource="{Binding DetailTabs}"
SelectedValue="{Binding SelectedTab, Mode=TwoWay}" ItemContainerStyle="{StaticResource DetailTabItem}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Header}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<TextBlock Text="{Binding Content}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
The content style template I want it to use
<DataTemplate x:Key="ValuationTabTemplate" DataType="{x:Type detailTabs1:ValuationTabViewModel}" >
<detailTabs:ValuationTab Margin="0,10,0,10" />
</DataTemplate>
And my Tab item ViewModel class
public class TabViewModel : ViewModelBase
{
private string _header;
private BaseTabContentViewModel _content;
public string Header
{
get => _header;
set
{
_header = value;
RaisePropertyChanged(nameof(Header));
}
}
public BaseTabContentViewModel Content
{
get => _content;
set
{
_content = value;
RaisePropertyChanged(nameof(Content));
}
}
public TabViewModel(string header, BaseTabContentViewModel viewModel)
{
Header = header;
Content = viewModel;
}
}
Remove the <TabControl.ContentTemplate> element and define an implicit DataTemplate (without an x:Key) for each type:
<TabControl Margin="0,-2,0,0" x:Name="SelectionTabs" Style="{StaticResource DetailTabControl}" ItemsSource="{Binding DetailTabs}"
SelectedValue="{Binding SelectedTab, Mode=TwoWay}" ItemContainerStyle="{StaticResource DetailTabItem}">
<TabControl.Resources>
<DataTemplate DataType="{x:Type detailTabs1:ValuationTabViewModel}">
<detailTabs:ValuationTab Margin="0,10,0,10" />
</DataTemplate>
</TabControl.Resources>
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Header}" />
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
I think your custom DataTemplate isn't being used because you have specified both a Key and a DataType for it, and the Key takes precedence.
As per the Microsoft docs:
... if you assign this DataTemplate an x:Key value, you are overriding the implicit x:Key and the DataTemplate would not be applied automatically
I would suggest removing the Key property and just using DataType:
<DataTemplate DataType="{x:Type detailTabs1:ValuationTabViewModel}">
...
</DataTemplate>
Also, as #mm8 implied, you are explicitly setting the ContentTemplate of your TabControl. You need to remove that from the XAML.

Windows Phone - custom control how propagate event to ViewModel

I am creating custom control for menu and I have ListBox in my control. Something like this:
<ListBox x:Name="MenuItemsList"
Grid.Row="1"
ItemsSource="{Binding ProgramList, Mode=OneWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Margin="10">
<TextBlock Text="{Binding Title}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Now when I want to catch Tap event, get properties from class and keep MVVM model. How can I catch this in MainPage.xaml.cs or in MainViewModel?
I have this code in my MainPage.xaml:
<controls:BottomMenu x:Name="BottomMenu" Canvas.Top="{Binding MenuCanvasTop}"
Width="480" Height="400">
</controls:BottomMenu>
I have prepare this code in my MainViewModel:
public RelayCommand<string> GoToSectionCommand
{
get
{
return goToArticleCommand
?? (goToArticleCommand = new RelayCommand<string>(
NavigateToSection));
}
}
But I don't know how can I call it. What's the best way?
Edit:
I tried to extend listbox:
<ListBox x:Name="MenuItemsList"
Grid.Row="1"
ItemsSource="{Binding ProgramList, Mode=OneWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Margin="10">
<Button Content="{Binding Title}"
Command="{Binding ListButtonClickCommand, Source={RelativeSource Self}}"
CommandParameter="{Binding Url}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
With code-behind:
public ICommand ListButtonClickCommand
{
get { return (ICommand)GetValue(ListButtonClickCommandProperty); }
set { SetValue(ListButtonClickCommandProperty, value); }
}
public static readonly DependencyProperty ListButtonClickCommandProperty =
DependencyProperty.Register("ListButtonClickCommand", typeof(ICommand), typeof(BottomMenu), new PropertyMetadata(null));
public BottomMenu()
{
InitializeComponent();
}
Then in MainPage.xaml:
<controls:BottomMenu x:Name="BottomMenu" Canvas.Top="{Binding MenuCanvasTop}"
Width="480" Height="400"
ListButtonClickCommand="{Binding MenuItemButtonCommand}">
</controls:BottomMenu>
And in MainViewModel:
private ICommand menuItemButtonCommand;
public ICommand MenuItemButtonCommand
{
get
{
return menuItemButtonCommand
?? (menuItemButtonCommand = new RelayCommand(
NavigateToArticleSection));
}
}
For now without luck. It's not working. RelayCommand isn't triggered.
Edit2
I guess the problem is with binding command in custom control but I don't know how to fix it.
You just need to bind to the UserControl -- using "RelativeSource Self" will bind to the Button, which is not what you want. You should be able to use an "ElementName" binding to locate the user control:
<UserControl x:Name="UserControlName" ... >
...
<Button Content="{Binding Title}"
Command="{Binding ElementName=UserControlName,Path=ListButtonClickCommand}"
CommandParameter="{Binding Url}"/>
...
</UserControl>

Categories