Add TabItems to an existing TabControl WPF/MVVM - c#

I have TabControl that has already define some TabItems on XAML. I need to create new TabItems and add to it.
If I use ItemSource I get an exception Items collection must be empty before using ItemsSource.
The solution I have found so far is to create those TabItems I have already defined on XAML but programmatically on the ViewModel, so I can created the others I really need, but doesn't seems to be a good solution.
Other solution would be to add the TabControl as a property and use the Code-Behind to bind it to the ViewModel, which I would like to avoid.
So, I'm just wondering if there is a way to do this only with XAML and MVVM.
Edit:
ItemSource attempt, which is working.
XAML:
<TabControl Grid.Row="1"
Grid.Column="0"
VerticalAlignment="Stretch"
BorderThickness="0.5"
BorderBrush="Black"
ItemsSource="{Binding Model.TabItems, Mode=TwoWay}">
<!--<TabControl.Items>
</TabControl.Items>-->
</TabControl>
Model
public ObservableCollection<TabItem> TabItems {get;set;}
VM
TabItem tabItem = new TabItem { Content = new DetailedViewModel((MyObject)inCommandParameter) };
Model.TabItems.Add(tabItem);

What you are doing here is NOT MvvM. Idea behind it is to keep parts of the app separate, i.e. Model should NOT return any UI elements. If you want to use this with any other UI framework for example WinForms then it will fail and will require additional work.
What you need is something like this, bear in mind that this is an example and you will need to modify this to comply with your requirements.
Model class:
namespace Model
{
public class Profile
{
public string Name { get; set; }
public static int I { get; set; } = 2;
}
}
After this you will need the ViewModel:
namespace VM
{
public class MainViewModel : BaseViewModel
{
public MainViewModel()
{
ProfilesCollection = new List<Profile>();
for (int i = 0; i < 100; i++)
{
ProfilesCollection.Add(new Profile() {Name = $"Name {i}"});
}
}
private List<Profile> profilesCollection;
public List<Profile> ProfilesCollection
{
get { return profilesCollection; }
set { profilesCollection = value; OnPropertyChanged(); }
}
}
}
Now we have base to work with. After that I assume you know how to add the relevant references in your xaml, but this might be seen by other people so I will include it anyway.
Here is a complete MainWindow.xaml:
<Window x:Class="SO_app.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:vm="clr-namespace:VM;assembly=VM"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:converter="clr-namespace:SO_app.Converters"
xmlns:validation="clr-namespace:SO_app.Validation"
xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
xmlns:local="clr-namespace:SO_app"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:model="clr-namespace:Model;assembly=Model"//reference to my model
mc:Ignorable="d"
Title="MainWindow" Height="452.762" Width="525" Closing="Window_Closing">
<!-- d:DataContext="{d:DesignInstance Type=vm:MainViewModel, IsDesignTimeCreatable=True}" -->
<Window.Resources>
<CollectionViewSource Source="{Binding ProfilesCollection}" x:Key="profiles"/> // this corresponds to our collection in VM
</Window.Resources>
<Window.DataContext>
<vm:MainViewModel/>//Data Context of the Window
</Window.DataContext>
<Window.Background>
<VisualBrush>
<VisualBrush.Visual>
<Rectangle Width="50" Height="50" Fill="ForestGreen"></Rectangle>
</VisualBrush.Visual>
</VisualBrush>
</Window.Background>
<TabControl>
<TabControl.Resources>
<DataTemplate DataType="{x:Type model:Profile}">//this data template will be used by the TabControl
<Grid>
<TextBlock Text="{Binding Name}"/>
</Grid>
</DataTemplate>
</TabControl.Resources>
<TabControl.ItemsSource>
<CompositeCollection>
<TabItem Header="First Item"/>
<TabItem Header="SecondItem"/>
<CollectionContainer Collection="{Binding Source={StaticResource profiles}}"/>
</CompositeCollection>
</TabControl.ItemsSource>
</TabControl>
If you want to add more items then just use Command which would be implemented in VM and just add profile to it and enjoy the show.

Related

Re-use xaml bindings and behaviors that are common among several controls (UWP)

I have several TextBoxes that are using identical bindings and behaviors. Is there a way to set these in the textbox style or template or some other creative solution to have repeatable bindings and/or behaviors across multiple xaml controls?
Here is an example of what I'm doing, you can see the behaviors are the same, I have a lot of TextBoxes that will need the validate command behavior and the Text Binding will be the same for each control minus the Converter Parameter but I can bind that to either the control name or tag.
<TextBox x:Name="Control1"
Text="{x:Bind NewItem.Params,Mode=TwoWay,Converter={StaticResource DictionaryConverter},ConverterParameter=Control1,UpdateSourceTrigger=PropertyChanged}"
AllowFocusOnInteraction="True"
Style="{StaticResource FormTextBox}"
Grid.Row="1">
<i:Interaction.Behaviors>
<ic:EventTriggerBehavior EventName="LostFocus">
<ic:InvokeCommandAction Command="{x:Bind NewItem.CommandValidate}"/>
</ic:EventTriggerBehavior>
</i:Interaction.Behaviors>
</TextBox>
<TextBox x:Name="Control2"
Text="{x:Bind NewItem.Params,Mode=TwoWay,Converter={StaticResource DictionaryConverter},ConverterParameter=Control2,UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource FormTextBoxNumber}"
Grid.Row="4">
<i:Interaction.Behaviors>
<ic:EventTriggerBehavior EventName="LostFocus">
<ic:InvokeCommandAction Command="{x:Bind NewItem.CommandValidate}"/>
</ic:EventTriggerBehavior>
</i:Interaction.Behaviors>
</TextBox>
For those of you who always get extremely confused by my questions and bark up the wrong trees I'll try to re-iterate the best I can: What is a simple/creative way to re-use xaml bindings and behaviors that are common among several controls?
For your requirement, you could use UserControl to encapsulate TextBox that block more details. For ease of testing I simplified your code. And this is my UserControl that only contain a TextBox.
<UserControl
x:Class="BindTest.MyUseerControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:BindTest"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:i="using:Microsoft.Xaml.Interactivity"
xmlns:ic="using:Microsoft.Xaml.Interactions.Core"
d:DesignHeight="300"
d:DesignWidth="400">
<TextBox x:Name="MyTextBox" Text="{x:Bind Source.Params,Mode=TwoWay}" AllowFocusOnInteraction="True" Height="44">
<i:Interaction.Behaviors>
<ic:EventTriggerBehavior EventName="LostFocus">
<ic:InvokeCommandAction Command="{x:Bind Source.CommandValidate}"/>
</ic:EventTriggerBehavior>
</i:Interaction.Behaviors>
</TextBox>
</UserControl>
And Then I declared Source DependencyProperty that use to store data source.
public Info Source
{
get { return (Info)GetValue(SourceProperty); }
set { SetValue(SourceProperty, value); }
}
public static readonly DependencyProperty SourceProperty =
DependencyProperty.Register("Source", typeof(Info), typeof(MyUseerControl), new PropertyMetadata(0));
I also create Info model class.
public class Info
{
public string Params { get; set; }
public ICommand CommandValidate { get; set; }
public Info()
{
this.Params = "Hi Nico";
this.CommandValidate = new RelayCommand(()=> {
Debug.WriteLine("This Method Invoke");
});
}
}
Usage
public MainPage()
{
this.InitializeComponent();
Item = new Info();
}
public Info Item { get; set; }
<local:MyUseerControl Source="{x:Bind Item}"/>

WPF controls not showing up in ItemTemplate(Listbox/ListView)

I have recently started with XAML and WPF. I just created a new project in wpf and add the below XAML code. But...none of the items which are added inside "Listbox.ItemTemplate" or "ListView.ItemTemplate" for that matter show up in designer window. what am i doing wrong? This is a fresh project and so no code-behind stuff added yet. i scratched my head for 15 mins with this but with no success. Please help
<Window x:Class="WpfApplication3.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">
<Grid Margin="10">
<ListBox Margin="10">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Name: " />
<TextBlock Text="Age: " />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
You should bind your ListBox or entire Window to some DataContext (usually this is viewmodel with the data you need to display) or specify items of the list explicitly.
In your snippet you specified only an item template, not the items itself.
The example of XAML-defined items (simple strings):
<Window x:Class="WpfApplication3.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">
<Grid Margin="10">
<ListBox Margin="10">
<ListBox.Items>
<ListBoxItem>123</ListBoxItem>
<ListBoxItem>456</ListBoxItem>
</ListBox.Items>
</ListBox>
</Grid>
</Window>
The example with DataContext and Bindings.
XAML:
<Window x:Class="WpfApplication3.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">
<Grid Margin="10">
<ListBox Margin="10" ItemsSource="{Binding Path=Persons}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label>Name:</Label><TextBlock VerticalAlignment="Center" Text="{Binding Path=Name}" />
<Label>Age:</Label><TextBlock VerticalAlignment="Center" Text="{Binding Path=Age}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
Codebehind:
namespace WpfApplication3
{
public class PersonViewModel
{
public PersonViewModel(string name, int age)
{
this.name = name;
this.age = age;
}
public string Name
{
get { return name; }
}
private string name;
public int Age
{
get { return age; }
}
private int age;
}
public class MainViewModel
{
public MainViewModel()
{
persons = new ObservableCollection<PersonViewModel>()
{
new PersonViewModel("Lez", 146),
new PersonViewModel("Binja", 158),
new PersonViewModel("Rufus the Destroyer", 9000)
};
}
public ObservableCollection<PersonViewModel> Persons
{
get { return persons; }
}
private ObservableCollection<PersonViewModel> persons;
}
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainViewModel();
}
}
}
IMPORTANT: Don't forget to properly implement INotifyPropertyChanged in case of mutable properties of viewmodels (for example, if you will have setters for "Name" and "Age" properties of PersonViewModel).
You don't see any items because your ListBox doesn't contain any data in the designer view. To populate it, a list should be binded to the property "ItemsSource" of your ListBox, or adding datas directly to the property "Items" (XAML or code behind).
If you want to show items in the designer view properly, you should have a ViewModel binding to the DataContext of your page and create a "Sample data" file (via Blend for example).
Item Templates are basically used to show customize data. Just Use simple string items first. It will show in list box.

Get the selected tab in the view model (wpf)

I have one main view which has a tab control. When a tab is selected, it calls the appropriate view to display. I have a function in view model which has to know which tab was selected to preform an operation. How do I achieve this? How will the view model know which tab is selected?
Quite simply:
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication2"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:TestViewModel x:Key="MainViewModel"/>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<TabControl DataContext="{StaticResource MainViewModel}"
SelectedIndex="{Binding Selected}"
Grid.Row="0"
x:Name="TestTabs">
<TabItem Header="Section 1"/>
<TabItem Header="Section 2"/>
<TabItem Header="Section 3"/>
</TabControl>
<Button Content="Check
Selected Index"
Grid.Row="1"
x:Name="TestButton"
Click="TestButton_OnClick"/>
</Grid>
</Window>
The model is defined here, declaratively, as a data context. The selectedindex property is bound to the model so any time it changes, the propery it is mapped to on the view model will also change
class TestViewModel : INotifyPropertyChanged
{
private int _selected;
public int Selected
{
get { return _selected; }
set
{
_selected = value;
OnPropertyChanged("Selected");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
This implements INotifyPropertyChanged so the view will register with it. In the handler here, I output the value of Selected to show the as you change them.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void TestButton_OnClick(object sender, RoutedEventArgs e)
{
var vm = TestTabs.DataContext as TestViewModel;
MessageBox.Show(string.Format("You selected tab {0}", vm.Selected));
}
}
This gets the viewmodel and then shows us that the properties were in fact updated.
In View, you put SelectedIndex property on TabControl:
xmlns:cal="http://www.caliburnproject.org"
<TabControl cal:Message.Attach="[SelectionChanged] = [OnTabSelectionChanged()]"
SelectedIndex="{Binding SelectedIndexTab}">
<TabItem Header="Tab 1"/>
<TabItem Header="Tab 2"/>
</TabControl>
In ViewModel, you declare a public property name SelectedIndexTab and OnTabSelectionChanged() method to operate.
public int SelectedIndexTab { get; set; }
In this example, I use Caliburn to catch SelectionChange event of TabControl.
You can use the SelectionChanged event provided by the Selector base class. The SelectionChangedEventArgs will contain the newly selected (and deselected) items. Alternatively, you can bind the SelectedItem of the Selector Base class to a property in your ViewModel, and then perform some logic in the setter.
Generally though, it's considered a violation of MVVM to pass view-specific objects to your ViewModels - it tightly couples the UI framework (WPF in this case) to the more generic ViewModel logic. A better route is to put event handlers in your UI code-behind which in turn act on the view-model appropriately, but without passing View objects as parameters.
Here is a simple example.
You should be binding the ItemsSource of the Tab to your ObservableCollection, and that should hold models with information about the tabs that should be created.
Here are the VM and the model which represents a tab page:
public class ViewModel
{
public ObservableCollection<TabItem> Tabs { get; set; }
public ViewModel()
{
Tabs = new ObservableCollection<TabItem>();
Tabs.Add(new TabItem { Header = "One", Content = "One's content" });
Tabs.Add(new TabItem { Header = "Two", Content = "Two's content" });
}
}
public class TabItem
{
public string Header { get; set; }
public string Content { get; set; }
}
Here`s the View and VM binding
<Window x:Class="WpfApplication12.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">
<Window.DataContext>
<ViewModel
xmlns="clr-namespace:WpfApplication12" />
</Window.DataContext>
<TabControl
ItemsSource="{Binding Tabs}">
<TabControl.ItemTemplate>
<!-- this is the header template-->
<DataTemplate>
<TextBlock
Text="{Binding Header}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<!-- this is the body of the TabItem template-->
<DataTemplate>
<----- usercontrol namespace goes here--->
</DataTemplate>
</TabControl.ContentTemplate>
Source:link

Resharper dosn't recognize the correct ViewModel Type

We are using Resharper and of course we want to take advantage of Resharper's xaml intellisense.
Our View's Data Context are bound to a CurrentViewmodel property of type ViewModelBase. At runtime this Property is set with a View model inheritating from ViewModelBase.
I already added those lines in the View model to set the correct Type:
xmlns:vms="clr-namespace:PQS.ViewModel.Report"
d:DataContext="{d:DesignInstance vms:ReportFilterViewModel, IsDesignTimeCreatable=False}"
But Resharper still keeps looking in ViewModelbase for the Properties.
What else can i try?
Some more Code:
Setting the DataContext:
<UserControl.DataContext>
<Binding Path="ReportMainViewModel.CurrentVm" Source="{StaticResource Locator}"/>
</UserControl.DataContext>
Binding Something (Products is a Property on ReportFilterViewmodel, r# keeps looking for it in ViewModelBase):
<ListBox ItemsSource="{Binding Products.View}" Background="White" DisplayMemberPath="Name.ActualTranslation">
</ListBox>
R# can't statically find concrete view model type that will be available in runtime, so you need to annotate data context type manually like this:
using System.Collections.Generic;
public partial class MainWindow {
public MainWindow() {
Current = new ConcreteViewModel {
Products = {
new Product(),
new Product()
}
};
InitializeComponent();
}
public ViewModelBase Current { get; set; }
}
public class ViewModelBase { }
public class ConcreteViewModel : ViewModelBase {
public ConcreteViewModel() {
Products = new List<Product>();
}
public List<Product> Products { get; private set; }
}
public class Product {
public string ProductName { get { return "Name1"; } }
}
And XAML part:
<Window x:Class="MainWindow" x:Name="MainWin"
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:global="clr-namespace:" mc:Ignorable="d"
DataContext="{Binding ElementName=MainWin, Path=Current}">
<!-- here the type of data context is ViewModelBase -->
<Grid d:DataContext="{d:DesignInstance global:ConcreteViewModel}">
<!-- and here is ConcreteViewModel -->
<ListBox ItemsSource="{Binding Path=Products}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ProductName}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
Or like this:
<Window x:Class="MainWindow" x:Name="MainWin"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:global="clr-namespace:"
DataContext="{Binding ElementName=MainWin, Path=Current}">
<Grid>
<ListBox ItemsSource="{Binding Path=(global:ConcreteViewModel.Products)}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ProductName}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>

Create Silverlight controls dynamically for each ItemsControl item

I want to bind list of objects each of it has UIType (ie textbox, checkbox, combo etc). And it has object Value property.
I want to bind this list to an ItemControl (ListBox, DataGrid..) where each item will have separate template corresponding to the particular UIType of each object (eg item for combo will have combo in the row and item for checkbox will have checkbox)...
obviously prop Value will be bound to relevant property on each item.
whats the most transparent and not too elaborate way to achieve this?
Silverlight 5.
EDIT: (working code based on the Jacob's solution)
code:
ObservableCollection<UIType> data;
public MainPage()
{
InitializeComponent();
data = new ObservableCollection<UIType>()
{
new UITypeTextBox() { Value = "Value.." },
new UITypeCheckBox(){ Value = true },
};
lb.ItemsSource = data;
}
public class UIType { }
public class UITypeTextBox : UIType { public object Value { get; set; }}
public class UITypeCheckBox : UIType { public object Value { get; set; } }
xaml:
<ListBox x:Name="lb">
<ListBox.Resources>
<DataTemplate DataType="local:UITypeTextBox">
<TextBox Text="{Binding Value}" />
</DataTemplate>
<DataTemplate DataType="local:UITypeCheckBox">
<CheckBox IsChecked="{Binding Value}" />
</DataTemplate>
</ListBox.Resources>
</ListBox>
I am not sure about Silverlight but in WPF you can use data templates to do this. For each of your UI types you define a data template which basically maps a type to a view which is just a user control defined i XAML.
Typically you will define the data templates in a resource dictionary:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:views="clr-namespace:MyApp.Views"
xmlns:uitypes="clr-namespace:MyApp.UITypes"
>
<DataTemplate DataType="{x:Type uitypes:TextBox}">
<views:TextBoxView />
</DataTemplate>
<DataTemplate DataType="{x:Type uitypes:CheckBox}">
<views:CheckBoxView />
</DataTemplate>
</ResourceDictionary>
Your views would be XAML files inheriting from UserControl. For example the XAML for the TextBox view might look like the following.
<UserControl x:Class="MyApp.Views.TextBox"
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"
<Grid>
<TextBox Text="{Binding Value}" />
</Grid>
</UserControl>
WPF (and hopefully Silverlight) automatically pics the right view when you add your UI types to an ItemControl.

Categories