TabControl doesn't show the tab collection - wpf

I'm trying to build a very simple and basic application that adds tab items to tab control using the MVVM pattern.
So i created:
a simple view with one button - "CustomerView.xaml"
an empty ViewModel class - it is empty cause the view doesn't save or extract any information from the Viewmodal (have only one button) - "CustomerViewModel.cs"
The MainWindow class code holds an observable collection of the CustomerViewModel
and have one "Add customer" button - to add a customer tab item to the tabcontrol and the tabcontrol itself.
i don't use commands cause it is not relevant at this time , i just was the new tabitem to appear when i add a new CustomerViewModel to the collection.
the result is that , although i can see that CustomerViewModels are added to the Observable collection, i still don't see tabitems added to the tabcontrol - The collection is not updating the the tabcontrol.
This is the MainWindow XAML:
<Window x:Class="MyViewModalTabControl.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:MyViewModalTabControl.ViewModal"
xmlns:vw="clr-namespace:MyViewModalTabControl.Views"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate DataType="{x:Type vm:CustomerViewModel}">
<vw:CustTabView />
</DataTemplate>
<DataTemplate x:Key="ClosableTabItemTemplate">
<DockPanel Width="120">
<Button
Content="X"
Cursor="Hand"
DockPanel.Dock="Right"
Focusable="False"
FontFamily="Courier"
FontSize="9"
FontWeight="Bold"
Margin="0,1,0,0"
Padding="0"
VerticalContentAlignment="Bottom"
Width="16" Height="16"
/>
<ContentPresenter
Content="Sample"
VerticalAlignment="Center"
/>
</DockPanel>
</DataTemplate>
</Window.Resources>
<Grid Margin="4" ShowGridLines="True">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Name="CustTabButton" Content="New Customer" Height="30" Margin="12,136,9,136" Click="CustTabButton_Click"></Button>
<TabControl Grid.Column="1" Grid.Row="0" Background="Red"
ItemsSource="{Binding CustomerTabs}"
ItemTemplate="{StaticResource ClosableTabItemTemplate}"
>
</TabControl>
</Grid>
This is the code behind of the MainWindow:
public partial class MainWindow : Window
{
private ObservableCollection<CustomerViewModel> _customertabs;
public ObservableCollection<CustomerViewModel> CustomerTabs
{
get
{
if (_customertabs == null)
{
_customertabs = new ObservableCollection<CustomerViewModel>();
// _workspaces.CollectionChanged += this.OnWorkspacesChanged;
}
return _customertabs;
}
}
public MainWindow()
{
InitializeComponent();
}
private void CustTabButton_Click(object sender, RoutedEventArgs e)
{
CustomerViewModel CustomerWorkSpace = new CustomerViewModel();
this.CustomerTabs.Add(CustomerWorkSpace);
}
}
This is the Viewmodel class:
public class CustomerViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
}
This is the View :
UserControl x:Class="MyViewModalTabControl.Views.CustTabView"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Button Name="CustTabButton" Content="New Customer" Height="30" Margin="12,136,9,136"></Button>
</Grid>
What am i missing ?

where do you set the datacontext for your mainwindow? Your bindings will just work with the right Datacontext.
and wouldn't it be better to create a mainviewmodel too, which handles the stuff you put in the mainwindow.cs at the moment?
EDIT: pls look at this msdn post from josh smith. there you can find a closable tab too.

Try any of the following
the ClosableTabItemTemplate should "return" TabItem that will be displayed in the Tab control not the DockPanel
create template for the TabItem control
do it in code

This is the fix:
public MainWindow()
{
InitializeComponent();
this.DataContext=this;
}

Related

Wpf adding more views to tabcontrol dynamically with prism

I looking for a hint or just a direction how to design the structure I need because its a little tricky. I am using MVVM and Prism.
I have 2 regions, one with 2 buttons and one tabcontrol region. After clicking on button1 I want to inject 3 views as tabs into the tabcontrol. After clicking on button2 I want to remove the tabs from button1 (but keep the data behind) and inject again 3 views as tabs in the tabcontrol. And here comes the tricky part :-), this should happen dynamically and 2 of the 3 views from button1 and button2 are based on the same view/viewmodel.
I am thankfull for everything I can get :-D
You should be able to do this using the ViewModel first approach. Here is a quick and dirty way to do this, clean it up at your own convenience.
View/ViewModel structure:
View1 - ViewModel1 : IViewModel
View2 - ViewModel2 : IViewModel
View3 - ViewModel3 : IViewModel
IViewModel requires a "Content" string property
MainWindow.xaml
<Window x:Class="SFQ1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SFQ1"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType="{x:Type local:ViewModel1}">
<local:View1/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:ViewModel2}">
<local:View2/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:ViewModel3}">
<local:View3/>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<TabControl Grid.Row="0" ItemsSource="{Binding VmObservable}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Content}"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<ContentControl Content="{Binding}">
</ContentControl>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
<Button Grid.Row="1" Margin="10,0,0,0" Width="100" Content="1"
HorizontalAlignment="Left" Command="{Binding SetOneCommand}"/>
<Button Grid.Row="1" Margin="130,0,0,0" Width="100" Content="2"
HorizontalAlignment="Left" Command="{Binding SetTwoCommand}"/>
</Grid>
</Window>
MainViewModel.cs
public class MainViewModel : BindableBase
{
private readonly List<IViewModel> _viewModelCollection;
private ObservableCollection<IViewModel> _vmObservable;
public ObservableCollection<IViewModel> VmObservable
{
get { return _vmObservable; }
set
{
SetProperty(ref _vmObservable, value);
RaisePropertyChanged();
}
}
private IViewModel _activeVm;
public IViewModel ActiveVm
{
get { return _activeVm; }
set
{
SetProperty(ref _activeVm, value);
RaisePropertyChanged();
}
}
public DelegateCommand SetOneCommand { get; set; }
public DelegateCommand SetTwoCommand { get; set; }
public MainViewModel()
{
SetOneCommand = new DelegateCommand(SetOne);
SetTwoCommand = new DelegateCommand(SetTwo);
VmObservable = new ObservableCollection<IViewModel>();
_viewModelCollection = new List<IViewModel>()
{
new ViewModel1(),
new ViewModel2(),
new ViewModel3()
};
}
private void SetOne()
{
VmObservable.Clear();
VmObservable.Add(GetViewModel(typeof(ViewModel1)));
VmObservable.Add(GetViewModel(typeof(ViewModel2)));
VmObservable.Add(GetViewModel(typeof(ViewModel3)));
}
private void SetTwo()
{
VmObservable.Clear();
VmObservable.Add(GetViewModel(typeof(ViewModel2)));
VmObservable.Add(GetViewModel(typeof(ViewModel3)));
}
private IViewModel GetViewModel( Type viewModelType )
{
return _viewModelCollection.Find(x => x.GetType().Equals(viewModelType));
}
}

How to bind ListView in a TabControl in xaml and .cs file

I have created a TabControl and there is a ListView. I am trying to bind the list created in .cs file for the same window , but the list is not getting reflected int he xaml file while binding.
I have tried create a tab with name "List view" and bind ListViewItems created in .cs file.
I have also tried to bind the text in textblock but it did not reflect.
Code-behind
namespace Shweta
{
/// <summary>
/// Interaction logic for Window5.xaml
///// </summary>
public partial class Window5 : Window
{
public Window5()
{
InitializeComponent();
}
}
public class CollectionViewModel
{
public List<string> ListViewItems
{
get
{
return new List<string>
{
"First name",
"Second name",
"Third name"
};
}
}
}
}
Xaml markup
<Window x:Class="Shweta.Window5"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window5" Height="300" Width="300">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="40*" />
<ColumnDefinition Width="238*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="47*" />
<RowDefinition Height="214*" />
</Grid.RowDefinitions>
<Grid Grid.Column="1" Grid.Row="1">
<TabControl>
<TabItem Header="List View">
<ListView ItemsSource="{Binding ListViewItems}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<Border Background="LightBlue" Padding="3"/>
<TextBlock Text="{Binding}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</TabItem>
</TabControl>
</Grid>
</Grid>
</Window>
Bindings to view model will work if DataContext is set with a view model object:
public Window5()
{
InitializeComponent();
this.DataContext = new CollectionViewModel();
}

Why is ItemsControl in the following WPF XAML not showing anything?

When I run the app, I expect to see 5 buttons (see ItemsControl\DataTemplate\Button in my XAML below) each with a content like "55/42" denoting max ad min temperature. However, the window is blank. I know this has to do with ItemsControl because I can display the data without using the ItemsControl. Can someone catch my mistake?
<Window x:Class="Embed_WeatherSummaryAsItemsControl_ToMain.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
FocusManager.FocusedElement="{Binding ElementName=InputCity}"
Title="Weather App" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<TextBox x:Name="InputCity" Grid.Row="0" Width="200" Text="{Binding CityAndOptionalCountry}"></TextBox>
<Button Grid.Row="0" Width="50" Content="Go" Margin="260,0,0,0" Command="{Binding GetWeatherReportCommand}"></Button>
<ItemsControl Grid.Row="1" ItemsSource="{Binding WeatherForecastSummaryCollection}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Path=MaxMinTemperature}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
As shown below, "WeatherForecastSummaryCollection" is a collection property on ViewModel class and "MaxMinTemperature" is a property of item in collection.
public class MainWindowViewModel : ViewModelBase
{
....
private List<WeatherForecastSummary> mWeatherForecastSummaryCollection;
public List<WeatherForecastSummary> WeatherForecastSummaryCollection
{
get { return mWeatherForecastSummaryCollection; }
set
{
mWeatherForecastSummaryCollection = value;
OnPropertyChanged("WeatherForecastSummaryCollection");
}
}
.....
}
public class WeatherForecastSummary
{
public string MaxMinTemperature { get; set; }
}
Thanks for helping!
I think you are missing the DataContext here.
Even if you are using the codebehind of the View, you need to write
this.DataContext = this;
or if you are using someother class as viewmodel you might want to point the datacontext to that class. Just replace "this" in the above code with your viewmodels object.
In this case it would be ,
this.DataContext = new MainWindowViewModel();

Wpf contentControl

New programmer question:
I'm trying to create a simple navigation welcome page similiar to wht you whould see on an ipad.
My MainWindow has a titlebar (which wont change) and the rest will be a container of sorts that will show differnt things based on events.
So here is my question how do I bind the container (contentcontrol) to show other views ie show welcomeview originally and then if a user click on a button from the welcome view the content control shows the view they selected.
I currently has the Welcome Page as:
<UserControl x:Class="ContentControl.Views.WelcomeView"
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:vm="clr-namespace:ContentControl.ViewModels"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.DataContext>
<vm:WelcomeViewModel />
</UserControl.DataContext>
<Grid Background="red">
<Grid.RowDefinitions >
<RowDefinition Height="25*" />
<RowDefinition Height="50*"/>
<RowDefinition Height="25*"/>
</Grid.RowDefinitions>
<Rectangle Grid.Row="0" Fill="Green"/>
<DockPanel Grid.Row="1" HorizontalAlignment="Center" Background="White">
<Button Height="50" Width="50" Margin="5" Content="DASH" Command="{Binding ViewChangedCommand}" CommandParameter="Dashboard"/>
<Button Height="50" Width="50" Margin="5" Content="ROOM" Command="{Binding ViewChangedCommand}" CommandParameter="MeetingRoom"/>
<Button Height="50" Width="50" Margin="5" Content="SCREEN" Command="{Binding ViewChangedCommand}" CommandParameter="Screen" />
</DockPanel>
<Rectangle Grid.Row="2" Fill="Blue"/>
</Grid>
</UserControl>
The viewModel is as follows:
class WelcomeViewModel : BaseViewModel
{
private MainWindowViewModel _mainWindowVm;
private RelayCommand<string> _viewChangedCommand;
public ICommand ViewChangedCommand
{
get { return _viewChangedCommand ?? (_viewChangedCommand = new RelayCommand<string>(OnViewChanged)); }
}
public event EventHandler ViewChanged;
private void OnViewChanged(string view)
{
EventHandler handler = ViewChanged;
if (handler != null) handler(view, EventArgs.Empty);
}
public MainWindowViewModel MainWindowVm
{
get { return _mainWindowVm; }
set
{
_mainWindowVm = value;
OnPropertyChanged("MainViewModel");
}
}
public WelcomeViewModel()
{
MainWindowVm = new MainWindowViewModel();
ViewChanged += MainWindowVm.ViewChanged;
}
}
}
And I have my Mainwindow as follows:
<Window x:Class="ContentControl.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:ContentControl.ViewModels"
xmlns:views="clr-namespace:ContentControl.Views"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<vm:MainWindowViewModel />
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType="{x:Type vm:ScreenViewModel}">
<views:ScreenView />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:WelcomeViewModel}">
<views:WelcomeView />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:MeetingRoomViewModel}">
<views:MeetingRoomView />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:DashboardViewModel}">
<views:DashboardView />
</DataTemplate>
</Window.Resources>
<Grid>
<StackPanel>
<Label>This Is My Label</Label>
<ContentControl x:Name="MainPanel" Content="{Binding Content, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
MinHeight="200"
MinWidth="200"
HorizontalContentAlignment="Left"
VerticalContentAlignment="Center"
Focusable="False">
</ContentControl>
</StackPanel>
</Grid>
</Window>
Main Window View Model:
class MainWindowViewModel : BaseViewModel
{
private UserControl _content;
public UserControl Content
{
get { return _content; }
set
{
_content = value;
OnPropertyChanged("Content");
}
}
public void ViewChanged(object o, EventArgs e)
{
switch (o.ToString())
{
case "Welcome":
{
var welcome = new WelcomeView();
Content = welcome;
break;
}
case "MeetingRoom":
{
var meetingRoom = new MeetingRoomView();
Content= meetingRoom;
break;
}
case "Dashboard":
{
var dashboard = new DashboardView();
Content = dashboard;
break;
}
case "Screen":
{
var screen = new ScreenView();
Content = screen;
break;
}
}
MessageBox.Show(o.ToString());
}
public MainWindowViewModel()
{
}
}
}
How do I hook these Up to work with each other So I can show the WelcomeView as the content of the contentcontrol and then when I press a button have the ContentControl change to what was selected.
Again sorry I am new to MVVM and WPF and these binding are messing with my head.
The Buttons DASH, ROOM, SCREEN, should be on the MainWindow. The ViewChangedCommand should be on the MainWindowViewModel. And the content will be showed by dynamic templating.
If you want the buttons on the controls:
Ok so, then let's put the buttons on the controls, but the change content command should be in the MainWindowViewModel. To make a binding like this you should do something like this:
Command="{Binding Path=DataContext.ChangeContentCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
in your buttons

Accessing methods of a control that is in a Content Template

Relative WPF new-comer, this will probably have a simple solution (I hope!). I have a class with two properties:
public class MyClass
{
public String Name { get; set; }
public String Description { get; set; }
}
I have a user control which has a textblock and a button: the textblock displays text (obviously) and the button is used to either bold or unbold the text of the text block:
MyControl.xaml:
<UserControl
x:Class="WpfApplication1.MyControl"
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"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="300"
xmlns:this="clr-namespace:WpfApplication1">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="48" />
</Grid.ColumnDefinitions>
<TextBlock
x:Name="txtDescription"
Grid.Column="0"
Text="{Binding Path=Description, RelativeSource={RelativeSource AncestorType={x:Type this:MyControl}}}" />
<Button
x:Name="btnBold"
Grid.Column="1"
Content="Bold"
Click="btnBold_Click" />
</Grid>
</UserControl>
MyControl.xaml.cs:
public partial class MyControl : UserControl
{
public static readonly DependencyProperty DescriptionProperty = DependencyProperty.Register("Description", typeof(String), typeof(MyControl));
public String Description
{
get { return GetValue(MyControl.DescriptionProperty) as String; }
set { SetValue(MyControl.DescriptionProperty, value); }
}
public MyControl()
{
InitializeComponent();
}
private void btnBold_Click(object sender, RoutedEventArgs e)
{
ToggleBold();
}
public void ToggleBold()
{
if (txtDescription.FontWeight == FontWeights.Bold)
{
btnBold.Content = "Bold";
txtDescription.FontWeight = FontWeights.Normal;
}
else
{
btnBold.Content = "Unbold";
txtDescription.FontWeight = FontWeights.Bold;
}
}
}
In my MainWindow I have a tab control which has an item template (to display MyClass.Name in the header of each tab) and a content template. The content template contains one of my above controls and MyClass.Description is bound to MyControl.Description:
MainWindow.xaml:
<Window
x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Height="350"
Width="525"
xmlns:this="clr-namespace:WpfApplication1">
<Grid>
<TabControl x:Name="tabItems">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<this:MyControl
Description="{Binding Description}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
</Window>
MainWindow.xaml.cs:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
List<MyClass> myClasses = new List<MyClass>();
myClasses.Add(new MyClass() { Name = "My Name", Description = "My Description" });
myClasses.Add(new MyClass() { Name = "Your Name", Description = "Your Description" });
tabItems.ItemsSource = myClasses;
}
}
When the program runs I add two objects of type MyClass to a List, set the list to the ItemsSource property of the tab control and it all works perfectly: I get two tabs with "My Name" and "Your Name" as the headers, the description is shown in the correct place and the button turns the bold on or off correctly.
My question is this, how do I add a button OUTSIDE of the tab control which could call the MyControl.ToggleBold method of the MyControl object which is in the content template of the selected item:
MainWindow.xaml:
<Window
x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Height="350"
Width="525"
xmlns:this="clr-namespace:WpfApplication1">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TabControl x:Name="tabItems" Grid.Row="0">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<this:MyControl
x:Name="myControl"
Description="{Binding Description}"/>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
<Button Grid.Row="1" Content="Toggle Selected Tab" Click="Button_Click" />
</Grid>
</Window>
MainWindow.xaml.cs:
...
private void Button_Click(object sender, RoutedEventArgs e)
{
MyClass myClass = tabItems.SelectedItem as MyClass;
MyControl myControl;
///get the instance of myControl that is contained
///in the content template of tabItems for the
///myClass item
myControl.ToggleBold();
}
...
I know I can access the data template by calling tabItems.SelectedContentTemplate but as far as I can tell I cannot access controls within the template (and I don't think I should be doing that either). There is the FindName method but I don't know pass as the templatedParent parameter.
Any help would be hugely appreciated.
You can navigate the VisualTree to find the control you're looking for.
For example, I use a set of custom VisualTreeHelpers which would allow me to call something like this:
var myControl = VisualTreeHelpers.FindChild<MyControl>(myTabControl);
if (myControl != null)
myControl.ToggleBold();

Resources