How to add subitem in dropbox - c#

I've got unfinished project and my first steps is to modify main menu which is based on combobox and looks like in GitHub desktop application
So now task is to group similar items into subgroups.
For example Log View, Cities View and Stores View should be placed under the group Administration. And years 2017...2020 should be placed under one group.
Unfortunately I can't find solution for this task, maybe ComboBox is not the best solution for this. Maybe someone can give me direction where my solution is? It can either collapsible group or like menu-style, when nested items appears on right of the group.
P.S Administration and Year Table Administration is just empty combobox items.

So, my solution is based on thies https://stackoverflow.com/a/3585244/7283900
I added Expander to my ComboBox, so entire solution now looks like
XAML:
<Window x:Class="WpfApp1_TEST.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:WpfApp1_TEST"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid>
<Grid.Resources>
<Style x:Key="GroupItem" TargetType="{x:Type GroupItem}">
<Setter Property="Margin" Value="0,0,0,5"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander x:Name="expander"
ExpandDirection="Down">
<Expander.Header>
<DockPanel>
<TextBlock Text="{Binding Name}" FontWeight="Bold" Margin="2,5,0,2" FontSize="14"/>
</DockPanel>
</Expander.Header>
<Expander.Content>
<Border Margin="5,0,0,0">
<ItemsPresenter >
</ItemsPresenter>
</Border>
</Expander.Content>
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Grid.Resources>
<ComboBox Height="27" HorizontalAlignment="Left"
ItemsSource="{Binding Item}"
Margin="162,109,0,0" VerticalAlignment="Top"
Width="195" Name="cboGroup" >
<ComboBox.GroupStyle>
<GroupStyle ContainerStyle="{StaticResource GroupItem}"/>
</ComboBox.GroupStyle>
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</Grid>
And CodeBehind of this page
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
List<Item> items = new List<Item>();
items.Add(new Item() { Name = "Item1", Category = "Category_1" });
items.Add(new Item() { Name = "Item2", Category = "Category_1" });
items.Add(new Item() { Name = "Item3", Category = "Category_1" });
items.Add(new Item() { Name = "Item4", Category = "Category_2" });
items.Add(new Item() { Name = "Item5", Category = "Category_2" });
ListCollectionView lcv = new ListCollectionView(items);
lcv.GroupDescriptions.Add(new PropertyGroupDescription("Category"));
//this.comboBox.ItemsSource = lcv;
this.cboGroup.ItemsSource = lcv;
}
public class Item
{
public string Name { get; set; }
public string Category { get; set; }
}
}

Related

Combobox : DataTemplate for each DataTrigger

I have two problems, one small and one big, and I need help;
First, this is my code :
<ComboBox Name="cmb1" Width="165" Height="25" Margin="25,5,10,10"
ItemsSource="{Binding ChoicesList}"
SelectedItem="{Binding SelectedChoiceList}">
</ComboBox>
<ComboBox Name="cmb2" Width="165" Height="25" Margin="10,5,25,10">
<ComboBox.Style>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="ItemsSource" Value="{Binding Sections}"></Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedChoiceList}" Value="Contract">
<Setter Property="ItemsSource" Value="{Binding Contract}"></Setter>
</DataTrigger>
<DataTrigger Binding="{Binding SelectedChoiceList}" Value="Service">
<Setter Property="ItemsSource" Value="{Binding Services}"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
<ComboBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
Text="{Binding Name}"
Margin="4,0"/>
<TextBlock Grid.Column="0"
Text="{Binding label}"
Margin="0"/>
<TextBlock Grid.Column="0"
Text="{Binding Start, StringFormat=d}"
Margin="0"/>
<TextBlock Grid.Column="1"
Text="{Binding End,StringFormat=d}"
Margin="0"/>
</Grid>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Another day, another problem!
In cmb2, I would like nothing to be loaded until I have made one of the three choices, is there a way?
(for the moment I load "section" list if nothing is chosen, and the binding changes according to the choices. I made this choice because the value of "section" can change, it can be "A" or "B" or "C" and more, so I would like "service" = service list, "contract" = contract list and "section"= all other values)
More importantly, I would like to rework this code because currently the DataTemplate is the same for all three binds, which was not awkward (If I choose "section", textblock displays "name" selected, cause section have no label, for example) but I would like, for contracts, to display "name" + "from" + "Starting date" + "To" + "Ending date".
If I add textblock "text = from" and "text = to", it will apply to all my choices, and I would have, for example, if I choose "section", a result like "nameOfSection + from + to", and I don't want that.
So I would like the textblock "from" and "to" to appear only for the choice "contract", I don't know if it's possible?
Hoping to have been clear, and thanking you for your time for the help you will give me;
Welcome to SO!
To answer your first point by removing the line that is setting the ItemSource you should be able to have nothing loaded
<Setter Property="ItemsSource" Value="{Binding Sections}"></Setter> can be removed and replaced with another DataTrigger.
To answer your second point you can use DataTemplates with the DataType property to apply a style to a specific data type. This means you can setup each of your, Service, Section and Contract to look differently / template their own properties.
<DataTemplate DataType="{x:Type classes:Contract}">
<Label Content="Contract"/>
</DataTemplate>
<DataTemplate DataType="{x:Type classes:Service}">
<Label Content="Service"/>
</DataTemplate>
<DataTemplate DataType="{x:Type classes:Section}">
<Label Content="Section"/>
</DataTemplate>
This bit of code demonstrates that.
Here is also a sample application I have made that should do what you've asked.
View
<Window x:Class="SOHelp.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:classes="clr-namespace:SOHelp.Classes"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<DataTemplate DataType="{x:Type classes:Contract}">
<Label Content="Contract"/>
</DataTemplate>
<DataTemplate DataType="{x:Type classes:Service}">
<Label Content="Service"/>
</DataTemplate>
<DataTemplate DataType="{x:Type classes:Section}">
<Label Content="Section"/>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<ComboBox Grid.Row="0" Width="165" Height="25"
ItemsSource="{Binding ChoicesList}"
DisplayMemberPath="Name"
SelectedItem="{Binding SelectedChoiceList}"/>
<ComboBox Grid.Row="1" Width="165" Height="25"
ItemsSource="{Binding SelectedChoiceList.Items}"/>
</Grid>
</Window>
Code behind / could be ViewModel
public partial class MainWindow : Window, INotifyPropertyChanged
{
private ObservableCollection<Option> _choicesList;
private Option _selectedChoiceList;
public Option SelectedChoiceList
{
get { return _selectedChoiceList; }
set { _selectedChoiceList = value; NotifyPropertyChanged(); }
}
public ObservableCollection<Option> ChoicesList
{
get { return _choicesList; }
set { _choicesList = value; NotifyPropertyChanged(); }
}
public MainWindow()
{
InitializeComponent();
ChoicesList = new ObservableCollection<Option>
{
new Option("Contract", new ObservableCollection<object>
{
new Contract(), new Contract(), new Contract()
}),
new Option("Service", new ObservableCollection<object>
{
new Service(), new Service(), new Service()
}),
new Option("Section", new ObservableCollection<object>
{
new Section(), new Section(), new Section()
}),
};
DataContext = this;
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Classes I created
public class Option
{
public string Name { get; set; }
public ObservableCollection<object> Items { get; set; }
public Option(string name, ObservableCollection<object> items)
{
Name = name;
Items = items;
}
}
public class Service
{
}
public class Section
{
}
public class Contract
{
}
Hope some of this helps. Let me know if you have any questions.

Add GroupDescription dynamically?

I'm looking for a way to add GroupDescriptions dynamically, in particular in the viewmodels, I actually add GroupDescription behind code like this:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
List<Country> items = new List<Country>();
items.Add(new Country() { Name = "Italy", League = new League { Name = "Serie A" } }); // Added "new League"
items.Add(new Country() { Name = "Italy", League = new League { Name = "Serie B" } });
items.Add(new Country() { Name = "England", League = new League { Name = "Premiere League" } });
items.Add(new Country() { Name = "Spain", League = new League { Name = "Primeira Division" } });
lvUsers.ItemsSource = items;
CollectionView view = (CollectionView)CollectionViewSource.GetDefaultView(lvUsers.ItemsSource);
PropertyGroupDescription groupNations = new PropertyGroupDescription("Name");
view.GroupDescriptions.Add(groupNations);
PropertyGroupDescription groupCompetions = new PropertyGroupDescription("League.Name"); // Changed "League" to "League.Name"
view.GroupDescriptions.Add(groupCompetions); // Fixed the variable name here
lvUsers.ItemsSource = view;
}
}
public struct League
{
public string Name { get; set; }
}
public struct Country
{
public string Name { get; set; }
public League League { get; set; } // added getter and setter
}
and this is my xaml:
<Window x:Class="GroupBox_Header.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:GroupBox_Header"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid Margin="10">
<ListView Name="lvUsers">
<ListView.View>
<GridView>
<GridViewColumn Header="Home Team" Width="50" DisplayMemberBinding="{Binding HomeTeam}" />
<GridViewColumn Header="Away Team" Width="50" DisplayMemberBinding="{Binding AwayTeam}" />
</GridView>
</ListView.View>
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Expander IsExpanded="True">
<Expander.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" FontWeight="Bold" Foreground="Gray" FontSize="22" VerticalAlignment="Bottom" />
<TextBlock Text="{Binding ItemCount}" FontSize="22" Foreground="Green" FontWeight="Bold" FontStyle="Italic" Margin="10,0,0,0" VerticalAlignment="Bottom" />
<TextBlock Text=" Items" FontSize="22" Foreground="Silver" FontStyle="Italic" VerticalAlignment="Bottom" />
</StackPanel>
</Expander.Header>
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Expander IsExpanded="True">
<Expander.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding League}" FontWeight="Bold" Foreground="Gray" FontSize="22" VerticalAlignment="Bottom" />
</StackPanel>
</Expander.Header>
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ListView.GroupStyle>
</ListView>
</Grid>
what I want to do is Bind in the ListView a collection that have the group description automatically.
If I understand your question right, you could either control the GroupDescriptions directly from the ViewModel, from code behind or create a custom ListView which let you bind to an ObservableCollection.
ViewModel
Use CollectionViewSource to get a hold of the CollectionView from the ViewModel. You could argue that this is an anti-pattern, but I think it is okay.
Code behind
Create a dependency property in you code behind that on change will update the group descriptors for the view. Then in you xaml bind that property to you ViewModel.
Custom ListView
Inherit from ListView and create a dependency property that let's you bind to an ObservableCollection.
Either way, you need to control different scenarios like, property
changed and collection changed.

How to insert multiple GroupDescription?

I've made an application that allow to get the live result of a match.
Now the organization of each match is this:
Nation->League->Match list
So I've the Nation that is the container of the League, and the League is the container of the Match. What I did firstly was create a XAML structure that allow me to achieve a container result as said before:
<Window x:Class="GroupBox_Header.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:GroupBox_Header"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid Margin="10">
<ListView Name="lvUsers">
<ListView.View>
<GridView>
<GridViewColumn Header="Home Team" Width="50" DisplayMemberBinding="{Binding HomeTeam}" />
<GridViewColumn Header="Away Team" Width="50" DisplayMemberBinding="{Binding AwayTeam}" />
</GridView>
</ListView.View>
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Expander IsExpanded="True">
<Expander.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" FontWeight="Bold" Foreground="Gray" FontSize="22" VerticalAlignment="Bottom" />
<TextBlock Text="{Binding ItemCount}" FontSize="22" Foreground="Green" FontWeight="Bold" FontStyle="Italic" Margin="10,0,0,0" VerticalAlignment="Bottom" />
<TextBlock Text=" Items" FontSize="22" Foreground="Silver" FontStyle="Italic" VerticalAlignment="Bottom" />
</StackPanel>
</Expander.Header>
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Expander IsExpanded="True">
<Expander.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding League}" FontWeight="Bold" Foreground="Gray" FontSize="22" VerticalAlignment="Bottom" />
</StackPanel>
</Expander.Header>
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ListView.GroupStyle>
</ListView>
</Grid>
How you can see there is a ListView that have a GroupStyle definition, in this I save all the Nation. You can try the XAML in your demo solution and check the result.
After this, I've created the struct that allow me to store the value:
public struct League
{
public string Name { get; set; }
}
public struct Country
{
public string Name { get; set; }
public League League;
}
Is very pretty simple, a League struct for save all the leagues and a Country struct for the nations. I've valorized the Country in this way:
List<Country> items = new List<Country>();
items.Add(new Country() { Name = "Italy", League = { Name = "Serie A"} });
items.Add(new Country() { Name = "Italy", League = { Name = "Serie B" } });
items.Add(new Country() { Name = "England", League = { Name = "Premiere League" } });
items.Add(new Country() { Name = "Spain", League = { Name = "Primeira Division" } });
lvUsers.ItemsSource = items;
So I actually have in list Italy (2), England, Spain. I then inserted items in the ItemsSource of the ListView (the XAML control).
For create the header group as the Nation name, I've used a CollectionView:
CollectionView view = (CollectionView)CollectionViewSource.GetDefaultView(lvUsers.ItemsSource);
PropertyGroupDescription groupNations = new PropertyGroupDescription("Name");
This code allow me to create multiple headers, and the result is exactly what I want. I also did the same for the League, 'cause what I need is insert as a sub-header the League name in Nation container (header):
view.GroupDescriptions.Add(groupNations);
PropertyGroupDescription groupCompetions = new PropertyGroupDescription("League");
view.GroupDescriptions.Add(groupNations);
but actually this is the result:
How you can see only the nation header appear, but the League header doesn't appear inside the Nation header. What am I doing wrong?
You can "fix" it by making League a property (add getter and setter - WPF bindings attach to properties, not fields), making the second group description group by "League.Name" instead of just "League" and make the second group style TextBlock binding bind to Name, not League.
Then you get this:
But I would say you are approaching this the wrong way and modeling your data in a really weird manner. If the ListView is intended to list Matches, then you should bind to a collection of Match objects (or a view over them), and each Match should reference its parent League, and each League should reference its parent Country.
Alternatively, use a tree instead of a list, and then have a Country with a collection of child Leagues, and each League with a collection of child Matches.
Or have both - a League that contains a collection of Matches, but each match has a parent League reference, and similar for Country/League.
The way it is in your code is kind of weird.
Also, I don't understand why you use structs for your objects instead of classes.
Changes to code behind (note, I am not saying this is a good way to do it, see above):
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
List<Country> items = new List<Country>();
items.Add(new Country() { Name = "Italy", League = new League { Name = "Serie A" } }); // Added "new League"
items.Add(new Country() { Name = "Italy", League = new League { Name = "Serie B" } });
items.Add(new Country() { Name = "England", League = new League { Name = "Premiere League" } });
items.Add(new Country() { Name = "Spain", League = new League { Name = "Primeira Division" } });
lvUsers.ItemsSource = items;
CollectionView view = (CollectionView)CollectionViewSource.GetDefaultView(lvUsers.ItemsSource);
PropertyGroupDescription groupNations = new PropertyGroupDescription("Name");
view.GroupDescriptions.Add(groupNations);
PropertyGroupDescription groupCompetions = new PropertyGroupDescription("League.Name"); // Changed "League" to "League.Name"
view.GroupDescriptions.Add(groupCompetions); // Fixed the variable name here
lvUsers.ItemsSource = view; // Added, you probably have it but didn't post it
}
}
public struct League
{
public string Name { get; set; }
}
public struct Country
{
public string Name { get; set; }
public League League { get; set; } // added getter and setter
}
Changes to XAML, just 1 line:
<TextBlock Text="{Binding Name}" FontWeight="Bold" Foreground="Gray" FontSize="22" VerticalAlignment="Bottom" />
{Binding Name} is {Binding League} in your original code.

In PivotItem, ListBox Binding dont show value

Im create collection for show in ListBoxTransactions and binding as Description. But in result i have only name collection in ListBoxTransactions.ItemsSource, but not value. adn a cant use ListBox.ItemTemplate
XAML
<phone:PivotItem Header="Journal">
<Grid>
<ListBox Name="ListBoxTransactions">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Description}" FontSize="35"/>
</StackPanel>
</ListBox>
</Grid>
</phone:PivotItem>
C#
public class TransactHelper
{
public string Description { get; set; }
}
public void ShowTransactions()
{
ListBoxTransactions.Items.Clear();
var transactFulls = _workerDb.GeTransactFull();
var list = new List<TransactHelper>();
foreach (var t in transactFulls)
{
list.Add(new TransactHelper { Description = t.Description });
}
this.ListBoxTransactions.ItemsSource = list; // dont view collection. only name collection
You should implement ItemContainerStyle for your items.
<ListBox Name="ListBoxTransactions" ItemContainerStyle="{DynamicResource MyItemStyle}">
<ListBox.Resources>
<Style x:Key="MyItemStyle" TargetType="{x:Type ListBoxItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Description}" FontSize="35"/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.Resources>
</ListBox>
And once note:
Don't use this pair
ListBoxTransactions.Items.Clear();
this.ListBoxTransactions.ItemsSource = list;
You need
ListBoxTransactions.ItemsSource = null;
this.ListBoxTransactions.ItemsSource = list;
Or Implement your collection as ObservableCollection

WPF ComboBox selection change after switching tabs

I made a project based on nested tabs.
the nested tabs are different instance of the same viemModel and the same UI.
when I switch between the tabs he comboboxes present in the tabs chenge thei selection depending on the tab that is loosing focus.
I add both the viewmodels and the view of my test project.
thank you in advance for your help
main window
<Window.Resources>
<DataTemplate DataType="{x:Type local:IntermediateViewModel}">
<local:IntermediateView />
</DataTemplate>
<DataTemplate x:Key="HeaderedTabItemTemplate">
<Grid>
<ContentPresenter
Content="{Binding Path=Header, UpdateSourceTrigger=PropertyChanged}"
VerticalAlignment="Center" >
</ContentPresenter>
</Grid>
</DataTemplate>
<Style x:Key="SimpleTabItemStyle" TargetType="TabItem">
<Setter Property="Foreground" Value="White"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Grid>
<Border Name="Border" BorderThickness="1" BorderBrush="#555959">
<ContentPresenter x:Name="ContentSite" VerticalAlignment="Center" HorizontalAlignment="Center"
ContentSource="Header" Margin="12,2,12,2" RecognizesAccessKey="True" Height ="40" MinWidth ="90"/>
</Border>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="Border" Property="Background" Value="#555959" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<DataTemplate x:Key="DefaultTabControlTemplate">
<TabControl IsSynchronizedWithCurrentItem="True"
BorderThickness="0"
ItemsSource="{Binding}"
ItemTemplate="{StaticResource HeaderedTabItemTemplate}"
ItemContainerStyle="{StaticResource SimpleTabItemStyle}"
SelectionChanged="TabControl_SelectionChanged"
/>
</DataTemplate>
<!---->
</Window.Resources>
<Grid MinHeight="200" MinWidth="300">
<Grid.RowDefinitions>
<RowDefinition Height="260*" />
<RowDefinition Height="51*" />
</Grid.RowDefinitions>
<Border >
<ContentControl
Content="{Binding Path=Workspaces}"
ContentTemplate="{DynamicResource DefaultTabControlTemplate}"
/>
</Border>
<Button Grid.Row="1" Content="Add" Command="{Binding AddCommand}"/>
</Grid>
view model (create a different istance each time)
class MainWindowViewModel : WorkspacesViewModel<IntermediateViewModel>
{
public MainWindowViewModel()
{
this.WorkspacesView.CurrentChanged += new EventHandler(WorkspacesView_CurrentChanged);
}
void WorkspacesView_CurrentChanged(object sender, EventArgs e)
{
}
RelayCommand myVar = null;
public ICommand AddCommand
{
get
{
return myVar ?? (myVar = new RelayCommand(param =>
{
SetWindow(new IntermediateViewModel("AA" + this.Workspaces.Count) );
}));
}
}
first level tab
<UserControl.Resources>
<DataTemplate DataType="{x:Type local:ClassViewModel}">
<local:ClassView />
</DataTemplate>
</UserControl.Resources>
<Border>
<ContentControl Content="{Binding Path=CurrentWorkspace, Mode=OneWay}" Loaded="ContentControl_Loaded" DataContextChanged="ContentControl_DataContextChanged" IsVisibleChanged="ContentControl_IsVisibleChanged" LayoutUpdated="ContentControl_LayoutUpdated" TargetUpdated="ContentControl_TargetUpdated" Unloaded="ContentControl_Unloaded" />
</Border>
first level viewmodel
class IntermediateViewModel : WorkspacesViewModel
{
public string Header { get; set; }
public IntermediateViewModel(string header)
{
Header = header;
SetWindow(new ClassViewModel(header));
}
}
nested tab
<UserControl.Resources>
<CollectionViewSource x:Key="StatusView" Source="{Binding Path=StatusList}"/>
</UserControl.Resources>
<Grid>
<ComboBox Name="_spl2Status" ItemsSource="{Binding Source={StaticResource StatusView}}"
SelectedValue="{Binding Path=MyProperty, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectedValuePath="FL_TYPE"
DisplayMemberPath="ID_TYPE" Margin="76,12,0,0" Height="40" VerticalAlignment="Top" HorizontalAlignment="Left" Width="146"
DataContextChanged="_spl2Status_DataContextChanged"
IsVisibleChanged="_spl2Status_IsVisibleChanged"
Loaded="_spl2Status_Loaded"
SelectionChanged="_spl2Status_SelectionChanged"
>
</ComboBox>
</Grid>
nested tab view model
public enum myTypes
{
tipo0 = 0,
tipo1 = 1,
tipo2 = 2,
}
class ClassViewModel : WorkspaceViewModel
{
public ClassViewModel(string name)
{
Name = name;
}
public string Name { get; set; }
private List<IntEnumType> _statusList = null;
public List<IntEnumType> StatusList
{
get
{
if (_statusList == null)
_statusList = new List<IntEnumType>()
{
new IntEnumType((int)myTypes.tipo0, myTypes.tipo0.ToString()),
new IntEnumType((int)myTypes.tipo1, myTypes.tipo1.ToString()),
new IntEnumType((int)myTypes.tipo2, myTypes.tipo2.ToString()),
};
return _statusList;
}
}
private int myVar = 1;
public int MyProperty
{
get
{
return myVar;
}
set
{
if (myVar != value)
{
myVar = value;
OnPropertyChanged(() => MyProperty);
}
}
}
}
public class TabItemStyleSelector : StyleSelector
{
public Style MainTabItem { get; set; }
public Style ChildrenTabItem { get; set; }
public Style SpecificationTabItem { get; set; }
public override Style SelectStyle(object item, DependencyObject container)
{
//if (item is IHome)
// return MainTabItem;
//else if (item is SpecificationItemViewModel)
// return SpecificationTabItem;
//else
return ChildrenTabItem;
}
}
The code is a little hard to completely follow, but I'm guessing that the issue is that there is only one instance of your ClassViewModel and it is where the selection for the combo box is stored {Binding Path=MyProperty, so whatever is stored in MyProperty will be reflected in all instances of the combo box regardless of where they live.
Well this is a bit late, but as I'm facing the same issue, I want to share my analysis.
When you change your tabs, you change the DataContext of the current Tab to your other ViewModel and hence also the ItemsSource of your ComboBox.
In case your previously selected Item (SelectedItem) is not contained within the new ItemsSource, the ComboBox fires a SelectionChanged-Event and therefore sets the SelectedIndex to -1.
Altough this default behaviour of the ComboBox might make sense, it's very annoying in many cases.
We've derived an own class from ComboBox, handling that. But it's not very satisfying as you loose some default behaviour you most probably need.
The problem is in your loaded event handlers.
When you switch tabs your unloading one tab and loading a new one.
I suspect your changing MyComboBox.SelectedIndex in _spl2Status_Loaded.

Categories