I have a bar chart which is built in wpf. The user specifies the items inside the bar.
BarChart.xaml.cs
public partial class BarChart : UserControl
{
List<BarItem> BarItems = new List<BarItem>();
List<Bar> Bars = new List<Bar>();
public List<BarItem> Items
{
get { return BarItems; }
set
{
BarItems = value;
Bars.Clear();
int i=0;
this.LayoutRoot.Children.Clear();
//This line should show up but doesn't suggesting that that the property is not being set?
Debug.WriteLine("SET");
foreach(BarItem Item in BarItems){
Bar ThisBar=new Bar();
ThisBar.CurrentLable=Item.Lable;
ThisBar.CurrentValue=Item.Value;
Debug.WriteLine("{0}:{1} at {2}",Item.Lable,Item.Value,(i*55));
ThisBar.CurrentX=i*55;
this.AddChild(ThisBar);
i++;
}
Debug.WriteLine(i);
}
}
public BarChart()
{
this.InitializeComponent();
}
}
BarChart.xaml
<UserControl
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:CipherCracker"
mc:Ignorable="d"
x:Class="CipherCracker.BarChart"
x:Name="BarChartList"
d:DesignWidth="640" d:DesignHeight="480">
<Grid x:Name="LayoutRoot"/>
</UserControl>
BarItem.cs
public class BarItem
{
public int Value
{
get;
set;
}
public string Lable
{
get;
set;
}
public BarItem()
{
}
}
To add a new BarChart you run
<local:BarChart>
<local:BarChart.Items>
<local:BarItem Lable="B" Value="75"/>
<local:BarItem Lable="A" Value="50"/>
</local:BarChart.Items>
</local:BarChart>
Yet according to the output no items have been added. What am I doing wrong and is there a better way?
Your problem is that the property setter never actually is being called.
Adding the BarItem objects the way you are doing in XAML, will add the items to the existing list instance. The property setter is only being called when you set the list to a new instance.
So I would create a new list in the code-behind, and set the property there. Doing that will call the setter, and your code will run. You might need to give the BarChart a name so you can reference it.
XAML
<local:BarChart x:Name="bar">
<!-- Doing this adds BarItems to the existing list.
<local:BarChart.Items>
<local:BarItem Lable="B" Value="75"/>
<local:BarItem Lable="A" Value="50"/>
</local:BarChart.Items>
-->
</local:BarChart>
Code-behind
public MainWindow()
{
InitializeComponent();
//Setting the Items property to a new list, will call the setter..
bar.Items = new List<BarItem>
{
new BarItem { Lable = "Test1", Value = 500 },
new BarItem { Lable = "Test2", Value = 1000 },
new BarItem { Lable = "Test3", Value = 1500 }
};
}
Also i think you could do this using an ObservableCollection<BarItem> and then register to the event CollectionChanged for controlling the insertions and deletes.
But I think the right way for doing this, is using a DependencyProperty of ICollection, then if the collection implements the ICollectionChanged interface you could control the insertion and deletions for updating the view.This should be very useful if you may want make bindings to this collection.
Hope this could helps to you,...
I would do that with Binding. The code-behind would be just holding a property:
public partial class BarChart : UserControl
{
private List<BarItem> _items;
public List<BarItem> Items
{
get { return _items ?? (_items = new List<BarItem>()); }
set { _items = value; }
}
public BarChart()
{
InitializeComponent();
}
}
While UI would be doing the rest:
<ItemsControl ItemsSource="{Binding Items, ElementName=BarChartList}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:Bar CurrentLable={Binding Lable} CurrentValue={Binding Value}/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
What has been changed in UI:
ItemsControl, unlike Grid, has useful ItemsSource property, which makes things easier;
StackPanel, unlike Grid, eliminates the need to position items manually by stacking them. You can set local:Bar Margin to a constant value if you want to have a gap between the items.
Related
I have what I believe to be a potentially unique situation.
My ListBox items consist of the following:
StackPanel
Image
ListItem
The ListItem and Image are inserted into the StackPanel, then the StackPanel is the inserted into the ListBox for each item in the array.
Now the challenging part comes in sorting the content by the ListItem's Content (text) as it's a child of the StackPanel. Naturally, the StackPanel does not contain a Content member, so using the below code fails.
this.Items.SortDescriptions.Add(new System.ComponentModel.SortDescription("Content",
System.ComponentModel.ListSortDirection.Ascending));
So I figured, what if I set my StackPanel's data context to my ListItem, then surely it will find it.
stackPanel.DataContext = this.Items;
However, that also fails.
I'm creating my ListItems programatically in the code behind, via data that is loaded in via Json.Net.
My goal here is to sort the items from A-Z, based on the Items Content. I would prefer to keep my current implementation (creating the data programatically) as it gives me more control over the visuals. Plus, it's only about 20 lines of code.
Is it possible to use SortDescriptions when the ListItem's content is a StackPanel ?
Thank you
PS: Only started with WPF today, but have been developing WinForms apps for nearly 2 months.
The WPF way to do it would be to bind your ListBox ItemsSource to an ObservableCollection containing your items.
You would then be able to sort your observableCollection liks so :
CollectionViewSource.GetDefaultView(YourObservableCollection).SortDescriptions.Add(new SortDescription("PropertyToSort", ListSortDirection.Ascending));
Here is a small project that highlights this :
XAML :
<Window x:Class="stackPanelTest.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:stackPanelTest"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ListBox ItemsSource="{Binding Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Label Content="{Binding Image}" />
<TextBlock Text="{Binding Item.Content}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
Code Behind :
public partial class MainWindow : Window
{
public ViewModel Items { get; set; } = new ViewModel();
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
}
ViewModel :
public class ViewModel : ObservableCollection<ListItem>
{
public ViewModel()
{
populateItems();
CollectionViewSource.GetDefaultView(this).SortDescriptions.Add(new SortDescription("Item.Content", ListSortDirection.Ascending));
}
private void populateItems()
{
addOneItem(0, "zero");
addOneItem(1, "one");
addOneItem(2, "two");
addOneItem(3, "three");
addOneItem(4, "four");
}
private void addOneItem(int img, string content)
{
ListItem item = new ListItem();
item.Image = img;
item.Item = new SomeItem { Content = content };
Add(item);
}
}
public class ListItem
{
public int Image { get; set; }
public SomeItem Item { get; set; }
}
public class SomeItem
{
public string Content { get; set; }
}
I took the liberty of renaming your "ListItem" into a "SomeItem" class because I didn't know what it was.
Then I made a "ListItem" class which is used to contain a Image/SomeItem pair (which is what your ListBox is composed of).
Also I used an int instead of an actual image but that should be easily changable.
Here's a screenshot of what I get when executing this code :
Hope this helps, good luck.
PS : if your items values are susceptible to change, don't forget to implement INotifyPropertyChanged in "SomeItem" and "ListItem", otherwise the change won't be updated in your view.
Is it possible to use SortDescriptions when the ListItem's content is a StackPanel ?
No. You will have to implement the sorting logic yourself.
There is no easy way to apply custom sorting to the ItemCollection that is returned from the Items property of the ListBox so instead of adding items to this one you could add the items to a List<StackPanel> and sort this one.
You could still create the data programatically just as before.
Here is an example for you:
Code:
public partial class MainWindow : Window
{
private List<StackPanel> _theItems = new List<StackPanel>();
public MainWindow()
{
InitializeComponent();
//create the items:
StackPanel sp1 = new StackPanel();
ListBoxItem lbi1 = new ListBoxItem() { Content = "b" };
Image img1 = new Image();
sp1.Children.Add(lbi1);
sp1.Children.Add(img1);
_theItems.Add(sp1);
StackPanel sp2 = new StackPanel();
ListBoxItem lbi2 = new ListBoxItem() { Content = "a" };
Image img2 = new Image();
sp2.Children.Add(lbi2);
sp2.Children.Add(img2);
_theItems.Add(sp2);
StackPanel sp3 = new StackPanel();
ListBoxItem lbi3 = new ListBoxItem() { Content = "c" };
Image img3 = new Image();
sp3.Children.Add(lbi3);
sp3.Children.Add(img3);
_theItems.Add(sp3);
//sort the items by the Content property of the ListBoxItem
lb.ItemsSource = _theItems.OrderBy(x => x.Children.OfType<ListBoxItem>().FirstOrDefault().Content.ToString()).ToList();
}
}
XAML:
<ListBox x:Name="lb" />
I have a panel and my idea is to have it populated by a stack panel containing two text boxes. When the user enters something in the left box, something should be generated in the right one, as follows.
<StackPanel Orientation="Horizontal">
<TextBox Name="Lefty" LostFocus="FillMyBuddy" />
<TextBox Name="Righty" LostFocus="FillMyBuddy" />
</StackPanel>
However, I'd like to add an option to add/remove rows and, since I wish not to limit myself to the number of such, I get a bit uncertain regarding the approach on two points.
Manipulating DOM (well, it's XAML/WPF but you see what I'm aiming at).
Event handling.
Is it a big no-no to programmatically affect the mark-up structure of the window? Or is it OK to add/remove panels during run-time?
What would the recommended way to be if I want the Lefty number 3 change stuff in Righty number 3? Anything more neat than checking the sender and pulling its siblings from the parent? I want to use a single event handler for any and all rows (knowing that the operations are always intra-row-wise).
You will want to follow MVVM, and have no code in your code-behind (programmatically affect the mark-up structure) files. The concept is easy when you grasp it, so learn it before you start writing your code.
In short, you are going to want to have a view model (something that implements INotifyPropertyChanged (INPC)) which holds your collection of items (which are going to be models, or view models in pure-MVVM). In "hybrid"-MVVM you could just have your models implement INPC.
Then, through the use of commands, you'd implement the logic to remove items from the list that its in. You can pass references, raise notification, using event bubbling, etc. (it's your preference) to have the item actually removed. In my case, I just passed a "manager" to the hybrid-model and held a reference to that. When the command is called (button is clicked), the model calls for the reference to remove itself from the list.
After you do that you define a DataTemplate to define what an "item" should look like one the View. You use a ItemsControl to show a collection of items, and bind to its ItemsSource so the collection of items are shown. Set your ItemsControl.ItemTemplate to the DataTemplate you created, and anything added to the collection bound to ItemsSource of the type defined in DataTemplate.DataType will render as you specify in the DataTemplate.
At the end of the day, you should learn about MVVM design, DataContext, INPC, Commands, Control types and their "main" properties, e.g. everything that inherits from ItemsControl has an ItemsSource property.
Here is a working example, where changing the original string, will reverse it and put it in the read-only right side text box:
MainWindow.xaml.cs (code-behind)
public partial class MainWindow : Window
{
StructureVm _struct = new StructureVm("Test");
public MainWindow()
{
InitializeComponent();
DataContext = _struct;
}
}
MainWindow.xaml (View)
<Window x:Class="DataTemplateWithCommands.MainWindow"
xmlns:local="clr-namespace:DataTemplateWithCommands"
Title="MainWindow" Height="350" Width="525" Background="Orange">
<Window.Resources>
<DataTemplate DataType="{x:Type local:Model}"
x:Key="VmItem">
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding Original, UpdateSourceTrigger=PropertyChanged}" />
<TextBox Text="{Binding Encoded}"
IsReadOnly="True" />
<Button Content="X"
Command="{Binding RemoveMeCommand}" />
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<ItemsControl ItemsSource="{Binding Items}"
ItemTemplate="{StaticResource VmItem}">
</ItemsControl>
</Grid>
</Window>
Interface (helpful for Dependency Injection)
public interface IStructureManager
{
bool RemoveItem(Model itemToRemove);
}
ViewModel
public class StructureVm : IStructureManager
{
private readonly ObservableCollection<Model> _items;
private readonly string _title;
public StructureVm(string title)
{
_title = title;
_items = new ObservableCollection<Model>
{
new Model(this, "12"),
new Model(this, "23"),
new Model(this, "34"),
new Model(this, "45"),
new Model(this, "56"),
new Model(this, "67"),
new Model(this, "78"),
new Model(this, "89"),
};
}}
public ObservableCollection<Model> Items
{
get
{
return _items;
}
}
public string Title
{
get
{
return _title;
}
}
public bool RemoveItem(Model itemToRemove)
{
return _items.Remove(itemToRemove);
}
}
Model (not pure-MVVM, pure MVVM models don't implement INPC, and don't have Command in them)
public class Model : INotifyPropertyChanged
{
private readonly RelayCommand _removeMe;
private string _original;
private string _encoded;
private readonly IStructureManager _manager;
public string Original
{
get
{
return _original;
}
set
{
_original = value;
Encoded = ReverseString(_original);
NotifyPropertyChanged();
}
}
public string Encoded
{
get
{
return _encoded;
}
set
{
_encoded = value;
NotifyPropertyChanged();
}
}
public ICommand RemoveMeCommand
{
get
{
return _removeMe;
}
}
public Model(IStructureManager manager, string original)
{
Original = original;
_manager = manager;
_removeMe = new RelayCommand(param => RemoveMe(), param => CanRemoveMe);
}
private void RemoveMe()
{
_manager.RemoveItem(this);
}
private bool CanRemoveMe
{
get
{
//Logic to enable/disable button
return true;
}
}
private string ReverseString(string s)
{
char[] arr = s.ToCharArray();
Array.Reverse(arr);
return new string(arr);
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName]string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
RelayCommand implementation
From here on out all you have to do is change the attributes of your controls to whatever you're happy with and call it good. The example might be ugly, but I'm leaving it as an exercise for you to figure out other properties/attributes of WPF controls.
I have a TabControl with multiple DataTemplate. the first DataTemplate will be used for search reasons and the second will be for displaying items obtained from that search. My XAML code will be as follows:
<UserControl.Resources>
<!--First template-->
<DataTemplate>
<!-- I will have a DataGrid here-->
</DataTemplate>
<!--Second template-->
<DataTemplate >
<!-- I will have details of one item of the DataGrid-->
</DataTemplate>
</UserControl.Resources>
<TabControl ItemsSource="{Binding }"/>
What I want to accomplish is that in the TabControl the first tab will contain the first DataTemplate (the search template) and when I double click on one row of my DataGrid, a tab will be added with the details of that row (in other words a tab with the second template).
Since I am using MVVM, I thought of creating two UserControls, one for each template and then catch the double click event, but after this I don't know how to add a tab since now my search template is a UserControl seperated from the one that contains the TabControl.
So how do I do this?
UPDATE:
As I read the answers I think I wasn't very clear in stating the problem.
My problem is how to add tabs with the second template, by catching double click events from the first template. I don't have any problem in adding the two templates independently.
Rather can creating two UserControls, you can create and use a DataTemplateSelector in order to switch different DataTemplates in.
Basically, create a new class that inhereits from DataTemplateSelector and override the SelecteTemplate method. Then declare an instance of it in the XAML (much like a value converter), and then apply it to ContentTemplateSelector property of the TabControl.
More info can be found here.
If you're going to do this with MVVM, your tab control should be bound to some ObservableCollection in your VM, and you just add and remove VM's to the collection as needed.
The VMs can be any type you like and your DataTemplates will show the correct view in the tab just like any other view, so yes, create two UserControls for the two views.
public class MainVM
{
public ObservableCollection<object> Views { get; private set; }
public MainVM()
{
this.Views = new ObservableCollection<object>();
this.Views.Add(new SearchVM(GotResults));
}
private void GotResults(Results results)
{
this.Views.Add(new ResultVM(results));
}
}
There are two options: Use datatemplate selector, or use implicit datatemplates and different types for each tabitem.
1. DataTemplateSelector:
public ObservableCollection<TabItemVM> Tabs { get; private set; }
public MainVM()
{
Tabs = ObservableCollection<TabItemVM>
{
new TabItemVM { Name="Tab 1" },
};
}
void AddTab(){
var newTab = new TabItemVM { Name="Tab 2" };
Tabs.Add(newTab);
//SelectedTab = newTab; //you may bind TabControl.SelectedItemProperty to viewmodel in order to be able to activate the tab from viewmodel
}
public class TabItemTemplateSelector : DataTemplateSelector
{
public DataTemplate Tab1Template { get; set; }
public DataTemplate Tab2Template { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var tabItem = item as TabItemVM;
if (tabItem.Name == "Tab 1") return Tab1Template;
if (tabItem.Name == "Tab 2") return Tab2Template;
return base.SelectTemplate(item, container);
}
}
<local:TabItemTemplateSelector
x:Key="TabItemTemplateSelector"
Tab1Template="{StaticResource Tab1Template}"
Tab2Template="{StaticResource Tab2Template}" />
2. Implicit Data Templates:
public class MainVM : ViewModelBase
{
public ObservableCollection<TabItemVM> Tabs { get; private set; }
public MainVM()
{
Tabs = new ObservableCollection<TabItemVM>
{
new Tab1VM(),
};
}
void AddTab()
{
var newTab = new Tab2VM()
Tabs.Add(newTab);
//SelectedTab = newTab;
}
}
public class TabItemBase
{
public string Name { get; protected set; }
}
public class Tab1VM : TabItemBase
{
public Tab1VM()
{
Name = "Tab 1";
}
}
public class Tab2VM : TabItemBase
{
public Tab2VM()
{
Name = "Tab 2";
}
}
<UserControl.Resources>
<!--First template-->
<DataTemplate DataType="local:Tab1VM">
<!-- I will have a DataGrid here-->
</DataTemplate>
<!--Second template-->
<DataTemplate DataType="local:Tab2VM">
<!-- I will have details of one item of the DataGrid-->
</DataTemplate>
</UserControl.Resources>
I have a ListView bounded to a List of a class I created. When doing an operating, it was supposed to add/remove items from the list, but my ListView wasn't updated even though I used INotifyPropertyChanged.
If I use ObservableCollection, it works but I need to have the list sorted, and ObservableCollection doesn't do sorting for WPF4.0 :(
Any way I can make the List binding work? Why didn't it work even though I used INotifyPropertyChanged?
XAML:
<ListView BorderThickness="0" ItemsSource="{Binding SelectedValues, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}" Padding="5">
<ListView.View>
<GridView ColumnHeaderContainerStyle="{StaticResource myHeaderStyle}">
<GridViewColumn DisplayMemberBinding="{Binding Value}"></GridViewColumn>
VM:
private List<CheckBoxItem> _selectedValues = new List<CheckBoxItem>();
public List<CheckBoxItem> SelectedValues
{
get { return _selectedValues; }
set
{
_selectedValues = value;
OnPropertyChanged();
}
}
private void UnselectValueCommandExecute(CheckBoxItem value)
{
value.IsSelected = false;
SelectedValues.Remove(value);
//OnPropertyChanged("SelectedValues");
OnPropertyChanged("IsAllFilteredValuesSelected");
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
The CheckBoxItem class contains 2 properties, Value and IsChecked, which I don't think is relevant here.
So basically, I have a button which uses the UnselectValueCommandExecute to remove items from the list, and I should see the list updated in the UI, but I'm not.
When I debug, I can see the SelectedValues list updated, but not my UI.
You need a CollectionViewSource in your UI.
The XAML:
<Window x:Class="WavTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<CollectionViewSource Source="{Binding TestSource}" x:Key="cvs">
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="Order"/>
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
</Window.Resources>
<ListView ItemsSource="{Binding Source={StaticResource cvs}}" DisplayMemberPath="Description"/>
</Window>
The code behind:
namespace WavTest
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var vm = new ViewModel();
this.DataContext = vm;
vm.TestSource.Add(new TestItem { Description="Zero", Order=0 });
}
}
public class ViewModel
{
public ObservableCollection<TestItem> TestSource { get; set; }
public ViewModel()
{
TestSource = new ObservableCollection<TestItem>();
TestSource.Add(new TestItem { Description = "Second", Order = 2 });
TestSource.Add(new TestItem { Description = "Third", Order = 3 });
TestSource.Add(new TestItem { Description = "First", Order = 1 });
}
}
public class TestItem
{
public int Order { get; set; }
public string Description { get; set; }
}
}
Explanation:
The ObservableCollection raises the PropertyChanged event as you expect, but you cannot sort it.
So, you need the CollectionView to sort it and bind the sorted collection to you ListView/ListBox.
As you can see, adding an element after the DataContext initialization affects the UI sorting the last added item ("Zero") correctly.
You need to use ObservableCollection because this raises a collection changed event which your wpf ListView will pick up on.
How about doing
Public ObservableCollection<object> MyList
{
get
{
return new ObservableCollection<object>(MySortedList);
}
}
and then whenever you change your sorted list raise a property changed event for MyList.
This obviously depends how you would like to sort your list as it might be possible to sort the ObservableCollection your question needs more info.
All, I have a custom DataGridView control which overrides the DataGidView's OnItemsSourceChanged event. Inside this event I need to get a reference to a data set in the relevant ViewModel. Code is
public class ResourceDataGrid : DataGrid
{
protected override void OnItemsSourceChanged(
System.Collections.IEnumerable oldValue,
System.Collections.IEnumerable newValue)
{
if (Equals(newValue, oldValue))
return;
base.OnItemsSourceChanged(oldValue, newValue);
ResourceCore.ResourceManager manager = ResourceCore.ResourceManager.Instance();
ResourceDataViewModel resourceDataViewModel = ?? // How do I get my ResourceDataViewModel
List<string> l = manger.GetDataFor(resourceDataViewModel);
...
}
}
On the marked line I want to know how to get a reference to ResourceDataViewModel resourceDataViewModel. The reson is that i have multiple tabs each tab contains a data grid and ascociated ViewModel, the ViewModel holds some data that I need to retrieve [via the ResourceManager] (or is there another, better way?).
The question is, from the above event, how can I get the ascociated ResourceDataViewModel?
Thanks for your time.
Get the DataContext and cast it to the view-model type:
var viewModel = this.DataContext as ResourceDataViewModel
Put a static reference to it on your app, when the VM is created place its reference on the static and access it as needed.
You ask if there is a better way... In my experience if you find yourself subclassing a UI element in WPF there ususally is.
You can get away from embedding business logic (the choice of which data to display in the grid), by databinding your entire tab control to a view model.
To demonstrate - here is a very simple example. This is my XAML for the window hosting the tab control:
<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">
<Grid>
<TabControl ItemsSource="{Binding Tabs}" SelectedItem="{Binding SelectedTab}">
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding TabName}"></Setter>
</Style>
</TabControl.ItemContainerStyle>
<TabControl.ContentTemplate>
<DataTemplate>
<Grid>
<DataGrid ItemsSource="{Binding TabData}"></DataGrid>
</Grid>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
</Window>
The data context of my window is a TabsViewModel (I am using the NotificationObject that can be found in the PRISM NuGet Package):
public class TabsViewModel: NotificationObject
{
public TabsViewModel()
{
Tabs = new[]
{
new TabViewModel("TAB1", "Data 1 Tab 1", "Data 2 Tab1"),
new TabViewModel("TAB2", "Data 1 Tab 2", "Data 2 Tab2"),
};
}
private TabViewModel _selectedTab;
public TabViewModel SelectedTab
{
get { return _selectedTab; }
set
{
if (Equals(value, _selectedTab)) return;
_selectedTab = value;
RaisePropertyChanged(() => SelectedTab);
}
}
public IEnumerable<TabViewModel> Tabs { get; set; }
}
public class TabViewModel
{
public TabViewModel(string tabName, params string[] data)
{
TabName = tabName;
TabData = data.Select(d => new RowData(){Property1 = d}).ToArray();
}
public string TabName { get; set; }
public RowData[] TabData { get; set; }
}
public class RowData
{
public string Property1 { get; set; }
}
This is obviously an over simplified case, but it means that if there is any business logic about precisely what data to show in each tab, this can reside in one of the view models, as opposed to the code behind. This gives you all the 'separation of concerns' benefits that MVVM is designed to encourage...