Switch out UserControl based on property - c#

How do I switch UserControls based on a property setting in my ViewModel?
If Vm.View = "A"
<Window>
<local:UserControlA/>
</Window>
If Vm.View = "B"
<Window>
<local:UserControlB/>
</Window>
Vm.View is an enum that someday may allow for C, D, and so on. Both UserControls are bound to the same Vm, but they present the data radically different based on the user's input. So a DataTemplate based on type doesn't really work here.
Thoughts?

Add ContentControl inside Window and based on View value you can set it's ContentTemplate using DataTriggers.
<ContentControl Content="{Binding}">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<local:UserControlA/>
</DataTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding View}" Value="B">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<local:UserControlB/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>

You might leverage DataTemplate's DataType property and let the binding engine take care of the rest...
XAML
<Window.Resources>
<DataTemplate DataType="localEnums:ProduceType.Apples">
<local:ApplesView />
</DataTemplate>
<DataTemplate DataType="localEnums:ProduceType.Oranges">
<local:OrangesView />
</DataTemplate>
</Window.Resources>
<StackPanel>
<ContentPresenter Content="{Binding ProduceType}" />
<Button Content="Change Produce" Click="Button_Click"/>
</StackPanel>
View Model
public class ProduceViewModel : ViewModel
{
public ProduceViewModel()
{
this.ProduceType = ProduceType.Apples;
}
private ProduceType _produceType;
public ProduceType ProduceType
{
get
{
return _produceType;
}
set
{
if (_produceType != value)
{
_produceType = value;
RaisePropertyChanged();
}
}
}
}
Button Click Handler (Violates pure MVVM but just to demonstrate the DataTemplate switching)
private void Button_Click(object sender, RoutedEventArgs e)
{
(this.DataContext as ProduceViewModel).ProduceType = ProduceType.Oranges;
}

Related

Pass custom properties in WPF templates

I'm trying to create a menu that works with radio buttons. The buttons are graphically prettied by a template. here I would like to display an icon and a text. However, I don't know how I can pass several parameters, so far I only pass the text and have not yet found a way to pass the image.
<StackPanel Grid.Row="1" Margin="0,10,0,0">
<RadioButton Content="Dashboard"
IsChecked="True"
Style="{StaticResource MenuButtonTheme}"/>
<RadioButton Content="Product"
Style="{StaticResource MenuButtonTheme}"/>
<RadioButton Content="Inventory"
Style="{StaticResource MenuButtonTheme}"/>
</StackPanel>
Style Of the Radiobutton
<Style BasedOn="{StaticResource {x:Type ToggleButton}}"
TargetType="{x:Type RadioButton}"
x:Key="MenuButtonTheme">
<Style.Setters>
<Setter Property="Foreground" Value="#FFFFFF"/>
<Setter Property="FontFamily" Value="/Fonts/#Poppins"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="RadioButton">
<Border Background="{TemplateBinding Background}"
CornerRadius="5"
Margin="5,0,5,0">
<Grid VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
Height="50"
>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="30"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<iconPacks:PackIconBoxIcons Kind="SolidPieChartAlt2"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Margin="10,0,0,0"/>
<TextBlock Grid.Column="1" Text="{TemplateBinding Property=Content}"
VerticalAlignment="Center"
FontSize="20"
FontWeight="Regular"
Margin="10,0,0,0"/>
</Grid>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderThickness" Value="0"/>
</Style.Setters>
<Style.Triggers>
<Trigger Property="IsChecked" Value="true">
<Setter Property="Background" Value="#212121"/>
<Setter Property="Foreground" Value="#4169E1"/>
</Trigger>
</Style.Triggers>
</Style>
Foreach particular part of UI in your application, I recommend you to make it a module, that is, a UserContol or ContentControl(recommened). These controls corresponds to View in MVVM, and foreach of them you should add a View Model.
namespace MyNameSpace{
public class View<T> : ContentControl {
public T ViewModel {
get { return (T)GetValue(ViewModelProperty); }
set { SetValue(ViewModelProperty, value); }
}
public static readonly DependencyProperty ViewModelProperty =
DependencyProperty.Register("ViewModel", typeof(T), typeof(View<T>), new PropertyMetadata());
}
public abstract class ViewModel : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged([CallerMemberName] String propertyName = null) {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
If the relative logic is purely UI, then Model is not needed in this case.
Your View's xaml should look like this:
<local:View x:TypeArguments="MyAViewModel" x:Name="view"
x:Class="MyNameSpace.MyAView"
skip
xmlns:local="clr-namespace:MyNameSpace">
<Image Source="{Binding ViewModel.ImageSource,ElementName=view}"/>
</local:View>
Your ViewModel should look like this:
public class MyAViewModel: ViewModel {
public AbilityViewModel() {//Constructor with parameter
//Set the image source here
}
private ImageSource imageSource;
public ImageSource ImageSource{
get => imageSource
set{
imageSource = value;
NotifyPropertyChanged();
}
}
}
In the root element of your UI hierarchy, for example your MainWindow, add your custom contols:
<Window x:Name="window" skip>
<Grid>
<local:MyAView ViewModel="{Binding MyAViewModel,ElementName=window}"/>
<local:MyBView ViewModel="{Binding MyBViewModel,ElementName=window}"/>
</Grid>
</Window>
You may either do so with adding dependency properies of the MyAViewModel and MyBViewModel to your MainWindow, or just set MyAView's ViewModel in MainWindow's constructor or loaded event. You may create the ViewModel to pass to view, in which ImageSource is initialized in constructor, or change it after its construction by somewhere in your code.
Above codes are just demo, directly written in stackoverflow's webpage and is not tested. You may ask me if there is any problem.

WPF Using bindings to ViewModel in style used to change controls

The objective is to swap the control at runtime based on a property in my ViewModel, and have the displayed control be have a binding that updates the properties in the ViewModel. I started by creating the following Style in the View.xaml:
<UserControl.Resources>
<Style x:Key="DisplayTextOrButton" TargetType="{x:Type ContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding TextNotButton}" Value="True">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBox Content="{Binding SomeText}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding TextNotButton}" Value="False">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Button Content="{Binding ButtonText}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
Note that the bindings LabelText and ButtonText are the bindings to the properties in the ViewModel.
Then further on in the View.xaml I have the following:
<ContentControl Content="{Binding TextNotButton}"
Style="{StaticResource DisplayTextOrButton}">
</ContentControl>
Finally, the ViewModel.cs has the following properties:
private bool textNotButton;
public bool TextNotButton
{
get => this.textNotButton;
set
{
this.textNoButton = value;
this.OnPropertyChanged("TextNotButton");
}
}
private string someText;
public string SomeText
{
get => this.someText;
set
{
this.someText = value;
this.OnPropertyChanged("SomeText");
}
}
private string buttonText;
public string ButtonText
{
get => this.buttonText;
set
{
this.buttonText = value;
this.OnPropertyChanged("ButtonText");
}
}
The style works well for swapping between the label and the button, but changing the text in the TextBox does not update the property in the ViewModel, and the Button's text is empty (I imagine because the binding hasn't worked)
I believe this is because the style is a static resource so the bindings SomeText and ButtonText in the style aren't actually the bindings in the ViewModel, but I'm not sure how to pass the reference of the other properties into the style. Or even if that's a thing. I'm pretty new to XAML so not sure on how to handle this

Tooltip set by Style is not working

The tooltip for the below listbox is set using a setter. Nothing appears for a tooltip on mouse over.
I suspect the issue is the itemssource of the listbox itself. The listbox is bound to a list of AttributeItems called CandidateAttributes. An element of that list is an observablecollection called AttributePath, and the property in the Attribute path I am trying to bind the tooltip to is called ConceptualPath. Below is the definition for CandidateAttributes-
public static List<AttributeItem> CoBRRaAttributes { get; set; }
The AttributeItems class-
public class AttributeItem
{
private string _displayName = "";
private ObservableCollection<CoBRRa_WPF.CoBRRaUtilities.ViewModels.QueryTool.AttributeCollection> _AttributePath;
public AttributeItem(int id, string displayName, ObservableCollection<CoBRRa_WPF.CoBRRaUtilities.ViewModels.QueryTool.AttributeCollection> attributePath)
{
DisplayName = displayName;
AttributePath = attributePath;
}
public ObservableCollection<CoBRRa_WPF.CoBRRaUtilities.ViewModels.QueryTool.AttributeCollection> AttributePath
{
get
{
return _AttributePath;
}
set
{
_AttributePath = value;
}
}
}
The xmal-
<ListBox
Name="lstCandidates"
ItemsSource="{Binding Path=UIProperties.CandidateAttributes}"
>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=DisplayName}">
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Control.ToolTip" Value="{Binding AttributePath.ConceptualPath}"/>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
I can substitute some text in the place of Binding AttributePath.ConceptualPath and the tooltip displays that text. Just can't figure out why it does not work in the binding. How can I get this to work?
You are binding to AttributePath.ConceptualPath but AttributePath returns an ObservableCollection<AttributeCollection> and this one has no ConceptualPath property.
You should either change the type of the AttributePath property to just CoBRRa_WPF.CoBRRaUtilities.ViewModels.QueryTool.AttributeCollection or bind to a specific AttributeCollection, for example the first one:
<Setter Property="Control.ToolTip" Value="{Binding AttributePath[0].ConceptualPath}"/>
Also make sure that ConceptualPath is a public property of the AttributeCollection class.
Edit:
If you want to the display a list of paths in the tooltip, you should use an ItemsControl:
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Control.ToolTip">
<Setter.Value>
<ItemsControl ItemsSource="{Binding AttributePath}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ConceptualPath}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Setter.Value>
</Setter>
</Style>

How do you hide a ListView Item placeholder when it's DataTemplate child is collapsed?

When the visibility of a CarViewControl is set to collapsed, it still shows a placeholder where it used to be (see screenshot below).
Is there any way to completely hide a ListViewItem when it is Collapsed?
XAML Code
<ScrollViewer>
<ListView ItemsSource="{Binding CarVM.UserCars}" ShowsScrollingPlaceholders="False">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<ctrl:CarViewControl Car="{Binding}" Visibility="{Binding HideCar, Converter={ThemeResource InverseVisConverter}}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ScrollViewer>
In the image above, there are three CarViewControls which are collapsed, followed by one which is not. One is highlighted. I want them to be completely invisible when the content is collapsed.
What I've tried:
Setting height of the DataTemplate control to 0 (just to see if it hides the placeholder which had no effect
Setting ShowsScrollingPlaceholders to False based on this documentation: MSDN ListView Placeholders
Reason For Collapse Requirement
Within each CarViewControl, a WebView exists which includes a security token (which maintains that the WebView is logged into a specific web site). If you try to pass the WebView by reference, due to what I can only assume are security measures, you lose that security token and must re-login to the site. That is why adding/removing the control from the ObservableCollection will not work in my case.
I would say your design is flawed, but I can't fix that; so I'll provide a "workaround."
The issue is that your DataTemplate is collapsing, which is great, but clearly the container it is in doesn't collapse. This won't happen inherently because the parent won't inherit from a child. First realization is every item is wrapped in a ListViewItem and you can observe that from setting your ItemContainerStyle. This leaves you with two solutions (workarounds). You can either set up some triggers on your ListViewItem or you can do something easier like I did--and if you don't mind the UI affects.
My full working application is below. The main point is that you have to edit the layout/behavior of the ListViewItem. In my example, the default values aren't BorderThickeness and Padding isn't "0, 0, 0, 0"... Setting those to 0 will get hide your items completely.
MainWindow.xaml
<Window x:Class="CollapsingListViewItemContainers.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:CollapsingListViewItemContainers"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVisConverter"></BooleanToVisibilityConverter>
</Window.Resources>
<Grid>
<StackPanel>
<Button Content="Disappear Car 3" Click="Button_Click" />
<ListView ItemsSource="{Binding Cars}">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="MinHeight" Value="0" />
<Setter Property="Padding" Value="0 0 0 0" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate DataType="{x:Type local:Car}">
<Grid Visibility="{Binding IsVisible, Converter={StaticResource BoolToVisConverter}}">
<StackPanel>
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding Title}" />
<TextBlock Text="{Binding Id}" />
</StackPanel>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</Grid>
</Window>
MainWindow.xaml.cs
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
namespace CollapsingListViewItemContainers
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public ObservableCollection<Car> _cars = new ObservableCollection<Car>
{
new Car("Not yours", "Mine", 1),
new Car("Not mine", "Yours", 2),
new Car("Not ours", "His", 3),
new Car("Not ours", "Hers", 4),
};
public ObservableCollection<Car> Cars
{
get
{
return _cars;
}
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Cars[2].IsVisible = !Cars[2].IsVisible;
}
}
public class Car : INotifyPropertyChanged
{
private bool _isVisible;
public bool IsVisible
{
get
{
return _isVisible;
}
set
{
_isVisible = value;
NotifyPropertyChanged("IsVisible");
}
}
public string Name
{
get; set;
}
public string Title
{
get; set;
}
public int Id
{
get; set;
}
public Car(string name, string title, int id)
{
Name = name;
Title = title;
Id = id;
IsVisible = true;
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
if(PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
Edit
Let's be honest, the above was a pretty cheap solution and I wasn't satisfied with it after thinking about it for another 3 minutes. The reason I'm dissatisfied is because you can still select the item if you have access to a keyboard. In the example above, click the first item, "hide" the item(s), then use your mouse and the ListView.SelectedItem will still change.
So below is a quick solution (workaround :D ) to actually remove the item from the list and preventing them from getting focus. Replace the ListView.ItemContainerStyle with this one and change the ActualHeight trigger value to match the values you're seeing. This will change based on OS themes I believe--I'll leave it up to you to test. Lastly, remember the ListViewItem.DataContext is going to be that of an item in the ItemsSource. This means the DataTrigger bound to IsVisible is bound to the Car.IsVisible property.
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Style.Triggers>
<Trigger Property="ActualHeight" Value="4">
<Setter Property="Visibility" Value="Collapsed" />
</Trigger>
<DataTrigger Binding="{Binding IsVisible}" Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListView.ItemContainerStyle>
Edit 2 (edited before I even posted the first edit.
Screw it, don't bind visibility of your CarViewControl; You don't need to. You solely need to focus on removing the item itself, and once the item is removed, the containing controls will be removed as well (though you should test this yourself and change IsTabStop and IsFocusable if you can still tab to items in CarViewControl). Also, since using an arbitrary number with the ActualHeight binding isn't very safe, just binding straight to the IsVisible property (or HideCar in your case) and triggering visibility of the ListViewItem should be sufficient.
Finally, here is my final XAML:
<Window x:Class="CollapsingListViewItemContainers.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:CollapsingListViewItemContainers"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVisConverter"></BooleanToVisibilityConverter>
</Window.Resources>
<Grid>
<StackPanel>
<Button Content="Disappear Car 3" Click="Button_Click" />
<ListView ItemsSource="{Binding Cars}">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsVisible}" Value="False">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
<DataTrigger Binding="{Binding IsVisible}" Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate DataType="{x:Type local:Car}">
<Grid>
<StackPanel>
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding Title}" />
<TextBlock Text="{Binding Id}" />
</StackPanel>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</Grid>
</Window>
Instead of attempting to hide item via the UI, I would remove the item from the View or Collection that the ItemsSource is bound against. It's a cleaner approach, and the UI never has to be concerned about Visibility of an item.
EDIT 1
When a user selects a specific car from the quick select menu, it shows that car and hides the others.
Makes sense; let's call that view "UserCars", and assume the ItemsSource is bound to UserCars.
How about this: change the ItemsSource to be bound to "SelectedCollection". When you want to show UserCars, point SelectedCollection to the UserCars collection.
To limit the set, you'd simply point SelectedCollection to a new SingleCar that you populate with only UserCars.SelectedItem
XAML:
<ListView ItemsSource="{Binding SelectedCollection}" SelectedItem="{Binding SelectedCar}">
ViewModel:
private Car _selectedCar;
public Car SelectedCar {
get { return _selectedCar; }
set { _selectedCar = value; OnPropertyChanged("SelectedCar"); }
}
private ObservableCollection<Car> _selectedCollection = CarVM.UserCars;
private ObservableCollection<Car> SelectedCollection {
get { return _selectedCollection; }
set { _selectedCollection = value; OnPropertyChanged("SelectedCollection"); }
}
private ObservableCollection<Car> _originalCollectionForReferenceKeepingOnly;
// called via RelayCommand
public void UserJustSelectedACar()
{
ObservableCollection<Car> singleItemCollection = new ObservableCollection<Car>();
singleItemCollection.Add(SelectedCar);
_originalCollectionForReferenceKeepingOnly = SelectedCollection;
SelectedCollection = singleItemCollection;
}
public void ReturnToFullUsedCarsList()
{
SelectedCollection = _originalCollectionForReferenceKeepingOnly;
}
EDIT 2
It appears that you are trying to make a ListView pretend to be a "Car Details" view by hiding these other items. This is inherently a bad idea; a different UI element bound to the Listview's Selected Car Item would make for a better solution. Since the new detail panel would just be looking at an already-generated Car instance, you wouldn't incur any data hit. Even if you can make this approach work right now, I'm worried you're going to just cause yourself more grief in the future.

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