WPF, TabItem Images - c#

Have TabControl, which binding to array , need add to TabItem different icons/image.
haml code:
<Grid>
<TabControl VirtualizingPanel.VirtualizationMode="Recycling" Style="{StaticResource TabControl}"
ItemsSource="{Binding Workspaces}" SelectedIndex="{Binding CurrentPage,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}">
<TabControl.ItemContainerStyle>
<Style BasedOn="{StaticResource TabItem}" TargetType="{x:Type TabItem}">
<Setter Property="Header" Value="{Binding HeaderText}"/>
</Style>
</TabControl.ItemContainerStyle>
<TabControl.ContentTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding Content}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>

You should set a ItemTemplateSelector at your TabControl:
<TabControl VirtualizingPanel.VirtualizationMode="Recycling" Style="{StaticResource TabControl}"
ItemsSource="{Binding Workspaces}" SelectedIndex="{Binding CurrentPage,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}"
ItemTemplateSelector="{StaticResource myItemDataTemplateSelector}" >
Your TemplateSelector should look like this:
public class MyItemDataTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
FrameworkElement element = container as FrameworkElement;
if (element != null && item != null && item is MyItem)
{
var myItem = item as MyItem;
var window = Application.Current.MainWindow;
switch (myItem.SpecialFeatures)
{
case SpecialFeatures.None:
return
element.FindResource("Item_None_DataTemplate")
as DataTemplate;
case SpecialFeatures.Color:
return
element.FindResource("Item_Color_DataTemplate")
as DataTemplate;
}
}
return null;
}
}
Reference: https://msdn.microsoft.com/en-us/library/system.windows.controls.itemscontrol.itemtemplateselector(v=vs.110).aspx

Related

Bind a VM to different views, depending on VM's properties

Now I have the following code:
ViewModel(s)
public class VMBase
{
public string TabID{get;set;}
public string TabHeader {get;set;}
}
public class VM1:VMBase //implements the properties in base class
{
}
public class VM2:VMBase //implements the properties in base class
{
}
And in my DataTemplate.xaml, I have the different local controls binded to the ViewModel, depending on the type of ViewModel it is, ie:
<DataTemplate DataType="{x:Type VM:VM1}">
<local: Control1 />
</DataTemplate>
<DataTemplate DataType="{x:Type VM:VM2}">
<local: Control2 />
</DataTemplate>
Control1 and Control2 are different types of UserControl:
public class Control1:UserControl
{
}
public class Control2:UserControl
{
}
Things are still manageable when I have only two derived classes for VMBase, but what if I have ten? Or more? It's going to get ugly.
Is it possible to bind a single VM to different views ( user control), so that I don't have to manually create so many derived class for VMBase? I will just need to specify the VM properties such as TabID and TabHeader, correct views will be bind as a result.
Edit:
Here are further details: my VM is bind to a ContentControl (ie: contentcontrol.Content=VM). And each VM has two properties TabID and Header. Whether the DataTemplateSelector should be invoked depends on whether it has a specific TabID ( if it has other TabID then this DataTemplateSelector shouldn't be invoked), and which DataTemplate ( the logic inside the DataTemplateSelector ) to invoke depends further on the Header. How to implement this?
Updated answer - v2 (as per question edit)
I think simply returning a null in your DataTemplateSelector when TabID is not a match should do the trick, as WPF will then try and pick the next best match (i.e. the template that matches the DataType). In case the TabID is a match, you can return the template based on the TabHeader value.
So your custom DataTemplateSelector would look like this:
public class TabHeaderDataTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var element = container as FrameworkElement;
if (element == null)
return null;
var viewModel = item as VMBase;
if (viewModel == null || viewModel.TabID != "02")
return null; //continue only if TabID is a match
if (viewModel != null)
{
switch(viewModel.TabHeader)
{
case "two":
return element.FindResource($"Template2") as DataTemplate;
case "three":
return element.FindResource($"Template3") as DataTemplate;
}
}
return null;
}
}
Sample XAML
<Window.Resources>
<!-- data template for VM1 -->
<DataTemplate DataType="{x:Type local:VM1}">
<Grid>
<Rectangle Stroke="Black" />
<TextBlock Margin="5" Text="{Binding TabHeader}" FontSize="18"/>
</Grid>
</DataTemplate>
<!-- data template for VM2 -->
<DataTemplate DataType="{x:Type local:VM2}">
<Grid>
<Rectangle Stroke="Red" />
<TextBlock Margin="5" Text="{Binding TabHeader}" FontSize="18"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="Template2">
<Grid>
<Ellipse Stroke="Green" StrokeThickness="4"/>
<TextBlock Margin="10" Text="{Binding TabHeader}" FontSize="24"
Foreground="Red" FontWeight="Bold"
HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
</DataTemplate>
<DataTemplate x:Key="Template3">
<Grid>
<TextBlock Margin="10" Text="{Binding TabHeader}" FontSize="24"
Foreground="White" Background="Black" FontWeight="Bold"
HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
</DataTemplate>
<Style TargetType="ContentControl">
<Setter Property="ContentTemplateSelector">
<Setter.Value>
<local:TabHeaderDataTemplateSelector />
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<StackPanel Margin="25">
<ContentControl Content="{Binding VmObj_1}" />
<ContentControl Content="{Binding VmObj_2}" />
<ContentControl Content="{Binding VmObj_3}" />
<ContentControl Content="{Binding VmObj_4}" />
</StackPanel>
and the code-behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new
{
VmObj_1 = new VM1 { TabID = "01", TabHeader = "one" },
VmObj_2 = new VM1 { TabID = "02", TabHeader = "two" },
VmObj_3 = new VM2 { TabID = "02", TabHeader = "three" },
VmObj_4 = new VM2 { TabID = "03", TabHeader = "four" },
};
}
}
public class VMBase
{
public string TabID { get; set; }
public string TabHeader { get; set; }
}
public class VM1 : VMBase { }
public class VM2 : VMBase { }
Updated answer - v1
You can approach this problem in two different ways. Each option has its own set of pros and cons; but my most recommended approach would be (as #jon-stødle suggested) is to use a DataTemplateSelector
Option 1 - Use DataTemplateSelector
As you are using Type based data-template(s) - then I assume you are most probably using a ContentControl (or a variant) to display the dynamic view-model driven UI. ContentControl and other templated controls such as Label, UserControl, ItemsControl, ListBox etc. usually have dependency property like ContentTemplateSelector or ItemTemplateSelector that you can bind your template-selector to.
You can refer this link for an example for using Label with DataTemplateSelector; or following example for usage with TabControl
XAML:
<Window.Resources>
<DataTemplate x:Key="Tab1Template">
<Grid>
<Rectangle Stroke="Black" />
<TextBlock Margin="5" Text="{Binding}" FontSize="18"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="Tab2Template">
<Grid>
<Ellipse Stroke="Green" StrokeThickness="4"/>
<TextBlock Margin="10" Text="{Binding}" FontSize="24"
Foreground="Red" FontWeight="Bold"
HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
</DataTemplate>
<DataTemplate x:Key="Tab3Template">
<Grid>
<TextBlock Margin="10" Text="{Binding}" FontSize="24"
Foreground="White" Background="Black" FontWeight="Bold"
HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
</DataTemplate>
<Style TargetType="{x:Type TabControl}">
<Setter Property="ContentTemplateSelector">
<Setter.Value>
<local:TabIdDataTemplateSelector />
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<TabControl>
<TabControl.ItemsSource>
<col:ArrayList>
<sys:String>1</sys:String>
<sys:String>2</sys:String>
<sys:String>3</sys:String>
</col:ArrayList>
</TabControl.ItemsSource>
</TabControl>
DataTemplateSelector:
public class TabIdDataTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var element = container as FrameworkElement;
if (element == null)
return null;
//var vm = item as VMBase;
//var id = vm.TabId;
string id = item as string;
if (id != null)
{
return element.FindResource($"Tab{id}Template") as DataTemplate;
}
return null;
}
}
Option 2 - Use Style based data-triggers
Another option is use data-trigger(s) on the property (i.e. TabId) of your ViewModel to update the ContentTemplate of your container-view (i.e. TabControl).
<Window.Resources>
<DataTemplate x:Key="Tab1Template">
<Grid>
<Rectangle Stroke="Black" />
<TextBlock Margin="5" Text="{Binding Key}" FontSize="18"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="Tab2Template">
<Grid>
<Ellipse Stroke="Green" StrokeThickness="4"/>
<TextBlock Margin="10" Text="{Binding Key}" FontSize="24"
Foreground="Red" FontWeight="Bold"
HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
</DataTemplate>
<DataTemplate x:Key="Tab3Template">
<Grid>
<TextBlock Margin="10" Text="{Binding Key}" FontSize="24"
Foreground="White" Background="Black" FontWeight="Bold"
HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
</DataTemplate>
<Style TargetType="{x:Type TabControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding TabId}" Value="1">
<Setter Property="ContentTemplate" Value="{StaticResource ResourceKey=Tab1Template}" />
</DataTrigger>
<DataTrigger Binding="{Binding TabId}" Value="2">
<Setter Property="ContentTemplate" Value="{StaticResource ResourceKey=Tab2Template}" />
</DataTrigger>
<DataTrigger Binding="{Binding TabId}" Value="3">
<Setter Property="ContentTemplate" Value="{StaticResource ResourceKey=Tab3Template}" />
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<TabControl DisplayMemberPath="Value" SelectedValuePath="Key"
SelectedValue="{Binding TabId}">
<TabControl.ItemsSource>
<col:Hashtable>
<sys:String x:Key="1">one</sys:String>
<sys:String x:Key="2">two</sys:String>
<sys:String x:Key="3">three</sys:String>
</col:Hashtable>
</TabControl.ItemsSource>
</TabControl>
What you are looking for is a DataTemplateSelector. It let's you choose a DataTemplate based on different criteria.
public class TaskListDataTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var element = container as FrameworkElement;
var vmBase = item as VMBase;
if (element != null && vmBase != null)
{
switch(vmBase.TabID)
{
case "Tab1": return element.FindResource("Tab1Template") as DataTemplate;
case "Tab2": return element.FindResource("Tab2Template") as DataTemplate;
default: return null;
}
}
}
}
You can read more about them in the docs or have a look at this tutorial.

How to set DataContext Binding on Style, Setter and HierarchicalDataTemplate for sub properties?

If I set the DataContext of a Window using
public partial class MainWindow : Window
{
readonly TreeViewViewModel TreeView;
public MainWindow()
{
//...
this.DataContext = TreeView;
}
}
then the following XAML works fine
<TreeView x:Name="radTreeView" Grid.Row="1"
Margin = "5,5,5,5"
ItemsSource = "{Binding FirstGeneration}"
Padding = "5"
ScrollViewer.HorizontalScrollBarVisibility = "Visible"
ScrollViewer.VerticalScrollBarVisibility = "Visible"
IsTextSearchEnabled="True" >
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem" >
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}" >
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding IsChecked, Mode=TwoWay}" />
<TextBlock VerticalAlignment="Center" Text="{Binding Name}" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
However, I need the DataContext to be a property of a ViewModel so that TreeViewViewModel TreeView is a property of MainViewModel mainViewModel and the DataContext set in codebehind as
public partial class MainWindow : Window
{
private MainViewModel mainViewModel;
public MainWindow()
{
//...
this.DataContext = this;
}
}
I can get parts of the binding working using DataContext = "{Binding MainViewModel.TreeView}" but this only works on the main TreeView element and not the Style, Setter, HierarchicalDataTemplate which don't have a DataContext property.
How do I correctly set the data context for all of the XAML TreeView elements?
Below is what I have tried so far but only ItemsSource = "{Binding FirstGeneration}" is working with the correct data context
<TreeView x:Name="radTreeView" Grid.Row="1"
Margin = "5,5,5,5"
DataContext = "{Binding MainViewModel.TreeView}"
ItemsSource = "{Binding FirstGeneration}"
Padding = "5"
ScrollViewer.HorizontalScrollBarVisibility = "Visible"
ScrollViewer.VerticalScrollBarVisibility = "Visible"
IsTextSearchEnabled="True" >
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem" >
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}" >
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding IsChecked, Mode=TwoWay}" />
<TextBlock VerticalAlignment="Center" Text="{Binding Name}" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Assuming that DataContext of the container for radTreeView is an instance of MainViewModel you can do
<TreeView ... DataContext="{Binding TreeView}" ItemsSource="{Binding FirstGeneration}">
or without changing DataContext
<TreeView ... ItemsSource="{Binding TreeView.FirstGeneration}">

TreeView's contextmenu appears on the topnode instead of TreeViewItem's

I've got a problem I couldn't find solution for. Here is my TreeView's XAML:
<TreeView ItemsSource="{Binding Parents}" ContextMenu="{StaticResource TreeViewContextMenu}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Image}" />
<TextBlock Text="{Binding Name}" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
And I've also got this style in my ContentControl.Resources:
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
<Setter Property="ContextMenu" Value="{StaticResource TreeViewItemContextMenu}" />
</Style>
When I rightclick any item apart from the very first one, the TreeViewItemContextMenu appears, just as intended.
But the issue is when I rightclick the topnode, the TreeViewContextMenu appears instead.
I tried to do without the TreeViewContextMenu at all, but then the topnode had no ContextMenu either.
I would be very glad to know, what and where I missed. Thanks in advance.
I'm trying using your xaml code. This is works.
<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">
<Window.Resources>
<ContextMenu x:Key="TreeViewItemContextMenu">
<MenuItem Header="Item Menu"/>
</ContextMenu>
<ContextMenu x:Key="TreeViewContextMenu">
<MenuItem Header="Tree Menu"/>
</ContextMenu>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
<Setter Property="ContextMenu" Value="{StaticResource TreeViewItemContextMenu}" />
</Style>
</Window.Resources>
<Grid>
<TreeView ItemsSource="{Binding Parents}" ContextMenu="{StaticResource TreeViewContextMenu}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Image}" />
<TextBlock Text="{Binding Name}" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Grid>
</Window>
In code
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Parents = new ObservableCollection<MyTreeItem>();
var children=new ObservableCollection<MyTreeItem>();
children.Add(new MyTreeItem(){ Name="child1"});
children.Add(new MyTreeItem(){ Name="child2"});
Parents.Add(new MyTreeItem() { Name = "Parent Node", Children = children });
this.DataContext = this;
}
public ObservableCollection<MyTreeItem> Parents { get; set; }
}
public class MyTreeItem {
public string Name { get; set; }
public ObservableCollection<MyTreeItem> Children { get; set; }
}
When i click on the top node and child node, Context menu is same

Showing Multiple Control Types in TabControl

All, I have created a TabControl to hold Tabitems that contain a single control type. The markup for MainWindow.xaml was as shown below
...
<TabControl x:Name="tabControl"
ItemsSource="{Binding Path=Workspaces}"
SelectedIndex="{Binding SelectedIndex}"
IsSynchronizedWithCurrentItem="true"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
TabStripPlacement="Top"
Margin="5,0,5,0">
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding Path=DisplayName}"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
</Style>
</TabControl.ItemContainerStyle>
<TabControl.ContentTemplate>
<DataTemplate>
<Views:ResourceControl DataContext="{Binding}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"/>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
...
This worked great for my Views:ResourceControls but I now want to extend the type of controls displayed in the TabControl. To do this I create controls that are both linked to view models that inherit from a base 'WorkspaceViewModel' and I have created the following resource file
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ViewModels="clr-namespace:ResourceStudio.ViewModels"
xmlns:Views="clr-namespace:ResourceStudio.Views">
<DataTemplate DataType="{x:Type ViewModels:ResourceDataViewModel}">
<Views:ResourceControl />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModels:StartPageViewModel}">
<Views:StartPageControl/>
</DataTemplate>
<DataTemplate x:Key="WorkspacesTemplate">
<TabControl x:Name="tabControl"
IsSynchronizedWithCurrentItem="true"
ItemsSource="{Binding}"
SelectedIndex="{Binding SelectedIndex}"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
TabStripPlacement="Top"
Margin="5,0,5,0">
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding Path=DisplayName}"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
</DataTemplate>
</ResourceDictionary>
and now in the MainWindow.xaml I have
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="MainWindowResources.xaml" />
</ResourceDictionary.MergedDictionaries>
<Style x:Key="DescriptionHeaderStyle" TargetType="Label">
<Setter Property="FontSize" Value="22" />
<Setter Property="HorizontalAlignment" Value="Center" />
</Style>
</ResourceDictionary>
</Window.Resources>
...
<ContentControl
Content="{Binding Path=Workspaces}"
ContentTemplate="{StaticResource WorkspacesTemplate}">
</ContentControl>
The bindings seem to register for the different views I want to display as the tabs display with the relevant headers. However, the actual controls do not display until I load another form. It seems that the SelectedIndex is not binding and the views not being updated when the tab item is switched/loaded.
How can I change the WorkspaceTemplate to fix this problem?
Thanks for your time.
Edit. Requested information regarding the MainViewModel.
public class MainWindowViewModel : WorkspaceViewModel
{
private readonly IDialogService dialogService;
private WorkspaceViewModel selectedWorkspace;
private ObservableCollection<WorkspaceViewModel> workspaces;
private Dictionary<string, string> resourceDictionary;
public MainWindowViewModel()
{
base.DisplayName = "SomeStringName";
resourceDictionary = new Dictionary<string, string>();
dialogService = ServiceLocator.Resolve<IDialogService>();
Contract.Requires(dialogService != null);
}
...
private void OnWorkspacesChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null && e.NewItems.Count != 0)
foreach (WorkspaceViewModel workspace in e.NewItems)
workspace.RequestClose += this.OnWorkspaceRequestClose;
if (e.OldItems != null && e.OldItems.Count != 0)
foreach (WorkspaceViewModel workspace in e.OldItems)
workspace.RequestClose -= this.OnWorkspaceRequestClose;
}
private void OnWorkspaceRequestClose(object sender, EventArgs e)
{
WorkspaceViewModel workspace = sender as WorkspaceViewModel;
workspace.Dispose();
int currentIndex = Workspaces.IndexOf(workspace);
this.Workspaces.Remove(workspace);
if (this.Workspaces.Count > 0)
this.SetActiveWorkspace(Workspaces[currentIndex - 1]);
}
private void SetActiveWorkspace(WorkspaceViewModel workspace)
{
Debug.Assert(this.Workspaces.Contains(workspace));
ICollectionView collectionView = CollectionViewSource.GetDefaultView(this.Workspaces);
if (collectionView != null)
collectionView.MoveCurrentTo(workspace);
}
public WorkspaceViewModel SelectedWorkspace
{
get { return selectedWorkspace; }
set { selectedWorkspace = value; }
}
private int selectedIndex = 0;
public int SelectedIndex
{
get { return selectedIndex; }
set
{
if (selectedIndex == value)
return;
selectedIndex = value;
OnPropertyChanged("SelectedIndex");
}
}
/// <summary>
/// Returns the collection of available workspaces to display.
/// A 'workspace' is a ViewModel that can request to be closed.
/// </summary>
public ObservableCollection<WorkspaceViewModel> Workspaces
{
get
{
if (workspaces == null)
{
workspaces = new ObservableCollection<WorkspaceViewModel>();
workspaces.CollectionChanged += this.OnWorkspacesChanged;
}
return workspaces;
}
}
...
}
I faced something similar by using your code. Not sure if it is the same one. When my application loaded it showed the 1st tab (0th) with its proper content. When select the 2nd content region got blank and nothing happened.
I moved the Content="{Binding Path=Workspaces}" to the TabControl i.e.
Content="{Binding}" and
<TabControl ItemsSource="{Binding Workspaces}"/>
and it started to show the content for the tabs. I used more stripped down version of your VM. just the collection of WorkSpaces.

Updating a ListBox with different Content On Button Clicks in WPF

So I have a listbox and a tool bar in my WPF app. The tool bar just has regular controls, and the listbox has vertical expanders.
I need the listbox to have a different set of expanders depending on what button is clicked. Right now it looks like such:
<ListBox>
<local:Select_Analysis_Panel/>
</ListBox>
Where local:Select_Analysis_Panel is seperate user control file containing the expanders. What is the best way to go about dynamically updating the ListBox control's content upon a button click?
For the last couple hours I've been trying to use set DataTemplates for each expander set and bind the to the items control property with little avail with the code below. I'm just trying to get basic framework laid out before setting up a MVVM interface. Later on I was going to replace the ItemsSource="Network_anal" with you know ItemsSource="{Binding WhatExpanderViewModelProperty}" or something like that.
<ListBox Width="250" Margin="5,0,0,0">
<ListBox.Resources>
<DataTemplate DataType="Select_Analysis_Panel">
<local:Select_Analysis_Panel/>
</DataTemplate>
<DataTemplate x:Key="Network_anal" DataType="NetworkAnalysis">
<local:NetworkAnalysis/>
</DataTemplate>.Resources>
<ListBox.Template>
<ControlTemplate>
<Border Background="Red"/>
</ControlTemplate>
</ListBox.Template>
<ItemsControl ItemsSource="Network_anal"/>
</ListBox>
Am I taking the right approach to this at all?
Here's what I'm trying to do. Below when the "File" button is clicked the side bar displays these 2 expanders:
And when "Network Design" button these expanders are dipslayed:
Option 1:
Subclassing the sections:
each of these sections could be subclassed from a base section class and a specific DataTemplate could be used for each:
<Window x:Class="MiscSamples.MultiToolbar"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MiscSamples"
Title="MultiToolbar" Height="300" Width="300">
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
</Window.Resources>
<DockPanel>
<ListBox ItemsSource="{Binding Sections}"
SelectedItem="{Binding SelectedSection}"
DisplayMemberPath="Name"
DockPanel.Dock="Top">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="IsEnabled" Value="{Binding IsEnabled}"/>
<Setter Property="Visibility" Value="{Binding IsVisible, Converter={StaticResource BoolToVisibilityConverter}}"/>
<Setter Property="MinWidth" Value="80"/>
<Setter Property="MinHeight" Value="40"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Border BorderBrush="Black" BorderThickness="1">
<ToggleButton IsChecked="{Binding IsSelected, Mode=TwoWay,RelativeSource={RelativeSource TemplatedParent}}">
<ContentPresenter ContentSource="Content"/>
</ToggleButton>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
<ScrollViewer Width="300" DockPanel.Dock="Left">
<ContentPresenter Content="{Binding SelectedSection}">
<ContentPresenter.Resources>
<DataTemplate DataType="{x:Type local:FileSection}">
<TextBlock Text="User Control For File Section"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:NetworkDesignSection}">
<TextBlock Text="User Control For Network Design"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:SelectAnalysisSection}">
<TextBlock Text="User Control For Select Analysis"/>
</DataTemplate>
</ContentPresenter.Resources>
</ContentPresenter>
</ScrollViewer>
<Grid Background="Gray">
<TextBlock Text="Design Surface" TextAlignment="Center" VerticalAlignment="Center" FontWeight="Bold"/>
</Grid>
</DockPanel>
</Window>
Code Behind:
public partial class MultiToolbar : Window
{
public MultiToolbar()
{
InitializeComponent();
var vm = new MainViewModel();
vm.Sections.Add(new FileSection() {Name = "File"});
vm.Sections.Add(new NetworkDesignSection() { Name = "Network Design" });
vm.Sections.Add(new SelectAnalysisSection() { Name = "Select Analysis" });
DataContext = vm;
}
}
Main ViewModel:
public class MainViewModel: PropertyChangedBase
{
private ObservableCollection<Section> _sections;
public ObservableCollection<Section> Sections
{
get { return _sections ?? (_sections = new ObservableCollection<Section>()); }
}
private Section _selectedSection;
public Section SelectedSection
{
get { return _selectedSection; }
set
{
_selectedSection = value;
OnPropertyChanged("SelectedSection");
}
}
}
Sections:
public abstract class Section:PropertyChangedBase
{
public string Name { get; set; }
private bool _isEnabled = true;
public bool IsEnabled
{
get { return _isEnabled; }
set
{
_isEnabled = value;
OnPropertyChanged("IsEnabled");
}
}
private bool _isVisible = true;
public bool IsVisible
{
get { return _isVisible; }
set
{
_isVisible = value;
OnPropertyChanged("IsVisible");
}
}
//Optionally
//public string ImageSource {get;set;}
//ImageSource = "/Resources/MySection.png";
}
public class FileSection: Section
{
///... Custom logic specific to this Section
}
public class NetworkDesignSection:Section
{
///... Custom logic specific to this Section
}
public class SelectAnalysisSection: Section
{
///... Custom logic specific to File Section
}
//...etc etc etc
Result:
Notice that I'm using ToggleButtons bound to the ListBoxItem.IsSelected property to simulate a TabControl-like behavior.
You can set the DataContext of the whole form and bind the ItemsSource of the listbox, or set ItemsSource of the listbox to some collection directly.

Categories

Resources